@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,266 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { formatDistanceToNow } from "date-fns";
|
|
4
|
+
import { useCallback, useMemo } from "react";
|
|
5
|
+
import { Sheet, SheetContent, SheetHeader, SheetTitle } from "@/ui/components/ui/sheet";
|
|
6
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "@/ui/components/ui/tooltip";
|
|
7
|
+
import { AUTH_COOKIES } from "@/ui/lib/auth";
|
|
8
|
+
import { getCookie } from "@/ui/lib/browser-cookies";
|
|
9
|
+
import { cn } from "@/ui/lib/utils";
|
|
10
|
+
import { DataTable } from "../table/data-table";
|
|
11
|
+
import type { Column, FilterConfig, ListTableProps, TableQueryParams, TableQueryResponse } from "../table/types";
|
|
12
|
+
import { RequestLogDetail } from "./request-log-detail";
|
|
13
|
+
|
|
14
|
+
// Request log response type (internal endpoint, not in SDK)
|
|
15
|
+
export interface RequestLogResponse {
|
|
16
|
+
id: string;
|
|
17
|
+
entity_id: string;
|
|
18
|
+
request_id: string;
|
|
19
|
+
method: string;
|
|
20
|
+
path: string;
|
|
21
|
+
res_status: string | null;
|
|
22
|
+
resource_type: string | null;
|
|
23
|
+
resource_id: string | null;
|
|
24
|
+
action: string | null;
|
|
25
|
+
req_body: Record<string, unknown> | null;
|
|
26
|
+
res_body: Record<string, unknown> | null;
|
|
27
|
+
headers: Record<string, unknown> | null;
|
|
28
|
+
created_at: string;
|
|
29
|
+
updated_at: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Get API base URL from environment
|
|
33
|
+
const getApiBaseUrl = () => {
|
|
34
|
+
if (typeof import.meta !== "undefined" && import.meta.env) {
|
|
35
|
+
return import.meta.env.VITE_API_URL || import.meta.env.BUN_PUBLIC_API_URL || "";
|
|
36
|
+
}
|
|
37
|
+
return "";
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const REQUEST_LOGS_CACHE_KEY = "request-logs";
|
|
41
|
+
|
|
42
|
+
const METHOD_COLORS: Record<string, string> = {
|
|
43
|
+
GET: "bg-blue-100 text-blue-700 dark:bg-blue-900 dark:text-blue-300",
|
|
44
|
+
POST: "bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300",
|
|
45
|
+
PATCH: "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-300",
|
|
46
|
+
PUT: "bg-yellow-100 text-yellow-700 dark:bg-yellow-900 dark:text-yellow-300",
|
|
47
|
+
DELETE: "bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function StatusDot({ status }: { status: string | null }) {
|
|
51
|
+
const statusCode = status ? Number.parseInt(status, 10) : 0;
|
|
52
|
+
let color = "bg-gray-400";
|
|
53
|
+
if (statusCode >= 200 && statusCode < 300) {
|
|
54
|
+
color = "bg-green-500";
|
|
55
|
+
} else if (statusCode >= 400 && statusCode < 500) {
|
|
56
|
+
color = "bg-yellow-500";
|
|
57
|
+
} else if (statusCode >= 500) {
|
|
58
|
+
color = "bg-red-500";
|
|
59
|
+
}
|
|
60
|
+
return <span className={cn("inline-block h-2.5 w-2.5 rounded-full", color)} />;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function MethodBadge({ method }: { method: string }) {
|
|
64
|
+
return (
|
|
65
|
+
<span
|
|
66
|
+
className={cn(
|
|
67
|
+
"inline-flex items-center rounded px-1.5 py-0.5 font-medium font-mono text-xs",
|
|
68
|
+
METHOD_COLORS[method] || "bg-gray-100 text-gray-700",
|
|
69
|
+
)}
|
|
70
|
+
>
|
|
71
|
+
{method}
|
|
72
|
+
</span>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
type RequestLogListTableProps = ListTableProps<RequestLogResponse> & {
|
|
77
|
+
/** Environment filter for account-level queries */
|
|
78
|
+
environment?: "live" | "sandbox";
|
|
79
|
+
/** Show entity column (for account-level view) */
|
|
80
|
+
showEntityColumn?: boolean;
|
|
81
|
+
/** Selected log for detail panel */
|
|
82
|
+
selectedLog?: RequestLogResponse | null;
|
|
83
|
+
/** Callback when a log is selected */
|
|
84
|
+
onSelectLog?: (log: RequestLogResponse | null) => void;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export function RequestLogListTable({
|
|
88
|
+
queryParams,
|
|
89
|
+
onChangeParams,
|
|
90
|
+
entityId,
|
|
91
|
+
environment,
|
|
92
|
+
showEntityColumn = false,
|
|
93
|
+
selectedLog,
|
|
94
|
+
onSelectLog,
|
|
95
|
+
}: RequestLogListTableProps) {
|
|
96
|
+
// Custom fetch function that handles both entity-scoped and account-scoped queries
|
|
97
|
+
// Don't use useTableFetch since we need special handling for environment
|
|
98
|
+
const handleFetch = useCallback(
|
|
99
|
+
async (params: TableQueryParams): Promise<TableQueryResponse<RequestLogResponse>> => {
|
|
100
|
+
const token = getCookie(AUTH_COOKIES.TOKEN);
|
|
101
|
+
if (!token) throw new Error("Not authenticated");
|
|
102
|
+
|
|
103
|
+
const queryParamsUrl = new URLSearchParams();
|
|
104
|
+
|
|
105
|
+
// Entity or environment filter
|
|
106
|
+
// For entity-scoped queries, use entity_id
|
|
107
|
+
// For account-scoped queries, use environment (defaults to "live")
|
|
108
|
+
if (entityId) {
|
|
109
|
+
queryParamsUrl.set("entity_id", entityId);
|
|
110
|
+
} else {
|
|
111
|
+
queryParamsUrl.set("environment", environment || "live");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Pagination
|
|
115
|
+
if (params.limit) queryParamsUrl.set("limit", String(params.limit));
|
|
116
|
+
if (params.next_cursor) queryParamsUrl.set("next_cursor", params.next_cursor);
|
|
117
|
+
if (params.prev_cursor) queryParamsUrl.set("prev_cursor", params.prev_cursor);
|
|
118
|
+
|
|
119
|
+
// Search maps to path filter
|
|
120
|
+
if (params.search) queryParamsUrl.set("path", params.search);
|
|
121
|
+
|
|
122
|
+
// HTTP method filter
|
|
123
|
+
if (params.filter_method) queryParamsUrl.set("method", params.filter_method);
|
|
124
|
+
|
|
125
|
+
// HTTP status code filter
|
|
126
|
+
if (params.filter_http_status) queryParamsUrl.set("status", params.filter_http_status);
|
|
127
|
+
|
|
128
|
+
// Date filters
|
|
129
|
+
if (params.filter_date_from) queryParamsUrl.set("date_from", params.filter_date_from);
|
|
130
|
+
if (params.filter_date_to) queryParamsUrl.set("date_to", params.filter_date_to);
|
|
131
|
+
|
|
132
|
+
const apiBaseUrl = getApiBaseUrl();
|
|
133
|
+
const response = await fetch(`${apiBaseUrl}/request-logs?${queryParamsUrl.toString()}`, {
|
|
134
|
+
headers: {
|
|
135
|
+
Authorization: `Bearer ${token}`,
|
|
136
|
+
"Content-Type": "application/json",
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
throw new Error(`Failed to fetch request logs: ${response.status}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return (await response.json()) as TableQueryResponse<RequestLogResponse>;
|
|
145
|
+
},
|
|
146
|
+
[entityId, environment],
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
const columns: Column<RequestLogResponse>[] = useMemo(() => {
|
|
150
|
+
const cols: Column<RequestLogResponse>[] = [
|
|
151
|
+
{
|
|
152
|
+
id: "status_dot",
|
|
153
|
+
header: "",
|
|
154
|
+
className: "w-8",
|
|
155
|
+
cell: (log) => <StatusDot status={log.res_status} />,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "res_status",
|
|
159
|
+
header: "Status",
|
|
160
|
+
className: "w-16",
|
|
161
|
+
cell: (log) => <span className="font-mono text-muted-foreground text-sm">{log.res_status || "—"}</span>,
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
id: "method",
|
|
165
|
+
header: "Method",
|
|
166
|
+
className: "w-20",
|
|
167
|
+
cell: (log) => <MethodBadge method={log.method} />,
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: "path",
|
|
171
|
+
header: "Path",
|
|
172
|
+
cell: (log) => <span className="truncate font-mono text-sm">{log.path}</span>,
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
|
|
176
|
+
if (showEntityColumn) {
|
|
177
|
+
cols.push({
|
|
178
|
+
id: "entity_id",
|
|
179
|
+
header: "Entity",
|
|
180
|
+
className: "hidden md:table-cell",
|
|
181
|
+
cell: (log) => (
|
|
182
|
+
<Tooltip>
|
|
183
|
+
<TooltipTrigger asChild>
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
className="cursor-pointer rounded bg-muted px-1.5 py-0.5 font-mono text-muted-foreground text-xs hover:bg-muted/80"
|
|
187
|
+
onClick={(e) => {
|
|
188
|
+
e.stopPropagation();
|
|
189
|
+
navigator.clipboard.writeText(log.entity_id);
|
|
190
|
+
}}
|
|
191
|
+
>
|
|
192
|
+
{log.entity_id}
|
|
193
|
+
</button>
|
|
194
|
+
</TooltipTrigger>
|
|
195
|
+
<TooltipContent>Copy</TooltipContent>
|
|
196
|
+
</Tooltip>
|
|
197
|
+
),
|
|
198
|
+
});
|
|
199
|
+
} else {
|
|
200
|
+
cols.push({
|
|
201
|
+
id: "resource_id",
|
|
202
|
+
header: "Resource",
|
|
203
|
+
className: "hidden sm:table-cell",
|
|
204
|
+
cell: (log) => <span className="text-muted-foreground text-xs">{log.resource_id || "—"}</span>,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
cols.push({
|
|
209
|
+
id: "created_at",
|
|
210
|
+
header: "Time",
|
|
211
|
+
align: "right",
|
|
212
|
+
sortable: true,
|
|
213
|
+
sortField: "created_at",
|
|
214
|
+
cell: (log) => (
|
|
215
|
+
<span className="text-muted-foreground text-xs">
|
|
216
|
+
{formatDistanceToNow(new Date(log.created_at), { addSuffix: true })}
|
|
217
|
+
</span>
|
|
218
|
+
),
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return cols;
|
|
222
|
+
}, [showEntityColumn]);
|
|
223
|
+
|
|
224
|
+
const filterConfig: FilterConfig = {
|
|
225
|
+
dateFields: [{ id: "created_at", label: "Date" }],
|
|
226
|
+
httpMethodFilter: true,
|
|
227
|
+
httpStatusCodeFilter: true,
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
const cacheKey = entityId
|
|
231
|
+
? `${REQUEST_LOGS_CACHE_KEY}-${entityId}`
|
|
232
|
+
: `${REQUEST_LOGS_CACHE_KEY}-account-${environment || "live"}`;
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<>
|
|
236
|
+
<DataTable
|
|
237
|
+
columns={columns}
|
|
238
|
+
cacheKey={cacheKey}
|
|
239
|
+
resourceName="request log"
|
|
240
|
+
onFetch={handleFetch}
|
|
241
|
+
queryParams={queryParams}
|
|
242
|
+
onChangeParams={onChangeParams}
|
|
243
|
+
entityId={entityId}
|
|
244
|
+
filterConfig={filterConfig}
|
|
245
|
+
onRowClick={(log) => onSelectLog?.(log)}
|
|
246
|
+
defaultOrderBy="-created_at"
|
|
247
|
+
/>
|
|
248
|
+
|
|
249
|
+
<Sheet open={!!selectedLog} onOpenChange={(open) => !open && onSelectLog?.(null)}>
|
|
250
|
+
<SheetContent className="w-full overflow-y-auto sm:max-w-2xl">
|
|
251
|
+
<SheetHeader>
|
|
252
|
+
<SheetTitle className="flex items-center gap-2">
|
|
253
|
+
{selectedLog && (
|
|
254
|
+
<>
|
|
255
|
+
<MethodBadge method={selectedLog.method} />
|
|
256
|
+
<span className="font-mono text-sm">{selectedLog.path}</span>
|
|
257
|
+
</>
|
|
258
|
+
)}
|
|
259
|
+
</SheetTitle>
|
|
260
|
+
</SheetHeader>
|
|
261
|
+
{selectedLog && <RequestLogDetail log={selectedLog} />}
|
|
262
|
+
</SheetContent>
|
|
263
|
+
</Sheet>
|
|
264
|
+
</>
|
|
265
|
+
);
|
|
266
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// Re-export from the new list table component
|
|
4
|
+
// Legacy component alias - deprecated, use RequestLogListTable instead
|
|
5
|
+
export {
|
|
6
|
+
REQUEST_LOGS_CACHE_KEY,
|
|
7
|
+
RequestLogListTable,
|
|
8
|
+
RequestLogListTable as RequestLogsPage,
|
|
9
|
+
type RequestLogResponse,
|
|
10
|
+
} from "./request-log-list-table";
|
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
# Table Component Library
|
|
2
|
+
|
|
3
|
+
A comprehensive, type-safe table system with built-in sorting, search, pagination, and loading states.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Type-safe**: Full TypeScript support with generics
|
|
8
|
+
- **Flexible**: Use default rendering or custom row/header components
|
|
9
|
+
- **State Management**: Built-in state with optional external control
|
|
10
|
+
- **TanStack Query**: Seamless integration with query caching
|
|
11
|
+
- **Accessible**: ARIA labels and keyboard navigation
|
|
12
|
+
- **Responsive**: Mobile-friendly with proper breakpoints
|
|
13
|
+
- **Loading States**: Skeleton loaders and empty states
|
|
14
|
+
- **Cursor Pagination**: Efficient server-side pagination
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
### Basic Usage (Simplified API)
|
|
19
|
+
|
|
20
|
+
The new API allows you to define columns with built-in cell renderers:
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { DataTable, FormattedDate } from "@space-invoices/ui";
|
|
24
|
+
import type { Invoice } from "@spaceinvoices/js-sdk";
|
|
25
|
+
|
|
26
|
+
function InvoiceTable() {
|
|
27
|
+
const { sdk } = useSDK();
|
|
28
|
+
|
|
29
|
+
return (
|
|
30
|
+
<DataTable<Invoice>
|
|
31
|
+
columns={[
|
|
32
|
+
{
|
|
33
|
+
id: "number",
|
|
34
|
+
header: "Invoice #",
|
|
35
|
+
sortable: true,
|
|
36
|
+
cell: (invoice) => (
|
|
37
|
+
<a href={`/invoices/${invoice.id}`} className="underline">
|
|
38
|
+
{invoice.number}
|
|
39
|
+
</a>
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "customer",
|
|
44
|
+
header: "Customer",
|
|
45
|
+
cell: (invoice) => invoice.customer?.name ?? "-",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "date",
|
|
49
|
+
header: "Date",
|
|
50
|
+
sortable: true,
|
|
51
|
+
cell: (invoice) => <FormattedDate date={invoice.date} />,
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "total",
|
|
55
|
+
header: "Total",
|
|
56
|
+
align: "right",
|
|
57
|
+
sortable: true,
|
|
58
|
+
cell: (invoice) => `$${invoice.total}`,
|
|
59
|
+
},
|
|
60
|
+
]}
|
|
61
|
+
cacheKey="invoices"
|
|
62
|
+
resourceName="invoice"
|
|
63
|
+
onFetch={(params) => sdk.invoices.getInvoices(params)}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Advanced Usage (Custom Rendering)
|
|
70
|
+
|
|
71
|
+
For more control, use custom row and header renderers:
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
import { DataTable } from "@space-invoices/ui";
|
|
75
|
+
import InvoiceListHeader from "./invoice-list-header";
|
|
76
|
+
import InvoiceListRow from "./invoice-list-row";
|
|
77
|
+
|
|
78
|
+
function InvoiceTable() {
|
|
79
|
+
const { sdk } = useSDK();
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<DataTable<Invoice>
|
|
83
|
+
columns={[
|
|
84
|
+
{ field: "number", header: "Number", sortable: true },
|
|
85
|
+
{ field: "customer", header: "Customer" },
|
|
86
|
+
{ field: "date", header: "Date", sortable: true },
|
|
87
|
+
{ field: "total", header: "Total", sortable: true, align: "right" },
|
|
88
|
+
]}
|
|
89
|
+
renderHeader={(props) => (
|
|
90
|
+
<InvoiceListHeader
|
|
91
|
+
orderBy={props.orderBy}
|
|
92
|
+
onSort={props.onSort}
|
|
93
|
+
/>
|
|
94
|
+
)}
|
|
95
|
+
renderRow={(invoice) => (
|
|
96
|
+
<InvoiceListRow
|
|
97
|
+
key={invoice.id}
|
|
98
|
+
invoice={invoice}
|
|
99
|
+
onRowClick={(item) => navigate(`/invoices/${item.id}`)}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
cacheKey="invoices"
|
|
103
|
+
resourceName="invoice"
|
|
104
|
+
onFetch={(params) => sdk.invoices.getInvoices(params)}
|
|
105
|
+
/>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## API Reference
|
|
111
|
+
|
|
112
|
+
### DataTable Props
|
|
113
|
+
|
|
114
|
+
| Prop | Type | Required | Description |
|
|
115
|
+
|------|------|----------|-------------|
|
|
116
|
+
| `columns` | `Column<T>[]` | Yes | Column definitions |
|
|
117
|
+
| `cacheKey` | `string` | Yes | Unique key for react-query cache |
|
|
118
|
+
| `onFetch` | `(params) => Promise<Response>` | Yes | Data fetch function |
|
|
119
|
+
| `resourceName` | `string` | Yes | Resource name for empty states |
|
|
120
|
+
| `defaultOrderBy` | `string` | No | Default sort order (e.g., "-id") |
|
|
121
|
+
| `queryParams` | `TableQueryParams` | No | External query parameters |
|
|
122
|
+
| `onChangeParams` | `(params) => void` | No | Callback for param changes |
|
|
123
|
+
| `entityId` | `string` | No | Entity ID for multi-tenant filtering |
|
|
124
|
+
| `createNewLink` | `string` | No | Link for "Create New" button |
|
|
125
|
+
| `createNewTrigger` | `ReactNode` | No | Custom create action |
|
|
126
|
+
| `onRowClick` | `(item: T) => void` | No | Row click handler |
|
|
127
|
+
| `renderRow` | `(item: T) => ReactNode` | No | Custom row renderer |
|
|
128
|
+
| `renderHeader` | `(props) => ReactNode` | No | Custom header renderer |
|
|
129
|
+
|
|
130
|
+
### Column Definition
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
type Column<T> = {
|
|
134
|
+
id: string; // Unique column identifier
|
|
135
|
+
header: ReactNode; // Header label or component
|
|
136
|
+
sortField?: string; // Field name for sorting (if different from id)
|
|
137
|
+
sortable?: boolean; // Enable sorting
|
|
138
|
+
align?: "left" | "center" | "right"; // Text alignment
|
|
139
|
+
cell?: (item: T) => ReactNode; // Cell renderer function
|
|
140
|
+
className?: string; // Optional CSS classes
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Hooks
|
|
145
|
+
|
|
146
|
+
### useTableState
|
|
147
|
+
|
|
148
|
+
Manages table state internally with optional URL sync:
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { useTableState } from "@space-invoices/ui";
|
|
152
|
+
|
|
153
|
+
const { params, handleSort, handleSearch, handlePageChange } = useTableState({
|
|
154
|
+
initialParams: { order_by: "-created_at" },
|
|
155
|
+
defaultOrderBy: "-id",
|
|
156
|
+
onChangeParams: (params) => {
|
|
157
|
+
// Optional: sync with router or external state
|
|
158
|
+
navigate({ search: params });
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### useTableQuery
|
|
164
|
+
|
|
165
|
+
TanStack Query wrapper for table data:
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import { useTableQuery } from "@space-invoices/ui";
|
|
169
|
+
|
|
170
|
+
const { data, isFetching } = useTableQuery({
|
|
171
|
+
cacheKey: "customers",
|
|
172
|
+
fetchFn: (params) => sdk.customers.getCustomers(params),
|
|
173
|
+
params: { order_by: "-id", search: "acme" },
|
|
174
|
+
entityId: "entity-123",
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### useTableFetch
|
|
179
|
+
|
|
180
|
+
Wraps fetch function to include entity ID:
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
import { useTableFetch } from "@space-invoices/ui";
|
|
184
|
+
|
|
185
|
+
const handleFetch = useTableFetch(
|
|
186
|
+
(params) => sdk.customers.getCustomers(params),
|
|
187
|
+
entityId
|
|
188
|
+
);
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Components
|
|
192
|
+
|
|
193
|
+
### SearchInput
|
|
194
|
+
|
|
195
|
+
Search input with optional debouncing:
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
<SearchInput
|
|
199
|
+
initialValue=""
|
|
200
|
+
onSearch={(value) => console.log(value)}
|
|
201
|
+
placeholder="Search customers..."
|
|
202
|
+
debounceMs={300} // Optional debouncing
|
|
203
|
+
/>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### SortableHeader
|
|
207
|
+
|
|
208
|
+
Sortable column header with visual indicators:
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
<SortableHeader
|
|
212
|
+
field="name"
|
|
213
|
+
currentOrder="-name"
|
|
214
|
+
onSort={(order) => console.log(order)}
|
|
215
|
+
align="left"
|
|
216
|
+
>
|
|
217
|
+
Customer Name
|
|
218
|
+
</SortableHeader>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Pagination
|
|
222
|
+
|
|
223
|
+
Cursor-based pagination controls:
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
<Pagination
|
|
227
|
+
cursorBefore="prev-cursor"
|
|
228
|
+
cursorAfter="next-cursor"
|
|
229
|
+
onPageChange={({ before, after }) => console.log(before, after)}
|
|
230
|
+
/>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### FormattedDate
|
|
234
|
+
|
|
235
|
+
Date formatting with error handling:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
<FormattedDate
|
|
239
|
+
date="2024-01-15"
|
|
240
|
+
format={{
|
|
241
|
+
year: "numeric",
|
|
242
|
+
month: "short",
|
|
243
|
+
day: "numeric",
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Usage Patterns
|
|
249
|
+
|
|
250
|
+
The table component supports two main usage patterns:
|
|
251
|
+
|
|
252
|
+
### Simple Tables (Column-driven)
|
|
253
|
+
|
|
254
|
+
For straightforward tables, define columns with cell renderers:
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
<DataTable
|
|
258
|
+
columns={[
|
|
259
|
+
{
|
|
260
|
+
id: "name",
|
|
261
|
+
header: "Name",
|
|
262
|
+
sortable: true,
|
|
263
|
+
cell: (item) => item.name,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: "email",
|
|
267
|
+
header: "Email",
|
|
268
|
+
cell: (item) => item.email,
|
|
269
|
+
},
|
|
270
|
+
]}
|
|
271
|
+
cacheKey="users"
|
|
272
|
+
resourceName="user"
|
|
273
|
+
onFetch={(params) => sdk.users.getUsers(params)}
|
|
274
|
+
/>
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Complex Tables (Custom renderers)
|
|
278
|
+
|
|
279
|
+
For advanced tables with complex row/header components:
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
<DataTable
|
|
283
|
+
columns={[
|
|
284
|
+
{ id: "name", header: "Name", sortable: true },
|
|
285
|
+
{ id: "email", header: "Email" },
|
|
286
|
+
]}
|
|
287
|
+
renderRow={(item) => (
|
|
288
|
+
<CustomRow key={item.id} item={item} />
|
|
289
|
+
)}
|
|
290
|
+
renderHeader={(props) => (
|
|
291
|
+
<CustomHeader {...props} />
|
|
292
|
+
)}
|
|
293
|
+
cacheKey="users"
|
|
294
|
+
resourceName="user"
|
|
295
|
+
onFetch={(params) => sdk.users.getUsers(params)}
|
|
296
|
+
/>
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Key Improvements
|
|
300
|
+
|
|
301
|
+
### 1. Simplified State Management
|
|
302
|
+
|
|
303
|
+
**Before:**
|
|
304
|
+
- Manual `useState` for params
|
|
305
|
+
- Manual URL sync with `updateQueryParams`
|
|
306
|
+
- Complex callback chains
|
|
307
|
+
|
|
308
|
+
**After:**
|
|
309
|
+
- `useTableState` hook handles everything
|
|
310
|
+
- Automatic URL sync
|
|
311
|
+
- Single source of truth
|
|
312
|
+
|
|
313
|
+
### 2. Cleaner Hooks
|
|
314
|
+
|
|
315
|
+
**Before:**
|
|
316
|
+
```tsx
|
|
317
|
+
const { data, isFetching } = useTableQuery({
|
|
318
|
+
cacheKey,
|
|
319
|
+
fetchFn,
|
|
320
|
+
params,
|
|
321
|
+
entityId,
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
// Unnecessary isMounted ref logic
|
|
325
|
+
const isMounted = useRef(true);
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**After:**
|
|
329
|
+
```tsx
|
|
330
|
+
// Simplified, no ref needed
|
|
331
|
+
const { data, isFetching } = useTableQuery({
|
|
332
|
+
cacheKey,
|
|
333
|
+
fetchFn,
|
|
334
|
+
params,
|
|
335
|
+
entityId,
|
|
336
|
+
});
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### 3. Better Column API
|
|
340
|
+
|
|
341
|
+
**Before:**
|
|
342
|
+
- Column definitions not used for rendering
|
|
343
|
+
- Must implement custom row/header components
|
|
344
|
+
|
|
345
|
+
**After:**
|
|
346
|
+
- Column definitions drive rendering
|
|
347
|
+
- Optional custom renderers for flexibility
|
|
348
|
+
- Built-in cell renderers via `cell` prop
|
|
349
|
+
|
|
350
|
+
### 4. Improved Type Safety
|
|
351
|
+
|
|
352
|
+
**Before:**
|
|
353
|
+
```typescript
|
|
354
|
+
// Loose typing on columns
|
|
355
|
+
columns: { field: string; header: React.ReactNode }[]
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**After:**
|
|
359
|
+
```typescript
|
|
360
|
+
// Strongly typed with generics
|
|
361
|
+
columns: Column<T>[]
|
|
362
|
+
// T is your data type (e.g., Invoice, Customer)
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### 5. Better UX
|
|
366
|
+
|
|
367
|
+
**Before:**
|
|
368
|
+
- Basic search input
|
|
369
|
+
- Minimal empty states
|
|
370
|
+
|
|
371
|
+
**After:**
|
|
372
|
+
- Search with clear button
|
|
373
|
+
- Rich empty states with icons
|
|
374
|
+
- Better error handling in date formatter
|
|
375
|
+
- Improved accessibility
|
|
376
|
+
|
|
377
|
+
## Best Practices
|
|
378
|
+
|
|
379
|
+
1. **Use column definitions for simple tables**: Less code, easier to maintain
|
|
380
|
+
2. **Use custom renderers for complex tables**: More control when needed
|
|
381
|
+
3. **Define cache keys as constants**: Reuse across queries and mutations
|
|
382
|
+
4. **Handle loading states**: The component handles this automatically
|
|
383
|
+
5. **Provide meaningful resource names**: Used in empty states
|
|
384
|
+
6. **Use FormattedDate**: Consistent date formatting with error handling
|
|
385
|
+
7. **Enable sorting selectively**: Not all columns need sorting
|
|
386
|
+
8. **Use proper TypeScript types**: Import from `@spaceinvoices/js-sdk`
|
|
387
|
+
|
|
388
|
+
## Testing
|
|
389
|
+
|
|
390
|
+
All table components are fully tested:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
cd packages/ui
|
|
394
|
+
bun test test/components/table/
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
## Accessibility
|
|
398
|
+
|
|
399
|
+
- ARIA labels on interactive elements
|
|
400
|
+
- Keyboard navigation support
|
|
401
|
+
- Screen reader friendly
|
|
402
|
+
- Semantic HTML structure
|
|
403
|
+
- Proper focus management
|
|
404
|
+
|
|
405
|
+
## Performance
|
|
406
|
+
|
|
407
|
+
- Efficient re-renders with proper memoization
|
|
408
|
+
- React Query caching (5-minute stale time)
|
|
409
|
+
- Cursor-based pagination (no offset issues)
|
|
410
|
+
- Skeleton loading (no layout shift)
|