@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,384 @@
|
|
|
1
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
2
|
+
import type { CreateEstimateRequest, Estimate } from "@spaceinvoices/js-sdk";
|
|
3
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { RefreshCw } from "lucide-react";
|
|
5
|
+
import type { ReactNode } from "react";
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import type { Resolver } from "react-hook-form";
|
|
8
|
+
import { useForm, useWatch } from "react-hook-form";
|
|
9
|
+
import type { z } from "zod";
|
|
10
|
+
import { Form } from "@/ui/components/ui/form";
|
|
11
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/ui/components/ui/tooltip";
|
|
12
|
+
import { createEstimateSchema } from "@/ui/generated/schemas";
|
|
13
|
+
import { useNextDocumentNumber } from "@/ui/hooks/use-next-document-number";
|
|
14
|
+
import type { ComponentTranslationProps } from "@/ui/lib/translation";
|
|
15
|
+
import { createTranslation } from "@/ui/lib/translation";
|
|
16
|
+
import { useEntities } from "@/ui/providers/entities-context";
|
|
17
|
+
import { useFormFooterRegistration } from "@/ui/providers/form-footer-context";
|
|
18
|
+
import { CUSTOMERS_CACHE_KEY } from "../../customers/customers.hooks";
|
|
19
|
+
import {
|
|
20
|
+
DocumentDetailsSection,
|
|
21
|
+
DocumentNoteField,
|
|
22
|
+
DocumentPaymentTermsField,
|
|
23
|
+
} from "../../documents/create/document-details-section";
|
|
24
|
+
import { DocumentItemsSection, type PriceModesMap } from "../../documents/create/document-items-section";
|
|
25
|
+
import { DocumentRecipientSection } from "../../documents/create/document-recipient-section";
|
|
26
|
+
import type { DocumentTypes } from "../../documents/types";
|
|
27
|
+
import { useCreateEstimate } from "../estimates.hooks";
|
|
28
|
+
import de from "./locales/de";
|
|
29
|
+
import sl from "./locales/sl";
|
|
30
|
+
import { prepareEstimateSubmission } from "./prepare-estimate-submission";
|
|
31
|
+
import { useEstimateCustomerForm } from "./use-estimate-customer-form";
|
|
32
|
+
|
|
33
|
+
const translations = {
|
|
34
|
+
sl,
|
|
35
|
+
de,
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
// Form values: extend schema with local-only fields (number is for display, not sent to API)
|
|
39
|
+
type CreateEstimateFormValues = z.infer<typeof createEstimateSchema> & {
|
|
40
|
+
number?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/** Preview payload extends request with display-only fields */
|
|
44
|
+
type EstimatePreviewPayload = Partial<CreateEstimateRequest> & { number?: string };
|
|
45
|
+
|
|
46
|
+
type CreateEstimateFormProps = {
|
|
47
|
+
type: DocumentTypes;
|
|
48
|
+
entityId: string;
|
|
49
|
+
onSuccess?: (data: Estimate) => void;
|
|
50
|
+
onError?: (error: unknown) => void;
|
|
51
|
+
onChange?: (data: EstimatePreviewPayload) => void;
|
|
52
|
+
onAddNewTax?: () => void;
|
|
53
|
+
/** Callback to update header action (title toggle) */
|
|
54
|
+
onHeaderActionChange?: (action: ReactNode) => void;
|
|
55
|
+
/** Initial values for form fields (used for document duplication) */
|
|
56
|
+
initialValues?: Partial<CreateEstimateRequest>;
|
|
57
|
+
} & ComponentTranslationProps;
|
|
58
|
+
|
|
59
|
+
export default function CreateEstimateForm({
|
|
60
|
+
type,
|
|
61
|
+
entityId,
|
|
62
|
+
onSuccess,
|
|
63
|
+
onError,
|
|
64
|
+
onChange,
|
|
65
|
+
onAddNewTax,
|
|
66
|
+
onHeaderActionChange,
|
|
67
|
+
initialValues,
|
|
68
|
+
t: translateProp,
|
|
69
|
+
namespace,
|
|
70
|
+
locale,
|
|
71
|
+
}: CreateEstimateFormProps) {
|
|
72
|
+
const t = createTranslation({
|
|
73
|
+
t: translateProp,
|
|
74
|
+
namespace,
|
|
75
|
+
locale,
|
|
76
|
+
translations,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
const { activeEntity } = useEntities();
|
|
80
|
+
const queryClient = useQueryClient();
|
|
81
|
+
|
|
82
|
+
// Title type state: "estimate" (default) or "quote"
|
|
83
|
+
const [titleType, setTitleType] = useState<"estimate" | "quote">((initialValues as any)?.title_type || "estimate");
|
|
84
|
+
|
|
85
|
+
// Draft submission state
|
|
86
|
+
const [isDraftPending, setIsDraftPending] = useState(false);
|
|
87
|
+
|
|
88
|
+
// Get default estimate note from entity settings
|
|
89
|
+
const defaultEstimateNote = (activeEntity?.settings as any)?.default_estimate_note || "";
|
|
90
|
+
|
|
91
|
+
// Fetch next estimate number
|
|
92
|
+
const { data: nextNumberData } = useNextDocumentNumber(entityId, "estimate", {
|
|
93
|
+
enabled: !!entityId,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Get default payment terms from entity settings
|
|
97
|
+
const defaultPaymentTerms = (activeEntity?.settings as any)?.default_estimate_payment_terms || "";
|
|
98
|
+
|
|
99
|
+
const form = useForm<CreateEstimateFormValues>({
|
|
100
|
+
resolver: zodResolver(createEstimateSchema) as Resolver<CreateEstimateFormValues>,
|
|
101
|
+
defaultValues: {
|
|
102
|
+
number: "",
|
|
103
|
+
date: initialValues?.date || new Date().toISOString(),
|
|
104
|
+
customer_id: initialValues?.customer_id ?? undefined,
|
|
105
|
+
// Cast customer to form schema type (API type may have additional fields)
|
|
106
|
+
customer: (initialValues?.customer as CreateEstimateFormValues["customer"]) ?? undefined,
|
|
107
|
+
items: initialValues?.items?.length
|
|
108
|
+
? initialValues.items.map((item) => ({
|
|
109
|
+
name: item.name || "",
|
|
110
|
+
description: item.description || "",
|
|
111
|
+
quantity: item.quantity ?? 1,
|
|
112
|
+
// Use gross_price if set, otherwise use price
|
|
113
|
+
price: item.gross_price ?? item.price,
|
|
114
|
+
taxes: item.taxes || [],
|
|
115
|
+
}))
|
|
116
|
+
: [
|
|
117
|
+
{
|
|
118
|
+
name: "",
|
|
119
|
+
description: "",
|
|
120
|
+
quantity: 1,
|
|
121
|
+
price: undefined,
|
|
122
|
+
taxes: [],
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
currency_code: initialValues?.currency_code || activeEntity?.currency_code || "EUR",
|
|
126
|
+
note: initialValues?.note ?? defaultEstimateNote,
|
|
127
|
+
payment_terms: initialValues?.payment_terms ?? defaultPaymentTerms,
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Price modes per item (gross vs net) - collected from component state at submit
|
|
132
|
+
const onHeaderActionChangeRef = useRef(onHeaderActionChange);
|
|
133
|
+
onHeaderActionChangeRef.current = onHeaderActionChange;
|
|
134
|
+
|
|
135
|
+
const onChangeRef = useRef(onChange);
|
|
136
|
+
onChangeRef.current = onChange;
|
|
137
|
+
|
|
138
|
+
const initialPriceModes = useMemo(() => {
|
|
139
|
+
if (!initialValues?.items) return {};
|
|
140
|
+
return initialValues.items.reduce((acc, item, index) => {
|
|
141
|
+
acc[index] = item.gross_price != null;
|
|
142
|
+
return acc;
|
|
143
|
+
}, {} as PriceModesMap);
|
|
144
|
+
}, [initialValues?.items]);
|
|
145
|
+
const priceModesRef = useRef<PriceModesMap>(initialPriceModes);
|
|
146
|
+
|
|
147
|
+
// Update number when fetched
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (nextNumberData?.number) {
|
|
150
|
+
form.setValue("number", nextNumberData.number);
|
|
151
|
+
}
|
|
152
|
+
}, [nextNumberData?.number, form]);
|
|
153
|
+
|
|
154
|
+
// Update default note when entity loads
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
const entityDefaultNote = (activeEntity?.settings as any)?.default_estimate_note;
|
|
157
|
+
if (entityDefaultNote && !form.getValues("note")) {
|
|
158
|
+
form.setValue("note", entityDefaultNote);
|
|
159
|
+
}
|
|
160
|
+
}, [activeEntity, form]);
|
|
161
|
+
|
|
162
|
+
// Auto-add tax field for tax subject entities
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (activeEntity?.is_tax_subject) {
|
|
165
|
+
const items = form.getValues("items") || [];
|
|
166
|
+
if (items.length > 0 && (!items[0].taxes || items[0].taxes.length === 0)) {
|
|
167
|
+
form.setValue("items.0.taxes", [{ tax_id: undefined }]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}, [activeEntity?.is_tax_subject, form]);
|
|
171
|
+
|
|
172
|
+
// Update header with clickable title toggle
|
|
173
|
+
useEffect(() => {
|
|
174
|
+
const callback = onHeaderActionChangeRef.current;
|
|
175
|
+
if (!callback) return;
|
|
176
|
+
|
|
177
|
+
const toggleTitle = () => {
|
|
178
|
+
setTitleType((prev) => (prev === "estimate" ? "quote" : "estimate"));
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
callback(
|
|
182
|
+
<TooltipProvider>
|
|
183
|
+
<Tooltip>
|
|
184
|
+
<TooltipTrigger asChild>
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
onClick={toggleTitle}
|
|
188
|
+
className="flex cursor-pointer items-center gap-2 font-bold text-2xl hover:text-primary"
|
|
189
|
+
>
|
|
190
|
+
{titleType === "estimate" ? t("Create Estimate") : t("Create Quote")}
|
|
191
|
+
<RefreshCw className="size-4 text-muted-foreground" />
|
|
192
|
+
</button>
|
|
193
|
+
</TooltipTrigger>
|
|
194
|
+
<TooltipContent side="bottom">
|
|
195
|
+
{titleType === "estimate" ? t("Click to switch to Quote") : t("Click to switch to Estimate")}
|
|
196
|
+
</TooltipContent>
|
|
197
|
+
</Tooltip>
|
|
198
|
+
</TooltipProvider>,
|
|
199
|
+
);
|
|
200
|
+
}, [titleType, t]);
|
|
201
|
+
|
|
202
|
+
const formValues = useWatch({
|
|
203
|
+
control: form.control,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Extract customer management logic into a custom hook
|
|
207
|
+
const {
|
|
208
|
+
originalCustomer,
|
|
209
|
+
showCustomerForm,
|
|
210
|
+
shouldFocusName,
|
|
211
|
+
selectedCustomerId,
|
|
212
|
+
initialCustomerName,
|
|
213
|
+
handleCustomerSelect,
|
|
214
|
+
handleCustomerClear,
|
|
215
|
+
} = useEstimateCustomerForm(form as any);
|
|
216
|
+
|
|
217
|
+
const { mutate: createEstimate, isPending } = useCreateEstimate({
|
|
218
|
+
entityId,
|
|
219
|
+
onSuccess: (data) => {
|
|
220
|
+
// Invalidate customers cache when a customer was created/linked
|
|
221
|
+
if (data.customer_id) {
|
|
222
|
+
queryClient.invalidateQueries({ queryKey: [CUSTOMERS_CACHE_KEY] });
|
|
223
|
+
}
|
|
224
|
+
onSuccess?.(data);
|
|
225
|
+
},
|
|
226
|
+
onError,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Shared submit logic for both regular save and save as draft
|
|
230
|
+
const submitEstimate = useCallback(
|
|
231
|
+
(values: CreateEstimateFormValues, isDraft: boolean) => {
|
|
232
|
+
try {
|
|
233
|
+
const submission = prepareEstimateSubmission(values, {
|
|
234
|
+
originalCustomer,
|
|
235
|
+
priceModes: priceModesRef.current,
|
|
236
|
+
titleType,
|
|
237
|
+
isDraft,
|
|
238
|
+
});
|
|
239
|
+
createEstimate(submission);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
console.error("Estimate submission error:", error);
|
|
242
|
+
if (onError) {
|
|
243
|
+
onError(error);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
},
|
|
247
|
+
[createEstimate, onError, originalCustomer, titleType],
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Handle save as draft
|
|
251
|
+
const handleSaveAsDraft = useCallback(async () => {
|
|
252
|
+
setIsDraftPending(true);
|
|
253
|
+
try {
|
|
254
|
+
const isValid = await form.trigger();
|
|
255
|
+
if (isValid) {
|
|
256
|
+
const values = form.getValues();
|
|
257
|
+
submitEstimate(values, true);
|
|
258
|
+
}
|
|
259
|
+
} finally {
|
|
260
|
+
setIsDraftPending(false);
|
|
261
|
+
}
|
|
262
|
+
}, [form, submitEstimate]);
|
|
263
|
+
|
|
264
|
+
const secondaryAction = useMemo(
|
|
265
|
+
() => ({
|
|
266
|
+
label: t("Save as Draft"),
|
|
267
|
+
onClick: handleSaveAsDraft,
|
|
268
|
+
isPending: isDraftPending,
|
|
269
|
+
}),
|
|
270
|
+
[t, handleSaveAsDraft, isDraftPending],
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
useFormFooterRegistration({
|
|
274
|
+
formId: "create-estimate-form",
|
|
275
|
+
isPending,
|
|
276
|
+
isDirty: form.formState.isDirty,
|
|
277
|
+
label: titleType === "estimate" ? t("Create Estimate") : t("Create Quote"),
|
|
278
|
+
secondaryAction,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Set default payment terms from entity settings when entity data is available
|
|
282
|
+
useEffect(() => {
|
|
283
|
+
const entityDefaultPaymentTerms = (activeEntity?.settings as any)?.default_estimate_payment_terms;
|
|
284
|
+
if (entityDefaultPaymentTerms && !form.getValues("payment_terms")) {
|
|
285
|
+
form.setValue("payment_terms", entityDefaultPaymentTerms);
|
|
286
|
+
}
|
|
287
|
+
}, [activeEntity, form]);
|
|
288
|
+
|
|
289
|
+
useEffect(() => {
|
|
290
|
+
const callback = onChangeRef.current;
|
|
291
|
+
if (!callback) return;
|
|
292
|
+
|
|
293
|
+
const currentItems = form.getValues("items") || [];
|
|
294
|
+
|
|
295
|
+
// Transform items to use gross_price when price mode is gross
|
|
296
|
+
const transformedItems = currentItems.map((item: any, index: number) => {
|
|
297
|
+
const { price, ...rest } = item;
|
|
298
|
+
const isGross = priceModesRef.current[index] ?? false;
|
|
299
|
+
if (isGross) {
|
|
300
|
+
return { ...rest, gross_price: price };
|
|
301
|
+
}
|
|
302
|
+
return { ...rest, price };
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const payload: EstimatePreviewPayload = {
|
|
306
|
+
number: formValues.number,
|
|
307
|
+
date: formValues.date,
|
|
308
|
+
customer_id: formValues.customer_id,
|
|
309
|
+
customer: formValues.customer,
|
|
310
|
+
items: transformedItems,
|
|
311
|
+
currency_code: formValues.currency_code,
|
|
312
|
+
note: formValues.note,
|
|
313
|
+
payment_terms: formValues.payment_terms,
|
|
314
|
+
title_type: titleType,
|
|
315
|
+
};
|
|
316
|
+
callback(payload);
|
|
317
|
+
}, [formValues, form, titleType]);
|
|
318
|
+
|
|
319
|
+
const onSubmit = (values: CreateEstimateFormValues) => {
|
|
320
|
+
submitEstimate(values, false);
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
return (
|
|
324
|
+
<Form {...form}>
|
|
325
|
+
<form id="create-estimate-form" onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
|
326
|
+
<div className="flex w-full flex-col md:flex-row md:gap-6">
|
|
327
|
+
<DocumentRecipientSection
|
|
328
|
+
control={form.control}
|
|
329
|
+
entityId={entityId}
|
|
330
|
+
onCustomerSelect={handleCustomerSelect}
|
|
331
|
+
onCustomerClear={handleCustomerClear}
|
|
332
|
+
showCustomerForm={showCustomerForm}
|
|
333
|
+
shouldFocusName={shouldFocusName}
|
|
334
|
+
selectedCustomerId={selectedCustomerId}
|
|
335
|
+
initialCustomerName={initialCustomerName}
|
|
336
|
+
t={t}
|
|
337
|
+
/>
|
|
338
|
+
|
|
339
|
+
<DocumentDetailsSection control={form.control} documentType={type} t={t} />
|
|
340
|
+
</div>
|
|
341
|
+
|
|
342
|
+
<DocumentItemsSection
|
|
343
|
+
control={form.control}
|
|
344
|
+
watch={form.watch}
|
|
345
|
+
setValue={form.setValue}
|
|
346
|
+
getValues={form.getValues}
|
|
347
|
+
entityId={entityId}
|
|
348
|
+
currencyCode={activeEntity?.currency_code ?? undefined}
|
|
349
|
+
onAddNewTax={onAddNewTax}
|
|
350
|
+
t={t}
|
|
351
|
+
maxTaxesPerItem={activeEntity?.country_rules?.max_taxes_per_item}
|
|
352
|
+
priceModesRef={priceModesRef}
|
|
353
|
+
initialPriceModes={initialPriceModes}
|
|
354
|
+
/>
|
|
355
|
+
|
|
356
|
+
<DocumentNoteField
|
|
357
|
+
control={form.control}
|
|
358
|
+
t={t}
|
|
359
|
+
entity={activeEntity}
|
|
360
|
+
document={{
|
|
361
|
+
number: formValues.number,
|
|
362
|
+
date: formValues.date,
|
|
363
|
+
date_valid_till: formValues.date_valid_till,
|
|
364
|
+
currency_code: formValues.currency_code,
|
|
365
|
+
customer: formValues.customer as any,
|
|
366
|
+
}}
|
|
367
|
+
/>
|
|
368
|
+
|
|
369
|
+
<DocumentPaymentTermsField
|
|
370
|
+
control={form.control}
|
|
371
|
+
t={t}
|
|
372
|
+
entity={activeEntity}
|
|
373
|
+
document={{
|
|
374
|
+
number: formValues.number,
|
|
375
|
+
date: formValues.date,
|
|
376
|
+
date_valid_till: formValues.date_valid_till,
|
|
377
|
+
currency_code: formValues.currency_code,
|
|
378
|
+
customer: formValues.customer as any,
|
|
379
|
+
}}
|
|
380
|
+
/>
|
|
381
|
+
</form>
|
|
382
|
+
</Form>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
"Create Estimate": "Angebot erstellen",
|
|
3
|
+
"Create Quote": "Angebot erstellen",
|
|
4
|
+
Estimate: "Angebot",
|
|
5
|
+
Quote: "Angebot",
|
|
6
|
+
"Click to switch to Quote": "Klicken Sie, um zu Angebot zu wechseln",
|
|
7
|
+
"Click to switch to Estimate": "Klicken Sie, um zu Angebot zu wechseln",
|
|
8
|
+
Details: "Details",
|
|
9
|
+
Number: "Nummer",
|
|
10
|
+
Date: "Datum",
|
|
11
|
+
"Due Date": "Fälligkeitsdatum",
|
|
12
|
+
"Valid Until": "Gültig bis",
|
|
13
|
+
Recipient: "Empfänger",
|
|
14
|
+
"Search or create customer...": "Kunde suchen oder erstellen...",
|
|
15
|
+
Clear: "Löschen",
|
|
16
|
+
Name: "Name",
|
|
17
|
+
Address: "Adresse",
|
|
18
|
+
"Address 2": "Adresse 2",
|
|
19
|
+
"Post Code": "Postleitzahl",
|
|
20
|
+
City: "Stadt",
|
|
21
|
+
State: "Bundesland",
|
|
22
|
+
Country: "Land",
|
|
23
|
+
"Tax Number": "Steuernummer",
|
|
24
|
+
Items: "Positionen",
|
|
25
|
+
"Add item": "Position hinzufügen",
|
|
26
|
+
Tax: "Steuer",
|
|
27
|
+
"Tax rate": "Steuersatz",
|
|
28
|
+
"Add tax": "Steuer hinzufügen",
|
|
29
|
+
"Select tax...": "Steuer auswählen...",
|
|
30
|
+
"Loading...": "Laden...",
|
|
31
|
+
"No taxes found": "Keine Steuern gefunden",
|
|
32
|
+
"Add new tax...": "Neue Steuer hinzufügen...",
|
|
33
|
+
// Note field
|
|
34
|
+
Note: "Notiz",
|
|
35
|
+
"Insert variable": "Variable einfügen",
|
|
36
|
+
"Add payment instructions, terms, or other notes...":
|
|
37
|
+
"Zahlungsanweisungen, Bedingungen oder andere Notizen hinzufügen...",
|
|
38
|
+
// Payment terms field
|
|
39
|
+
"Payment Terms": "Zahlungsbedingungen",
|
|
40
|
+
"Add payment terms...": "Zahlungsbedingungen hinzufügen...",
|
|
41
|
+
// Document item fields
|
|
42
|
+
Quantity: "Menge",
|
|
43
|
+
Price: "Preis",
|
|
44
|
+
Unit: "Einheit",
|
|
45
|
+
Discount: "Rabatt",
|
|
46
|
+
Description: "Beschreibung",
|
|
47
|
+
Save: "Speichern",
|
|
48
|
+
"Save as Draft": "Als Entwurf speichern",
|
|
49
|
+
"Pick a date": "Datum auswählen",
|
|
50
|
+
// Currency
|
|
51
|
+
Currency: "Währung",
|
|
52
|
+
"Select currency": "Währung auswählen",
|
|
53
|
+
// Discount types
|
|
54
|
+
"Percentage discount": "Prozentualer Rabatt",
|
|
55
|
+
"Fixed amount discount": "Fester Rabatt",
|
|
56
|
+
Percentage: "Prozent",
|
|
57
|
+
"Fixed amount": "Fester Betrag",
|
|
58
|
+
"Taxes disabled": "Steuern deaktiviert",
|
|
59
|
+
// Gross price support
|
|
60
|
+
"Gross price": "Bruttopreis",
|
|
61
|
+
"Net price": "Nettopreis",
|
|
62
|
+
"Gross price (tax included)": "Bruttopreis (inkl. MwSt.)",
|
|
63
|
+
"Net price (before tax)": "Nettopreis (exkl. MwSt.)",
|
|
64
|
+
} as const;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
"Create Estimate": "Ustvari predračun",
|
|
3
|
+
"Create Quote": "Ustvari ponudbo",
|
|
4
|
+
Estimate: "Predračun",
|
|
5
|
+
Quote: "Ponudba",
|
|
6
|
+
"Click to switch to Quote": "Kliknite za zamenjavo v Ponudbo",
|
|
7
|
+
"Click to switch to Estimate": "Kliknite za zamenjavo v Predračun",
|
|
8
|
+
Details: "Podrobnosti",
|
|
9
|
+
Number: "Številka",
|
|
10
|
+
Date: "Datum",
|
|
11
|
+
"Due Date": "Rok plačila",
|
|
12
|
+
"Valid Until": "Veljavno do",
|
|
13
|
+
Recipient: "Prejemnik",
|
|
14
|
+
"Search or create customer...": "Išči ali ustvari stranko...",
|
|
15
|
+
Clear: "Počisti",
|
|
16
|
+
Name: "Ime",
|
|
17
|
+
Address: "Naslov",
|
|
18
|
+
"Address 2": "Naslov 2",
|
|
19
|
+
"Post Code": "Poštna številka",
|
|
20
|
+
City: "Mesto",
|
|
21
|
+
State: "Regija",
|
|
22
|
+
Country: "Država",
|
|
23
|
+
"Tax Number": "Davčna številka",
|
|
24
|
+
Items: "Postavke",
|
|
25
|
+
"Add item": "Dodaj postavko",
|
|
26
|
+
Tax: "Davek",
|
|
27
|
+
"Tax rate": "Davčna stopnja",
|
|
28
|
+
"Add tax": "Dodaj davek",
|
|
29
|
+
"Select tax...": "Izberi davek...",
|
|
30
|
+
"Loading...": "Nalaganje...",
|
|
31
|
+
"No taxes found": "Ni najdenih davkov",
|
|
32
|
+
"Add new tax...": "Dodaj nov davek...",
|
|
33
|
+
// Note field
|
|
34
|
+
Note: "Opomba",
|
|
35
|
+
"Insert variable": "Vstavi spremenljivko",
|
|
36
|
+
"Add payment instructions, terms, or other notes...": "Dodajte navodila za plačilo, pogoje ali druge opombe...",
|
|
37
|
+
// Payment terms field
|
|
38
|
+
"Payment Terms": "Plačilni pogoji",
|
|
39
|
+
"Add payment terms...": "Dodajte plačilne pogoje...",
|
|
40
|
+
// Document item fields
|
|
41
|
+
Quantity: "Količina",
|
|
42
|
+
Price: "Cena",
|
|
43
|
+
Unit: "Enota",
|
|
44
|
+
Discount: "Popust",
|
|
45
|
+
Description: "Opis",
|
|
46
|
+
Save: "Shrani",
|
|
47
|
+
"Save as Draft": "Shrani kot osnutek",
|
|
48
|
+
"Pick a date": "Izberi datum",
|
|
49
|
+
// Currency
|
|
50
|
+
Currency: "Valuta",
|
|
51
|
+
"Select currency": "Izberi valuto",
|
|
52
|
+
// Discount types
|
|
53
|
+
"Percentage discount": "Odstotni popust",
|
|
54
|
+
"Fixed amount discount": "Fiksni popust",
|
|
55
|
+
Percentage: "Odstotek",
|
|
56
|
+
"Fixed amount": "Fiksni znesek",
|
|
57
|
+
"Taxes disabled": "Davki onemogočeni",
|
|
58
|
+
// Gross price support
|
|
59
|
+
"Gross price": "Bruto cena",
|
|
60
|
+
"Net price": "Neto cena",
|
|
61
|
+
"Gross price (tax included)": "Bruto cena (z davkom)",
|
|
62
|
+
"Net price (before tax)": "Neto cena (brez davka)",
|
|
63
|
+
} as const;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { CreateEstimateRequest } from "@spaceinvoices/js-sdk";
|
|
2
|
+
import type { CreateEstimateSchema } from "@/ui/generated/schemas";
|
|
3
|
+
import { prepareDocumentSubmission } from "../../documents/create/prepare-document-submission";
|
|
4
|
+
import type { CustomerData } from "../../documents/create/use-document-customer-form";
|
|
5
|
+
|
|
6
|
+
/** Map of item index to gross price mode */
|
|
7
|
+
type PriceModesMap = Record<number, boolean>;
|
|
8
|
+
|
|
9
|
+
type PrepareOptions = {
|
|
10
|
+
originalCustomer: CustomerData | null;
|
|
11
|
+
/** Map of item index to gross price mode (collected from component state) */
|
|
12
|
+
priceModes?: PriceModesMap;
|
|
13
|
+
/** Title type: "estimate" or "quote" */
|
|
14
|
+
titleType?: "estimate" | "quote";
|
|
15
|
+
/** Whether to save as draft (skips numbering) */
|
|
16
|
+
isDraft?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Prepares estimate form data for API submission
|
|
21
|
+
* Handles customer data transformation (no payment data for estimates)
|
|
22
|
+
*/
|
|
23
|
+
export function prepareEstimateSubmission(
|
|
24
|
+
values: CreateEstimateSchema,
|
|
25
|
+
options: PrepareOptions,
|
|
26
|
+
): CreateEstimateRequest {
|
|
27
|
+
const baseSubmission = prepareDocumentSubmission(values, {
|
|
28
|
+
originalCustomer: options.originalCustomer,
|
|
29
|
+
documentType: "estimate",
|
|
30
|
+
secondaryDate: values.date_valid_till,
|
|
31
|
+
priceModes: options.priceModes,
|
|
32
|
+
isDraft: options.isDraft,
|
|
33
|
+
}) as CreateEstimateRequest;
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
...baseSubmission,
|
|
37
|
+
title_type: options.titleType,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { CreateEstimateRequest, Estimate } from "@spaceinvoices/js-sdk";
|
|
2
|
+
|
|
3
|
+
import { createResourceHooks } from "@/ui/hooks/create-resource-hooks";
|
|
4
|
+
|
|
5
|
+
// Define a constant for the estimates cache key
|
|
6
|
+
export const ESTIMATES_CACHE_KEY = "estimates";
|
|
7
|
+
|
|
8
|
+
// Create estimate-specific hooks using the factory
|
|
9
|
+
const {
|
|
10
|
+
useCreateResource: useCreateEstimate,
|
|
11
|
+
useUpdateResource: useUpdateEstimate,
|
|
12
|
+
useDeleteResource: useDeleteEstimate,
|
|
13
|
+
} = createResourceHooks<Estimate, CreateEstimateRequest>("estimates", ESTIMATES_CACHE_KEY);
|
|
14
|
+
|
|
15
|
+
export { useCreateEstimate, useUpdateEstimate, useDeleteEstimate };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Estimates components - Main exports
|
|
3
|
+
*/
|
|
4
|
+
export { default as CreateEstimateForm } from "./create/create-estimate-form";
|
|
5
|
+
export { ESTIMATES_CACHE_KEY, useCreateEstimate, useDeleteEstimate, useUpdateEstimate } from "./estimates.hooks";
|
|
6
|
+
export { default as EstimateListTable } from "./list/list-table";
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { Estimate } from "@spaceinvoices/js-sdk";
|
|
2
|
+
|
|
3
|
+
import { Copy, Download, Eye, Link2Off, Loader2, MoreHorizontal } from "lucide-react";
|
|
4
|
+
import { Button } from "@/ui/components/ui/button";
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuGroup,
|
|
9
|
+
DropdownMenuItem,
|
|
10
|
+
DropdownMenuLabel,
|
|
11
|
+
DropdownMenuSeparator,
|
|
12
|
+
DropdownMenuTrigger,
|
|
13
|
+
} from "@/ui/components/ui/dropdown-menu";
|
|
14
|
+
import type { ComponentTranslationProps } from "@/ui/lib/translation";
|
|
15
|
+
import { createTranslation } from "@/ui/lib/translation";
|
|
16
|
+
import { useEstimateDownload } from "./use-estimate-download";
|
|
17
|
+
|
|
18
|
+
type EstimateListRowActionsProps = {
|
|
19
|
+
estimate: Estimate;
|
|
20
|
+
onDuplicate?: (estimate: Estimate) => void;
|
|
21
|
+
onDownloadStart?: () => void;
|
|
22
|
+
onDownloadSuccess?: (fileName: string) => void;
|
|
23
|
+
onDownloadError?: (error: string) => void;
|
|
24
|
+
onUnshare?: (estimate: Estimate) => Promise<void>;
|
|
25
|
+
isUnsharing?: boolean;
|
|
26
|
+
} & ComponentTranslationProps;
|
|
27
|
+
|
|
28
|
+
export default function EstimateListRowActions({
|
|
29
|
+
estimate,
|
|
30
|
+
onDuplicate,
|
|
31
|
+
onDownloadStart,
|
|
32
|
+
onDownloadSuccess,
|
|
33
|
+
onDownloadError,
|
|
34
|
+
onUnshare,
|
|
35
|
+
isUnsharing,
|
|
36
|
+
...i18nProps
|
|
37
|
+
}: EstimateListRowActionsProps) {
|
|
38
|
+
const t = createTranslation(i18nProps);
|
|
39
|
+
const { isDownloading, downloadPDF } = useEstimateDownload({
|
|
40
|
+
onDownloadStart,
|
|
41
|
+
onDownloadSuccess,
|
|
42
|
+
onDownloadError,
|
|
43
|
+
...i18nProps,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<DropdownMenu>
|
|
48
|
+
<DropdownMenuTrigger asChild>
|
|
49
|
+
<Button variant="ghost" className="h-8 w-8 cursor-pointer p-0" id="action-menu-trigger">
|
|
50
|
+
<span className="sr-only">{t("Open menu")}</span>
|
|
51
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
52
|
+
</Button>
|
|
53
|
+
</DropdownMenuTrigger>
|
|
54
|
+
<DropdownMenuContent align="end">
|
|
55
|
+
<DropdownMenuLabel>{t("Actions")}</DropdownMenuLabel>
|
|
56
|
+
<DropdownMenuGroup>
|
|
57
|
+
<DropdownMenuItem className="cursor-pointer" onClick={() => navigator.clipboard.writeText(estimate.id)}>
|
|
58
|
+
<Copy className="h-4 w-4" />
|
|
59
|
+
{t("Copy estimate ID")}
|
|
60
|
+
</DropdownMenuItem>
|
|
61
|
+
</DropdownMenuGroup>
|
|
62
|
+
<DropdownMenuSeparator />
|
|
63
|
+
<DropdownMenuGroup>
|
|
64
|
+
<DropdownMenuItem
|
|
65
|
+
className="cursor-pointer"
|
|
66
|
+
onClick={() => {
|
|
67
|
+
// TODO: Use router
|
|
68
|
+
window.location.href = `/app/estimates/view/${estimate.id}`;
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<Eye className="h-4 w-4" />
|
|
72
|
+
{t("View estimate")}
|
|
73
|
+
</DropdownMenuItem>
|
|
74
|
+
<DropdownMenuItem className="cursor-pointer" onClick={() => downloadPDF(estimate)} disabled={isDownloading}>
|
|
75
|
+
<Download className="h-4 w-4" />
|
|
76
|
+
{isDownloading ? t("Downloading...") : t("Download PDF")}
|
|
77
|
+
</DropdownMenuItem>
|
|
78
|
+
{onDuplicate && (
|
|
79
|
+
<DropdownMenuItem className="cursor-pointer" onClick={() => onDuplicate(estimate)}>
|
|
80
|
+
<Copy className="h-4 w-4" />
|
|
81
|
+
{t("Duplicate")}
|
|
82
|
+
</DropdownMenuItem>
|
|
83
|
+
)}
|
|
84
|
+
</DropdownMenuGroup>
|
|
85
|
+
{estimate.shareable_id && onUnshare && (
|
|
86
|
+
<>
|
|
87
|
+
<DropdownMenuSeparator />
|
|
88
|
+
<DropdownMenuGroup>
|
|
89
|
+
<DropdownMenuItem
|
|
90
|
+
className="cursor-pointer text-destructive focus:text-destructive"
|
|
91
|
+
onClick={() => onUnshare(estimate)}
|
|
92
|
+
disabled={isUnsharing}
|
|
93
|
+
>
|
|
94
|
+
{isUnsharing ? <Loader2 className="h-4 w-4 animate-spin" /> : <Link2Off className="h-4 w-4" />}
|
|
95
|
+
{t("Unshare")}
|
|
96
|
+
</DropdownMenuItem>
|
|
97
|
+
</DropdownMenuGroup>
|
|
98
|
+
</>
|
|
99
|
+
)}
|
|
100
|
+
</DropdownMenuContent>
|
|
101
|
+
</DropdownMenu>
|
|
102
|
+
);
|
|
103
|
+
}
|