@spaceinvoices/react-ui 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +340 -0
- package/cli/dist/index.js +922 -0
- package/package.json +87 -0
- package/registry.json +600 -0
- package/spaceinvoices.schema.json +47 -0
- package/src/app.tsx +25 -0
- package/src/common/autocomplete.tsx +135 -0
- package/src/components/activities/activity-timeline.tsx +160 -0
- package/src/components/activities/index.ts +1 -0
- package/src/components/activities/locales/de.ts +30 -0
- package/src/components/activities/locales/sl.ts +30 -0
- package/src/components/advance-invoices/advance-invoices.hooks.ts +75 -0
- package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +702 -0
- package/src/components/advance-invoices/create/locales/de.ts +29 -0
- package/src/components/advance-invoices/create/locales/sl.ts +25 -0
- package/src/components/advance-invoices/create/prepare-advance-invoice-submission.ts +74 -0
- package/src/components/advance-invoices/index.ts +5 -0
- package/src/components/advance-invoices/list/index.ts +3 -0
- package/src/components/advance-invoices/list/list-row-actions.tsx +119 -0
- package/src/components/advance-invoices/list/list-table.tsx +178 -0
- package/src/components/advance-invoices/list/locales/de.ts +32 -0
- package/src/components/advance-invoices/list/locales/sl.ts +32 -0
- package/src/components/advance-invoices/list/use-advance-invoice-download.ts +63 -0
- package/src/components/button-loader.tsx +11 -0
- package/src/components/combobox.tsx +96 -0
- package/src/components/company-registry/company-registry-autocomplete.tsx +151 -0
- package/src/components/company-registry/company-registry.hooks.ts +67 -0
- package/src/components/company-registry/index.ts +7 -0
- package/src/components/credit-notes/create/create-credit-note-form.tsx +332 -0
- package/src/components/credit-notes/create/index.ts +1 -0
- package/src/components/credit-notes/create/locales/de.ts +69 -0
- package/src/components/credit-notes/create/locales/sl.ts +67 -0
- package/src/components/credit-notes/credit-notes.hooks.ts +22 -0
- package/src/components/credit-notes/index.ts +10 -0
- package/src/components/credit-notes/list/index.ts +3 -0
- package/src/components/credit-notes/list/list-row-actions.tsx +116 -0
- package/src/components/credit-notes/list/list-table.tsx +183 -0
- package/src/components/credit-notes/list/locales/de.ts +33 -0
- package/src/components/credit-notes/list/locales/sl.ts +33 -0
- package/src/components/credit-notes/list/use-credit-note-download.ts +65 -0
- package/src/components/customers/create-customer-form/create-customer-form.tsx +134 -0
- package/src/components/customers/create-customer-form/locales/de.ts +20 -0
- package/src/components/customers/create-customer-form/locales/sl.ts +20 -0
- package/src/components/customers/customer-autocomplete.tsx +173 -0
- package/src/components/customers/customer-combobox.tsx +130 -0
- package/src/components/customers/customer-list-table/customer-list-row-actions.tsx +48 -0
- package/src/components/customers/customer-list-table/customer-list-table.tsx +124 -0
- package/src/components/customers/customer-list-table/index.ts +2 -0
- package/src/components/customers/customer-list-table/locales/de.ts +16 -0
- package/src/components/customers/customer-list-table/locales/sl.ts +16 -0
- package/src/components/customers/customers.hooks.test.ts +348 -0
- package/src/components/customers/customers.hooks.ts +57 -0
- package/src/components/customers/index.ts +5 -0
- package/src/components/dashboard/chart-empty-state.tsx +29 -0
- package/src/components/dashboard/collection-rate-card/collection-rate-card.tsx +80 -0
- package/src/components/dashboard/collection-rate-card/index.ts +4 -0
- package/src/components/dashboard/collection-rate-card/locales/sl.ts +3 -0
- package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +74 -0
- package/src/components/dashboard/index.ts +54 -0
- package/src/components/dashboard/invoice-status-chart/index.ts +4 -0
- package/src/components/dashboard/invoice-status-chart/invoice-status-chart.tsx +130 -0
- package/src/components/dashboard/invoice-status-chart/locales/sl.ts +9 -0
- package/src/components/dashboard/invoice-status-chart/use-invoice-status.ts +105 -0
- package/src/components/dashboard/loading-card.tsx +19 -0
- package/src/components/dashboard/payment-methods-chart/index.ts +4 -0
- package/src/components/dashboard/payment-methods-chart/locales/sl.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/payment-methods-chart.tsx +152 -0
- package/src/components/dashboard/payment-methods-chart/use-payment-methods.ts +50 -0
- package/src/components/dashboard/payment-trend-chart/index.ts +4 -0
- package/src/components/dashboard/payment-trend-chart/locales/sl.ts +5 -0
- package/src/components/dashboard/payment-trend-chart/payment-trend-chart.tsx +137 -0
- package/src/components/dashboard/payment-trend-chart/use-payment-trend.ts +92 -0
- package/src/components/dashboard/revenue-card.tsx +49 -0
- package/src/components/dashboard/revenue-trend-chart/index.ts +4 -0
- package/src/components/dashboard/revenue-trend-chart/locales/sl.ts +5 -0
- package/src/components/dashboard/revenue-trend-chart/revenue-trend-chart.tsx +137 -0
- package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +93 -0
- package/src/components/dashboard/shared/index.ts +5 -0
- package/src/components/dashboard/shared/use-revenue-data.ts +160 -0
- package/src/components/dashboard/shared/use-stats-counts.ts +89 -0
- package/src/components/dashboard/shared/use-stats-query.ts +38 -0
- package/src/components/dashboard/stat-card.tsx +41 -0
- package/src/components/dashboard/tax-collected-card/index.ts +2 -0
- package/src/components/dashboard/tax-collected-card/tax-collected-card.tsx +77 -0
- package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +145 -0
- package/src/components/dashboard/top-customers-chart/index.ts +4 -0
- package/src/components/dashboard/top-customers-chart/locales/sl.ts +5 -0
- package/src/components/dashboard/top-customers-chart/top-customers-chart.tsx +130 -0
- package/src/components/dashboard/top-customers-chart/use-top-customers.ts +72 -0
- package/src/components/documents/create/document-add-item-form.tsx +379 -0
- package/src/components/documents/create/document-add-item-tax-rate-field.tsx +120 -0
- package/src/components/documents/create/document-details-section.tsx +597 -0
- package/src/components/documents/create/document-items-section.tsx +133 -0
- package/src/components/documents/create/document-recipient-section.tsx +101 -0
- package/src/components/documents/create/form-types.ts +36 -0
- package/src/components/documents/create/index.ts +9 -0
- package/src/components/documents/create/live-preview.tsx +235 -0
- package/src/components/documents/create/mark-as-paid-section.tsx +82 -0
- package/src/components/documents/create/prepare-document-submission.test.ts +132 -0
- package/src/components/documents/create/prepare-document-submission.ts +187 -0
- package/src/components/documents/create/prepare-preview-data.test.ts +155 -0
- package/src/components/documents/create/prepare-preview-data.ts +16 -0
- package/src/components/documents/create/smart-code-insert-button.tsx +139 -0
- package/src/components/documents/create/use-document-customer-form.ts +161 -0
- package/src/components/documents/document-preview.tsx +13 -0
- package/src/components/documents/documents.hooks.ts +146 -0
- package/src/components/documents/index.ts +23 -0
- package/src/components/documents/shared/document-preview-display.tsx +172 -0
- package/src/components/documents/shared/index.ts +3 -0
- package/src/components/documents/shared/scaled-document-preview.tsx +70 -0
- package/src/components/documents/shared/use-a4-scaling.ts +62 -0
- package/src/components/documents/types.ts +61 -0
- package/src/components/documents/view/document-actions-bar.tsx +328 -0
- package/src/components/documents/view/document-details-card.tsx +179 -0
- package/src/components/documents/view/document-payments-list.tsx +256 -0
- package/src/components/documents/view/index.ts +4 -0
- package/src/components/documents/view/locales/de.ts +85 -0
- package/src/components/documents/view/locales/sl.ts +84 -0
- package/src/components/documents/view/use-document-download.ts +125 -0
- package/src/components/entities/create-entity-form.tsx +105 -0
- package/src/components/entities/entities.hooks.ts +50 -0
- package/src/components/entities/entity-settings-form/email-template-variables-info.tsx +103 -0
- package/src/components/entities/entity-settings-form/entity-settings-form.tsx +1326 -0
- package/src/components/entities/entity-settings-form/image-upload-with-crop.tsx +222 -0
- package/src/components/entities/entity-settings-form/index.ts +2 -0
- package/src/components/entities/entity-settings-form/input-with-preview.tsx +190 -0
- package/src/components/entities/entity-settings-form/locales/de.ts +192 -0
- package/src/components/entities/entity-settings-form/locales/sl.ts +188 -0
- package/src/components/entities/furs-settings-form/furs-settings-form.tsx +410 -0
- package/src/components/entities/furs-settings-form/furs-settings.hooks.ts +320 -0
- package/src/components/entities/furs-settings-form/index.ts +3 -0
- package/src/components/entities/furs-settings-form/locales/de.ts +233 -0
- package/src/components/entities/furs-settings-form/locales/en.ts +194 -0
- package/src/components/entities/furs-settings-form/locales/sl.ts +196 -0
- package/src/components/entities/furs-settings-form/sections/certificate-settings-section.tsx +242 -0
- package/src/components/entities/furs-settings-form/sections/enable-fiscalization-section.tsx +139 -0
- package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +252 -0
- package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +370 -0
- package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +420 -0
- package/src/components/entities/keys.ts +2 -0
- package/src/components/entities/settings/branding-settings-form.tsx +274 -0
- package/src/components/entities/settings/company-settings-form.tsx +256 -0
- package/src/components/entities/settings/defaults-settings-form.tsx +501 -0
- package/src/components/entities/settings/email-settings-form.tsx +288 -0
- package/src/components/entities/settings/eslog-settings-form.tsx +113 -0
- package/src/components/entities/settings/index.ts +8 -0
- package/src/components/entities/settings/number-format-settings-form.tsx +244 -0
- package/src/components/entities/settings/pdf-template-selector/demo-invoice-data.ts +164 -0
- package/src/components/entities/settings/pdf-template-selector/index.ts +2 -0
- package/src/components/entities/settings/pdf-template-selector/locales/de.ts +18 -0
- package/src/components/entities/settings/pdf-template-selector/locales/sl.ts +18 -0
- package/src/components/entities/settings/pdf-template-selector/pdf-template-cards.tsx +49 -0
- package/src/components/entities/settings/settings-footer.tsx +16 -0
- package/src/components/entities/settings/tax-rules-settings-form.tsx +346 -0
- package/src/components/estimates/create/create-estimate-form.tsx +384 -0
- package/src/components/estimates/create/locales/de.ts +64 -0
- package/src/components/estimates/create/locales/sl.ts +63 -0
- package/src/components/estimates/create/prepare-estimate-submission.ts +39 -0
- package/src/components/estimates/create/use-estimate-customer-form.ts +5 -0
- package/src/components/estimates/estimates.hooks.ts +15 -0
- package/src/components/estimates/index.ts +6 -0
- package/src/components/estimates/list/index.ts +3 -0
- package/src/components/estimates/list/list-row-actions.tsx +103 -0
- package/src/components/estimates/list/list-table.tsx +171 -0
- package/src/components/estimates/list/locales/de.ts +26 -0
- package/src/components/estimates/list/locales/sl.ts +26 -0
- package/src/components/estimates/list/use-estimate-download.ts +63 -0
- package/src/components/export/document-export-form.tsx +288 -0
- package/src/components/export/index.ts +2 -0
- package/src/components/form/form-input.tsx +89 -0
- package/src/components/form/index.ts +1 -0
- package/src/components/invoices/create/create-invoice-form.tsx +852 -0
- package/src/components/invoices/create/eslog-validation.test.ts +242 -0
- package/src/components/invoices/create/eslog-validation.ts +208 -0
- package/src/components/invoices/create/locales/de.ts +118 -0
- package/src/components/invoices/create/locales/sl.ts +114 -0
- package/src/components/invoices/create/prepare-invoice-submission.test.ts +777 -0
- package/src/components/invoices/create/prepare-invoice-submission.ts +79 -0
- package/src/components/invoices/create/use-invoice-customer-form.ts +5 -0
- package/src/components/invoices/index.ts +9 -0
- package/src/components/invoices/invoices-furs.hooks.ts +28 -0
- package/src/components/invoices/invoices.hooks.ts +110 -0
- package/src/components/invoices/list/index.ts +3 -0
- package/src/components/invoices/list/list-row-actions.tsx +132 -0
- package/src/components/invoices/list/list-table.tsx +165 -0
- package/src/components/invoices/list/locales/de.ts +33 -0
- package/src/components/invoices/list/locales/sl.ts +33 -0
- package/src/components/invoices/list/use-invoice-download.ts +62 -0
- package/src/components/invoices/send-email-dialog/index.ts +1 -0
- package/src/components/invoices/send-email-dialog/locales/de.ts +18 -0
- package/src/components/invoices/send-email-dialog/locales/sl.ts +17 -0
- package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +289 -0
- package/src/components/invoices/send-email-dialog.tsx +2 -0
- package/src/components/invoices/shared/index.ts +2 -0
- package/src/components/invoices/shared/scaled-document-preview.tsx +32 -0
- package/src/components/invoices/shared/use-a4-scaling.tsx +39 -0
- package/src/components/invoices/view/eslog-info-display.tsx +160 -0
- package/src/components/invoices/view/furs-info-display.tsx +213 -0
- package/src/components/items/create-item-form/create-item-form.tsx +155 -0
- package/src/components/items/create-item-form/locales/de.ts +14 -0
- package/src/components/items/create-item-form/locales/en.ts +9 -0
- package/src/components/items/create-item-form/locales/sl.ts +14 -0
- package/src/components/items/item-combobox.tsx +147 -0
- package/src/components/items/item-list-table/item-list-header.tsx +33 -0
- package/src/components/items/item-list-table/item-list-row-actions.tsx +48 -0
- package/src/components/items/item-list-table/item-list-row.tsx +32 -0
- package/src/components/items/item-list-table/item-list-table.tsx +76 -0
- package/src/components/items/item-list-table/locales/de.ts +10 -0
- package/src/components/items/item-list-table/locales/en.ts +10 -0
- package/src/components/items/item-list-table/locales/sl.ts +10 -0
- package/src/components/items/items.hooks.ts +63 -0
- package/src/components/loading-spinner.tsx +24 -0
- package/src/components/payments/create-payment-form/create-payment-form.tsx +222 -0
- package/src/components/payments/create-payment-form/locales/de.ts +20 -0
- package/src/components/payments/create-payment-form/locales/sl.ts +20 -0
- package/src/components/payments/edit-payment-form/edit-payment-form.tsx +230 -0
- package/src/components/payments/edit-payment-form/index.ts +1 -0
- package/src/components/payments/edit-payment-form/locales/de.ts +20 -0
- package/src/components/payments/edit-payment-form/locales/sl.ts +20 -0
- package/src/components/payments/index.ts +4 -0
- package/src/components/payments/list/index.ts +2 -0
- package/src/components/payments/list/list-row-actions.tsx +98 -0
- package/src/components/payments/list/list-table.tsx +186 -0
- package/src/components/payments/list/locales/de.ts +19 -0
- package/src/components/payments/list/locales/sl.ts +19 -0
- package/src/components/payments/payments.hooks.ts +15 -0
- package/src/components/request-logs/index.ts +3 -0
- package/src/components/request-logs/request-log-detail.tsx +242 -0
- package/src/components/request-logs/request-log-list-table.tsx +266 -0
- package/src/components/request-logs/request-logs-page.tsx +10 -0
- package/src/components/table/README.md +410 -0
- package/src/components/table/data-table.tsx +251 -0
- package/src/components/table/date-cell.tsx +35 -0
- package/src/components/table/filter-bar.tsx +114 -0
- package/src/components/table/filter-panel.tsx +407 -0
- package/src/components/table/hooks/use-table-fetch.ts +17 -0
- package/src/components/table/hooks/use-table-query.ts +36 -0
- package/src/components/table/hooks/use-table-state.ts +293 -0
- package/src/components/table/index.ts +35 -0
- package/src/components/table/search-input.tsx +85 -0
- package/src/components/table/sortable-header.tsx +56 -0
- package/src/components/table/table-empty-state.tsx +40 -0
- package/src/components/table/table-no-results.tsx +41 -0
- package/src/components/table/table-pagination.tsx +42 -0
- package/src/components/table/table-skeleton.tsx +54 -0
- package/src/components/table/types.ts +136 -0
- package/src/components/tax-reports/index.ts +1 -0
- package/src/components/tax-reports/kir-export-form.tsx +172 -0
- package/src/components/taxes/create-tax-form/create-tax-form.tsx +112 -0
- package/src/components/taxes/create-tax-form/locales/de.ts +8 -0
- package/src/components/taxes/create-tax-form/locales/en.ts +7 -0
- package/src/components/taxes/create-tax-form/locales/sl.ts +8 -0
- package/src/components/taxes/tax-list-table/locales/de.ts +11 -0
- package/src/components/taxes/tax-list-table/locales/en.ts +10 -0
- package/src/components/taxes/tax-list-table/locales/sl.ts +11 -0
- package/src/components/taxes/tax-list-table/tax-list-header.tsx +29 -0
- package/src/components/taxes/tax-list-table/tax-list-row-actions.tsx +43 -0
- package/src/components/taxes/tax-list-table/tax-list-row.tsx +46 -0
- package/src/components/taxes/tax-list-table/tax-list-table.tsx +59 -0
- package/src/components/taxes/taxes.hooks.ts +35 -0
- package/src/components/ui/alert-dialog.tsx +61 -0
- package/src/components/ui/alert.tsx +72 -0
- package/src/components/ui/badge.tsx +48 -0
- package/src/components/ui/breadcrumb.tsx +132 -0
- package/src/components/ui/button.tsx +61 -0
- package/src/components/ui/calendar.tsx +213 -0
- package/src/components/ui/card.tsx +94 -0
- package/src/components/ui/chart.tsx +380 -0
- package/src/components/ui/checkbox.tsx +27 -0
- package/src/components/ui/collapsible.tsx +56 -0
- package/src/components/ui/command.tsx +187 -0
- package/src/components/ui/dialog.tsx +187 -0
- package/src/components/ui/drawer.tsx +123 -0
- package/src/components/ui/dropdown-menu.tsx +291 -0
- package/src/components/ui/form.tsx +166 -0
- package/src/components/ui/input-group.tsx +149 -0
- package/src/components/ui/input.tsx +20 -0
- package/src/components/ui/label.tsx +18 -0
- package/src/components/ui/loading-spinner.tsx +16 -0
- package/src/components/ui/popover.tsx +108 -0
- package/src/components/ui/radio-group.tsx +37 -0
- package/src/components/ui/select.tsx +200 -0
- package/src/components/ui/separator.tsx +23 -0
- package/src/components/ui/sheet.tsx +145 -0
- package/src/components/ui/sidebar.tsx +771 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +60 -0
- package/src/components/ui/spinner.tsx +10 -0
- package/src/components/ui/sticky-form-footer.tsx +55 -0
- package/src/components/ui/switch.tsx +30 -0
- package/src/components/ui/table.tsx +101 -0
- package/src/components/ui/tabs.tsx +80 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +89 -0
- package/src/components/wl-subscription/index.ts +2 -0
- package/src/components/wl-subscription/locked-feature.tsx +173 -0
- package/src/components/wl-subscription/upgrade-modal.tsx +209 -0
- package/src/frontend.tsx +28 -0
- package/src/generate-schemas.ts +265 -0
- package/src/generated/schemas/advanceinvoice.ts +177 -0
- package/src/generated/schemas/creditnote.ts +187 -0
- package/src/generated/schemas/customer.ts +29 -0
- package/src/generated/schemas/entity.ts +252 -0
- package/src/generated/schemas/estimate.ts +159 -0
- package/src/generated/schemas/furssettings.ts +25 -0
- package/src/generated/schemas/index.ts +24 -0
- package/src/generated/schemas/invoice.ts +167 -0
- package/src/generated/schemas/item.ts +38 -0
- package/src/generated/schemas/payment.ts +44 -0
- package/src/generated/schemas/previewadvanceinvoice_body.ts +354 -0
- package/src/generated/schemas/previewestimate_body.ts +309 -0
- package/src/generated/schemas/registerfursmovablepremise_body.ts +22 -0
- package/src/generated/schemas/registerfursrealestatepremise_body.ts +32 -0
- package/src/generated/schemas/renderdocument_body.ts +594 -0
- package/src/generated/schemas/sendemail_body.ts +26 -0
- package/src/generated/schemas/startpdfexport_body.ts +20 -0
- package/src/generated/schemas/tax.ts +48 -0
- package/src/generated/schemas/uploadfile_body.ts +23 -0
- package/src/generated/schemas/uploadfurscertificate_body.ts +20 -0
- package/src/generated/schemas/userfurssettings.ts +19 -0
- package/src/hooks/create-resource-hooks.test.ts +483 -0
- package/src/hooks/create-resource-hooks.ts +300 -0
- package/src/hooks/use-debounce.ts +12 -0
- package/src/hooks/use-duplicate-document.ts +185 -0
- package/src/hooks/use-media-query.tsx +19 -0
- package/src/hooks/use-mobile.ts +39 -0
- package/src/hooks/use-next-document-number.ts +57 -0
- package/src/hooks/use-resource-mutation.ts +118 -0
- package/src/hooks/use-vies-check.ts +130 -0
- package/src/index.css +11 -0
- package/src/index.html +13 -0
- package/src/index.tsx +12 -0
- package/src/lib/auth.ts +4 -0
- package/src/lib/browser-cookies.ts +70 -0
- package/src/lib/constants.ts +287 -0
- package/src/lib/cookies.ts +36 -0
- package/src/lib/schemas/advance-invoice.ts +43 -0
- package/src/lib/schemas/credit-note.ts +32 -0
- package/src/lib/schemas/estimate.ts +31 -0
- package/src/lib/schemas/index.ts +18 -0
- package/src/lib/schemas/invoice.ts +43 -0
- package/src/lib/schemas/shared.ts +79 -0
- package/src/lib/translation.ts +38 -0
- package/src/lib/utils.ts +6 -0
- package/src/providers/entities-context.tsx +41 -0
- package/src/providers/entities-provider.tsx +201 -0
- package/src/providers/form-footer-context.tsx +72 -0
- package/src/providers/sdk-provider.tsx +164 -0
- package/src/providers/white-label-provider.tsx +91 -0
- package/src/providers/wl-subscription-provider.tsx +277 -0
- package/src/utils/string-helpers.ts +111 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared document items section for invoices and estimates
|
|
3
|
+
* Handles: item management (add, remove, reorder)
|
|
4
|
+
*/
|
|
5
|
+
import { PlusIcon } from "lucide-react";
|
|
6
|
+
import type { MutableRefObject } from "react";
|
|
7
|
+
import type { UseFormGetValues, UseFormSetValue, UseFormWatch } from "react-hook-form";
|
|
8
|
+
import { Button } from "@/ui/components/ui/button";
|
|
9
|
+
import DocumentAddItemForm from "./document-add-item-form";
|
|
10
|
+
import type { AnyControl } from "./form-types";
|
|
11
|
+
|
|
12
|
+
/** Map of item index to gross price mode */
|
|
13
|
+
export type PriceModesMap = Record<number, boolean>;
|
|
14
|
+
|
|
15
|
+
type DocumentItemsSectionProps = {
|
|
16
|
+
control: AnyControl;
|
|
17
|
+
|
|
18
|
+
watch: UseFormWatch<any>;
|
|
19
|
+
|
|
20
|
+
setValue: UseFormSetValue<any>;
|
|
21
|
+
|
|
22
|
+
getValues: UseFormGetValues<any>;
|
|
23
|
+
entityId: string;
|
|
24
|
+
currencyCode?: string;
|
|
25
|
+
onAddNewTax?: () => void;
|
|
26
|
+
t: (key: string) => string;
|
|
27
|
+
/** When true, tax controls are disabled (e.g., for VIES reverse charge) */
|
|
28
|
+
taxesDisabled?: boolean;
|
|
29
|
+
/** Message to show when taxes are disabled */
|
|
30
|
+
taxesDisabledMessage?: string;
|
|
31
|
+
/** Maximum number of taxes per item, derived from country rules. Defaults to 1. */
|
|
32
|
+
maxTaxesPerItem?: number;
|
|
33
|
+
/** Ref to collect price modes per item (for submit transformation) */
|
|
34
|
+
priceModesRef?: MutableRefObject<PriceModesMap>;
|
|
35
|
+
/** Initial price modes (from duplicated document) */
|
|
36
|
+
initialPriceModes?: PriceModesMap;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function DocumentItemsSection({
|
|
40
|
+
control,
|
|
41
|
+
watch,
|
|
42
|
+
setValue,
|
|
43
|
+
getValues,
|
|
44
|
+
entityId,
|
|
45
|
+
currencyCode,
|
|
46
|
+
onAddNewTax,
|
|
47
|
+
t,
|
|
48
|
+
taxesDisabled,
|
|
49
|
+
taxesDisabledMessage,
|
|
50
|
+
maxTaxesPerItem,
|
|
51
|
+
priceModesRef,
|
|
52
|
+
initialPriceModes = {},
|
|
53
|
+
}: DocumentItemsSectionProps) {
|
|
54
|
+
const addItem = () => {
|
|
55
|
+
const currentItems = getValues("items") || [];
|
|
56
|
+
setValue("items", [
|
|
57
|
+
...currentItems,
|
|
58
|
+
{
|
|
59
|
+
name: "",
|
|
60
|
+
description: "",
|
|
61
|
+
quantity: 1,
|
|
62
|
+
price: undefined,
|
|
63
|
+
taxes: [],
|
|
64
|
+
},
|
|
65
|
+
]);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const removeItem = (index: number) => {
|
|
69
|
+
const currentItems = getValues("items") || [];
|
|
70
|
+
setValue(
|
|
71
|
+
"items",
|
|
72
|
+
currentItems.filter((_: unknown, i: number) => i !== index),
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const moveItemUp = (index: number) => {
|
|
77
|
+
if (index === 0) return;
|
|
78
|
+
const items = getValues("items");
|
|
79
|
+
const newItems = [...items];
|
|
80
|
+
[newItems[index], newItems[index - 1]] = [newItems[index - 1], newItems[index]];
|
|
81
|
+
setValue("items", newItems);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const moveItemDown = (index: number) => {
|
|
85
|
+
const items = getValues("items");
|
|
86
|
+
if (index === items.length - 1) return;
|
|
87
|
+
const newItems = [...items];
|
|
88
|
+
[newItems[index], newItems[index + 1]] = [newItems[index + 1], newItems[index]];
|
|
89
|
+
setValue("items", newItems);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const items = watch("items") || [];
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div className="flex flex-col gap-4">
|
|
96
|
+
<h2 className="font-bold text-xl">{t("Items")}</h2>
|
|
97
|
+
|
|
98
|
+
{items.map((_: unknown, index: number) => (
|
|
99
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: index is stable
|
|
100
|
+
<div key={index}>
|
|
101
|
+
<DocumentAddItemForm
|
|
102
|
+
form={{ control, watch, setValue, getValues } as any}
|
|
103
|
+
index={index}
|
|
104
|
+
control={control}
|
|
105
|
+
entityId={entityId}
|
|
106
|
+
currencyCode={currencyCode}
|
|
107
|
+
onRemove={() => removeItem(index)}
|
|
108
|
+
onMoveUp={() => moveItemUp(index)}
|
|
109
|
+
onMoveDown={() => moveItemDown(index)}
|
|
110
|
+
onAddNewTax={onAddNewTax}
|
|
111
|
+
showRemove={items.length > 1}
|
|
112
|
+
showMoveUp={index > 0}
|
|
113
|
+
showMoveDown={index < items.length - 1}
|
|
114
|
+
t={t}
|
|
115
|
+
taxesDisabled={taxesDisabled}
|
|
116
|
+
taxesDisabledMessage={taxesDisabledMessage}
|
|
117
|
+
maxTaxesPerItem={maxTaxesPerItem}
|
|
118
|
+
initialIsGrossPrice={initialPriceModes[index] ?? false}
|
|
119
|
+
onPriceModeChange={(isGross) => {
|
|
120
|
+
if (priceModesRef) {
|
|
121
|
+
priceModesRef.current[index] = isGross;
|
|
122
|
+
}
|
|
123
|
+
}}
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
))}
|
|
127
|
+
|
|
128
|
+
<Button type="button" variant="outline" onClick={addItem} className="w-full cursor-pointer border-dashed">
|
|
129
|
+
<PlusIcon className="mr-2 h-4 w-4" /> {t("Add item")}
|
|
130
|
+
</Button>
|
|
131
|
+
</div>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared document recipient section for invoices and estimates
|
|
3
|
+
* Handles customer selection and inline customer form
|
|
4
|
+
*/
|
|
5
|
+
import { X } from "lucide-react";
|
|
6
|
+
import { useEffect, useRef } from "react";
|
|
7
|
+
import { FormInput } from "@/ui/components/form";
|
|
8
|
+
import { Button } from "@/ui/components/ui/button";
|
|
9
|
+
import { Label } from "@/ui/components/ui/label";
|
|
10
|
+
import { cn } from "@/ui/lib/utils";
|
|
11
|
+
import { CustomerAutocomplete } from "../../customers/customer-autocomplete";
|
|
12
|
+
import type { AnyControl } from "./form-types";
|
|
13
|
+
|
|
14
|
+
type DocumentRecipientSectionProps = {
|
|
15
|
+
control: AnyControl;
|
|
16
|
+
entityId: string;
|
|
17
|
+
|
|
18
|
+
onCustomerSelect: (customerId: string, customer: any) => void;
|
|
19
|
+
onCustomerClear: () => void;
|
|
20
|
+
showCustomerForm: boolean;
|
|
21
|
+
shouldFocusName: boolean;
|
|
22
|
+
selectedCustomerId?: string;
|
|
23
|
+
/** Initial customer name for display (used when duplicating documents) */
|
|
24
|
+
initialCustomerName?: string;
|
|
25
|
+
t: (key: string) => string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function DocumentRecipientSection({
|
|
29
|
+
control,
|
|
30
|
+
entityId,
|
|
31
|
+
onCustomerSelect,
|
|
32
|
+
onCustomerClear,
|
|
33
|
+
showCustomerForm,
|
|
34
|
+
shouldFocusName,
|
|
35
|
+
selectedCustomerId,
|
|
36
|
+
initialCustomerName,
|
|
37
|
+
t,
|
|
38
|
+
}: DocumentRecipientSectionProps) {
|
|
39
|
+
const nameInputRef = useRef<HTMLInputElement>(null);
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (showCustomerForm && shouldFocusName) {
|
|
43
|
+
// Small delay to ensure the input is rendered
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
nameInputRef.current?.focus();
|
|
46
|
+
}, 0);
|
|
47
|
+
}
|
|
48
|
+
}, [showCustomerForm, shouldFocusName]);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<div className="flex-1 space-y-4">
|
|
52
|
+
<div className="flex items-center justify-between">
|
|
53
|
+
<h2 className="font-bold text-xl">{t("Recipient")}</h2>
|
|
54
|
+
{showCustomerForm && (
|
|
55
|
+
<Button
|
|
56
|
+
type="button"
|
|
57
|
+
variant="outline"
|
|
58
|
+
size="sm"
|
|
59
|
+
onClick={onCustomerClear}
|
|
60
|
+
className={cn("h-7 cursor-pointer px-2 text-xs")}
|
|
61
|
+
>
|
|
62
|
+
<X className="size-3" />
|
|
63
|
+
{t("Clear")}
|
|
64
|
+
</Button>
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div className="space-y-2">
|
|
69
|
+
<Label>{t("Name")}</Label>
|
|
70
|
+
<CustomerAutocomplete
|
|
71
|
+
entityId={entityId}
|
|
72
|
+
value={selectedCustomerId}
|
|
73
|
+
onValueChange={onCustomerSelect}
|
|
74
|
+
onClear={onCustomerClear}
|
|
75
|
+
placeholder={t("Search or create customer...")}
|
|
76
|
+
initialDisplayName={initialCustomerName}
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{showCustomerForm && (
|
|
81
|
+
<>
|
|
82
|
+
<FormInput control={control} name="customer.address" placeholder={t("Address")} label="" ref={nameInputRef} />
|
|
83
|
+
|
|
84
|
+
<FormInput control={control} name="customer.address_2" placeholder={t("Address 2")} label="" />
|
|
85
|
+
|
|
86
|
+
<div className="grid grid-cols-2 gap-4">
|
|
87
|
+
<FormInput control={control} name="customer.post_code" placeholder={t("Post Code")} label="" />
|
|
88
|
+
<FormInput control={control} name="customer.city" placeholder={t("City")} label="" />
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<div className="grid grid-cols-2 gap-4">
|
|
92
|
+
<FormInput control={control} name="customer.state" placeholder={t("State")} label="" />
|
|
93
|
+
<FormInput control={control} name="customer.country" placeholder={t("Country")} label="" />
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<FormInput control={control} name="customer.tax_number" placeholder={t("Tax Number")} label="" />
|
|
97
|
+
</>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared form types for document components.
|
|
3
|
+
*
|
|
4
|
+
* This file provides type utilities to handle react-hook-form's complex generic types
|
|
5
|
+
* when passing control and other form utilities between components.
|
|
6
|
+
*/
|
|
7
|
+
import type { Control, FieldValues, UseFormGetValues, UseFormSetValue, UseFormWatch } from "react-hook-form";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* A more permissive Control type that accepts any form control.
|
|
11
|
+
* Use this in component props when the component doesn't need to know
|
|
12
|
+
* the exact form type, only that it has certain fields.
|
|
13
|
+
*/
|
|
14
|
+
// biome-ignore lint/suspicious/noExplicitAny: Required for react-hook-form compatibility
|
|
15
|
+
export type AnyControl = Control<any, any, any>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Base props for document form sections that receive form utilities.
|
|
19
|
+
* Components using these props can work with any form that has the expected fields.
|
|
20
|
+
*/
|
|
21
|
+
export type DocumentFormSectionProps = {
|
|
22
|
+
control: AnyControl;
|
|
23
|
+
// biome-ignore lint/suspicious/noExplicitAny: Required for react-hook-form compatibility
|
|
24
|
+
watch: UseFormWatch<any>;
|
|
25
|
+
// biome-ignore lint/suspicious/noExplicitAny: Required for react-hook-form compatibility
|
|
26
|
+
setValue: UseFormSetValue<any>;
|
|
27
|
+
// biome-ignore lint/suspicious/noExplicitAny: Required for react-hook-form compatibility
|
|
28
|
+
getValues: UseFormGetValues<any>;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Props for components that only need control (no watch/setValue/getValues).
|
|
33
|
+
*/
|
|
34
|
+
export type ControlOnlyProps = {
|
|
35
|
+
control: AnyControl;
|
|
36
|
+
};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as DocumentAddItemForm } from "./document-add-item-form";
|
|
2
|
+
export { default as DocumentAddItemTaxRateField } from "./document-add-item-tax-rate-field";
|
|
3
|
+
export { DocumentDetailsSection } from "./document-details-section";
|
|
4
|
+
export { DocumentItemsSection } from "./document-items-section";
|
|
5
|
+
export { DocumentRecipientSection } from "./document-recipient-section";
|
|
6
|
+
export { LiveInvoicePreview } from "./live-preview";
|
|
7
|
+
export { MarkAsPaidSection } from "./mark-as-paid-section";
|
|
8
|
+
export { prepareDocumentSubmission } from "./prepare-document-submission";
|
|
9
|
+
export { useDocumentCustomerForm } from "./use-document-customer-form";
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { CreateInvoiceRequest } from "@spaceinvoices/js-sdk";
|
|
4
|
+
import { Loader2 } from "lucide-react";
|
|
5
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
6
|
+
import { cn } from "@/ui/lib/utils";
|
|
7
|
+
import { useEntities } from "@/ui/providers/entities-context";
|
|
8
|
+
import { useSDK } from "@/ui/providers/sdk-provider";
|
|
9
|
+
import { ScaledDocumentPreview } from "../shared/scaled-document-preview";
|
|
10
|
+
import { useA4Scaling } from "../shared/use-a4-scaling";
|
|
11
|
+
import { filterUnresolvedTaxes } from "./prepare-preview-data";
|
|
12
|
+
|
|
13
|
+
export type PdfTemplateId = "modern" | "classic" | "minimal" | "fashion";
|
|
14
|
+
|
|
15
|
+
type LiveInvoicePreviewProps = {
|
|
16
|
+
data: Partial<CreateInvoiceRequest>;
|
|
17
|
+
currency?: string;
|
|
18
|
+
template?: PdfTemplateId;
|
|
19
|
+
className?: string;
|
|
20
|
+
apiBaseUrl?: string;
|
|
21
|
+
getAuthToken?: () => string | undefined;
|
|
22
|
+
/** Locale for document rendering (e.g., "en-US", "sl-SI"). Uses user's UI language. */
|
|
23
|
+
locale?: string;
|
|
24
|
+
/** Fixed scale to use instead of dynamic scaling. Useful to prevent layout shifts. */
|
|
25
|
+
fixedScale?: number;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Live Invoice Preview Component
|
|
30
|
+
*
|
|
31
|
+
* Generates a real-time HTML preview of an invoice as the user fills out the form.
|
|
32
|
+
* Uses debouncing to avoid excessive API calls and shows loading states.
|
|
33
|
+
*
|
|
34
|
+
* Features:
|
|
35
|
+
* - Debounced API requests (500ms delay after user stops typing)
|
|
36
|
+
* - Loading skeleton while fetching preview
|
|
37
|
+
* - Error handling with fallback display
|
|
38
|
+
* - Fully styled HTML with scoped CSS (prevents style leakage)
|
|
39
|
+
*/
|
|
40
|
+
export function LiveInvoicePreview({
|
|
41
|
+
data,
|
|
42
|
+
currency: _currency = "EUR",
|
|
43
|
+
template,
|
|
44
|
+
className,
|
|
45
|
+
locale,
|
|
46
|
+
fixedScale,
|
|
47
|
+
}: LiveInvoicePreviewProps) {
|
|
48
|
+
const [previewHtml, setPreviewHtml] = useState<string>("");
|
|
49
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
50
|
+
const [error, setError] = useState<string | null>(null);
|
|
51
|
+
const { activeEntity } = useEntities();
|
|
52
|
+
const { sdk } = useSDK();
|
|
53
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
54
|
+
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
55
|
+
|
|
56
|
+
const { containerRef, contentRef, scale: dynamicScale, contentHeight, A4_WIDTH_PX } = useA4Scaling(previewHtml);
|
|
57
|
+
const scale = fixedScale ?? dynamicScale;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Fetch preview from API
|
|
61
|
+
*/
|
|
62
|
+
const fetchPreview = useCallback(
|
|
63
|
+
async (invoiceData: Partial<CreateInvoiceRequest>) => {
|
|
64
|
+
// Don't fetch if no items exist (name can be empty for preview)
|
|
65
|
+
if (!invoiceData.items || invoiceData.items.length === 0) {
|
|
66
|
+
setPreviewHtml("");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Cancel any pending request
|
|
71
|
+
if (abortControllerRef.current) {
|
|
72
|
+
abortControllerRef.current.abort();
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Create new abort controller for this request
|
|
76
|
+
const abortController = new AbortController();
|
|
77
|
+
abortControllerRef.current = abortController;
|
|
78
|
+
|
|
79
|
+
setIsLoading(true);
|
|
80
|
+
setError(null);
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
if (!sdk || !activeEntity?.id) {
|
|
84
|
+
throw new Error("Authentication required");
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Prepare preview data with active entity as issuer (if not already set)
|
|
88
|
+
// Exclude 'number' as it's auto-generated by the render endpoint
|
|
89
|
+
const { number: _number, ...invoiceDataWithoutNumber } = invoiceData as any;
|
|
90
|
+
const previewData = {
|
|
91
|
+
...invoiceDataWithoutNumber,
|
|
92
|
+
// Filter out unresolved tax_ids (race condition: form may add
|
|
93
|
+
// { tax_id: undefined } before the tax dropdown auto-selects a value)
|
|
94
|
+
items: filterUnresolvedTaxes(invoiceData.items),
|
|
95
|
+
issuer: invoiceData.issuer || {
|
|
96
|
+
name: activeEntity.name,
|
|
97
|
+
address: activeEntity.address,
|
|
98
|
+
address_2: activeEntity.address_2,
|
|
99
|
+
post_code: activeEntity.post_code,
|
|
100
|
+
city: activeEntity.city,
|
|
101
|
+
state: activeEntity.state,
|
|
102
|
+
country: activeEntity.country,
|
|
103
|
+
tax_number: activeEntity.tax_number,
|
|
104
|
+
},
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Call the render API using the SDK wrapper
|
|
108
|
+
const html = await sdk.invoices.renderInvoicePreview(
|
|
109
|
+
previewData as any,
|
|
110
|
+
{ partial: "true", template, locale },
|
|
111
|
+
{ entity_id: activeEntity.id },
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
setPreviewHtml(html);
|
|
115
|
+
setError(null);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
// Ignore abort errors (they're expected when user keeps typing)
|
|
118
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Ignore 422 validation errors - expected while user is still filling the form
|
|
123
|
+
if (err instanceof Error && "status" in err && (err as any).status === 422) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
setError(err instanceof Error ? err.message : "Failed to generate preview");
|
|
128
|
+
setPreviewHtml("");
|
|
129
|
+
} finally {
|
|
130
|
+
// Only set loading to false if this request wasn't aborted
|
|
131
|
+
if (!abortController.signal.aborted) {
|
|
132
|
+
setIsLoading(false);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
[
|
|
137
|
+
activeEntity?.id,
|
|
138
|
+
activeEntity?.address,
|
|
139
|
+
activeEntity?.address_2,
|
|
140
|
+
activeEntity?.country,
|
|
141
|
+
activeEntity?.state,
|
|
142
|
+
activeEntity?.tax_number,
|
|
143
|
+
activeEntity?.post_code,
|
|
144
|
+
activeEntity?.city,
|
|
145
|
+
activeEntity?.name,
|
|
146
|
+
template,
|
|
147
|
+
locale,
|
|
148
|
+
sdk,
|
|
149
|
+
],
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Debounced preview fetch
|
|
154
|
+
* Waits 500ms after user stops typing before fetching
|
|
155
|
+
*/
|
|
156
|
+
useEffect(() => {
|
|
157
|
+
// Clear previous timeout
|
|
158
|
+
if (debounceTimeoutRef.current) {
|
|
159
|
+
clearTimeout(debounceTimeoutRef.current);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Set new timeout
|
|
163
|
+
debounceTimeoutRef.current = setTimeout(() => {
|
|
164
|
+
fetchPreview(data);
|
|
165
|
+
}, 500);
|
|
166
|
+
|
|
167
|
+
// Cleanup
|
|
168
|
+
return () => {
|
|
169
|
+
if (debounceTimeoutRef.current) {
|
|
170
|
+
clearTimeout(debounceTimeoutRef.current);
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
}, [data, fetchPreview]);
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Cleanup on unmount
|
|
177
|
+
*/
|
|
178
|
+
useEffect(() => {
|
|
179
|
+
return () => {
|
|
180
|
+
// Cancel any pending request
|
|
181
|
+
if (abortControllerRef.current) {
|
|
182
|
+
abortControllerRef.current.abort();
|
|
183
|
+
}
|
|
184
|
+
// Clear timeout
|
|
185
|
+
if (debounceTimeoutRef.current) {
|
|
186
|
+
clearTimeout(debounceTimeoutRef.current);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}, []);
|
|
190
|
+
|
|
191
|
+
return (
|
|
192
|
+
<div ref={containerRef} className={cn("relative", className)}>
|
|
193
|
+
{/* Loading overlay */}
|
|
194
|
+
{isLoading && (
|
|
195
|
+
<div className="absolute inset-0 z-10 flex items-center justify-center bg-background/80 backdrop-blur-sm">
|
|
196
|
+
<div className="flex flex-col items-center gap-2">
|
|
197
|
+
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
|
198
|
+
<p className="text-muted-foreground text-sm">Generating preview...</p>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
)}
|
|
202
|
+
|
|
203
|
+
{/* Error state */}
|
|
204
|
+
{error && !isLoading && (
|
|
205
|
+
<div className="flex min-h-[200px] items-center justify-center rounded-lg border border-destructive/50 bg-destructive/10 p-8">
|
|
206
|
+
<div className="text-center">
|
|
207
|
+
<p className="font-semibold text-destructive">Preview Error</p>
|
|
208
|
+
<p className="text-muted-foreground text-sm">{error}</p>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
|
|
213
|
+
{/* Empty state */}
|
|
214
|
+
{!previewHtml && !isLoading && !error && (
|
|
215
|
+
<div className="flex min-h-[200px] items-center justify-center rounded-lg border border-dashed p-8">
|
|
216
|
+
<div className="text-center">
|
|
217
|
+
<p className="font-semibold text-muted-foreground">Invoice Preview</p>
|
|
218
|
+
<p className="text-muted-foreground text-sm">Start filling the form to see a live preview</p>
|
|
219
|
+
</div>
|
|
220
|
+
</div>
|
|
221
|
+
)}
|
|
222
|
+
|
|
223
|
+
{/* Preview - Scoped HTML injection with A4 scaling */}
|
|
224
|
+
{previewHtml && !error && (
|
|
225
|
+
<ScaledDocumentPreview
|
|
226
|
+
htmlContent={previewHtml}
|
|
227
|
+
scale={scale}
|
|
228
|
+
contentHeight={contentHeight}
|
|
229
|
+
A4_WIDTH_PX={A4_WIDTH_PX}
|
|
230
|
+
contentRef={contentRef}
|
|
231
|
+
/>
|
|
232
|
+
)}
|
|
233
|
+
</div>
|
|
234
|
+
);
|
|
235
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { HelpCircle } from "lucide-react";
|
|
2
|
+
import { Checkbox } from "@/ui/components/ui/checkbox";
|
|
3
|
+
import { Label } from "@/ui/components/ui/label";
|
|
4
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/ui/components/ui/select";
|
|
5
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@/ui/components/ui/tooltip";
|
|
6
|
+
import { cn } from "@/ui/lib/utils";
|
|
7
|
+
|
|
8
|
+
// Regular payment types (excluding special types like credit_note and advance)
|
|
9
|
+
const regularPaymentTypes = ["cash", "bank_transfer", "card", "check", "other"] as const;
|
|
10
|
+
|
|
11
|
+
// Labels for payment types (used for translations)
|
|
12
|
+
const PAYMENT_TYPE_LABELS: Record<string, string> = {
|
|
13
|
+
cash: "Cash",
|
|
14
|
+
bank_transfer: "Bank Transfer",
|
|
15
|
+
card: "Card",
|
|
16
|
+
check: "Check",
|
|
17
|
+
other: "Other",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type MarkAsPaidSectionProps = {
|
|
21
|
+
/** Whether the document is marked as paid */
|
|
22
|
+
checked: boolean;
|
|
23
|
+
/** Called when the checkbox changes */
|
|
24
|
+
onCheckedChange: (checked: boolean) => void;
|
|
25
|
+
/** Selected payment type */
|
|
26
|
+
paymentType: string;
|
|
27
|
+
/** Called when payment type changes */
|
|
28
|
+
onPaymentTypeChange: (value: string) => void;
|
|
29
|
+
/** Translation function */
|
|
30
|
+
t: (key: string) => string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function MarkAsPaidSection({
|
|
34
|
+
checked,
|
|
35
|
+
onCheckedChange,
|
|
36
|
+
paymentType,
|
|
37
|
+
onPaymentTypeChange,
|
|
38
|
+
t,
|
|
39
|
+
}: MarkAsPaidSectionProps) {
|
|
40
|
+
return (
|
|
41
|
+
<div className={cn("flex flex-col gap-4 rounded-md border p-4", checked && "md:flex-row md:items-center md:gap-6")}>
|
|
42
|
+
<div className="flex flex-row items-center space-x-3 space-y-0">
|
|
43
|
+
<Checkbox checked={checked} onCheckedChange={(v) => onCheckedChange(v === true)} />
|
|
44
|
+
<div className="flex items-center gap-1 leading-none">
|
|
45
|
+
<Label>{checked ? t("Paid") : t("Mark as Paid")}</Label>
|
|
46
|
+
{!checked && (
|
|
47
|
+
<Tooltip>
|
|
48
|
+
<TooltipTrigger asChild>
|
|
49
|
+
<button
|
|
50
|
+
type="button"
|
|
51
|
+
className="rounded-full p-1 transition-colors hover:bg-accent"
|
|
52
|
+
onClick={(e) => e.preventDefault()}
|
|
53
|
+
>
|
|
54
|
+
<HelpCircle className="size-4 text-muted-foreground" />
|
|
55
|
+
</button>
|
|
56
|
+
</TooltipTrigger>
|
|
57
|
+
<TooltipContent side="top">{t("Invoice will be marked as fully paid upon creation")}</TooltipContent>
|
|
58
|
+
</Tooltip>
|
|
59
|
+
)}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
{checked && (
|
|
64
|
+
<>
|
|
65
|
+
<div className="hidden flex-1 md:block" />
|
|
66
|
+
<Select value={paymentType} onValueChange={(v) => v && onPaymentTypeChange(v)}>
|
|
67
|
+
<SelectTrigger className="w-full md:w-fit">
|
|
68
|
+
<SelectValue placeholder={t("Select payment type")}>{t(PAYMENT_TYPE_LABELS[paymentType])}</SelectValue>
|
|
69
|
+
</SelectTrigger>
|
|
70
|
+
<SelectContent>
|
|
71
|
+
{regularPaymentTypes.map((type) => (
|
|
72
|
+
<SelectItem key={type} value={type}>
|
|
73
|
+
{t(PAYMENT_TYPE_LABELS[type])}
|
|
74
|
+
</SelectItem>
|
|
75
|
+
))}
|
|
76
|
+
</SelectContent>
|
|
77
|
+
</Select>
|
|
78
|
+
</>
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|