@hub-kit/core 0.3.0
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/README.md +125 -0
- package/cli/hub-kit.mjs +112 -0
- package/dist/adapters/bank-reconciliation.d.ts +100 -0
- package/dist/adapters/bank-reconciliation.js +1 -0
- package/dist/adapters/bank-sync-log.d.ts +46 -0
- package/dist/adapters/bank-sync-log.js +1 -0
- package/dist/adapters/category-rules.d.ts +94 -0
- package/dist/adapters/category-rules.js +1 -0
- package/dist/adapters/companies.d.ts +85 -0
- package/dist/adapters/companies.js +3 -0
- package/dist/adapters/customers.d.ts +71 -0
- package/dist/adapters/customers.js +1 -0
- package/dist/adapters/exclusion-rules.d.ts +34 -0
- package/dist/adapters/exclusion-rules.js +15 -0
- package/dist/adapters/file-naming.d.ts +36 -0
- package/dist/adapters/file-naming.js +1 -0
- package/dist/adapters/index.d.ts +24 -0
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/invoice-detail.d.ts +126 -0
- package/dist/adapters/invoice-detail.js +1 -0
- package/dist/adapters/invoice-list.d.ts +63 -0
- package/dist/adapters/invoice-list.js +1 -0
- package/dist/adapters/invoice-upload.d.ts +26 -0
- package/dist/adapters/invoice-upload.js +1 -0
- package/dist/adapters/manual-bookings.d.ts +56 -0
- package/dist/adapters/manual-bookings.js +1 -0
- package/dist/adapters/notifications.d.ts +31 -0
- package/dist/adapters/notifications.js +1 -0
- package/dist/adapters/open-items.d.ts +42 -0
- package/dist/adapters/open-items.js +1 -0
- package/dist/adapters/opos-whitelist.d.ts +38 -0
- package/dist/adapters/opos-whitelist.js +1 -0
- package/dist/adapters/outgoing-invoices.d.ts +32 -0
- package/dist/adapters/outgoing-invoices.js +1 -0
- package/dist/adapters/overview.d.ts +68 -0
- package/dist/adapters/overview.js +1 -0
- package/dist/adapters/processing-log.d.ts +48 -0
- package/dist/adapters/processing-log.js +1 -0
- package/dist/adapters/properties.d.ts +87 -0
- package/dist/adapters/properties.js +1 -0
- package/dist/adapters/supplier-detail.d.ts +64 -0
- package/dist/adapters/supplier-detail.js +1 -0
- package/dist/adapters/supplier-list.d.ts +21 -0
- package/dist/adapters/supplier-list.js +1 -0
- package/dist/adapters/team.d.ts +79 -0
- package/dist/adapters/team.js +1 -0
- package/dist/adapters/trash.d.ts +26 -0
- package/dist/adapters/trash.js +1 -0
- package/dist/adapters/vat-rules.d.ts +83 -0
- package/dist/adapters/vat-rules.js +1 -0
- package/dist/components/access/index.d.ts +3 -0
- package/dist/components/access/index.js +3 -0
- package/dist/components/access/permission-checklist.d.ts +13 -0
- package/dist/components/access/permission-checklist.js +19 -0
- package/dist/components/access/permission-matrix.d.ts +12 -0
- package/dist/components/access/permission-matrix.js +18 -0
- package/dist/components/access/types.d.ts +15 -0
- package/dist/components/access/types.js +1 -0
- package/dist/components/dashboard/date-range-calendar.d.ts +19 -0
- package/dist/components/dashboard/date-range-calendar.js +97 -0
- package/dist/components/dashboard/index.d.ts +23 -0
- package/dist/components/dashboard/index.js +14 -0
- package/dist/components/dashboard/kpi-card.d.ts +13 -0
- package/dist/components/dashboard/kpi-card.js +7 -0
- package/dist/components/dashboard/money-cards-row.d.ts +29 -0
- package/dist/components/dashboard/money-cards-row.js +28 -0
- package/dist/components/dashboard/money-trend-chart.d.ts +13 -0
- package/dist/components/dashboard/money-trend-chart.js +16 -0
- package/dist/components/dashboard/panel.d.ts +9 -0
- package/dist/components/dashboard/panel.js +5 -0
- package/dist/components/dashboard/period-picker.d.ts +25 -0
- package/dist/components/dashboard/period-picker.js +82 -0
- package/dist/components/dashboard/periods.d.ts +13 -0
- package/dist/components/dashboard/periods.js +63 -0
- package/dist/components/dashboard/pipeline-stages-panel.d.ts +14 -0
- package/dist/components/dashboard/pipeline-stages-panel.js +12 -0
- package/dist/components/dashboard/processing-summary-panel.d.ts +34 -0
- package/dist/components/dashboard/processing-summary-panel.js +11 -0
- package/dist/components/dashboard/ranked-bars.d.ts +15 -0
- package/dist/components/dashboard/ranked-bars.js +16 -0
- package/dist/components/dashboard/ranked-list-panel.d.ts +14 -0
- package/dist/components/dashboard/ranked-list-panel.js +9 -0
- package/dist/components/dashboard/stage-tiles.d.ts +15 -0
- package/dist/components/dashboard/stage-tiles.js +6 -0
- package/dist/components/dashboard/stat-rows-panel.d.ts +25 -0
- package/dist/components/dashboard/stat-rows-panel.js +7 -0
- package/dist/components/dashboard/stat-tile.d.ts +20 -0
- package/dist/components/dashboard/stat-tile.js +9 -0
- package/dist/components/feedback/index.d.ts +4 -0
- package/dist/components/feedback/index.js +2 -0
- package/dist/components/feedback/query-states.d.ts +27 -0
- package/dist/components/feedback/query-states.js +38 -0
- package/dist/components/feedback/table-pagination.d.ts +20 -0
- package/dist/components/feedback/table-pagination.js +15 -0
- package/dist/components/invoice-queue/index.d.ts +5 -0
- package/dist/components/invoice-queue/index.js +3 -0
- package/dist/components/invoice-queue/next-action-cell.d.ts +5 -0
- package/dist/components/invoice-queue/next-action-cell.js +9 -0
- package/dist/components/invoice-queue/queue-kpi-card.d.ts +19 -0
- package/dist/components/invoice-queue/queue-kpi-card.js +31 -0
- package/dist/components/invoice-queue/queue-kpi-row.d.ts +22 -0
- package/dist/components/invoice-queue/queue-kpi-row.js +12 -0
- package/dist/components/notifications/alerts.d.ts +25 -0
- package/dist/components/notifications/alerts.js +66 -0
- package/dist/components/notifications/bell.d.ts +14 -0
- package/dist/components/notifications/bell.js +17 -0
- package/dist/components/notifications/highlight.d.ts +4 -0
- package/dist/components/notifications/highlight.js +7 -0
- package/dist/components/notifications/index.d.ts +7 -0
- package/dist/components/notifications/index.js +6 -0
- package/dist/components/notifications/reminder-dialog.d.ts +24 -0
- package/dist/components/notifications/reminder-dialog.js +54 -0
- package/dist/components/notifications/types.d.ts +27 -0
- package/dist/components/notifications/types.js +1 -0
- package/dist/components/notifications/use-notification-acks.d.ts +7 -0
- package/dist/components/notifications/use-notification-acks.js +37 -0
- package/dist/components/shell/breadcrumbs.d.ts +7 -0
- package/dist/components/shell/breadcrumbs.js +38 -0
- package/dist/components/shell/index.d.ts +3 -0
- package/dist/components/shell/index.js +3 -0
- package/dist/components/shell/shell.d.ts +30 -0
- package/dist/components/shell/shell.js +110 -0
- package/dist/components/shell/types.d.ts +23 -0
- package/dist/components/shell/types.js +3 -0
- package/dist/components/tour/find-target.d.ts +1 -0
- package/dist/components/tour/find-target.js +3 -0
- package/dist/components/tour/index.d.ts +14 -0
- package/dist/components/tour/index.js +8 -0
- package/dist/components/tour/labels.d.ts +10 -0
- package/dist/components/tour/labels.js +9 -0
- package/dist/components/tour/seen-store.d.ts +3 -0
- package/dist/components/tour/seen-store.js +34 -0
- package/dist/components/tour/tour-blocks.d.ts +4 -0
- package/dist/components/tour/tour-blocks.js +27 -0
- package/dist/components/tour/tour-button.d.ts +4 -0
- package/dist/components/tour/tour-button.js +12 -0
- package/dist/components/tour/tour-card.d.ts +11 -0
- package/dist/components/tour/tour-card.js +7 -0
- package/dist/components/tour/tour-context.d.ts +16 -0
- package/dist/components/tour/tour-context.js +2 -0
- package/dist/components/tour/tour-overlay.d.ts +1 -0
- package/dist/components/tour/tour-overlay.js +165 -0
- package/dist/components/tour/tour-provider.d.ts +9 -0
- package/dist/components/tour/tour-provider.js +80 -0
- package/dist/components/tour/types.d.ts +44 -0
- package/dist/components/tour/types.js +1 -0
- package/dist/components/tour/use-tour.d.ts +3 -0
- package/dist/components/tour/use-tour.js +9 -0
- package/dist/hooks/use-mobile.d.ts +1 -0
- package/dist/hooks/use-mobile.js +15 -0
- package/dist/i18n/index.d.ts +15 -0
- package/dist/i18n/index.js +15 -0
- package/dist/lib/bank-import/classify.d.ts +8 -0
- package/dist/lib/bank-import/classify.js +89 -0
- package/dist/lib/bank-import/column-guess.d.ts +2 -0
- package/dist/lib/bank-import/column-guess.js +40 -0
- package/dist/lib/bank-import/csv.d.ts +3 -0
- package/dist/lib/bank-import/csv.js +20 -0
- package/dist/lib/bank-import/duplicates.d.ts +2 -0
- package/dist/lib/bank-import/duplicates.js +21 -0
- package/dist/lib/bank-import/index.d.ts +12 -0
- package/dist/lib/bank-import/index.js +9 -0
- package/dist/lib/bank-import/mapping-storage.d.ts +3 -0
- package/dist/lib/bank-import/mapping-storage.js +29 -0
- package/dist/lib/bank-import/normalize.d.ts +4 -0
- package/dist/lib/bank-import/normalize.js +82 -0
- package/dist/lib/bank-import/parse-file.d.ts +7 -0
- package/dist/lib/bank-import/parse-file.js +23 -0
- package/dist/lib/bank-import/types.d.ts +31 -0
- package/dist/lib/bank-import/types.js +1 -0
- package/dist/lib/bank-import/xlsx.d.ts +2 -0
- package/dist/lib/bank-import/xlsx.js +25 -0
- package/dist/lib/class-names.d.ts +2 -0
- package/dist/lib/class-names.js +5 -0
- package/dist/lib/download.d.ts +2 -0
- package/dist/lib/download.js +14 -0
- package/dist/lib/formatters.d.ts +6 -0
- package/dist/lib/formatters.js +24 -0
- package/dist/lib/index.d.ts +9 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/opos-whitelist/index.d.ts +2 -0
- package/dist/lib/opos-whitelist/index.js +1 -0
- package/dist/lib/opos-whitelist/opos.d.ts +14 -0
- package/dist/lib/opos-whitelist/opos.js +49 -0
- package/dist/lib/query-result.d.ts +12 -0
- package/dist/lib/query-result.js +1 -0
- package/dist/lib/tour-placeholders/index.d.ts +2 -0
- package/dist/lib/tour-placeholders/index.js +1 -0
- package/dist/lib/tour-placeholders/overview.d.ts +12 -0
- package/dist/lib/tour-placeholders/overview.js +139 -0
- package/dist/pages/bank-accounts/BankAccountsPage.d.ts +7 -0
- package/dist/pages/bank-accounts/BankAccountsPage.js +21 -0
- package/dist/pages/bank-accounts/index.d.ts +4 -0
- package/dist/pages/bank-accounts/index.js +2 -0
- package/dist/pages/bank-accounts/labels.d.ts +15 -0
- package/dist/pages/bank-accounts/labels.js +14 -0
- package/dist/pages/bank-import/BankImportWizard.d.ts +9 -0
- package/dist/pages/bank-import/BankImportWizard.js +158 -0
- package/dist/pages/bank-import/index.d.ts +4 -0
- package/dist/pages/bank-import/index.js +2 -0
- package/dist/pages/bank-import/labels.d.ts +44 -0
- package/dist/pages/bank-import/labels.js +43 -0
- package/dist/pages/bank-sync-log/BankSyncLogPage.d.ts +7 -0
- package/dist/pages/bank-sync-log/BankSyncLogPage.js +77 -0
- package/dist/pages/bank-sync-log/index.d.ts +4 -0
- package/dist/pages/bank-sync-log/index.js +2 -0
- package/dist/pages/bank-sync-log/labels.d.ts +21 -0
- package/dist/pages/bank-sync-log/labels.js +20 -0
- package/dist/pages/bank-transactions/BankMatchPanel.d.ts +8 -0
- package/dist/pages/bank-transactions/BankMatchPanel.js +75 -0
- package/dist/pages/bank-transactions/BankTransactionsPage.d.ts +7 -0
- package/dist/pages/bank-transactions/BankTransactionsPage.js +59 -0
- package/dist/pages/bank-transactions/badges.d.ts +8 -0
- package/dist/pages/bank-transactions/badges.js +24 -0
- package/dist/pages/bank-transactions/index.d.ts +6 -0
- package/dist/pages/bank-transactions/index.js +4 -0
- package/dist/pages/bank-transactions/labels.d.ts +41 -0
- package/dist/pages/bank-transactions/labels.js +40 -0
- package/dist/pages/bank-transactions/sort-control.d.ts +17 -0
- package/dist/pages/bank-transactions/sort-control.js +8 -0
- package/dist/pages/bank-transactions/use-table-view.d.ts +23 -0
- package/dist/pages/bank-transactions/use-table-view.js +54 -0
- package/dist/pages/category-rules/CategoryRulesPage.d.ts +7 -0
- package/dist/pages/category-rules/CategoryRulesPage.js +160 -0
- package/dist/pages/category-rules/index.d.ts +4 -0
- package/dist/pages/category-rules/index.js +2 -0
- package/dist/pages/category-rules/labels.d.ts +66 -0
- package/dist/pages/category-rules/labels.js +72 -0
- package/dist/pages/companies/company-detail-page.d.ts +10 -0
- package/dist/pages/companies/company-detail-page.js +184 -0
- package/dist/pages/companies/company-form.d.ts +20 -0
- package/dist/pages/companies/company-form.js +23 -0
- package/dist/pages/companies/company-list-page.d.ts +9 -0
- package/dist/pages/companies/company-list-page.js +109 -0
- package/dist/pages/companies/known-spellings-card.d.ts +8 -0
- package/dist/pages/companies/known-spellings-card.js +65 -0
- package/dist/pages/companies/labels.d.ts +105 -0
- package/dist/pages/companies/labels.js +104 -0
- package/dist/pages/companies/sort-control.d.ts +17 -0
- package/dist/pages/companies/sort-control.js +8 -0
- package/dist/pages/companies/use-infinite-rows.d.ts +6 -0
- package/dist/pages/companies/use-infinite-rows.js +24 -0
- package/dist/pages/companies/use-table-view.d.ts +23 -0
- package/dist/pages/companies/use-table-view.js +56 -0
- package/dist/pages/customers/company-chip.d.ts +6 -0
- package/dist/pages/customers/company-chip.js +9 -0
- package/dist/pages/customers/copy-button.d.ts +6 -0
- package/dist/pages/customers/copy-button.js +31 -0
- package/dist/pages/customers/customer-detail-page.d.ts +10 -0
- package/dist/pages/customers/customer-detail-page.js +83 -0
- package/dist/pages/customers/customer-list-page.d.ts +9 -0
- package/dist/pages/customers/customer-list-page.js +65 -0
- package/dist/pages/customers/edit-customer-dialog.d.ts +9 -0
- package/dist/pages/customers/edit-customer-dialog.js +72 -0
- package/dist/pages/customers/form-hints.d.ts +6 -0
- package/dist/pages/customers/form-hints.js +9 -0
- package/dist/pages/customers/invoice-status-badge.d.ts +10 -0
- package/dist/pages/customers/invoice-status-badge.js +22 -0
- package/dist/pages/customers/labels.d.ts +116 -0
- package/dist/pages/customers/labels.js +117 -0
- package/dist/pages/customers/new-customer-dialog.d.ts +6 -0
- package/dist/pages/customers/new-customer-dialog.js +93 -0
- package/dist/pages/customers/sort-control.d.ts +14 -0
- package/dist/pages/customers/sort-control.js +9 -0
- package/dist/pages/customers/use-table-view.d.ts +23 -0
- package/dist/pages/customers/use-table-view.js +56 -0
- package/dist/pages/exclusion-rules/exclusion-rules-page.d.ts +7 -0
- package/dist/pages/exclusion-rules/exclusion-rules-page.js +114 -0
- package/dist/pages/exclusion-rules/labels.d.ts +53 -0
- package/dist/pages/exclusion-rules/labels.js +61 -0
- package/dist/pages/file-naming/build-filename.d.ts +3 -0
- package/dist/pages/file-naming/build-filename.js +82 -0
- package/dist/pages/file-naming/file-naming-page.d.ts +10 -0
- package/dist/pages/file-naming/file-naming-page.js +120 -0
- package/dist/pages/file-naming/labels.d.ts +44 -0
- package/dist/pages/file-naming/labels.js +44 -0
- package/dist/pages/index.d.ts +63 -0
- package/dist/pages/index.js +48 -0
- package/dist/pages/invoice-detail/InvoiceDetailPage.d.ts +11 -0
- package/dist/pages/invoice-detail/InvoiceDetailPage.js +292 -0
- package/dist/pages/invoice-detail/OutgoingInvoiceFlag.d.ts +26 -0
- package/dist/pages/invoice-detail/OutgoingInvoiceFlag.js +16 -0
- package/dist/pages/invoice-detail/ReviewBadge.d.ts +15 -0
- package/dist/pages/invoice-detail/ReviewBadge.js +23 -0
- package/dist/pages/invoice-detail/ReviewCard.d.ts +17 -0
- package/dist/pages/invoice-detail/ReviewCard.js +12 -0
- package/dist/pages/invoice-detail/SideBySide.d.ts +5 -0
- package/dist/pages/invoice-detail/SideBySide.js +5 -0
- package/dist/pages/invoice-detail/WorkflowHistoryList.d.ts +23 -0
- package/dist/pages/invoice-detail/WorkflowHistoryList.js +32 -0
- package/dist/pages/invoice-detail/WorkflowLadder.d.ts +20 -0
- package/dist/pages/invoice-detail/WorkflowLadder.js +16 -0
- package/dist/pages/invoice-detail/history.d.ts +23 -0
- package/dist/pages/invoice-detail/history.js +76 -0
- package/dist/pages/invoice-detail/index.d.ts +21 -0
- package/dist/pages/invoice-detail/index.js +11 -0
- package/dist/pages/invoice-detail/labels.d.ts +79 -0
- package/dist/pages/invoice-detail/labels.js +139 -0
- package/dist/pages/invoice-detail/pipeline-types.d.ts +58 -0
- package/dist/pages/invoice-detail/pipeline-types.js +1 -0
- package/dist/pages/invoice-detail/review-recheck.d.ts +8 -0
- package/dist/pages/invoice-detail/review-recheck.js +103 -0
- package/dist/pages/invoice-detail/review.d.ts +91 -0
- package/dist/pages/invoice-detail/review.js +310 -0
- package/dist/pages/invoice-list/badges.d.ts +23 -0
- package/dist/pages/invoice-list/badges.js +26 -0
- package/dist/pages/invoice-list/index.d.ts +5 -0
- package/dist/pages/invoice-list/index.js +3 -0
- package/dist/pages/invoice-list/invoice-list-page.d.ts +13 -0
- package/dist/pages/invoice-list/invoice-list-page.js +114 -0
- package/dist/pages/invoice-list/labels.d.ts +36 -0
- package/dist/pages/invoice-list/labels.js +29 -0
- package/dist/pages/invoice-list/sort-control.d.ts +17 -0
- package/dist/pages/invoice-list/sort-control.js +8 -0
- package/dist/pages/invoice-list/use-table-view.d.ts +23 -0
- package/dist/pages/invoice-list/use-table-view.js +54 -0
- package/dist/pages/invoice-upload/InvoiceUploadPage.d.ts +7 -0
- package/dist/pages/invoice-upload/InvoiceUploadPage.js +115 -0
- package/dist/pages/invoice-upload/index.d.ts +4 -0
- package/dist/pages/invoice-upload/index.js +2 -0
- package/dist/pages/invoice-upload/labels.d.ts +29 -0
- package/dist/pages/invoice-upload/labels.js +28 -0
- package/dist/pages/manual-bookings/ManualBookingsPage.d.ts +7 -0
- package/dist/pages/manual-bookings/ManualBookingsPage.js +161 -0
- package/dist/pages/manual-bookings/index.d.ts +4 -0
- package/dist/pages/manual-bookings/index.js +2 -0
- package/dist/pages/manual-bookings/labels.d.ts +53 -0
- package/dist/pages/manual-bookings/labels.js +52 -0
- package/dist/pages/notifications/labels.d.ts +22 -0
- package/dist/pages/notifications/labels.js +21 -0
- package/dist/pages/notifications/notifications-page.d.ts +14 -0
- package/dist/pages/notifications/notifications-page.js +81 -0
- package/dist/pages/open-items/OpenItemsPage.d.ts +11 -0
- package/dist/pages/open-items/OpenItemsPage.js +110 -0
- package/dist/pages/open-items/index.d.ts +6 -0
- package/dist/pages/open-items/index.js +3 -0
- package/dist/pages/open-items/labels.d.ts +37 -0
- package/dist/pages/open-items/labels.js +36 -0
- package/dist/pages/open-items/urgency.d.ts +8 -0
- package/dist/pages/open-items/urgency.js +27 -0
- package/dist/pages/opos-whitelist/OposWhitelistPage.d.ts +7 -0
- package/dist/pages/opos-whitelist/OposWhitelistPage.js +165 -0
- package/dist/pages/opos-whitelist/index.d.ts +4 -0
- package/dist/pages/opos-whitelist/index.js +2 -0
- package/dist/pages/opos-whitelist/labels.d.ts +56 -0
- package/dist/pages/opos-whitelist/labels.js +70 -0
- package/dist/pages/outgoing-invoices/OutgoingInvoicesPage.d.ts +7 -0
- package/dist/pages/outgoing-invoices/OutgoingInvoicesPage.js +169 -0
- package/dist/pages/outgoing-invoices/index.d.ts +4 -0
- package/dist/pages/outgoing-invoices/index.js +2 -0
- package/dist/pages/outgoing-invoices/labels.d.ts +43 -0
- package/dist/pages/outgoing-invoices/labels.js +42 -0
- package/dist/pages/outgoing-invoices/sort-control.d.ts +17 -0
- package/dist/pages/outgoing-invoices/sort-control.js +8 -0
- package/dist/pages/outgoing-invoices/use-table-view.d.ts +23 -0
- package/dist/pages/outgoing-invoices/use-table-view.js +54 -0
- package/dist/pages/overview/labels.d.ts +35 -0
- package/dist/pages/overview/labels.js +34 -0
- package/dist/pages/overview/overview-page.d.ts +22 -0
- package/dist/pages/overview/overview-page.js +127 -0
- package/dist/pages/processing-log/labels.d.ts +54 -0
- package/dist/pages/processing-log/labels.js +53 -0
- package/dist/pages/processing-log/mail-parsing.d.ts +21 -0
- package/dist/pages/processing-log/mail-parsing.js +52 -0
- package/dist/pages/processing-log/processing-log-page.d.ts +9 -0
- package/dist/pages/processing-log/processing-log-page.js +206 -0
- package/dist/pages/properties/badges.d.ts +20 -0
- package/dist/pages/properties/badges.js +45 -0
- package/dist/pages/properties/company-assignment-field.d.ts +11 -0
- package/dist/pages/properties/company-assignment-field.js +13 -0
- package/dist/pages/properties/form-field.d.ts +6 -0
- package/dist/pages/properties/form-field.js +9 -0
- package/dist/pages/properties/labels.d.ts +124 -0
- package/dist/pages/properties/labels.js +128 -0
- package/dist/pages/properties/name-variants-card.d.ts +7 -0
- package/dist/pages/properties/name-variants-card.js +60 -0
- package/dist/pages/properties/property-detail-page.d.ts +10 -0
- package/dist/pages/properties/property-detail-page.js +150 -0
- package/dist/pages/properties/property-list-page.d.ts +13 -0
- package/dist/pages/properties/property-list-page.js +201 -0
- package/dist/pages/properties/sort-control.d.ts +14 -0
- package/dist/pages/properties/sort-control.js +9 -0
- package/dist/pages/properties/use-infinite-rows.d.ts +6 -0
- package/dist/pages/properties/use-infinite-rows.js +26 -0
- package/dist/pages/properties/use-table-view.d.ts +23 -0
- package/dist/pages/properties/use-table-view.js +56 -0
- package/dist/pages/supplier-detail/SupplierDetailPage.d.ts +10 -0
- package/dist/pages/supplier-detail/SupplierDetailPage.js +157 -0
- package/dist/pages/supplier-detail/index.d.ts +4 -0
- package/dist/pages/supplier-detail/index.js +2 -0
- package/dist/pages/supplier-detail/labels.d.ts +46 -0
- package/dist/pages/supplier-detail/labels.js +45 -0
- package/dist/pages/supplier-list/SupplierListPage.d.ts +9 -0
- package/dist/pages/supplier-list/SupplierListPage.js +102 -0
- package/dist/pages/supplier-list/index.d.ts +4 -0
- package/dist/pages/supplier-list/index.js +2 -0
- package/dist/pages/supplier-list/labels.d.ts +33 -0
- package/dist/pages/supplier-list/labels.js +32 -0
- package/dist/pages/supplier-list/sort-control.d.ts +17 -0
- package/dist/pages/supplier-list/sort-control.js +8 -0
- package/dist/pages/supplier-list/use-table-view.d.ts +23 -0
- package/dist/pages/supplier-list/use-table-view.js +54 -0
- package/dist/pages/team/account-dialogs.d.ts +12 -0
- package/dist/pages/team/account-dialogs.js +49 -0
- package/dist/pages/team/employee-editor.d.ts +19 -0
- package/dist/pages/team/employee-editor.js +124 -0
- package/dist/pages/team/labels.d.ts +99 -0
- package/dist/pages/team/labels.js +98 -0
- package/dist/pages/team/new-employee.d.ts +8 -0
- package/dist/pages/team/new-employee.js +103 -0
- package/dist/pages/team/roles-tab.d.ts +6 -0
- package/dist/pages/team/roles-tab.js +46 -0
- package/dist/pages/team/team-page.d.ts +7 -0
- package/dist/pages/team/team-page.js +81 -0
- package/dist/pages/team/temp-password-dialog.d.ts +11 -0
- package/dist/pages/team/temp-password-dialog.js +23 -0
- package/dist/pages/trash/labels.d.ts +68 -0
- package/dist/pages/trash/labels.js +69 -0
- package/dist/pages/trash/trash-page.d.ts +9 -0
- package/dist/pages/trash/trash-page.js +259 -0
- package/dist/pages/vat-rules/labels.d.ts +110 -0
- package/dist/pages/vat-rules/labels.js +109 -0
- package/dist/pages/vat-rules/new-rule-dialog.d.ts +7 -0
- package/dist/pages/vat-rules/new-rule-dialog.js +134 -0
- package/dist/pages/vat-rules/rule-row.d.ts +20 -0
- package/dist/pages/vat-rules/rule-row.js +120 -0
- package/dist/pages/vat-rules/vat-rules-page.d.ts +9 -0
- package/dist/pages/vat-rules/vat-rules-page.js +61 -0
- package/dist/ui/alert-dialog.d.ts +20 -0
- package/dist/ui/alert-dialog.js +41 -0
- package/dist/ui/badge.d.ts +9 -0
- package/dist/ui/badge.js +20 -0
- package/dist/ui/breadcrumb.d.ts +19 -0
- package/dist/ui/breadcrumb.js +23 -0
- package/dist/ui/button.d.ts +11 -0
- package/dist/ui/button.js +33 -0
- package/dist/ui/calendar.d.ts +8 -0
- package/dist/ui/calendar.js +75 -0
- package/dist/ui/card.d.ts +8 -0
- package/dist/ui/card.js +16 -0
- package/dist/ui/chart.d.ts +62 -0
- package/dist/ui/chart.js +118 -0
- package/dist/ui/checkbox.d.ts +4 -0
- package/dist/ui/checkbox.js +11 -0
- package/dist/ui/collapsible.d.ts +5 -0
- package/dist/ui/collapsible.js +5 -0
- package/dist/ui/combobox.d.ts +24 -0
- package/dist/ui/combobox.js +55 -0
- package/dist/ui/command.d.ts +80 -0
- package/dist/ui/command.js +29 -0
- package/dist/ui/dialog.d.ts +19 -0
- package/dist/ui/dialog.js +58 -0
- package/dist/ui/dropdown-menu.d.ts +27 -0
- package/dist/ui/dropdown-menu.js +33 -0
- package/dist/ui/field-context.d.ts +4 -0
- package/dist/ui/field-context.js +6 -0
- package/dist/ui/field.d.ts +3 -0
- package/dist/ui/field.js +8 -0
- package/dist/ui/icon-action.d.ts +3 -0
- package/dist/ui/icon-action.js +8 -0
- package/dist/ui/index.d.ts +32 -0
- package/dist/ui/index.js +32 -0
- package/dist/ui/input.d.ts +3 -0
- package/dist/ui/input.js +11 -0
- package/dist/ui/label.d.ts +5 -0
- package/dist/ui/label.js +15 -0
- package/dist/ui/multi-combobox.d.ts +18 -0
- package/dist/ui/multi-combobox.js +40 -0
- package/dist/ui/popover.d.ts +9 -0
- package/dist/ui/popover.js +12 -0
- package/dist/ui/scroll-area.d.ts +5 -0
- package/dist/ui/scroll-area.js +9 -0
- package/dist/ui/scroll-shadow.d.ts +8 -0
- package/dist/ui/scroll-shadow.js +57 -0
- package/dist/ui/select.d.ts +13 -0
- package/dist/ui/select.js +31 -0
- package/dist/ui/separator.d.ts +4 -0
- package/dist/ui/separator.js +7 -0
- package/dist/ui/sheet.d.ts +25 -0
- package/dist/ui/sheet.js +40 -0
- package/dist/ui/sidebar.d.ts +65 -0
- package/dist/ui/sidebar.js +207 -0
- package/dist/ui/skeleton.d.ts +2 -0
- package/dist/ui/skeleton.js +6 -0
- package/dist/ui/sonner.d.ts +5 -0
- package/dist/ui/sonner.js +13 -0
- package/dist/ui/switch.d.ts +4 -0
- package/dist/ui/switch.js +7 -0
- package/dist/ui/table.d.ts +12 -0
- package/dist/ui/table.js +24 -0
- package/dist/ui/tabs.d.ts +7 -0
- package/dist/ui/tabs.js +12 -0
- package/dist/ui/textarea.d.ts +3 -0
- package/dist/ui/textarea.js +11 -0
- package/dist/ui/tooltip.d.ts +7 -0
- package/dist/ui/tooltip.js +11 -0
- package/package.json +80 -0
- package/themes/TOKENS.md +57 -0
- package/themes/default.css +60 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from "react";
|
|
3
|
+
import { ArchiveRestore, ChevronDown, ChevronUp, ChevronsUpDown, Lock, Search, Trash2, } from "lucide-react";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
import { Badge } from "../../ui/badge.js";
|
|
6
|
+
import { Button } from "../../ui/button.js";
|
|
7
|
+
import { Checkbox } from "../../ui/checkbox.js";
|
|
8
|
+
import { Input } from "../../ui/input.js";
|
|
9
|
+
import { Textarea } from "../../ui/textarea.js";
|
|
10
|
+
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "../../ui/dialog.js";
|
|
11
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../../ui/tooltip.js";
|
|
12
|
+
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "../../ui/alert-dialog.js";
|
|
13
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "../../ui/select.js";
|
|
14
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "../../ui/table.js";
|
|
15
|
+
import { ErrorState, TableSkeleton, readableErrorMessage, } from "../../components/feedback/query-states.js";
|
|
16
|
+
import { TablePagination } from "../../components/feedback/table-pagination.js";
|
|
17
|
+
import { englishFormatters } from "../../lib/formatters.js";
|
|
18
|
+
import { englishTrashPageLabels } from "./labels.js";
|
|
19
|
+
const ALL = "__all";
|
|
20
|
+
const DEFAULT_PAGE_SIZE = 25;
|
|
21
|
+
const AGE_OPTIONS = [
|
|
22
|
+
{ value: "any", minimumDays: 0 },
|
|
23
|
+
{ value: "olderThanThirtyDays", minimumDays: 30 },
|
|
24
|
+
{ value: "olderThanNinetyDays", minimumDays: 90 },
|
|
25
|
+
{ value: "olderThanOneYear", minimumDays: 365 },
|
|
26
|
+
];
|
|
27
|
+
function ageInDays(deletedAt) {
|
|
28
|
+
return Math.floor((Date.now() - new Date(deletedAt).getTime()) / 86_400_000);
|
|
29
|
+
}
|
|
30
|
+
function recordKey(record) {
|
|
31
|
+
return `${record.tableName}-${record.id}`;
|
|
32
|
+
}
|
|
33
|
+
function ageOptionLabel(value, labels) {
|
|
34
|
+
if (value === "olderThanThirtyDays")
|
|
35
|
+
return labels.olderThanThirtyDays;
|
|
36
|
+
if (value === "olderThanNinetyDays")
|
|
37
|
+
return labels.olderThanNinetyDays;
|
|
38
|
+
if (value === "olderThanOneYear")
|
|
39
|
+
return labels.olderThanOneYear;
|
|
40
|
+
return labels.anyAge;
|
|
41
|
+
}
|
|
42
|
+
export function TrashPage({ adapter, labels = englishTrashPageLabels, formatters = englishFormatters, }) {
|
|
43
|
+
const [tableFilter, setTableFilter] = useState(ALL);
|
|
44
|
+
const [searchInput, setSearchInput] = useState("");
|
|
45
|
+
const [ageFilter, setAgeFilter] = useState("any");
|
|
46
|
+
const [sortKey, setSortKey] = useState("deletedAt");
|
|
47
|
+
const [sortAscending, setSortAscending] = useState(false);
|
|
48
|
+
const [pageIndex, setPageIndex] = useState(0);
|
|
49
|
+
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
|
|
50
|
+
const [selectedKeys, setSelectedKeys] = useState(new Set());
|
|
51
|
+
const recordsQuery = adapter.useTrashedRecords(tableFilter === ALL ? undefined : tableFilter);
|
|
52
|
+
const allRecords = useMemo(() => recordsQuery.data ?? [], [recordsQuery.data]);
|
|
53
|
+
// The filter options come from the backend; fall back to the types actually present.
|
|
54
|
+
const tablesQuery = adapter.useTrashTableNames();
|
|
55
|
+
const eligibleTables = useMemo(() => {
|
|
56
|
+
const fromBackend = tablesQuery.data?.eligible ?? [];
|
|
57
|
+
if (fromBackend.length > 0)
|
|
58
|
+
return fromBackend;
|
|
59
|
+
return [...new Set(allRecords.map((record) => record.tableName))].sort();
|
|
60
|
+
}, [tablesQuery.data, allRecords]);
|
|
61
|
+
const purgeableTables = useMemo(() => new Set(tablesQuery.data?.purgeable ?? eligibleTables), [tablesQuery.data, eligibleTables]);
|
|
62
|
+
const filteredRecords = useMemo(() => {
|
|
63
|
+
const needle = searchInput.trim().toLowerCase();
|
|
64
|
+
const minimumDays = AGE_OPTIONS.find((option) => option.value === ageFilter)?.minimumDays ?? 0;
|
|
65
|
+
const matches = allRecords.filter((record) => {
|
|
66
|
+
if (minimumDays > 0 && ageInDays(record.deletedAt) < minimumDays)
|
|
67
|
+
return false;
|
|
68
|
+
if (!needle)
|
|
69
|
+
return true;
|
|
70
|
+
return [record.label, record.deleteReason, record.deletedBy, record.tableName]
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.some((field) => String(field).toLowerCase().includes(needle));
|
|
73
|
+
});
|
|
74
|
+
const direction = sortAscending ? 1 : -1;
|
|
75
|
+
return [...matches].sort((a, b) => {
|
|
76
|
+
if (sortKey === "deletedAt") {
|
|
77
|
+
return (new Date(a.deletedAt).getTime() - new Date(b.deletedAt).getTime()) * direction;
|
|
78
|
+
}
|
|
79
|
+
const left = sortKey === "type" ? a.tableName : (a.label ?? "");
|
|
80
|
+
const right = sortKey === "type" ? b.tableName : (b.label ?? "");
|
|
81
|
+
return left.localeCompare(right) * direction;
|
|
82
|
+
});
|
|
83
|
+
}, [allRecords, searchInput, ageFilter, sortKey, sortAscending]);
|
|
84
|
+
const totalPages = Math.max(1, Math.ceil(filteredRecords.length / pageSize));
|
|
85
|
+
const currentPageIndex = Math.min(pageIndex, totalPages - 1);
|
|
86
|
+
const visibleRecords = filteredRecords.slice(currentPageIndex * pageSize, (currentPageIndex + 1) * pageSize);
|
|
87
|
+
const selectedRecords = filteredRecords.filter((record) => selectedKeys.has(recordKey(record)));
|
|
88
|
+
// The header checkbox is scoped to this page; selecting the whole result set is a separate, explicit click.
|
|
89
|
+
const visibleKeys = visibleRecords.map(recordKey);
|
|
90
|
+
const wholePageSelected = visibleRecords.length > 0 && visibleKeys.every((key) => selectedKeys.has(key));
|
|
91
|
+
const pagePartlySelected = !wholePageSelected && visibleKeys.some((key) => selectedKeys.has(key));
|
|
92
|
+
const allFilteredSelected = filteredRecords.length > 0 && selectedRecords.length === filteredRecords.length;
|
|
93
|
+
const moreThanOnePage = filteredRecords.length > visibleRecords.length;
|
|
94
|
+
function togglePageSelection() {
|
|
95
|
+
setSelectedKeys((previous) => {
|
|
96
|
+
const next = new Set(previous);
|
|
97
|
+
if (wholePageSelected)
|
|
98
|
+
visibleKeys.forEach((key) => next.delete(key));
|
|
99
|
+
else
|
|
100
|
+
visibleKeys.forEach((key) => next.add(key));
|
|
101
|
+
return next;
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
function selectAllFiltered() {
|
|
105
|
+
setSelectedKeys(new Set(filteredRecords.map(recordKey)));
|
|
106
|
+
}
|
|
107
|
+
function toggleOne(record) {
|
|
108
|
+
setSelectedKeys((previous) => {
|
|
109
|
+
const next = new Set(previous);
|
|
110
|
+
const key = recordKey(record);
|
|
111
|
+
if (next.has(key))
|
|
112
|
+
next.delete(key);
|
|
113
|
+
else
|
|
114
|
+
next.add(key);
|
|
115
|
+
return next;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function sortBy(key) {
|
|
119
|
+
if (key === sortKey)
|
|
120
|
+
setSortAscending((value) => !value);
|
|
121
|
+
else {
|
|
122
|
+
setSortKey(key);
|
|
123
|
+
setSortAscending(key !== "deletedAt");
|
|
124
|
+
}
|
|
125
|
+
setPageIndex(0);
|
|
126
|
+
}
|
|
127
|
+
return (_jsx(TooltipProvider, { delayDuration: 150, children: _jsxs("div", { children: [_jsx("div", { className: "flex flex-col items-start gap-4 sm:flex-row sm:items-end sm:justify-between", children: _jsxs("div", { children: [_jsx("h1", { className: "mt-2 text-2xl font-semibold tracking-tight text-foreground", children: labels.title }), _jsx("p", { className: "mt-1 max-w-2xl text-sm text-muted-foreground", children: labels.subtitle })] }) }), _jsxs("div", { className: "mt-6 flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center", children: [_jsxs("div", { className: "relative w-full sm:max-w-xs", children: [_jsx(Search, { className: "pointer-events-none absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground", "aria-hidden": true }), _jsx(Input, { type: "search", className: "pl-9", "aria-label": labels.searchLabel, placeholder: labels.searchPlaceholder, value: searchInput, onChange: (event) => {
|
|
128
|
+
setSearchInput(event.target.value);
|
|
129
|
+
setPageIndex(0);
|
|
130
|
+
} })] }), _jsxs(Select, { value: tableFilter, onValueChange: (value) => {
|
|
131
|
+
setTableFilter(value);
|
|
132
|
+
setPageIndex(0);
|
|
133
|
+
setSelectedKeys(new Set());
|
|
134
|
+
}, children: [_jsx(SelectTrigger, { className: "w-full sm:w-[220px]", "aria-label": labels.columns.type, children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: ALL, children: labels.allTypes }), eligibleTables.map((tableName) => (_jsx(SelectItem, { value: tableName, children: labels.tableLabel(tableName) }, tableName)))] })] }), _jsxs(Select, { value: ageFilter, onValueChange: (value) => {
|
|
135
|
+
setAgeFilter(value);
|
|
136
|
+
setPageIndex(0);
|
|
137
|
+
}, children: [_jsx(SelectTrigger, { className: "w-full sm:w-[190px]", "aria-label": labels.ageFilterLabel, children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: AGE_OPTIONS.map((option) => (_jsx(SelectItem, { value: option.value, children: ageOptionLabel(option.value, labels) }, option.value))) })] })] }), recordsQuery.isError ? (_jsx("div", { className: "mt-6", children: _jsx(ErrorState, { error: recordsQuery.error, onRetry: () => recordsQuery.refetch() }) })) : recordsQuery.isLoading ? (_jsx("div", { className: "mt-6", children: _jsx(TableSkeleton, { rows: 6, columns: 6 }) })) : (_jsxs(_Fragment, { children: [_jsx(BulkActionBar, { adapter: adapter, labels: labels, selectedRecords: selectedRecords, purgeableTables: purgeableTables, filteredTotal: filteredRecords.length, allFilteredSelected: allFilteredSelected, canSelectAllFiltered: wholePageSelected && moreThanOnePage && !allFilteredSelected, moreThanOnePage: moreThanOnePage, onSelectAllFiltered: selectAllFiltered, onDone: () => setSelectedKeys(new Set()) }), _jsx("div", { className: "mt-3 hidden overflow-hidden rounded-xl border border-border bg-card sm:block", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { className: "bg-muted/40", children: [_jsx(TableHead, { className: "w-[44px]", children: _jsx(Checkbox, { checked: wholePageSelected ? true : pagePartlySelected ? "indeterminate" : false, onCheckedChange: togglePageSelection, "aria-label": labels.selection.selectPage, disabled: visibleRecords.length === 0 }) }), _jsx(SortableHead, { label: labels.columns.type, labels: labels, active: sortKey === "type", ascending: sortAscending, onClick: () => sortBy("type") }), _jsx(SortableHead, { label: labels.columns.label, labels: labels, active: sortKey === "label", ascending: sortAscending, onClick: () => sortBy("label") }), _jsx(SortableHead, { label: labels.columns.deletedAt, labels: labels, active: sortKey === "deletedAt", ascending: sortAscending, onClick: () => sortBy("deletedAt") }), _jsx(TableHead, { children: labels.columns.deletedBy }), _jsx(TableHead, { children: labels.columns.reason }), _jsx(TableHead, { className: "w-[110px]" })] }) }), _jsxs(TableBody, { children: [visibleRecords.map((record) => (_jsx(TrashTableRow, { adapter: adapter, labels: labels, formatters: formatters, record: record, purgeable: purgeableTables.has(record.tableName), selected: selectedKeys.has(recordKey(record)), onToggle: () => toggleOne(record) }, recordKey(record)))), visibleRecords.length === 0 && (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 7, className: "py-12 text-center text-muted-foreground", children: labels.empty }) }))] })] }) }), _jsxs("div", { className: "mt-3 space-y-3 sm:hidden", children: [visibleRecords.map((record) => (_jsx(TrashCard, { adapter: adapter, labels: labels, formatters: formatters, record: record, purgeable: purgeableTables.has(record.tableName), selected: selectedKeys.has(recordKey(record)), onToggle: () => toggleOne(record) }, recordKey(record)))), visibleRecords.length === 0 && (_jsx("p", { className: "rounded-xl border border-border bg-card px-4 py-12 text-center text-sm text-muted-foreground", children: labels.empty }))] }), filteredRecords.length > 0 && (_jsx(TablePagination, { page: currentPageIndex + 1, totalPages: totalPages, pageSize: pageSize, total: filteredRecords.length, from: currentPageIndex * pageSize + 1, to: Math.min((currentPageIndex + 1) * pageSize, filteredRecords.length), onPage: (page) => setPageIndex(page - 1), onPageSize: (size) => {
|
|
138
|
+
setPageSize(size);
|
|
139
|
+
setPageIndex(0);
|
|
140
|
+
} }))] }))] }) }));
|
|
141
|
+
}
|
|
142
|
+
function SortableHead({ label, labels, active, ascending, onClick, }) {
|
|
143
|
+
const Icon = !active ? ChevronsUpDown : ascending ? ChevronUp : ChevronDown;
|
|
144
|
+
return (_jsx(TableHead, { "aria-sort": active ? (ascending ? "ascending" : "descending") : "none", children: _jsxs("button", { type: "button", onClick: onClick, className: "inline-flex items-center gap-1 hover:text-foreground", "aria-label": `${label}: ${active && ascending ? labels.sortDescending : labels.sortAscending}`, children: [label, _jsx(Icon, { className: "size-3.5 opacity-60", "aria-hidden": true })] }) }));
|
|
145
|
+
}
|
|
146
|
+
// Says how long the record has been sitting in the trash, next to the formatted date.
|
|
147
|
+
function DeletionAge({ deletedAt, labels }) {
|
|
148
|
+
const days = ageInDays(deletedAt);
|
|
149
|
+
const text = days < 1
|
|
150
|
+
? labels.deletedToday
|
|
151
|
+
: days < 30
|
|
152
|
+
? labels.deletedDaysAgo(days)
|
|
153
|
+
: days < 365
|
|
154
|
+
? labels.deletedMonthsAgo(Math.floor(days / 30))
|
|
155
|
+
: labels.deletedYearsAgo(Math.floor(days / 365));
|
|
156
|
+
return _jsx("span", { className: "text-xs text-muted-foreground", children: text });
|
|
157
|
+
}
|
|
158
|
+
// Short reasons wrap in place; long ones clamp to two lines with a dialog showing the full text.
|
|
159
|
+
function ReasonCell({ reason, labels }) {
|
|
160
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
161
|
+
const text = (reason ?? "").trim();
|
|
162
|
+
if (!text)
|
|
163
|
+
return _jsx("span", { className: "text-muted-foreground", children: "\u2014" });
|
|
164
|
+
const isLong = text.length > 80;
|
|
165
|
+
return (_jsxs("div", { className: "max-w-xs", children: [_jsx("p", { className: `whitespace-pre-wrap break-words text-sm text-muted-foreground ${isLong ? "line-clamp-2" : ""}`, children: text }), isLong && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "link", size: "sm", className: "h-auto p-0 text-xs", onClick: () => setIsOpen(true), children: labels.fullReason.showMore }), _jsx(Dialog, { open: isOpen, onOpenChange: setIsOpen, children: _jsx(DialogContent, { children: _jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: labels.fullReason.title }), _jsx(DialogDescription, { className: "whitespace-pre-wrap break-words text-left text-foreground", children: text })] }) }) })] }))] }));
|
|
166
|
+
}
|
|
167
|
+
// A real button so the lock reason is reachable by keyboard and on touch screens.
|
|
168
|
+
function PurgeLockedHint({ reason }) {
|
|
169
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
170
|
+
return (_jsxs(Tooltip, { open: isOpen, onOpenChange: setIsOpen, children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx(Button, { type: "button", variant: "ghost", size: "icon", "aria-label": reason, className: "text-muted-foreground", onClick: () => setIsOpen((value) => !value), children: _jsx(Lock, { className: "size-4" }) }) }), _jsx(TooltipContent, { className: "max-w-xs whitespace-pre-wrap text-left", children: reason })] }));
|
|
171
|
+
}
|
|
172
|
+
function RecordActions({ adapter, labels, record, purgeable, }) {
|
|
173
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
174
|
+
const [restoreOpen, setRestoreOpen] = useState(false);
|
|
175
|
+
const [purgeOpen, setPurgeOpen] = useState(false);
|
|
176
|
+
const [restoreReason, setRestoreReason] = useState("");
|
|
177
|
+
async function restore() {
|
|
178
|
+
setIsSaving(true);
|
|
179
|
+
try {
|
|
180
|
+
await adapter.restoreRecord({
|
|
181
|
+
tableName: record.tableName,
|
|
182
|
+
id: record.id,
|
|
183
|
+
reason: restoreReason.trim() || null,
|
|
184
|
+
});
|
|
185
|
+
toast.success(labels.restore.succeeded(record.label));
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
toast.error(labels.restore.failed(readableErrorMessage(error, "")));
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
setIsSaving(false);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async function purge() {
|
|
195
|
+
setIsSaving(true);
|
|
196
|
+
try {
|
|
197
|
+
await adapter.purgeRecord({ tableName: record.tableName, id: record.id });
|
|
198
|
+
toast.success(labels.purge.succeeded(record.label));
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
toast.error(labels.purge.failed(readableErrorMessage(error, "")));
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
setIsSaving(false);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return (_jsxs("div", { className: "flex justify-end gap-1", children: [_jsx(Button, { variant: "ghost", size: "icon", className: "text-brand hover:bg-brand/10 hover:text-brand", "aria-label": labels.restore.action, disabled: isSaving, onClick: () => setRestoreOpen(true), children: _jsx(ArchiveRestore, { className: "size-4" }) }), _jsx(AlertDialog, { open: restoreOpen, onOpenChange: setRestoreOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: labels.restore.title }), _jsx(AlertDialogDescription, { children: labels.restore.description(record.label) })] }), _jsxs("div", { className: "space-y-2", children: [_jsx("label", { className: "text-sm text-muted-foreground", htmlFor: `restore-reason-${record.id}`, children: labels.restore.reasonLabel }), _jsx(Textarea, { id: `restore-reason-${record.id}`, rows: 2, value: restoreReason, placeholder: labels.restore.reasonPlaceholder, onChange: (event) => setRestoreReason(event.target.value) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: labels.restore.cancel }), _jsx(AlertDialogAction, { onClick: restore, children: labels.restore.confirm })] })] }) }), !purgeable ? (_jsx(PurgeLockedHint, { reason: labels.purge.lockedReason(record.tableName) })) : (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "ghost", size: "icon", className: "text-destructive hover:bg-destructive/10 hover:text-destructive", "aria-label": labels.purge.action, disabled: isSaving, onClick: () => setPurgeOpen(true), children: _jsx(Trash2, { className: "size-4" }) }), _jsx(AlertDialog, { open: purgeOpen, onOpenChange: setPurgeOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: labels.purge.title }), _jsx(AlertDialogDescription, { children: labels.purge.description(record.label) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: labels.purge.cancel }), _jsx(AlertDialogAction, { onClick: purge, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: labels.purge.confirm })] })] }) })] }))] }));
|
|
208
|
+
}
|
|
209
|
+
// Restores or purges everything ticked in one pass; purge confirms once, naming the count.
|
|
210
|
+
function BulkActionBar({ adapter, labels, selectedRecords, purgeableTables, filteredTotal, allFilteredSelected, canSelectAllFiltered, moreThanOnePage, onSelectAllFiltered, onDone, }) {
|
|
211
|
+
const [purgeOpen, setPurgeOpen] = useState(false);
|
|
212
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
213
|
+
if (selectedRecords.length === 0)
|
|
214
|
+
return null;
|
|
215
|
+
const purgeableRecords = selectedRecords.filter((record) => purgeableTables.has(record.tableName));
|
|
216
|
+
const lockedCount = selectedRecords.length - purgeableRecords.length;
|
|
217
|
+
async function restoreAll() {
|
|
218
|
+
setIsSaving(true);
|
|
219
|
+
let succeeded = 0;
|
|
220
|
+
for (const record of selectedRecords) {
|
|
221
|
+
try {
|
|
222
|
+
await adapter.restoreRecord({ tableName: record.tableName, id: record.id, reason: null });
|
|
223
|
+
succeeded++;
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
toast.error(labels.restore.failed(readableErrorMessage(error, "")));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
setIsSaving(false);
|
|
230
|
+
if (succeeded > 0)
|
|
231
|
+
toast.success(labels.restore.manySucceeded(succeeded));
|
|
232
|
+
onDone();
|
|
233
|
+
}
|
|
234
|
+
async function purgeAll() {
|
|
235
|
+
setIsSaving(true);
|
|
236
|
+
let succeeded = 0;
|
|
237
|
+
for (const record of purgeableRecords) {
|
|
238
|
+
try {
|
|
239
|
+
await adapter.purgeRecord({ tableName: record.tableName, id: record.id });
|
|
240
|
+
succeeded++;
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
toast.error(labels.purge.failed(readableErrorMessage(error, "")));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
setIsSaving(false);
|
|
247
|
+
setPurgeOpen(false);
|
|
248
|
+
if (succeeded > 0)
|
|
249
|
+
toast.success(labels.purge.manySucceeded(succeeded));
|
|
250
|
+
onDone();
|
|
251
|
+
}
|
|
252
|
+
return (_jsxs("div", { className: "mt-4 flex flex-wrap items-center gap-3 rounded-xl border border-border bg-muted/40 px-4 py-3", children: [_jsx("span", { className: "text-sm font-medium text-foreground", children: labels.selection.selectedCount(selectedRecords.length) }), _jsx(Button, { size: "sm", variant: "outline", disabled: isSaving, onClick: restoreAll, children: labels.selection.restoreSelected(selectedRecords.length) }), _jsx(Button, { size: "sm", variant: "outline", className: "text-destructive", disabled: isSaving || purgeableRecords.length === 0, onClick: () => setPurgeOpen(true), children: labels.selection.purgeSelected(purgeableRecords.length) }), _jsx(Button, { size: "sm", variant: "ghost", disabled: isSaving, onClick: onDone, children: labels.selection.clearSelection }), canSelectAllFiltered && (_jsx(Button, { size: "sm", variant: "link", className: "h-auto p-0", disabled: isSaving, onClick: onSelectAllFiltered, children: labels.selection.selectAllFiltered(filteredTotal) })), allFilteredSelected && moreThanOnePage && (_jsx("span", { className: "text-xs text-muted-foreground", children: labels.selection.allFilteredSelected(filteredTotal) })), lockedCount > 0 && (_jsx("span", { className: "text-xs text-muted-foreground", children: labels.selection.notPurgeableCount(lockedCount) })), _jsx(AlertDialog, { open: purgeOpen, onOpenChange: setPurgeOpen, children: _jsxs(AlertDialogContent, { children: [_jsxs(AlertDialogHeader, { children: [_jsx(AlertDialogTitle, { children: labels.purge.manyTitle(purgeableRecords.length) }), _jsx(AlertDialogDescription, { children: labels.purge.manyDescription(purgeableRecords.length) })] }), _jsxs(AlertDialogFooter, { children: [_jsx(AlertDialogCancel, { children: labels.purge.cancel }), _jsx(AlertDialogAction, { onClick: purgeAll, className: "bg-destructive text-destructive-foreground hover:bg-destructive/90", children: labels.purge.confirm })] })] }) })] }));
|
|
253
|
+
}
|
|
254
|
+
function TrashTableRow({ adapter, labels, formatters, record, purgeable, selected, onToggle, }) {
|
|
255
|
+
return (_jsxs(TableRow, { "data-state": selected ? "selected" : undefined, children: [_jsx(TableCell, { children: _jsx(Checkbox, { checked: selected, onCheckedChange: onToggle, "aria-label": labels.selection.selectRow(record.label) }) }), _jsx(TableCell, { children: _jsx(Badge, { variant: "secondary", children: labels.tableLabel(record.tableName) }) }), _jsx(TableCell, { className: "font-medium text-foreground", children: record.label }), _jsxs(TableCell, { className: "text-sm text-muted-foreground", children: [_jsx("div", { children: formatters.formatDateTime(record.deletedAt) }), _jsx(DeletionAge, { deletedAt: record.deletedAt, labels: labels })] }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: record.deletedBy ?? "—" }), _jsx(TableCell, { className: "text-sm text-muted-foreground", children: _jsx(ReasonCell, { reason: record.deleteReason, labels: labels }) }), _jsx(TableCell, { children: _jsx(RecordActions, { adapter: adapter, labels: labels, record: record, purgeable: purgeable }) })] }));
|
|
256
|
+
}
|
|
257
|
+
function TrashCard({ adapter, labels, formatters, record, purgeable, selected, onToggle, }) {
|
|
258
|
+
return (_jsxs("div", { className: "rounded-xl border border-border bg-card p-4", children: [_jsxs("div", { className: "flex items-start justify-between gap-3", children: [_jsxs("div", { className: "flex min-w-0 gap-3", children: [_jsx(Checkbox, { checked: selected, onCheckedChange: onToggle, "aria-label": labels.selection.selectRow(record.label), className: "mt-1" }), _jsxs("div", { className: "min-w-0", children: [_jsx(Badge, { variant: "secondary", children: labels.tableLabel(record.tableName) }), _jsx("div", { className: "mt-1 truncate font-medium text-foreground", children: record.label })] })] }), _jsx(RecordActions, { adapter: adapter, labels: labels, record: record, purgeable: purgeable })] }), _jsxs("div", { className: "mt-3 space-y-1 border-t border-border pt-2 text-xs text-muted-foreground", children: [_jsxs("div", { children: [labels.columns.deletedAt, ": ", formatters.formatDateTime(record.deletedAt), " \u00B7", " ", _jsx(DeletionAge, { deletedAt: record.deletedAt, labels: labels })] }), _jsxs("div", { children: [labels.columns.deletedBy, ": ", record.deletedBy ?? "—"] }), record.deleteReason && (_jsxs("div", { children: [_jsxs("span", { className: "mr-1", children: [labels.columns.reason, ":"] }), _jsx(ReasonCell, { reason: record.deleteReason, labels: labels })] }))] })] }));
|
|
259
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export interface VatRulesLabels {
|
|
2
|
+
title: string;
|
|
3
|
+
subtitle: string;
|
|
4
|
+
companyLabel: string;
|
|
5
|
+
allCompanies: string;
|
|
6
|
+
rulesTab: string;
|
|
7
|
+
reserveTab: string;
|
|
8
|
+
rulesHint: string;
|
|
9
|
+
newRuleButton: string;
|
|
10
|
+
countBySupplier: (count: number) => string;
|
|
11
|
+
countByProperty: (count: number) => string;
|
|
12
|
+
countByCompany: (count: number) => string;
|
|
13
|
+
countGlobal: (count: number) => string;
|
|
14
|
+
empty: string;
|
|
15
|
+
emptyHint: string;
|
|
16
|
+
columns: {
|
|
17
|
+
value: string;
|
|
18
|
+
scope: string;
|
|
19
|
+
effect: string;
|
|
20
|
+
active: string;
|
|
21
|
+
actions: string;
|
|
22
|
+
};
|
|
23
|
+
vatRateValue: (rate: number) => string;
|
|
24
|
+
vatTreatmentName: (value: string) => string;
|
|
25
|
+
vatSpecialCaseName: (value: string) => string;
|
|
26
|
+
deductibleShare: (percent: number) => string;
|
|
27
|
+
scopeNames: {
|
|
28
|
+
supplier: string;
|
|
29
|
+
property: string;
|
|
30
|
+
company: string;
|
|
31
|
+
referencePattern: string;
|
|
32
|
+
};
|
|
33
|
+
effect: (wouldChange: number, matches: number) => string;
|
|
34
|
+
effectHint: string;
|
|
35
|
+
toggleActivated: string;
|
|
36
|
+
toggleDeactivated: string;
|
|
37
|
+
actionFailed: (error: string) => string;
|
|
38
|
+
apply: {
|
|
39
|
+
action: string;
|
|
40
|
+
title: string;
|
|
41
|
+
description: (wouldChange: number, matches: number) => string;
|
|
42
|
+
nothingToApply: string;
|
|
43
|
+
confirm: (count: number) => string;
|
|
44
|
+
cancel: string;
|
|
45
|
+
appliedNone: string;
|
|
46
|
+
applied: (changed: number, matches: number) => string;
|
|
47
|
+
appliedWithSkipped: (changed: number, matches: number, skipped: number) => string;
|
|
48
|
+
};
|
|
49
|
+
deleteRule: {
|
|
50
|
+
action: string;
|
|
51
|
+
title: string;
|
|
52
|
+
description: (value: string) => string;
|
|
53
|
+
reasonPlaceholder: string;
|
|
54
|
+
cancel: string;
|
|
55
|
+
confirm: string;
|
|
56
|
+
deleted: string;
|
|
57
|
+
};
|
|
58
|
+
newRule: {
|
|
59
|
+
title: string;
|
|
60
|
+
description: string;
|
|
61
|
+
vatRateField: string;
|
|
62
|
+
vatRatePlaceholder: string;
|
|
63
|
+
vatRateInvalid: string;
|
|
64
|
+
vatRateOutOfRange: string;
|
|
65
|
+
vatRateUnusual: string;
|
|
66
|
+
treatmentField: string;
|
|
67
|
+
treatmentNone: string;
|
|
68
|
+
treatmentHint: string;
|
|
69
|
+
deductibleField: string;
|
|
70
|
+
deductiblePlaceholder: string;
|
|
71
|
+
deductibleInvalid: string;
|
|
72
|
+
deductibleHint: string;
|
|
73
|
+
specialCaseField: string;
|
|
74
|
+
specialCaseNone: string;
|
|
75
|
+
hospitalityHint: string;
|
|
76
|
+
scopeHeading: string;
|
|
77
|
+
scopeHint: string;
|
|
78
|
+
anyScopeOption: string;
|
|
79
|
+
referencePatternField: string;
|
|
80
|
+
referencePatternPlaceholder: string;
|
|
81
|
+
referencePatternHint: string;
|
|
82
|
+
noteField: string;
|
|
83
|
+
previewHeading: string;
|
|
84
|
+
previewLoading: string;
|
|
85
|
+
previewError: string;
|
|
86
|
+
previewText: (wouldChange: number, matches: number) => string;
|
|
87
|
+
previewHint: string;
|
|
88
|
+
valueMissing: string;
|
|
89
|
+
scopeMissing: string;
|
|
90
|
+
alreadyExists: string;
|
|
91
|
+
cancel: string;
|
|
92
|
+
save: string;
|
|
93
|
+
created: string;
|
|
94
|
+
createFailed: (error: string) => string;
|
|
95
|
+
};
|
|
96
|
+
reserve: {
|
|
97
|
+
title: string;
|
|
98
|
+
hint: string;
|
|
99
|
+
totalInputVat: string;
|
|
100
|
+
deductible: string;
|
|
101
|
+
nondeductible: string;
|
|
102
|
+
outputVat: string;
|
|
103
|
+
reserveAmount: string;
|
|
104
|
+
refund: string;
|
|
105
|
+
unresolved: (count: number, amountText: string) => string;
|
|
106
|
+
unresolvedShort: (count: number, amountText: string) => string;
|
|
107
|
+
emptyAllCompanies: string;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
export declare const englishVatRulesLabels: VatRulesLabels;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export const englishVatRulesLabels = {
|
|
2
|
+
title: "VAT rules & tax reserve",
|
|
3
|
+
subtitle: "Rules that set the VAT rate, tax treatment and deductibility of incoming invoices, and the reserve they suggest.",
|
|
4
|
+
companyLabel: "Company",
|
|
5
|
+
allCompanies: "All companies",
|
|
6
|
+
rulesTab: "Rules",
|
|
7
|
+
reserveTab: "Tax reserve",
|
|
8
|
+
rulesHint: "A rule sets the VAT rate and deductibility for every invoice that matches its scope. More specific rules win over broader ones.",
|
|
9
|
+
newRuleButton: "New VAT rule",
|
|
10
|
+
countBySupplier: (count) => `${count} per supplier`,
|
|
11
|
+
countByProperty: (count) => `${count} per property`,
|
|
12
|
+
countByCompany: (count) => `${count} per company`,
|
|
13
|
+
countGlobal: (count) => `${count} global`,
|
|
14
|
+
empty: "No VAT rules yet.",
|
|
15
|
+
emptyHint: "Create a rule to set VAT rate and deductibility automatically.",
|
|
16
|
+
columns: {
|
|
17
|
+
value: "Value",
|
|
18
|
+
scope: "Scope",
|
|
19
|
+
effect: "Effect",
|
|
20
|
+
active: "Active",
|
|
21
|
+
actions: "Actions",
|
|
22
|
+
},
|
|
23
|
+
vatRateValue: (rate) => `${rate} %`,
|
|
24
|
+
vatTreatmentName: (value) => value,
|
|
25
|
+
vatSpecialCaseName: (value) => value,
|
|
26
|
+
deductibleShare: (percent) => `${percent}% deductible`,
|
|
27
|
+
scopeNames: {
|
|
28
|
+
supplier: "Supplier",
|
|
29
|
+
property: "Property",
|
|
30
|
+
company: "Company",
|
|
31
|
+
referencePattern: "Reference",
|
|
32
|
+
},
|
|
33
|
+
effect: (wouldChange, matches) => `${wouldChange} of ${matches}`,
|
|
34
|
+
effectHint: "How many existing invoices this rule would still change if applied retroactively.",
|
|
35
|
+
toggleActivated: "Rule activated.",
|
|
36
|
+
toggleDeactivated: "Rule deactivated.",
|
|
37
|
+
actionFailed: (error) => `The action failed: ${error}`,
|
|
38
|
+
apply: {
|
|
39
|
+
action: "Apply",
|
|
40
|
+
title: "Apply this rule to existing invoices?",
|
|
41
|
+
description: (wouldChange, matches) => `This rule matches ${matches} existing invoices and would change ${wouldChange} of them.`,
|
|
42
|
+
nothingToApply: "This rule would not change any existing invoice.",
|
|
43
|
+
confirm: (count) => `Change ${count} invoices`,
|
|
44
|
+
cancel: "Cancel",
|
|
45
|
+
appliedNone: "No invoices needed a change.",
|
|
46
|
+
applied: (changed, matches) => `Changed ${changed} of ${matches} matching invoices.`,
|
|
47
|
+
appliedWithSkipped: (changed, matches, skipped) => `Changed ${changed} of ${matches} matching invoices; ${skipped} were skipped.`,
|
|
48
|
+
},
|
|
49
|
+
deleteRule: {
|
|
50
|
+
action: "Delete",
|
|
51
|
+
title: "Delete this rule?",
|
|
52
|
+
description: (value) => `The rule "${value}" stays visible in the audit trail but stops affecting new invoices.`,
|
|
53
|
+
reasonPlaceholder: "Reason for deleting",
|
|
54
|
+
cancel: "Cancel",
|
|
55
|
+
confirm: "Delete rule",
|
|
56
|
+
deleted: "Rule deleted.",
|
|
57
|
+
},
|
|
58
|
+
newRule: {
|
|
59
|
+
title: "New VAT rule",
|
|
60
|
+
description: "Sets VAT rate, tax treatment and deductibility for every invoice matching the scope below.",
|
|
61
|
+
vatRateField: "VAT rate (%)",
|
|
62
|
+
vatRatePlaceholder: "19",
|
|
63
|
+
vatRateInvalid: "Please enter a number.",
|
|
64
|
+
vatRateOutOfRange: "The VAT rate must be between 0 and 100.",
|
|
65
|
+
vatRateUnusual: "This is an unusual VAT rate — please double-check it.",
|
|
66
|
+
treatmentField: "Tax treatment",
|
|
67
|
+
treatmentNone: "Not set",
|
|
68
|
+
treatmentHint: "How the invoice is treated for tax purposes.",
|
|
69
|
+
deductibleField: "Deductible share (%)",
|
|
70
|
+
deductiblePlaceholder: "100",
|
|
71
|
+
deductibleInvalid: "The deductible share must be between 0 and 100.",
|
|
72
|
+
deductibleHint: "How much of the input VAT can be deducted. Leave empty for fully deductible.",
|
|
73
|
+
specialCaseField: "Special case",
|
|
74
|
+
specialCaseNone: "None",
|
|
75
|
+
hospitalityHint: "Hospitality costs are typically 70% deductible.",
|
|
76
|
+
scopeHeading: "Scope",
|
|
77
|
+
scopeHint: "At least one scope is required, so the rule cannot silently match everything.",
|
|
78
|
+
anyScopeOption: "Any",
|
|
79
|
+
referencePatternField: "Reference pattern",
|
|
80
|
+
referencePatternPlaceholder: "e.g. maintenance contract",
|
|
81
|
+
referencePatternHint: "Matches when the invoice reference contains this text.",
|
|
82
|
+
noteField: "Note",
|
|
83
|
+
previewHeading: "Retroactive effect",
|
|
84
|
+
previewLoading: "Calculating …",
|
|
85
|
+
previewError: "The preview is not available right now.",
|
|
86
|
+
previewText: (wouldChange, matches) => `This rule matches ${matches} existing invoices and would change ${wouldChange} of them.`,
|
|
87
|
+
previewHint: "The gap between the two numbers is invoices that are already correct or were decided by a person.",
|
|
88
|
+
valueMissing: "Please enter a valid VAT rate first.",
|
|
89
|
+
scopeMissing: "Please narrow the rule down to at least one scope.",
|
|
90
|
+
alreadyExists: "A rule with this exact scope already exists.",
|
|
91
|
+
cancel: "Cancel",
|
|
92
|
+
save: "Create rule",
|
|
93
|
+
created: "VAT rule created.",
|
|
94
|
+
createFailed: (error) => `Creating the rule failed: ${error}`,
|
|
95
|
+
},
|
|
96
|
+
reserve: {
|
|
97
|
+
title: "Recommended tax reserve",
|
|
98
|
+
hint: "A recommendation based on the current invoices — never a booking.",
|
|
99
|
+
totalInputVat: "Input VAT total",
|
|
100
|
+
deductible: "Of which deductible",
|
|
101
|
+
nondeductible: "Of which non-deductible",
|
|
102
|
+
outputVat: "Output VAT",
|
|
103
|
+
reserveAmount: "Recommended reserve",
|
|
104
|
+
refund: "Expected refund",
|
|
105
|
+
unresolved: (count, amountText) => `${count} invoices totalling ${amountText} have no resolved deductibility yet and are not included.`,
|
|
106
|
+
unresolvedShort: (count, amountText) => `${count} unresolved (${amountText})`,
|
|
107
|
+
emptyAllCompanies: "No data available yet.",
|
|
108
|
+
},
|
|
109
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { VatRulesAdapter } from "../../adapters/vat-rules";
|
|
2
|
+
import type { VatRulesLabels } from "./labels";
|
|
3
|
+
export declare function NewVatRuleDialog({ adapter, labels, defaultCompanyId, }: {
|
|
4
|
+
adapter: VatRulesAdapter;
|
|
5
|
+
labels: VatRulesLabels;
|
|
6
|
+
defaultCompanyId?: string;
|
|
7
|
+
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useId, useMemo, useState } from "react";
|
|
3
|
+
import { Plus, Wand2 } from "lucide-react";
|
|
4
|
+
import { toast } from "sonner";
|
|
5
|
+
import { Button } from "../../ui/button.js";
|
|
6
|
+
import { Input } from "../../ui/input.js";
|
|
7
|
+
import { Label } from "../../ui/label.js";
|
|
8
|
+
import { Field } from "../../ui/field.js";
|
|
9
|
+
import { Combobox } from "../../ui/combobox.js";
|
|
10
|
+
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "../../ui/dialog.js";
|
|
11
|
+
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "../../ui/select.js";
|
|
12
|
+
import { readableErrorMessage } from "../../components/feedback/query-states.js";
|
|
13
|
+
const NONE = "__none";
|
|
14
|
+
export function NewVatRuleDialog({ adapter, labels, defaultCompanyId, }) {
|
|
15
|
+
const scopeGroupId = useId();
|
|
16
|
+
const [open, setOpen] = useState(false);
|
|
17
|
+
const [isSaving, setIsSaving] = useState(false);
|
|
18
|
+
const [vatRateText, setVatRateText] = useState("");
|
|
19
|
+
const [treatment, setTreatment] = useState(NONE);
|
|
20
|
+
const [deductibleText, setDeductibleText] = useState("");
|
|
21
|
+
const [specialCase, setSpecialCase] = useState(NONE);
|
|
22
|
+
const [supplierId, setSupplierId] = useState(NONE);
|
|
23
|
+
const [propertyId, setPropertyId] = useState(NONE);
|
|
24
|
+
const [companyId, setCompanyId] = useState(defaultCompanyId ?? NONE);
|
|
25
|
+
const [referencePattern, setReferencePattern] = useState("");
|
|
26
|
+
const [note, setNote] = useState("");
|
|
27
|
+
const suppliersQuery = adapter.useSuppliers();
|
|
28
|
+
const propertiesQuery = adapter.useProperties();
|
|
29
|
+
const companiesQuery = adapter.useCompanies();
|
|
30
|
+
const parsedRate = vatRateText.trim() === "" ? null : Number(vatRateText.replace(",", "."));
|
|
31
|
+
const rateNotANumber = parsedRate != null && !Number.isFinite(parsedRate);
|
|
32
|
+
const rateOutOfRange = parsedRate != null && Number.isFinite(parsedRate) && (parsedRate < 0 || parsedRate > 100);
|
|
33
|
+
const rateInvalid = rateNotANumber || rateOutOfRange;
|
|
34
|
+
// Not an error: reduced and foreign rates exist, so an unusual value only asks for a second look.
|
|
35
|
+
const rateUnusual = parsedRate != null && !rateInvalid && ![0, 7, 19].includes(parsedRate);
|
|
36
|
+
const parsedDeductible = deductibleText.trim() === "" ? null : Number(deductibleText.replace(",", "."));
|
|
37
|
+
const deductibleInvalid = parsedDeductible != null &&
|
|
38
|
+
(!Number.isFinite(parsedDeductible) || parsedDeductible < 0 || parsedDeductible > 100);
|
|
39
|
+
// The rule as it would be stored — also what the impact preview is computed from.
|
|
40
|
+
const draft = useMemo(() => {
|
|
41
|
+
if (parsedRate == null || !Number.isFinite(parsedRate))
|
|
42
|
+
return null;
|
|
43
|
+
if (rateOutOfRange || deductibleInvalid)
|
|
44
|
+
return null;
|
|
45
|
+
return {
|
|
46
|
+
vatRate: parsedRate,
|
|
47
|
+
vatTreatment: treatment === NONE ? null : treatment,
|
|
48
|
+
vatDeductiblePercent: parsedDeductible,
|
|
49
|
+
vatSpecialCase: specialCase === NONE ? null : specialCase,
|
|
50
|
+
supplierId: supplierId === NONE ? null : supplierId,
|
|
51
|
+
propertyId: propertyId === NONE ? null : propertyId,
|
|
52
|
+
companyId: companyId === NONE ? null : companyId,
|
|
53
|
+
referencePattern: referencePattern.trim() === "" ? null : referencePattern.trim(),
|
|
54
|
+
};
|
|
55
|
+
}, [
|
|
56
|
+
parsedRate,
|
|
57
|
+
rateOutOfRange,
|
|
58
|
+
deductibleInvalid,
|
|
59
|
+
treatment,
|
|
60
|
+
parsedDeductible,
|
|
61
|
+
specialCase,
|
|
62
|
+
supplierId,
|
|
63
|
+
propertyId,
|
|
64
|
+
companyId,
|
|
65
|
+
referencePattern,
|
|
66
|
+
]);
|
|
67
|
+
const previewQuery = adapter.useDraftImpact(draft);
|
|
68
|
+
const scopeEmpty = supplierId === NONE &&
|
|
69
|
+
propertyId === NONE &&
|
|
70
|
+
companyId === NONE &&
|
|
71
|
+
referencePattern.trim() === "";
|
|
72
|
+
function resetForm() {
|
|
73
|
+
setVatRateText("");
|
|
74
|
+
setTreatment(NONE);
|
|
75
|
+
setDeductibleText("");
|
|
76
|
+
setSpecialCase(NONE);
|
|
77
|
+
setSupplierId(NONE);
|
|
78
|
+
setPropertyId(NONE);
|
|
79
|
+
setCompanyId(defaultCompanyId ?? NONE);
|
|
80
|
+
setReferencePattern("");
|
|
81
|
+
setNote("");
|
|
82
|
+
}
|
|
83
|
+
async function saveRule() {
|
|
84
|
+
if (!draft) {
|
|
85
|
+
toast.error(labels.newRule.valueMissing);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (scopeEmpty) {
|
|
89
|
+
toast.error(labels.newRule.scopeMissing);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
setIsSaving(true);
|
|
93
|
+
try {
|
|
94
|
+
await adapter.createVatRule({ ...draft, note: note.trim() === "" ? null : note.trim() });
|
|
95
|
+
toast.success(labels.newRule.created);
|
|
96
|
+
resetForm();
|
|
97
|
+
setOpen(false);
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
toast.error(adapter.isDuplicateRuleError(error)
|
|
101
|
+
? labels.newRule.alreadyExists
|
|
102
|
+
: labels.newRule.createFailed(readableErrorMessage(error, "")));
|
|
103
|
+
}
|
|
104
|
+
finally {
|
|
105
|
+
setIsSaving(false);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return (_jsxs(Dialog, { open: open, onOpenChange: setOpen, children: [_jsx(DialogTrigger, { asChild: true, children: _jsxs(Button, { className: "gap-2", children: [_jsx(Plus, { className: "size-4" }), " ", labels.newRuleButton] }) }), _jsxs(DialogContent, { className: "sm:max-w-lg", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: labels.newRule.title }), _jsx(DialogDescription, { children: labels.newRule.description })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs(Field, { className: "space-y-1.5", children: [_jsx(Label, { children: labels.newRule.vatRateField }), _jsx(Input, { value: vatRateText, onChange: (event) => setVatRateText(event.target.value), placeholder: labels.newRule.vatRatePlaceholder, inputMode: "decimal" }), rateNotANumber ? (_jsx("p", { className: "text-xs text-destructive", children: labels.newRule.vatRateInvalid })) : rateOutOfRange ? (_jsx("p", { className: "text-xs text-destructive", children: labels.newRule.vatRateOutOfRange })) : rateUnusual ? (_jsx("p", { className: "text-xs text-warning", children: labels.newRule.vatRateUnusual })) : null] }), _jsxs(Field, { className: "space-y-1.5", children: [_jsx(Label, { children: labels.newRule.treatmentField }), _jsxs(Select, { value: treatment, onValueChange: setTreatment, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: NONE, children: labels.newRule.treatmentNone }), adapter.vatTreatmentValues.map((value) => (_jsx(SelectItem, { value: value, children: labels.vatTreatmentName(value) }, value)))] })] }), _jsx("p", { className: "text-xs text-muted-foreground", children: labels.newRule.treatmentHint })] }), _jsxs(Field, { className: "space-y-1.5", children: [_jsx(Label, { children: labels.newRule.deductibleField }), _jsx(Input, { value: deductibleText, onChange: (event) => setDeductibleText(event.target.value), placeholder: labels.newRule.deductiblePlaceholder, inputMode: "decimal" }), deductibleInvalid ? (_jsx("p", { className: "text-xs text-destructive", children: labels.newRule.deductibleInvalid })) : (_jsx("p", { className: "text-xs text-muted-foreground", children: labels.newRule.deductibleHint }))] }), _jsxs(Field, { className: "space-y-1.5", children: [_jsx(Label, { children: labels.newRule.specialCaseField }), _jsxs(Select, { value: specialCase, onValueChange: (value) => {
|
|
109
|
+
setSpecialCase(value);
|
|
110
|
+
if (value === "hospitality" && deductibleText.trim() === "") {
|
|
111
|
+
setDeductibleText("70");
|
|
112
|
+
}
|
|
113
|
+
}, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: NONE, children: labels.newRule.specialCaseNone }), adapter.vatSpecialCaseValues.map((value) => (_jsx(SelectItem, { value: value, children: labels.vatSpecialCaseName(value) }, value)))] })] }), specialCase === "hospitality" ? (_jsx("p", { className: "text-xs text-muted-foreground", children: labels.newRule.hospitalityHint })) : null] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("p", { id: scopeGroupId, className: "text-sm leading-none font-medium", children: labels.newRule.scopeHeading }), _jsx("p", { className: "text-xs text-muted-foreground", children: labels.newRule.scopeHint })] }), _jsxs("div", { role: "group", "aria-labelledby": scopeGroupId, className: "grid gap-3 sm:grid-cols-2", children: [_jsxs(Field, { className: "space-y-1", children: [_jsx(Label, { className: "text-xs font-normal text-muted-foreground", children: labels.scopeNames.supplier }), _jsx(Combobox, { value: supplierId, onValueChange: setSupplierId, options: [
|
|
114
|
+
{ value: NONE, label: labels.newRule.anyScopeOption },
|
|
115
|
+
...(suppliersQuery.data ?? []).map((supplier) => ({
|
|
116
|
+
value: supplier.id,
|
|
117
|
+
label: supplier.name,
|
|
118
|
+
})),
|
|
119
|
+
] })] }), _jsxs(Field, { className: "space-y-1", children: [_jsx(Label, { className: "text-xs font-normal text-muted-foreground", children: labels.scopeNames.property }), _jsx(Combobox, { value: propertyId, onValueChange: setPropertyId, options: [
|
|
120
|
+
{ value: NONE, label: labels.newRule.anyScopeOption },
|
|
121
|
+
...(propertiesQuery.data ?? []).map((property) => ({
|
|
122
|
+
value: property.id,
|
|
123
|
+
label: property.name ? `${property.code} · ${property.name}` : property.code,
|
|
124
|
+
keywords: property.name ?? "",
|
|
125
|
+
})),
|
|
126
|
+
] })] }), _jsxs(Field, { className: "space-y-1", children: [_jsx(Label, { className: "text-xs font-normal text-muted-foreground", children: labels.scopeNames.company }), _jsx(Combobox, { value: companyId, onValueChange: setCompanyId, options: [
|
|
127
|
+
{ value: NONE, label: labels.newRule.anyScopeOption },
|
|
128
|
+
...(companiesQuery.data ?? []).map((company) => ({
|
|
129
|
+
value: company.id,
|
|
130
|
+
label: `${company.code} · ${company.name}`,
|
|
131
|
+
keywords: company.name,
|
|
132
|
+
})),
|
|
133
|
+
] })] })] }), _jsxs(Field, { className: "space-y-1.5", children: [_jsx(Label, { children: labels.newRule.referencePatternField }), _jsx(Input, { value: referencePattern, onChange: (event) => setReferencePattern(event.target.value), placeholder: labels.newRule.referencePatternPlaceholder }), _jsx("p", { className: "text-xs text-muted-foreground", children: labels.newRule.referencePatternHint })] }), _jsxs(Field, { className: "space-y-1.5", children: [_jsx(Label, { children: labels.newRule.noteField }), _jsx(Input, { value: note, onChange: (event) => setNote(event.target.value) })] }), _jsxs("div", { className: "rounded-lg border border-border bg-muted/40 p-3 text-sm", children: [_jsxs("div", { className: "flex items-center gap-1.5 font-medium text-foreground", children: [_jsx(Wand2, { className: "size-4" }), " ", labels.newRule.previewHeading] }), scopeEmpty ? (_jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: labels.newRule.scopeMissing })) : !draft ? (_jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: labels.newRule.valueMissing })) : previewQuery.isLoading ? (_jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: labels.newRule.previewLoading })) : previewQuery.isError ? (_jsx("p", { className: "mt-1 text-xs text-destructive", children: labels.newRule.previewError })) : (_jsxs(_Fragment, { children: [_jsx("p", { className: "mt-1", children: labels.newRule.previewText(previewQuery.data?.wouldChange ?? 0, previewQuery.data?.matches ?? 0) }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: labels.newRule.previewHint })] }))] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setOpen(false), children: labels.newRule.cancel }), _jsx(Button, { onClick: saveRule, disabled: isSaving || !draft || scopeEmpty, children: labels.newRule.save })] })] })] }));
|
|
134
|
+
}
|