Skip to content

Commit

Permalink
Merge pull request #620 from invoiceninja/develop
Browse files Browse the repository at this point in the history
Sync develop with main
  • Loading branch information
beganovich authored Apr 11, 2023
2 parents 2f6ef3b + 4a3ca2d commit ec9f5cd
Show file tree
Hide file tree
Showing 188 changed files with 3,863 additions and 837 deletions.
39 changes: 16 additions & 23 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
"@emotion/styled": "^11.10.5",
"@headlessui/react": "^1.7.4",
"@hello-pangea/dnd": "^16.2.0",
"@leecheuk/react-google-login": "^5.4.1",
"@monaco-editor/react": "^4.4.6",
"@react-oauth/google": "^0.9.0",
"@reduxjs/toolkit": "^1.9.1",
"@sentry/react": "^7.24.1",
"@sentry/tracing": "^7.24.1",
Expand Down
127 changes: 117 additions & 10 deletions src/common/generic/DesignSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,131 @@
* @license https://www.elastic.co/licensing/elastic-license
*/

import { endpoint } from '$app/common/helpers';
import { request } from '$app/common/helpers/request';
import { toast } from '$app/common/helpers/toast/toast';
import { Design } from '$app/common/interfaces/design';
import { GenericSelectorProps } from '$app/common/interfaces/generic-selector-props';
import { ValidationBag } from '$app/common/interfaces/validation-bag';
import { useBlankDesignQuery } from '$app/common/queries/designs';
import { Button, InputField } from '$app/components/forms';
import {
DebouncedCombobox,
Record,
} from '$app/components/forms/DebouncedCombobox';
import { Modal } from '$app/components/Modal';
import { AxiosError } from 'axios';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useQueryClient } from 'react-query';

export function DesignSelector(props: GenericSelectorProps<Design>) {
const [isModalVisible, setIsModalVisible] = useState(false);
const [design, setDesign] = useState<Design | null>(null);
const [errors, setErrors] = useState<ValidationBag | null>(null);

const { t } = useTranslation();
const { data } = useBlankDesignQuery({ enabled: isModalVisible });

const queryClient = useQueryClient();

useEffect(() => {
if (data) {
setDesign(data);
}
}, [data]);

const handleCreate = () => {
if (design) {
toast.processing();
setErrors(null);

request('POST', endpoint('/api/v1/designs'), design)
.then(() => {
toast.success('created_design');

window.dispatchEvent(
new CustomEvent('invalidate.combobox.queries', {
detail: {
url: endpoint('/api/v1/designs'),
},
})
);

queryClient.invalidateQueries('/api/v1/designs');

setDesign(null);
setIsModalVisible(false);
})
.catch((e: AxiosError<ValidationBag>) => {
if (e.response?.status === 422) {
setErrors(e.response.data);

return toast.dismiss();
}

toast.error();
console.error(e);
});
}
};

return (
<DebouncedCombobox
{...props}
value="id"
endpoint="/api/v1/designs"
label="name"
defaultValue={props.value}
onChange={(design: Record<Design>) =>
design.resource && props.onChange(design.resource)
}
/>
<>
<Modal
title={t('new_design')}
visible={isModalVisible}
onClose={setIsModalVisible}
overflowVisible
>
<InputField
label={t('name')}
onValueChange={(value) =>
setDesign((current) => current && { ...current, name: value })
}
errorMessage={errors?.errors.name}
/>

<DebouncedCombobox
{...props}
value="id"
endpoint="/api/v1/designs?per_page=500"
label="name"
defaultValue={props.value}
onChange={(design: Record<Design>) =>
setDesign((current) => {
if (current && design.resource?.design) {
return { ...current, design: design.resource.design };
}

return current;
})
}
inputLabel={t('design')}
errorMessage={
errors?.errors['design.header'] ||
errors?.errors['design.body'] ||
errors?.errors['design.footer'] ||
errors?.errors['design.includes']
}
/>

<Button onClick={handleCreate}>{t('save')}</Button>
</Modal>

<DebouncedCombobox
{...props}
value="id"
endpoint="/api/v1/designs"
label="name"
defaultValue={props.value}
onChange={(design: Record<Design>) => {
design.resource && props.onChange(design.resource);
}
}
actionLabel={t('new_design')}
onActionClick={() => setIsModalVisible(true)}
/>
</>
);
}
3 changes: 2 additions & 1 deletion src/common/guards/Guard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useCurrentCompanyUser } from '$app/common/hooks/useCurrentCompanyUser';
import { useCurrentUser } from '$app/common/hooks/useCurrentUser';
import { CompanyUser } from '$app/common/interfaces/company-user';
import { User } from '$app/common/interfaces/user';
import { Fallback } from '$app/components/Fallback';
import { Default } from '$app/components/layouts/Default';
import { Spinner } from '$app/components/Spinner';
import { Unauthorized } from '$app/pages/errors/401';
Expand Down Expand Up @@ -78,5 +79,5 @@ export function Guard(props: Props) {
return <Unauthorized />;
}

return props.component;
return <Fallback>{props.component}</Fallback>;
}
26 changes: 15 additions & 11 deletions src/common/hooks/useEntityCustomFields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,22 @@ import { customField } from '$app/components/CustomField';
import { useTranslation } from 'react-i18next';
import { useCurrentCompany } from './useCurrentCompany';

export type Entity =
| 'company'
| 'client'
| 'contact'
| 'product'
| 'invoice'
| 'payment'
| 'project'
| 'task'
| 'vendor'
| 'expense'
| 'quote'
| 'credit';

interface Params {
entity:
| 'client'
| 'product'
| 'invoice'
| 'payment'
| 'project'
| 'task'
| 'vendor'
| 'expense'
| 'quote'
| 'credit';
entity: Entity;
}

export function useEntityCustomFields(params: Params) {
Expand Down
5 changes: 5 additions & 0 deletions src/common/interfaces/company.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export interface Settings {
postmark_secret: string;
mailgun_secret: string;
mailgun_domain: string;
mailgun_endpoint: string;
purchase_order_number_pattern: string;
purchase_order_number_counter: number;
shared_invoice_quote_counter: boolean;
Expand Down Expand Up @@ -263,6 +264,10 @@ export interface Settings {
qr_iban: string;
besr_id: string;
vendor_portal_enable_uploads: boolean;
company_logo_size: string;
show_paid_stamp: boolean;
show_shipping_address: boolean;
sync_invoice_quote_columns: boolean;
client_initiated_payments: boolean;
client_initiated_payments_minimum: number;
}
11 changes: 10 additions & 1 deletion src/common/interfaces/design.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@
* @license https://www.elastic.co/licensing/elastic-license
*/

export interface Parts {
includes: string;
header: string;
body: string;
product: string;
task: string;
footer: string;
}

export interface Design {
id: string;
is_custom: boolean;
name: string;
design: Record<string, any>;
design: Parts;
created_at: number;
is_active: boolean;
is_deleted: boolean;
Expand Down
2 changes: 2 additions & 0 deletions src/common/interfaces/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ export interface Parameters {
show_aging_table: boolean;
status: string;
clients: string[];
entity: 'invoice' | 'credit' | 'quote' | 'purchase_order';
entity_id: string;
}

export interface Schedule {
Expand Down
57 changes: 47 additions & 10 deletions src/common/queries/designs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,54 @@

import { endpoint } from '$app/common/helpers';
import { request } from '$app/common/helpers/request';
import { Design } from '$app/common/interfaces/design';
import { GenericManyResponse } from '$app/common/interfaces/generic-many-response';
import { useQuery } from 'react-query';
import { Params } from './common/params.interface';
import { AxiosResponse } from 'axios';
import { GenericQueryOptions } from '$app/common/queries/invoices';
import { route } from '$app/common/helpers/route';
import { GenericSingleResourceResponse } from '$app/common/interfaces/generic-api-response';

export function useDesignsQuery(params: Params) {
return useQuery(['/api/v1/designs', params], () =>
request(
'GET',
endpoint('/api/v1/designs?per_page=:perPage&page=:currentPage', {
perPage: params.perPage,
currentPage: params.currentPage,
})
)
export function useDesignsQuery() {
return useQuery<Design[]>(
['/api/v1/designs'],
() =>
request('GET', endpoint('/api/v1/designs')).then(
(response: AxiosResponse<GenericManyResponse<Design>>) =>
response.data.data
),
{ staleTime: Infinity }
);
}

interface DesignQueryOptions extends GenericQueryOptions {
id: string | undefined;
}

export function useDesignQuery(params: DesignQueryOptions) {
return useQuery<Design>(
route('/api/v1/designs/:id', { id: params.id }),
() =>
request(
'GET',
endpoint('/api/v1/designs/:id?include=client', { id: params.id })
).then(
(response: GenericSingleResourceResponse<Design>) => response.data.data
),
{ staleTime: Infinity, ...params }
);
}

export function useBlankDesignQuery(options?: GenericQueryOptions) {
return useQuery<Design>(
route('/api/v1/designs/create'),
() =>
request('GET', endpoint('/api/v1/designs/create')).then(
(response: GenericSingleResourceResponse<Design>) => response.data.data
),
{
...options,
staleTime: Infinity,
}
);
}
Loading

0 comments on commit ec9f5cd

Please sign in to comment.