diff --git a/src/common/constants/invoice-status.ts b/src/common/constants/invoice-status.ts index a8bd1913b..9e275f2ba 100644 --- a/src/common/constants/invoice-status.ts +++ b/src/common/constants/invoice-status.ts @@ -18,6 +18,5 @@ export default { [InvoiceStatus.Sent]: 'sent', [InvoiceStatus.Partial]: 'partial', [InvoiceStatus.Paid]: 'paid', - [InvoiceStatus.Cancelled]: 'cancelled', [InvoiceStatus.Reversed]: 'reversed', }; diff --git a/src/common/queries/invoices.ts b/src/common/queries/invoices.ts index 1de6207e9..97ce48819 100644 --- a/src/common/queries/invoices.ts +++ b/src/common/queries/invoices.ts @@ -43,7 +43,7 @@ export function useInvoiceQuery(params: InvoiceQueryParams) { request( 'GET', endpoint( - `/api/v1/invoices/:id?include=client.group_settings${isLockedParam}`, + `/api/v1/invoices/:id?include=payments,client.group_settings${isLockedParam}`, { id: params.id, } diff --git a/src/components/forms/Combobox.tsx b/src/components/forms/Combobox.tsx index 23b4e7487..5c604b29e 100644 --- a/src/components/forms/Combobox.tsx +++ b/src/components/forms/Combobox.tsx @@ -258,6 +258,7 @@ export function Combobox({ useClickAway(comboboxRef, () => { setIsOpen(false); + onInputValueChange?.(inputValue); if ( selectedOption && @@ -340,7 +341,6 @@ export function Combobox({ onFocus(); } }} - onBlur={() => onInputValueChange?.(inputValue)} placeholder={inputOptions.placeholder} disabled={readonly} defaultValue={ diff --git a/src/components/forms/InputField.tsx b/src/components/forms/InputField.tsx index 909ec25f3..04d484565 100644 --- a/src/components/forms/InputField.tsx +++ b/src/components/forms/InputField.tsx @@ -104,13 +104,15 @@ export function InputField(props: Props) { )} placeholder={props.placeholder || ''} onBlur={(event) => { - event.target.value = - event.target.value === '' && props.type === 'number' - ? '0' - : event.target.value; + if (!props.changeOverride) { + event.target.value = + event.target.value === '' && props.type === 'number' + ? '0' + : event.target.value; - props.onValueChange && props.onValueChange(event.target.value); - props.onChange && props.onChange(event); + props.onValueChange && props.onValueChange(event.target.value); + props.onChange && props.onChange(event); + } }} onChange={(event) => { event.target.value = diff --git a/src/components/forms/MarkdownEditor.tsx b/src/components/forms/MarkdownEditor.tsx index 1545ad97c..621d21b2e 100644 --- a/src/components/forms/MarkdownEditor.tsx +++ b/src/components/forms/MarkdownEditor.tsx @@ -19,7 +19,7 @@ interface Props { onChange: (value: string) => unknown; label?: string; disabled?: boolean; - handleOnBlur?: () => void; + handleChangeOnlyOnUserInput?: boolean; } export function MarkdownEditor(props: Props) { @@ -96,9 +96,14 @@ export function MarkdownEditor(props: Props) { browser_spellcheck: true, convert_urls: false, }} - onEditorChange={handleChange} - onBlur={() => { - props.handleOnBlur?.(); + onEditorChange={(currentValue) => { + if (props.handleChangeOnlyOnUserInput) { + if (currentValue !== props.value) { + handleChange(currentValue); + } + } else { + handleChange(currentValue); + } }} disabled={props.disabled} /> diff --git a/src/pages/invoices/common/hooks/useTabs.tsx b/src/pages/invoices/common/hooks/useTabs.tsx index 35c67be68..e3a1a2a1b 100644 --- a/src/pages/invoices/common/hooks/useTabs.tsx +++ b/src/pages/invoices/common/hooks/useTabs.tsx @@ -8,6 +8,7 @@ * @license https://www.elastic.co/licensing/elastic-license */ +import { InvoiceStatus } from '$app/common/enums/invoice-status'; import { route } from '$app/common/helpers/route'; import { useHasPermission } from '$app/common/hooks/permissions/useHasPermission'; import { useCurrentCompany } from '$app/common/hooks/useCurrentCompany'; @@ -97,6 +98,11 @@ export function useTabs(params: Params) { name: t('email_history'), href: route('/invoices/:id/email_history', { id }), }, + { + name: t('payments'), + href: route('/invoices/:id/payments', { id }), + enabled: invoice?.status_id === InvoiceStatus.Paid, + }, ]; return tabs; diff --git a/src/pages/invoices/edit/Edit.tsx b/src/pages/invoices/edit/Edit.tsx index f3cc33551..56f94f0ab 100644 --- a/src/pages/invoices/edit/Edit.tsx +++ b/src/pages/invoices/edit/Edit.tsx @@ -99,6 +99,7 @@ export default function Edit() { {invoice && (
{t('status')} +
)} diff --git a/src/pages/invoices/edit/components/Payments.tsx b/src/pages/invoices/edit/components/Payments.tsx new file mode 100644 index 000000000..826660b0c --- /dev/null +++ b/src/pages/invoices/edit/components/Payments.tsx @@ -0,0 +1,85 @@ +/** + * Invoice Ninja (https://invoiceninja.com). + * + * @link https://github.com/invoiceninja/invoiceninja source repository + * + * @copyright Copyright (c) 2022. Invoice Ninja LLC (https://invoiceninja.com) + * + * @license https://www.elastic.co/licensing/elastic-license + */ + +import { Paymentable } from '$app/common/interfaces/payment'; + +import { useOutletContext } from 'react-router-dom'; +import { Context } from '../Edit'; +import { ClickableElement } from '$app/components/cards/ClickableElement'; +import { useDisableNavigation } from '$app/common/hooks/useDisableNavigation'; +import { Payment } from '$app/common/interfaces/payment'; +import { useTranslation } from 'react-i18next'; +import { date } from '$app/common/helpers'; +import { PaymentStatus } from '$app/pages/payments/common/components/PaymentStatus'; +import { useCurrentCompanyDateFormats } from '$app/common/hooks/useCurrentCompanyDateFormats'; +import { useFormatMoney } from '$app/common/hooks/money/useFormatMoney'; +import { Card } from '$app/components/cards'; + +function Payments() { + const [t] = useTranslation(); + + const context: Context = useOutletContext(); + const { invoice } = context; + + const formatMoney = useFormatMoney(); + const disableNavigation = useDisableNavigation(); + + const { dateFormat } = useCurrentCompanyDateFormats(); + + return ( + +
+ {invoice?.payments && + invoice.payments.map((payment: Payment) => + payment.paymentables + .filter( + (item) => + item.invoice_id == invoice?.id && item.archived_at == 0 + ) + .map((paymentable: Paymentable) => ( + +
+

+ {t('payment')} {payment.number} +

+ +

+

+ {formatMoney( + paymentable.amount, + payment.client?.country_id, + payment.client?.settings.currency_id + )} +

+

·

+

{date(paymentable.created_at, dateFormat)}

+

+ +
+ +
+
+
+ )) + )} +
+
+ ); +} + +export default Payments; diff --git a/src/pages/invoices/email/components/Mailer.tsx b/src/pages/invoices/email/components/Mailer.tsx index e8378750b..80a28eb50 100644 --- a/src/pages/invoices/email/components/Mailer.tsx +++ b/src/pages/invoices/email/components/Mailer.tsx @@ -228,11 +228,9 @@ export const Mailer = forwardRef((props, ref) => { { - setPayloadData((current) => ({ ...current, ccEmail: value })); - - setTriggerTemplateGeneration(true); - }} + onValueChange={(value) => + setPayloadData((current) => ({ ...current, ccEmail: value })) + } errorMessage={errors?.errors.cc_email} /> )} @@ -246,16 +244,19 @@ export const Mailer = forwardRef((props, ref) => { setTriggerTemplateGeneration(true); }} disabled={freePlan() && isHosted()} + changeOverride errorMessage={errors?.errors.subject} /> {(proPlan() || enterprisePlan()) && ( - setPayloadData((current) => ({ ...current, body: value })) - } - handleOnBlur={() => setTriggerTemplateGeneration(true)} + onChange={(value) => { + setPayloadData((current) => ({ ...current, body: value })); + + setTriggerTemplateGeneration(true); + }} + handleChangeOnlyOnUserInput /> )} diff --git a/src/pages/invoices/routes.tsx b/src/pages/invoices/routes.tsx index cccf7c9be..5dca6751e 100644 --- a/src/pages/invoices/routes.tsx +++ b/src/pages/invoices/routes.tsx @@ -30,6 +30,9 @@ const EInvoice = lazy( const Documents = lazy( () => import('$app/pages/invoices/edit/components/Documents') ); +const Payments = lazy( + () => import('$app/pages/invoices/edit/components/Payments') +); const Settings = lazy( () => import('$app/pages/invoices/edit/components/Settings') ); @@ -117,6 +120,7 @@ export const invoiceRoutes = ( } /> } /> } /> + } />