@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,213 @@
|
|
|
1
|
+
import type { FursFiscalizationResponse, Invoice } from "@spaceinvoices/js-sdk";
|
|
2
|
+
import { AlertCircle, Check, CheckCircle2, Clock, Copy, XCircle } from "lucide-react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { Alert, AlertDescription, AlertTitle } from "@/ui/components/ui/alert";
|
|
5
|
+
import { Badge } from "@/ui/components/ui/badge";
|
|
6
|
+
import { Button } from "@/ui/components/ui/button";
|
|
7
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/ui/components/ui/card";
|
|
8
|
+
import type { ComponentTranslationProps } from "@/ui/lib/translation";
|
|
9
|
+
import { createTranslation } from "@/ui/lib/translation";
|
|
10
|
+
import de from "../../entities/furs-settings-form/locales/de";
|
|
11
|
+
import en from "../../entities/furs-settings-form/locales/en";
|
|
12
|
+
import sl from "../../entities/furs-settings-form/locales/sl";
|
|
13
|
+
|
|
14
|
+
const translations = { de, sl, en } as const;
|
|
15
|
+
|
|
16
|
+
// Type alias for easier use
|
|
17
|
+
type FursData = FursFiscalizationResponse;
|
|
18
|
+
|
|
19
|
+
interface FursInfoDisplayProps extends ComponentTranslationProps {
|
|
20
|
+
invoice: Invoice;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* FURS Fiscalization Info Display Component
|
|
25
|
+
*
|
|
26
|
+
* Shows FURS fiscalization status, ZOI, EOR, QR code, and other related data
|
|
27
|
+
*/
|
|
28
|
+
export function FursInfoDisplay({ invoice, t: translateFn, namespace, locale }: FursInfoDisplayProps) {
|
|
29
|
+
const [copiedField, setCopiedField] = useState<string | null>(null);
|
|
30
|
+
|
|
31
|
+
const t = createTranslation({
|
|
32
|
+
t: translateFn,
|
|
33
|
+
namespace,
|
|
34
|
+
locale,
|
|
35
|
+
translations,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Cast furs to the proper type (SDK has it as object, but it's actually FursData)
|
|
39
|
+
const furs = invoice.furs as FursData | undefined;
|
|
40
|
+
|
|
41
|
+
// If no FURS data, don't render anything
|
|
42
|
+
if (!furs) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const copyToClipboard = async (text: string, fieldName: string) => {
|
|
47
|
+
try {
|
|
48
|
+
await navigator.clipboard.writeText(text);
|
|
49
|
+
setCopiedField(fieldName);
|
|
50
|
+
setTimeout(() => setCopiedField(null), 2000);
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error("Failed to copy:", err);
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const getStatusBadge = () => {
|
|
57
|
+
switch (furs.status) {
|
|
58
|
+
case "success":
|
|
59
|
+
return (
|
|
60
|
+
<Badge className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-100">
|
|
61
|
+
<CheckCircle2 className="mr-1 h-3 w-3" />
|
|
62
|
+
{t("Fiscalized")}
|
|
63
|
+
</Badge>
|
|
64
|
+
);
|
|
65
|
+
case "pending":
|
|
66
|
+
return (
|
|
67
|
+
<Badge className="bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-100">
|
|
68
|
+
<Clock className="mr-1 h-3 w-3" />
|
|
69
|
+
{t("Pending")}
|
|
70
|
+
</Badge>
|
|
71
|
+
);
|
|
72
|
+
case "failed":
|
|
73
|
+
return (
|
|
74
|
+
<Badge className="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-100">
|
|
75
|
+
<XCircle className="mr-1 h-3 w-3" />
|
|
76
|
+
{t("Failed")}
|
|
77
|
+
</Badge>
|
|
78
|
+
);
|
|
79
|
+
default:
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Card>
|
|
86
|
+
<CardHeader>
|
|
87
|
+
<CardTitle className="flex items-center gap-2">
|
|
88
|
+
{t("FURS Fiscalization")}
|
|
89
|
+
{getStatusBadge()}
|
|
90
|
+
</CardTitle>
|
|
91
|
+
<CardDescription>{t("Slovenian tax authority fiscalization details")}</CardDescription>
|
|
92
|
+
</CardHeader>
|
|
93
|
+
<CardContent className="space-y-4">
|
|
94
|
+
{/* Error Message */}
|
|
95
|
+
{furs.status === "failed" && furs.error && (
|
|
96
|
+
<Alert variant="destructive">
|
|
97
|
+
<AlertCircle className="h-4 w-4" />
|
|
98
|
+
<AlertTitle>{t("Fiscalization Error")}</AlertTitle>
|
|
99
|
+
<AlertDescription>{furs.error}</AlertDescription>
|
|
100
|
+
</Alert>
|
|
101
|
+
)}
|
|
102
|
+
|
|
103
|
+
{/* Cancellation Info */}
|
|
104
|
+
{furs.cancellation_reason && (
|
|
105
|
+
<Alert>
|
|
106
|
+
<AlertCircle className="h-4 w-4" />
|
|
107
|
+
<AlertTitle>{t("Cancelled")}</AlertTitle>
|
|
108
|
+
<AlertDescription>{furs.cancellation_reason}</AlertDescription>
|
|
109
|
+
</Alert>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{/* FURS Data */}
|
|
113
|
+
{furs.data && (
|
|
114
|
+
<div className="space-y-3">
|
|
115
|
+
{/* ZOI Code */}
|
|
116
|
+
{furs.data.zoi && (
|
|
117
|
+
<div className="flex items-center justify-between rounded-lg border p-3">
|
|
118
|
+
<div className="space-y-1">
|
|
119
|
+
<p className="font-medium text-sm">{t("ZOI")}</p>
|
|
120
|
+
<p className="font-mono text-muted-foreground text-xs">{furs.data.zoi}</p>
|
|
121
|
+
</div>
|
|
122
|
+
<Button variant="ghost" size="sm" onClick={() => copyToClipboard(furs.data!.zoi!, "zoi")}>
|
|
123
|
+
{copiedField === "zoi" ? <Check className="h-4 w-4 text-green-600" /> : <Copy className="h-4 w-4" />}
|
|
124
|
+
</Button>
|
|
125
|
+
</div>
|
|
126
|
+
)}
|
|
127
|
+
|
|
128
|
+
{/* EOR Code */}
|
|
129
|
+
{furs.data.eor && (
|
|
130
|
+
<div className="flex items-center justify-between rounded-lg border p-3">
|
|
131
|
+
<div className="space-y-1">
|
|
132
|
+
<p className="font-medium text-sm">{t("EOR")}</p>
|
|
133
|
+
<p className="font-mono text-muted-foreground text-xs">{furs.data.eor}</p>
|
|
134
|
+
</div>
|
|
135
|
+
<Button variant="ghost" size="sm" onClick={() => copyToClipboard(furs.data!.eor!, "eor")}>
|
|
136
|
+
{copiedField === "eor" ? <Check className="h-4 w-4 text-green-600" /> : <Copy className="h-4 w-4" />}
|
|
137
|
+
</Button>
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
|
|
141
|
+
{/* Cancelled EOR */}
|
|
142
|
+
{furs.data.cancelled && furs.data.cancelled_eor && (
|
|
143
|
+
<div className="flex items-center justify-between rounded-lg border border-red-200 bg-red-50 p-3 dark:border-red-800 dark:bg-red-950">
|
|
144
|
+
<div className="space-y-1">
|
|
145
|
+
<p className="font-medium text-red-900 text-sm dark:text-red-100">{t("Cancellation EOR")}</p>
|
|
146
|
+
<p className="font-mono text-red-700 text-xs dark:text-red-300">{furs.data.cancelled_eor}</p>
|
|
147
|
+
</div>
|
|
148
|
+
<Button
|
|
149
|
+
variant="ghost"
|
|
150
|
+
size="sm"
|
|
151
|
+
onClick={() => copyToClipboard(furs.data!.cancelled_eor!, "cancelled_eor")}
|
|
152
|
+
>
|
|
153
|
+
{copiedField === "cancelled_eor" ? (
|
|
154
|
+
<Check className="h-4 w-4 text-green-600" />
|
|
155
|
+
) : (
|
|
156
|
+
<Copy className="h-4 w-4" />
|
|
157
|
+
)}
|
|
158
|
+
</Button>
|
|
159
|
+
</div>
|
|
160
|
+
)}
|
|
161
|
+
|
|
162
|
+
{/* Business Premise & Device */}
|
|
163
|
+
<div className="grid grid-cols-2 gap-3">
|
|
164
|
+
{furs.data.business_premise_name && (
|
|
165
|
+
<div className="space-y-1">
|
|
166
|
+
<p className="font-medium text-sm">{t("Business Premise")}</p>
|
|
167
|
+
<p className="text-muted-foreground text-sm">{furs.data.business_premise_name}</p>
|
|
168
|
+
</div>
|
|
169
|
+
)}
|
|
170
|
+
{furs.data.electronic_device_name && (
|
|
171
|
+
<div className="space-y-1">
|
|
172
|
+
<p className="font-medium text-sm">{t("Electronic Device")}</p>
|
|
173
|
+
<p className="text-muted-foreground text-sm">{furs.data.electronic_device_name}</p>
|
|
174
|
+
</div>
|
|
175
|
+
)}
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
{/* Invoice Number & Iteration */}
|
|
179
|
+
{furs.data.invoice_number && (
|
|
180
|
+
<div className="grid grid-cols-2 gap-3">
|
|
181
|
+
<div className="space-y-1">
|
|
182
|
+
<p className="font-medium text-sm">{t("Invoice Number")}</p>
|
|
183
|
+
<p className="text-muted-foreground text-sm">{furs.data.invoice_number}</p>
|
|
184
|
+
</div>
|
|
185
|
+
{furs.data.iteration !== undefined && (
|
|
186
|
+
<div className="space-y-1">
|
|
187
|
+
<p className="font-medium text-sm">{t("Iteration")}</p>
|
|
188
|
+
<p className="text-muted-foreground text-sm">{furs.data.iteration}</p>
|
|
189
|
+
</div>
|
|
190
|
+
)}
|
|
191
|
+
</div>
|
|
192
|
+
)}
|
|
193
|
+
|
|
194
|
+
{/* QR Code */}
|
|
195
|
+
{furs.data.qr_code && (
|
|
196
|
+
<div className="flex flex-col items-center gap-2 rounded-lg border p-4">
|
|
197
|
+
<p className="font-medium text-sm">{t("QR Code")}</p>
|
|
198
|
+
<img src={`data:image/png;base64,${furs.data.qr_code}`} alt="FURS QR Code" className="h-48 w-48" />
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
)}
|
|
203
|
+
|
|
204
|
+
{/* Fiscalized Timestamp */}
|
|
205
|
+
{furs.fiscalized_at && (
|
|
206
|
+
<div className="pt-2 text-muted-foreground text-sm">
|
|
207
|
+
{t("Fiscalized at")}: {new Date(furs.fiscalized_at).toLocaleString()}
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
</CardContent>
|
|
211
|
+
</Card>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { zodResolver } from "@hookform/resolvers/zod";
|
|
2
|
+
import type { CreateItemRequest, Item } from "@spaceinvoices/js-sdk";
|
|
3
|
+
import { Minus, Plus } from "lucide-react";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { useForm } from "react-hook-form";
|
|
6
|
+
import { FormInput } from "@/ui/components/form";
|
|
7
|
+
import { Button } from "@/ui/components/ui/button";
|
|
8
|
+
import {
|
|
9
|
+
DropdownMenu,
|
|
10
|
+
DropdownMenuContent,
|
|
11
|
+
DropdownMenuRadioGroup,
|
|
12
|
+
DropdownMenuRadioItem,
|
|
13
|
+
DropdownMenuTrigger,
|
|
14
|
+
} from "@/ui/components/ui/dropdown-menu";
|
|
15
|
+
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/ui/components/ui/form";
|
|
16
|
+
import { Input } from "@/ui/components/ui/input";
|
|
17
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@/ui/components/ui/tooltip";
|
|
18
|
+
import type { CreateItemSchema } from "@/ui/generated/schemas";
|
|
19
|
+
import { createItemSchema } from "@/ui/generated/schemas";
|
|
20
|
+
import type { ComponentTranslationProps } from "@/ui/lib/translation";
|
|
21
|
+
import { createTranslation } from "@/ui/lib/translation";
|
|
22
|
+
|
|
23
|
+
import { useCreateItem } from "../items.hooks";
|
|
24
|
+
import de from "./locales/de";
|
|
25
|
+
import sl from "./locales/sl";
|
|
26
|
+
|
|
27
|
+
const translations = {
|
|
28
|
+
sl,
|
|
29
|
+
de,
|
|
30
|
+
} as const;
|
|
31
|
+
|
|
32
|
+
type CreateItemFormProps = {
|
|
33
|
+
entityId: string;
|
|
34
|
+
onSuccess?: (item: Item) => void;
|
|
35
|
+
onError?: (error: Error) => void;
|
|
36
|
+
renderSubmitButton?: (props: { isSubmitting: boolean; submit: () => void }) => React.ReactNode;
|
|
37
|
+
} & ComponentTranslationProps;
|
|
38
|
+
|
|
39
|
+
export default function CreateItemForm({
|
|
40
|
+
entityId,
|
|
41
|
+
onSuccess,
|
|
42
|
+
onError,
|
|
43
|
+
renderSubmitButton,
|
|
44
|
+
...i18nProps
|
|
45
|
+
}: CreateItemFormProps) {
|
|
46
|
+
const t = createTranslation({
|
|
47
|
+
...i18nProps,
|
|
48
|
+
translations,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const [isGrossPrice, setIsGrossPrice] = useState(false);
|
|
52
|
+
|
|
53
|
+
const form = useForm<CreateItemSchema>({
|
|
54
|
+
resolver: zodResolver(createItemSchema),
|
|
55
|
+
defaultValues: {
|
|
56
|
+
name: "",
|
|
57
|
+
description: "",
|
|
58
|
+
price: 0,
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const setPriceMode = (mode: string) => {
|
|
63
|
+
setIsGrossPrice(mode === "gross");
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const { mutate: createItem, isPending } = useCreateItem({
|
|
67
|
+
entityId,
|
|
68
|
+
onSuccess: (item, _variables, _context) => {
|
|
69
|
+
onSuccess?.(item);
|
|
70
|
+
form.reset(); // Reset form after successful submission
|
|
71
|
+
},
|
|
72
|
+
onError: (error, _variables, _context) => {
|
|
73
|
+
form.setError("root", {
|
|
74
|
+
type: "submit",
|
|
75
|
+
message: t("There was an error creating the item"),
|
|
76
|
+
});
|
|
77
|
+
onError?.(error);
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const onSubmit = async (values: CreateItemSchema) => {
|
|
82
|
+
// Zod validation ensures required fields (name, price) are present before this is called
|
|
83
|
+
// The type cast is safe because React Hook Form's DeepPartial doesn't reflect runtime validation
|
|
84
|
+
const { price, ...rest } = values;
|
|
85
|
+
|
|
86
|
+
// Transform price based on is_gross_price flag
|
|
87
|
+
const payload = isGrossPrice ? { ...rest, gross_price: price } : { ...rest, price };
|
|
88
|
+
|
|
89
|
+
createItem(payload as CreateItemRequest);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const handleSubmitClick = () => {
|
|
93
|
+
form.handleSubmit(onSubmit)();
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<Form {...form}>
|
|
98
|
+
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
|
99
|
+
<FormInput control={form.control} name="name" label={t("Name")} placeholder={t("Enter name")} />
|
|
100
|
+
|
|
101
|
+
<FormInput
|
|
102
|
+
control={form.control}
|
|
103
|
+
name="description"
|
|
104
|
+
label={t("Description")}
|
|
105
|
+
placeholder={t("Enter description")}
|
|
106
|
+
/>
|
|
107
|
+
|
|
108
|
+
<FormField
|
|
109
|
+
control={form.control}
|
|
110
|
+
name="price"
|
|
111
|
+
render={({ field }) => (
|
|
112
|
+
<FormItem>
|
|
113
|
+
<FormLabel>
|
|
114
|
+
{isGrossPrice ? t("Gross price") : t("Price")} <span className="text-red-500">*</span>
|
|
115
|
+
</FormLabel>
|
|
116
|
+
<div className="flex items-center gap-1">
|
|
117
|
+
<FormControl className="min-w-0 flex-1">
|
|
118
|
+
<Input type="number" {...field} onChange={(e) => field.onChange(Number(e.target.value))} />
|
|
119
|
+
</FormControl>
|
|
120
|
+
<Tooltip>
|
|
121
|
+
<TooltipTrigger asChild>
|
|
122
|
+
<div>
|
|
123
|
+
<DropdownMenu>
|
|
124
|
+
<DropdownMenuTrigger asChild>
|
|
125
|
+
<Button type="button" variant="ghost" size="icon" className="h-9 w-9 shrink-0">
|
|
126
|
+
{isGrossPrice ? <Minus className="h-4 w-4" /> : <Plus className="h-4 w-4" />}
|
|
127
|
+
</Button>
|
|
128
|
+
</DropdownMenuTrigger>
|
|
129
|
+
<DropdownMenuContent align="end">
|
|
130
|
+
<DropdownMenuRadioGroup value={isGrossPrice ? "gross" : "net"} onValueChange={setPriceMode}>
|
|
131
|
+
<DropdownMenuRadioItem value="net">{t("Net price")}</DropdownMenuRadioItem>
|
|
132
|
+
<DropdownMenuRadioItem value="gross">{t("Gross price")}</DropdownMenuRadioItem>
|
|
133
|
+
</DropdownMenuRadioGroup>
|
|
134
|
+
</DropdownMenuContent>
|
|
135
|
+
</DropdownMenu>
|
|
136
|
+
</div>
|
|
137
|
+
</TooltipTrigger>
|
|
138
|
+
<TooltipContent>
|
|
139
|
+
{isGrossPrice ? t("Gross price (tax included)") : t("Net price (before tax)")}
|
|
140
|
+
</TooltipContent>
|
|
141
|
+
</Tooltip>
|
|
142
|
+
</div>
|
|
143
|
+
<FormMessage />
|
|
144
|
+
</FormItem>
|
|
145
|
+
)}
|
|
146
|
+
/>
|
|
147
|
+
|
|
148
|
+
{renderSubmitButton?.({
|
|
149
|
+
isSubmitting: isPending || form.formState.isSubmitting,
|
|
150
|
+
submit: handleSubmitClick,
|
|
151
|
+
})}
|
|
152
|
+
</form>
|
|
153
|
+
</Form>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
Name: "Name",
|
|
3
|
+
"Enter name": "Namen eingeben",
|
|
4
|
+
Description: "Beschreibung",
|
|
5
|
+
"Enter description": "Beschreibung eingeben",
|
|
6
|
+
Unit: "Einheit",
|
|
7
|
+
Price: "Preis",
|
|
8
|
+
"There was an error creating the item": "Beim Erstellen des Artikels ist ein Fehler aufgetreten",
|
|
9
|
+
// Gross price support
|
|
10
|
+
"Gross price": "Bruttopreis",
|
|
11
|
+
"Net price": "Nettopreis",
|
|
12
|
+
"Gross price (tax included)": "Bruttopreis (inkl. MwSt.)",
|
|
13
|
+
"Net price (before tax)": "Nettopreis (exkl. MwSt.)",
|
|
14
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
Name: "Naziv",
|
|
3
|
+
"Enter name": "Vnesite naziv",
|
|
4
|
+
Description: "Opis",
|
|
5
|
+
"Enter description": "Vnesite opis",
|
|
6
|
+
Unit: "Enota",
|
|
7
|
+
Price: "Cena",
|
|
8
|
+
"There was an error creating the item": "Prišlo je do napake pri ustvarjanju izdelka",
|
|
9
|
+
// Gross price support
|
|
10
|
+
"Gross price": "Bruto cena",
|
|
11
|
+
"Net price": "Neto cena",
|
|
12
|
+
"Gross price (tax included)": "Bruto cena (z davkom)",
|
|
13
|
+
"Net price (before tax)": "Neto cena (brez davka)",
|
|
14
|
+
};
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { Item } from "@spaceinvoices/js-sdk";
|
|
2
|
+
|
|
3
|
+
import { Plus } from "lucide-react";
|
|
4
|
+
import { useEffect, useMemo, useState } from "react";
|
|
5
|
+
|
|
6
|
+
import { Autocomplete } from "@/ui/common/autocomplete";
|
|
7
|
+
import { useDebounce } from "@/ui/hooks/use-debounce";
|
|
8
|
+
|
|
9
|
+
import { useItemSearch, useRecentItems } from "./items.hooks";
|
|
10
|
+
|
|
11
|
+
type ItemComboboxProps = {
|
|
12
|
+
entityId: string;
|
|
13
|
+
value?: string;
|
|
14
|
+
onSelect?: (item: Item | null, customName?: string) => void;
|
|
15
|
+
placeholder?: string;
|
|
16
|
+
className?: string;
|
|
17
|
+
disabled?: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Autocomplete for selecting saved catalog items
|
|
22
|
+
* Shows recent items, allows search, and supports custom names
|
|
23
|
+
*/
|
|
24
|
+
export function ItemCombobox({
|
|
25
|
+
entityId,
|
|
26
|
+
value,
|
|
27
|
+
onSelect,
|
|
28
|
+
placeholder = "Search or enter item name...",
|
|
29
|
+
className,
|
|
30
|
+
disabled,
|
|
31
|
+
}: ItemComboboxProps) {
|
|
32
|
+
const [search, setSearch] = useState("");
|
|
33
|
+
const [displayValue, setDisplayValue] = useState("");
|
|
34
|
+
const debouncedSearch = useDebounce(search, 300);
|
|
35
|
+
|
|
36
|
+
// Fetch recent items (non-blocking, cached)
|
|
37
|
+
const { data: recentData } = useRecentItems(entityId);
|
|
38
|
+
const recentItems = recentData?.data || [];
|
|
39
|
+
|
|
40
|
+
// Fetch search results
|
|
41
|
+
const { data: searchData, isLoading } = useItemSearch(entityId, debouncedSearch);
|
|
42
|
+
const searchResults = searchData?.data || [];
|
|
43
|
+
|
|
44
|
+
// Use search results if searching, otherwise show recent items
|
|
45
|
+
const items = useMemo(() => {
|
|
46
|
+
if (debouncedSearch) {
|
|
47
|
+
return searchResults;
|
|
48
|
+
}
|
|
49
|
+
return recentItems;
|
|
50
|
+
}, [debouncedSearch, searchResults, recentItems]);
|
|
51
|
+
|
|
52
|
+
// Format price for display
|
|
53
|
+
const formatPrice = (item: Item) => {
|
|
54
|
+
const price = item.gross_price ?? item.price;
|
|
55
|
+
if (price === null || price === undefined) return "";
|
|
56
|
+
return ` - ${price.toFixed(2)}`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const options = items.map((item) => ({
|
|
60
|
+
value: item.id,
|
|
61
|
+
label: (
|
|
62
|
+
<div className="flex flex-col overflow-hidden">
|
|
63
|
+
<span className="truncate">{item.name}</span>
|
|
64
|
+
<span className="truncate text-muted-foreground text-xs">
|
|
65
|
+
{formatPrice(item)}
|
|
66
|
+
{item.description && ` · ${item.description}`}
|
|
67
|
+
</span>
|
|
68
|
+
</div>
|
|
69
|
+
),
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
// Add "Use custom name" option when there's search text
|
|
73
|
+
if (debouncedSearch?.trim()) {
|
|
74
|
+
options.unshift({
|
|
75
|
+
value: `__custom__:${debouncedSearch}`,
|
|
76
|
+
label: (
|
|
77
|
+
<span className="flex items-center gap-2">
|
|
78
|
+
<Plus className="size-4" />
|
|
79
|
+
Use "{debouncedSearch}"
|
|
80
|
+
</span>
|
|
81
|
+
),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const handleSearch = (value: string) => {
|
|
86
|
+
setSearch(value);
|
|
87
|
+
// Clear displayValue when user starts typing
|
|
88
|
+
if (displayValue && value !== displayValue) {
|
|
89
|
+
setDisplayValue("");
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const handleValueChange = (selectedValue: string) => {
|
|
94
|
+
// Handle custom name selection
|
|
95
|
+
if (selectedValue.startsWith("__custom__:")) {
|
|
96
|
+
const customName = selectedValue.replace("__custom__:", "");
|
|
97
|
+
onSelect?.(null, customName);
|
|
98
|
+
setSearch(customName);
|
|
99
|
+
setDisplayValue(customName);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Find selected item
|
|
104
|
+
const selectedItem =
|
|
105
|
+
items.find((i) => i.id === selectedValue) ||
|
|
106
|
+
recentItems.find((i) => i.id === selectedValue) ||
|
|
107
|
+
searchResults.find((i) => i.id === selectedValue);
|
|
108
|
+
|
|
109
|
+
if (selectedItem) {
|
|
110
|
+
onSelect?.(selectedItem);
|
|
111
|
+
setSearch(selectedItem.name);
|
|
112
|
+
setDisplayValue(selectedItem.name);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const handleBlur = () => {
|
|
117
|
+
// If nothing was selected but there's text, treat as custom name
|
|
118
|
+
if (!displayValue && search) {
|
|
119
|
+
handleValueChange(`__custom__:${search}`);
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Reset when value changes externally
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!value) {
|
|
126
|
+
setSearch("");
|
|
127
|
+
setDisplayValue("");
|
|
128
|
+
}
|
|
129
|
+
}, [value]);
|
|
130
|
+
|
|
131
|
+
return (
|
|
132
|
+
<Autocomplete
|
|
133
|
+
searchValue={search}
|
|
134
|
+
onSearch={handleSearch}
|
|
135
|
+
value={value}
|
|
136
|
+
onValueChange={handleValueChange}
|
|
137
|
+
onBlur={handleBlur}
|
|
138
|
+
options={options}
|
|
139
|
+
placeholder={placeholder}
|
|
140
|
+
className={className}
|
|
141
|
+
disabled={disabled}
|
|
142
|
+
loading={isLoading}
|
|
143
|
+
emptyText={debouncedSearch ? "No items found" : "Recent items"}
|
|
144
|
+
displayValue={displayValue}
|
|
145
|
+
/>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TableHead, TableHeader, TableRow } from "@/ui/components/ui/table";
|
|
2
|
+
import type { ComponentTranslationProps } from "@/ui/lib/translation";
|
|
3
|
+
import { createTranslation } from "@/ui/lib/translation";
|
|
4
|
+
|
|
5
|
+
import { SortableHeader } from "../../table/sortable-header";
|
|
6
|
+
|
|
7
|
+
type ItemListHeaderProps = {
|
|
8
|
+
orderBy?: string;
|
|
9
|
+
onSort?: (order: string | null) => void;
|
|
10
|
+
} & ComponentTranslationProps;
|
|
11
|
+
|
|
12
|
+
export default function ItemListHeader({ orderBy, onSort, ...i18nProps }: ItemListHeaderProps) {
|
|
13
|
+
const t = createTranslation(i18nProps);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<TableHeader>
|
|
17
|
+
<TableRow>
|
|
18
|
+
<TableHead>
|
|
19
|
+
<SortableHeader field="name" currentOrder={orderBy} onSort={onSort}>
|
|
20
|
+
{t("Name")}
|
|
21
|
+
</SortableHeader>
|
|
22
|
+
</TableHead>
|
|
23
|
+
<TableHead>
|
|
24
|
+
<SortableHeader field="description" currentOrder={orderBy} onSort={onSort}>
|
|
25
|
+
{t("Description")}
|
|
26
|
+
</SortableHeader>
|
|
27
|
+
</TableHead>
|
|
28
|
+
<TableHead className="text-right">{t("Price")}</TableHead>
|
|
29
|
+
<TableHead className="w-[42px] text-right" />
|
|
30
|
+
</TableRow>
|
|
31
|
+
</TableHeader>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { Item } from "@spaceinvoices/js-sdk";
|
|
2
|
+
|
|
3
|
+
import { MoreHorizontal } from "lucide-react";
|
|
4
|
+
import { Button } from "@/ui/components/ui/button";
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuItem,
|
|
9
|
+
DropdownMenuLabel,
|
|
10
|
+
DropdownMenuSeparator,
|
|
11
|
+
DropdownMenuTrigger,
|
|
12
|
+
} from "@/ui/components/ui/dropdown-menu";
|
|
13
|
+
import type { ComponentTranslationProps } from "@/ui/lib/translation";
|
|
14
|
+
import { createTranslation } from "@/ui/lib/translation";
|
|
15
|
+
|
|
16
|
+
type ItemListRowActionsProps = {
|
|
17
|
+
item: Item;
|
|
18
|
+
} & ComponentTranslationProps;
|
|
19
|
+
|
|
20
|
+
export default function ItemListRowActions({ item, ...i18nProps }: ItemListRowActionsProps) {
|
|
21
|
+
const t = createTranslation(i18nProps);
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<DropdownMenu>
|
|
25
|
+
<DropdownMenuTrigger asChild>
|
|
26
|
+
<Button variant="ghost" className="h-8 w-8 p-0" id="action-menu-trigger">
|
|
27
|
+
<span className="sr-only">{t("Open menu")}</span>
|
|
28
|
+
<MoreHorizontal className="h-4 w-4" />
|
|
29
|
+
</Button>
|
|
30
|
+
</DropdownMenuTrigger>
|
|
31
|
+
<DropdownMenuContent align="end">
|
|
32
|
+
<DropdownMenuLabel>{t("Actions")}</DropdownMenuLabel>
|
|
33
|
+
<DropdownMenuItem className="cursor-pointer" onClick={() => navigator.clipboard.writeText(item.id)}>
|
|
34
|
+
{t("Copy item ID")}
|
|
35
|
+
</DropdownMenuItem>
|
|
36
|
+
<DropdownMenuSeparator />
|
|
37
|
+
<DropdownMenuItem
|
|
38
|
+
className="cursor-pointer"
|
|
39
|
+
onClick={() => {
|
|
40
|
+
window.location.href = `/app/items/${item.id}`;
|
|
41
|
+
}}
|
|
42
|
+
>
|
|
43
|
+
{t("View item")}
|
|
44
|
+
</DropdownMenuItem>
|
|
45
|
+
</DropdownMenuContent>
|
|
46
|
+
</DropdownMenu>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Item } from "@spaceinvoices/js-sdk";
|
|
2
|
+
import { Package } from "lucide-react";
|
|
3
|
+
import { TableCell, TableRow } from "@/ui/components/ui/table";
|
|
4
|
+
import type { ComponentTranslationProps } from "@/ui/lib/translation";
|
|
5
|
+
import { createTranslation } from "@/ui/lib/translation";
|
|
6
|
+
import { Button } from "../../ui/button";
|
|
7
|
+
import ItemListRowActions from "./item-list-row-actions";
|
|
8
|
+
|
|
9
|
+
type ItemListRowProps = {
|
|
10
|
+
item: Item;
|
|
11
|
+
onRowClick?: (item: Item) => void;
|
|
12
|
+
} & ComponentTranslationProps;
|
|
13
|
+
|
|
14
|
+
export default function ItemListRow({ item, onRowClick, ...i18nProps }: ItemListRowProps) {
|
|
15
|
+
const t = createTranslation(i18nProps);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<TableRow>
|
|
19
|
+
<TableCell className="font-medium">
|
|
20
|
+
<Button variant="link" className="py-0 underline" onClick={() => onRowClick?.(item)}>
|
|
21
|
+
<Package className="h-4 w-4 flex-shrink-0" />
|
|
22
|
+
{item.name}
|
|
23
|
+
</Button>
|
|
24
|
+
</TableCell>
|
|
25
|
+
<TableCell>{item.description}</TableCell>
|
|
26
|
+
<TableCell className="text-right">{item.price}</TableCell>
|
|
27
|
+
<TableCell className="text-right">
|
|
28
|
+
<ItemListRowActions item={item} t={t} />
|
|
29
|
+
</TableCell>
|
|
30
|
+
</TableRow>
|
|
31
|
+
);
|
|
32
|
+
}
|