@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,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advance invoice form schema.
|
|
3
|
+
*
|
|
4
|
+
* Requires: ./shared.ts
|
|
5
|
+
*/
|
|
6
|
+
import type { CreateAdvanceInvoiceBody } from "@spaceinvoices/js-sdk";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { customerSchema, lineItemSchema, transformItemsForApi } from "./shared";
|
|
9
|
+
|
|
10
|
+
// Advance invoices don't have payment_terms - they are documents requesting payment
|
|
11
|
+
export const advanceInvoiceFormSchema = z.object({
|
|
12
|
+
number: z.string().max(100).optional(),
|
|
13
|
+
date: z.string().optional(),
|
|
14
|
+
date_due: z.string().optional(),
|
|
15
|
+
customer_id: z.string().nullish(),
|
|
16
|
+
customer: customerSchema,
|
|
17
|
+
items: z.array(lineItemSchema).min(1),
|
|
18
|
+
note: z.string().nullish(),
|
|
19
|
+
currency_code: z.string().max(3).optional(),
|
|
20
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
21
|
+
linked_documents: z.array(z.string()).optional(),
|
|
22
|
+
furs: z
|
|
23
|
+
.object({
|
|
24
|
+
business_premise_name: z.string().optional(),
|
|
25
|
+
electronic_device_name: z.string().optional(),
|
|
26
|
+
operator_tax_number: z.string().optional(),
|
|
27
|
+
operator_label: z.string().optional(),
|
|
28
|
+
skip: z.boolean().optional(),
|
|
29
|
+
})
|
|
30
|
+
.optional(),
|
|
31
|
+
eslog: z.object({ validation_enabled: z.boolean().optional() }).optional(),
|
|
32
|
+
bank_account_index: z.number().optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export type AdvanceInvoiceFormValues = z.infer<typeof advanceInvoiceFormSchema>;
|
|
36
|
+
|
|
37
|
+
/** Convert form values to API request */
|
|
38
|
+
export function toCreateAdvanceInvoiceRequest(values: AdvanceInvoiceFormValues): CreateAdvanceInvoiceBody {
|
|
39
|
+
return {
|
|
40
|
+
...values,
|
|
41
|
+
items: transformItemsForApi(values.items ?? []),
|
|
42
|
+
} as CreateAdvanceInvoiceBody;
|
|
43
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credit note form schema.
|
|
3
|
+
*
|
|
4
|
+
* Requires: ./shared.ts
|
|
5
|
+
*/
|
|
6
|
+
import type { CreateCreditNoteBody } from "@spaceinvoices/js-sdk";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { customerSchema, lineItemSchema, transformItemsForApi } from "./shared";
|
|
9
|
+
|
|
10
|
+
export const creditNoteFormSchema = z.object({
|
|
11
|
+
number: z.string().max(100).optional(),
|
|
12
|
+
date: z.string().optional(),
|
|
13
|
+
customer_id: z.string().nullish(),
|
|
14
|
+
customer: customerSchema,
|
|
15
|
+
items: z.array(lineItemSchema).min(1),
|
|
16
|
+
note: z.string().nullish(),
|
|
17
|
+
payment_terms: z.string().nullish(),
|
|
18
|
+
currency_code: z.string().max(3).optional(),
|
|
19
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
20
|
+
linked_documents: z.array(z.string()).optional(),
|
|
21
|
+
eslog: z.object({ validation_enabled: z.boolean().optional() }).optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export type CreditNoteFormValues = z.infer<typeof creditNoteFormSchema>;
|
|
25
|
+
|
|
26
|
+
/** Convert form values to API request */
|
|
27
|
+
export function toCreateCreditNoteRequest(values: CreditNoteFormValues): CreateCreditNoteBody {
|
|
28
|
+
return {
|
|
29
|
+
...values,
|
|
30
|
+
items: transformItemsForApi(values.items ?? []),
|
|
31
|
+
} as CreateCreditNoteBody;
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Estimate form schema.
|
|
3
|
+
*
|
|
4
|
+
* Requires: ./shared.ts
|
|
5
|
+
*/
|
|
6
|
+
import type { CreateEstimateBody } from "@spaceinvoices/js-sdk";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { customerSchema, lineItemSchema, transformItemsForApi } from "./shared";
|
|
9
|
+
|
|
10
|
+
export const estimateFormSchema = z.object({
|
|
11
|
+
number: z.string().max(100).optional(),
|
|
12
|
+
date: z.string().optional(),
|
|
13
|
+
date_valid_till: z.string().optional(),
|
|
14
|
+
customer_id: z.string().nullish(),
|
|
15
|
+
customer: customerSchema,
|
|
16
|
+
items: z.array(lineItemSchema).min(1),
|
|
17
|
+
note: z.string().nullish(),
|
|
18
|
+
payment_terms: z.string().nullish(),
|
|
19
|
+
currency_code: z.string().max(3).optional(),
|
|
20
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export type EstimateFormValues = z.infer<typeof estimateFormSchema>;
|
|
24
|
+
|
|
25
|
+
/** Convert form values to API request */
|
|
26
|
+
export function toCreateEstimateRequest(values: EstimateFormValues): CreateEstimateBody {
|
|
27
|
+
return {
|
|
28
|
+
...values,
|
|
29
|
+
items: transformItemsForApi(values.items ?? []),
|
|
30
|
+
} as CreateEstimateBody;
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Form schemas for document creation.
|
|
3
|
+
*
|
|
4
|
+
* For copy-paste usage, grab the specific schema file you need + shared.ts:
|
|
5
|
+
* - shared.ts (required) - common building blocks
|
|
6
|
+
* - invoice.ts - invoice form
|
|
7
|
+
* - credit-note.ts - credit note form
|
|
8
|
+
* - estimate.ts - estimate form
|
|
9
|
+
* - advance-invoice.ts - advance invoice form
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export * from "./advance-invoice";
|
|
13
|
+
export * from "./credit-note";
|
|
14
|
+
export * from "./estimate";
|
|
15
|
+
// Document-specific schemas
|
|
16
|
+
export * from "./invoice";
|
|
17
|
+
// Shared building blocks
|
|
18
|
+
export * from "./shared";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invoice form schema.
|
|
3
|
+
*
|
|
4
|
+
* Requires: ./shared.ts
|
|
5
|
+
*/
|
|
6
|
+
import type { CreateInvoiceBody } from "@spaceinvoices/js-sdk";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { customerSchema, lineItemSchema, transformItemsForApi } from "./shared";
|
|
9
|
+
|
|
10
|
+
export const invoiceFormSchema = z.object({
|
|
11
|
+
number: z.string().max(100).optional(),
|
|
12
|
+
date: z.string().optional(),
|
|
13
|
+
date_due: z.string().optional(),
|
|
14
|
+
customer_id: z.string().nullish(),
|
|
15
|
+
customer: customerSchema,
|
|
16
|
+
items: z.array(lineItemSchema).min(1),
|
|
17
|
+
note: z.string().nullish(),
|
|
18
|
+
payment_terms: z.string().nullish(),
|
|
19
|
+
currency_code: z.string().max(3).optional(),
|
|
20
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
21
|
+
linked_documents: z.array(z.string()).optional(),
|
|
22
|
+
furs: z
|
|
23
|
+
.object({
|
|
24
|
+
business_premise_name: z.string().optional(),
|
|
25
|
+
electronic_device_name: z.string().optional(),
|
|
26
|
+
operator_tax_number: z.string().optional(),
|
|
27
|
+
operator_label: z.string().optional(),
|
|
28
|
+
skip: z.boolean().optional(),
|
|
29
|
+
})
|
|
30
|
+
.optional(),
|
|
31
|
+
eslog: z.object({ validation_enabled: z.boolean().optional() }).optional(),
|
|
32
|
+
bank_account_index: z.number().optional(),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export type InvoiceFormValues = z.infer<typeof invoiceFormSchema>;
|
|
36
|
+
|
|
37
|
+
/** Convert form values to API request */
|
|
38
|
+
export function toCreateInvoiceRequest(values: InvoiceFormValues): CreateInvoiceBody {
|
|
39
|
+
return {
|
|
40
|
+
...values,
|
|
41
|
+
items: transformItemsForApi(values.items ?? []),
|
|
42
|
+
} as CreateInvoiceBody;
|
|
43
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared form schema building blocks.
|
|
3
|
+
*
|
|
4
|
+
* These are reusable across document forms (invoice, estimate, credit note, etc.)
|
|
5
|
+
* Copy this file along with any document-specific schema you need.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
|
|
9
|
+
/** Customer schema - for inline customer data on documents */
|
|
10
|
+
export const customerSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
name: z.string().nullish(),
|
|
13
|
+
email: z.string().email().nullish(),
|
|
14
|
+
address: z.string().nullish(),
|
|
15
|
+
address_2: z.string().nullish(),
|
|
16
|
+
post_code: z.string().nullish(),
|
|
17
|
+
city: z.string().nullish(),
|
|
18
|
+
state: z.string().nullish(),
|
|
19
|
+
country: z.string().nullish(),
|
|
20
|
+
country_code: z.string().nullish(),
|
|
21
|
+
tax_number: z.string().nullish(),
|
|
22
|
+
save_customer: z.boolean().optional(),
|
|
23
|
+
})
|
|
24
|
+
.nullish();
|
|
25
|
+
|
|
26
|
+
export type CustomerFormData = z.infer<typeof customerSchema>;
|
|
27
|
+
|
|
28
|
+
/** Tax schema for line items */
|
|
29
|
+
export const taxSchema = z.object({
|
|
30
|
+
rate: z.number().optional(),
|
|
31
|
+
tax_id: z.string().optional(),
|
|
32
|
+
classification: z.string().optional(),
|
|
33
|
+
reverse_charge: z.boolean().optional(),
|
|
34
|
+
amount: z.number().optional(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type TaxFormData = z.infer<typeof taxSchema>;
|
|
38
|
+
|
|
39
|
+
/** Discount schema for line items */
|
|
40
|
+
export const discountSchema = z.object({
|
|
41
|
+
value: z.number().min(0),
|
|
42
|
+
type: z.enum(["percent", "amount"]).optional().default("percent"),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export type DiscountFormData = z.infer<typeof discountSchema>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Line item schema with UI-only is_gross_price flag.
|
|
49
|
+
*
|
|
50
|
+
* The `is_gross_price` field is UI-only - when true, the `price` field
|
|
51
|
+
* contains the gross (tax-inclusive) price and should be sent as `gross_price`
|
|
52
|
+
* to the API instead of `price`.
|
|
53
|
+
*/
|
|
54
|
+
export const lineItemSchema = z.object({
|
|
55
|
+
name: z.string().min(1),
|
|
56
|
+
description: z.string().nullish(),
|
|
57
|
+
price: z.number().optional(),
|
|
58
|
+
gross_price: z.number().optional(),
|
|
59
|
+
quantity: z.number(),
|
|
60
|
+
unit: z.string().nullish(),
|
|
61
|
+
taxes: z.array(taxSchema).optional(),
|
|
62
|
+
discounts: z.array(discountSchema).max(5).optional(),
|
|
63
|
+
metadata: z.unknown().optional(),
|
|
64
|
+
/** UI-only: when true, price field contains gross (tax-inclusive) price */
|
|
65
|
+
is_gross_price: z.boolean().optional().default(false),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
export type LineItemFormData = z.infer<typeof lineItemSchema>;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Transform line items for API submission.
|
|
72
|
+
* Converts is_gross_price flag to proper price/gross_price fields.
|
|
73
|
+
*/
|
|
74
|
+
export function transformItemsForApi<T extends LineItemFormData>(items: T[]) {
|
|
75
|
+
return items.map(({ is_gross_price, price, ...item }) => ({
|
|
76
|
+
...item,
|
|
77
|
+
...(is_gross_price ? { gross_price: price } : { price }),
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
type TranslationFunction = (key: string) => string;
|
|
4
|
+
|
|
5
|
+
export type ComponentTranslationProps = {
|
|
6
|
+
t?: TranslationFunction;
|
|
7
|
+
namespace?: string;
|
|
8
|
+
locale?: string;
|
|
9
|
+
translations?: Record<string, Record<string, string>>;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function createTranslation({ t, namespace, locale = "en", translations = {} }: ComponentTranslationProps = {}) {
|
|
13
|
+
return useCallback(
|
|
14
|
+
(key: string): string => {
|
|
15
|
+
// 1. If external translation function provided, use it
|
|
16
|
+
if (t) {
|
|
17
|
+
const fullKey = namespace ? `${namespace}.${key}` : key;
|
|
18
|
+
const result = t(fullKey);
|
|
19
|
+
|
|
20
|
+
// If translation found (result is not the key), return it.
|
|
21
|
+
// We check against both fullKey (standard i18next behavior) and key (in case namespace was stripped)
|
|
22
|
+
if (result !== fullKey && result !== key) {
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// 2. Look up in local translations for current locale
|
|
28
|
+
if (locale !== "en" && translations[locale]) {
|
|
29
|
+
const translation = translations[locale][key];
|
|
30
|
+
if (translation) return translation;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 3. Fall back to key itself (which is the English text)
|
|
34
|
+
return key;
|
|
35
|
+
},
|
|
36
|
+
[t, namespace, locale, translations],
|
|
37
|
+
);
|
|
38
|
+
}
|
package/src/lib/utils.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { GetEntities200DataItem } from "@spaceinvoices/js-sdk";
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
|
|
5
|
+
/** Entity type with country_rules included (from getEntities response) */
|
|
6
|
+
export type Entity = GetEntities200DataItem;
|
|
7
|
+
|
|
8
|
+
export type EntityEnvironment = "live" | "sandbox";
|
|
9
|
+
|
|
10
|
+
export type EntitiesContextType = {
|
|
11
|
+
entities: Entity[];
|
|
12
|
+
activeEntity: Entity | null;
|
|
13
|
+
setActiveEntity: (entity: Entity | null) => void;
|
|
14
|
+
environment: EntityEnvironment;
|
|
15
|
+
setEnvironment: (environment: EntityEnvironment) => void;
|
|
16
|
+
isLoading: boolean;
|
|
17
|
+
refetchEntities: () => Promise<void>;
|
|
18
|
+
isError: boolean;
|
|
19
|
+
error: Error | null;
|
|
20
|
+
status: "loading" | "error" | "success" | "pending";
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const EntitiesContext = createContext<EntitiesContextType | undefined>(undefined);
|
|
24
|
+
|
|
25
|
+
export const useEntities = () => {
|
|
26
|
+
const context = useContext(EntitiesContext);
|
|
27
|
+
if (!context) {
|
|
28
|
+
throw new Error("useEntities must be used within an EntitiesProvider");
|
|
29
|
+
}
|
|
30
|
+
return context;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** Optional version that returns undefined when outside EntitiesProvider (for public views) */
|
|
34
|
+
export const useEntitiesOptional = () => {
|
|
35
|
+
return useContext(EntitiesContext);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const useActiveEntity = () => {
|
|
39
|
+
const { activeEntity, setActiveEntity } = useEntities();
|
|
40
|
+
return { activeEntity, setActiveEntity };
|
|
41
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { useQuery } from "@tanstack/react-query";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
4
|
+
import { useCookies } from "react-cookie";
|
|
5
|
+
import { ACTIVE_ENTITY_COOKIE, ACTIVE_ENVIRONMENT_COOKIE } from "@/ui/components/entities/keys";
|
|
6
|
+
|
|
7
|
+
import { useSDK } from "@/ui/providers/sdk-provider";
|
|
8
|
+
|
|
9
|
+
import { EntitiesContext, type Entity, type EntityEnvironment } from "./entities-context";
|
|
10
|
+
|
|
11
|
+
// Define a constant for the entities cache key
|
|
12
|
+
export const ENTITIES_QUERY_KEY = ["entities"] as const;
|
|
13
|
+
|
|
14
|
+
function LoadingFallback() {
|
|
15
|
+
return (
|
|
16
|
+
<div className="fixed inset-0 flex items-center justify-center bg-background/50">
|
|
17
|
+
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary border-t-transparent" />
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type EntitiesProviderProps = {
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
initialActiveEntity?: Entity | null;
|
|
25
|
+
onNoEntities?: () => void;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export function EntitiesProvider({ children, initialActiveEntity, onNoEntities }: EntitiesProviderProps) {
|
|
29
|
+
const { sdk, isInitialized } = useSDK();
|
|
30
|
+
const [cookies, setCookie, removeCookie] = useCookies([ACTIVE_ENTITY_COOKIE, ACTIVE_ENVIRONMENT_COOKIE]);
|
|
31
|
+
const isInitialMount = useRef(true);
|
|
32
|
+
const initialEnvironmentFromCookie = cookies[ACTIVE_ENVIRONMENT_COOKIE] as EntityEnvironment | undefined;
|
|
33
|
+
const initialEntityFromCookie = cookies[ACTIVE_ENTITY_COOKIE] as Entity | undefined;
|
|
34
|
+
|
|
35
|
+
const resolvedInitialEnvironment: EntityEnvironment =
|
|
36
|
+
initialEnvironmentFromCookie ??
|
|
37
|
+
(initialEntityFromCookie?.environment as EntityEnvironment | undefined) ??
|
|
38
|
+
(initialActiveEntity?.environment as EntityEnvironment | undefined) ??
|
|
39
|
+
"live";
|
|
40
|
+
|
|
41
|
+
const [environment, setEnvironmentState] = useState<EntityEnvironment>(resolvedInitialEnvironment);
|
|
42
|
+
const previousEnvironment = useRef(environment);
|
|
43
|
+
|
|
44
|
+
// Initialize active entity from cookie or prop that matches the selected environment
|
|
45
|
+
const [activeEntityState, setActiveEntityState] = useState<Entity | null>(() => {
|
|
46
|
+
try {
|
|
47
|
+
if (initialEntityFromCookie && initialEntityFromCookie.environment === resolvedInitialEnvironment) {
|
|
48
|
+
return initialEntityFromCookie;
|
|
49
|
+
}
|
|
50
|
+
} catch (_e) {
|
|
51
|
+
// Ignore cookie parsing errors
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (initialActiveEntity && initialActiveEntity.environment === resolvedInitialEnvironment) {
|
|
55
|
+
return initialActiveEntity;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return null;
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const {
|
|
62
|
+
data: entities = [],
|
|
63
|
+
isLoading,
|
|
64
|
+
refetch,
|
|
65
|
+
isError,
|
|
66
|
+
error,
|
|
67
|
+
status,
|
|
68
|
+
} = useQuery({
|
|
69
|
+
queryKey: [...ENTITIES_QUERY_KEY, environment],
|
|
70
|
+
queryFn: async () => {
|
|
71
|
+
if (!sdk) return [];
|
|
72
|
+
|
|
73
|
+
// Pass environment to filter entities for user tokens
|
|
74
|
+
const response = await sdk.entities.list({ limit: 100, environment });
|
|
75
|
+
return response.data;
|
|
76
|
+
},
|
|
77
|
+
enabled: !!sdk && isInitialized,
|
|
78
|
+
staleTime: 1000 * 60 * 5,
|
|
79
|
+
gcTime: 1000 * 60 * 60,
|
|
80
|
+
retry: 2,
|
|
81
|
+
refetchOnMount: true,
|
|
82
|
+
refetchOnWindowFocus: false,
|
|
83
|
+
refetchOnReconnect: true,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Redirect to add entity page when no entities exist
|
|
87
|
+
const hasCalledNoEntities = useRef(false);
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
if (!isLoading && entities.length === 0 && !hasCalledNoEntities.current) {
|
|
90
|
+
hasCalledNoEntities.current = true;
|
|
91
|
+
onNoEntities?.();
|
|
92
|
+
}
|
|
93
|
+
}, [isLoading, entities.length, onNoEntities]);
|
|
94
|
+
|
|
95
|
+
// Memoize entities to prevent unnecessary re-renders
|
|
96
|
+
const memoizedEntities = useMemo(() => entities, [entities]);
|
|
97
|
+
|
|
98
|
+
useEffect(() => {
|
|
99
|
+
if (previousEnvironment.current !== environment) {
|
|
100
|
+
previousEnvironment.current = environment;
|
|
101
|
+
setActiveEntityState(null);
|
|
102
|
+
}
|
|
103
|
+
}, [environment]);
|
|
104
|
+
|
|
105
|
+
// Sync active entity when entities list changes
|
|
106
|
+
// Use ref to read current activeEntityState without causing re-runs
|
|
107
|
+
const activeEntityRef = useRef(activeEntityState);
|
|
108
|
+
activeEntityRef.current = activeEntityState;
|
|
109
|
+
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!memoizedEntities.length) return;
|
|
112
|
+
|
|
113
|
+
const currentActive = activeEntityRef.current;
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
if (currentActive) {
|
|
117
|
+
const updatedEntity = memoizedEntities.find((entity) => entity.id === currentActive.id);
|
|
118
|
+
|
|
119
|
+
if (updatedEntity) {
|
|
120
|
+
// Always update with fresh data from the server
|
|
121
|
+
setActiveEntityState(updatedEntity);
|
|
122
|
+
} else {
|
|
123
|
+
// If active entity no longer exists, fall back to first entity
|
|
124
|
+
setActiveEntityState(memoizedEntities[0]);
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
setActiveEntityState(memoizedEntities[0]);
|
|
128
|
+
}
|
|
129
|
+
} catch (_e) {
|
|
130
|
+
if (memoizedEntities.length > 0) {
|
|
131
|
+
setActiveEntityState(memoizedEntities[0]);
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
if (isInitialMount.current) {
|
|
135
|
+
isInitialMount.current = false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}, [memoizedEntities]); // Only depend on entities list
|
|
139
|
+
|
|
140
|
+
// Update cookie when active entity changes
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (activeEntityState) {
|
|
143
|
+
setCookie(ACTIVE_ENTITY_COOKIE, activeEntityState, {
|
|
144
|
+
path: "/",
|
|
145
|
+
maxAge: 60 * 60 * 24 * 365,
|
|
146
|
+
sameSite: "lax",
|
|
147
|
+
});
|
|
148
|
+
} else {
|
|
149
|
+
removeCookie(ACTIVE_ENTITY_COOKIE, { path: "/" });
|
|
150
|
+
}
|
|
151
|
+
}, [activeEntityState, setCookie, removeCookie]);
|
|
152
|
+
|
|
153
|
+
useEffect(() => {
|
|
154
|
+
setCookie(ACTIVE_ENVIRONMENT_COOKIE, environment, {
|
|
155
|
+
path: "/",
|
|
156
|
+
maxAge: 60 * 60 * 24 * 365,
|
|
157
|
+
sameSite: "lax",
|
|
158
|
+
});
|
|
159
|
+
}, [environment, setCookie]);
|
|
160
|
+
|
|
161
|
+
const refetchEntities = useCallback(async () => {
|
|
162
|
+
await refetch();
|
|
163
|
+
}, [refetch]);
|
|
164
|
+
|
|
165
|
+
const handleEnvironmentChange = useCallback((nextEnvironment: EntityEnvironment) => {
|
|
166
|
+
setEnvironmentState((current) => (current === nextEnvironment ? current : nextEnvironment));
|
|
167
|
+
}, []);
|
|
168
|
+
|
|
169
|
+
const value = useMemo(
|
|
170
|
+
() => ({
|
|
171
|
+
entities: memoizedEntities,
|
|
172
|
+
activeEntity: activeEntityState,
|
|
173
|
+
setActiveEntity: setActiveEntityState,
|
|
174
|
+
environment,
|
|
175
|
+
setEnvironment: handleEnvironmentChange,
|
|
176
|
+
isLoading,
|
|
177
|
+
refetchEntities,
|
|
178
|
+
isError,
|
|
179
|
+
error,
|
|
180
|
+
status,
|
|
181
|
+
}),
|
|
182
|
+
[
|
|
183
|
+
memoizedEntities,
|
|
184
|
+
activeEntityState,
|
|
185
|
+
environment,
|
|
186
|
+
handleEnvironmentChange,
|
|
187
|
+
isLoading,
|
|
188
|
+
refetchEntities,
|
|
189
|
+
isError,
|
|
190
|
+
error,
|
|
191
|
+
status,
|
|
192
|
+
],
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Show loading state only if entities are still loading
|
|
196
|
+
if (isLoading) {
|
|
197
|
+
return <LoadingFallback />;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return <EntitiesContext value={value}>{children}</EntitiesContext>;
|
|
201
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { createContext, type ReactNode, useContext, useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
type SecondaryAction = {
|
|
4
|
+
label: string;
|
|
5
|
+
onClick: () => void;
|
|
6
|
+
isPending?: boolean;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type FormFooterState = {
|
|
10
|
+
formId: string;
|
|
11
|
+
isPending: boolean;
|
|
12
|
+
isDirty: boolean;
|
|
13
|
+
label: string;
|
|
14
|
+
onSubmit?: () => void;
|
|
15
|
+
secondaryAction?: SecondaryAction;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type FormFooterContextType = {
|
|
19
|
+
state: FormFooterState | null;
|
|
20
|
+
setFormFooter: (state: FormFooterState | null) => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const FormFooterContext = createContext<FormFooterContextType | undefined>(undefined);
|
|
24
|
+
|
|
25
|
+
export function FormFooterProvider({ children }: { children: ReactNode }) {
|
|
26
|
+
const [state, setState] = useState<FormFooterState | null>(null);
|
|
27
|
+
|
|
28
|
+
return <FormFooterContext.Provider value={{ state, setFormFooter: setState }}>{children}</FormFooterContext.Provider>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useFormFooterContext() {
|
|
32
|
+
const context = useContext(FormFooterContext);
|
|
33
|
+
if (!context) {
|
|
34
|
+
throw new Error("useFormFooterContext must be used within FormFooterProvider");
|
|
35
|
+
}
|
|
36
|
+
return context;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
type UseFormFooterRegistrationProps = {
|
|
40
|
+
formId: string;
|
|
41
|
+
isPending: boolean;
|
|
42
|
+
isDirty: boolean;
|
|
43
|
+
label: string;
|
|
44
|
+
onSubmit?: () => void;
|
|
45
|
+
secondaryAction?: SecondaryAction;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function useFormFooterRegistration({
|
|
49
|
+
formId,
|
|
50
|
+
isPending,
|
|
51
|
+
isDirty,
|
|
52
|
+
label,
|
|
53
|
+
onSubmit,
|
|
54
|
+
secondaryAction,
|
|
55
|
+
}: UseFormFooterRegistrationProps) {
|
|
56
|
+
const { setFormFooter } = useFormFooterContext();
|
|
57
|
+
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
setFormFooter({
|
|
60
|
+
formId,
|
|
61
|
+
isPending,
|
|
62
|
+
isDirty,
|
|
63
|
+
label,
|
|
64
|
+
onSubmit,
|
|
65
|
+
secondaryAction,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return () => {
|
|
69
|
+
setFormFooter(null);
|
|
70
|
+
};
|
|
71
|
+
}, [formId, isPending, isDirty, label, onSubmit, secondaryAction, setFormFooter]);
|
|
72
|
+
}
|