shutro-erp 0.3.3 → 0.3.5
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/dist/account_portal/.kosh/bindings.js +1 -1
- package/dist/account_portal/.kosh/schema.d.ts +181 -3
- package/dist/account_portal/module.d.ts +359 -6
- package/dist/accounting/.kosh/bindings.js +1 -1
- package/dist/accounting/.kosh/schema.d.ts +181 -3
- package/dist/accounting/index.js +214 -2
- package/dist/accounting/module.d.ts +217 -5
- package/dist/accounting/module.js +23 -2
- package/dist/calendar/.kosh/bindings.d.ts +22 -0
- package/dist/calendar/.kosh/bindings.js +15 -0
- package/dist/calendar/.kosh/schema.d.ts +795 -0
- package/dist/calendar/demo.d.ts +6 -0
- package/dist/calendar/demo.js +24 -0
- package/dist/calendar/index.d.ts +3 -0
- package/dist/calendar/index.js +291 -0
- package/dist/calendar/module.d.ts +1705 -0
- package/dist/calendar/module.js +180 -0
- package/dist/cli/init.js +48 -0
- package/dist/cli/templates.d.ts +1 -1
- package/dist/cli/templates.js +137 -0
- package/dist/contacts/.kosh/bindings.js +1 -1
- package/dist/contacts/.kosh/schema.d.ts +205 -3
- package/dist/contacts/demo.js +30 -1
- package/dist/contacts/index.d.ts +1 -1
- package/dist/contacts/index.js +155 -9
- package/dist/contacts/module.d.ts +338 -2
- package/dist/contacts/module.js +82 -5
- package/dist/crm/.kosh/bindings.d.ts +22 -0
- package/dist/crm/.kosh/bindings.js +15 -0
- package/dist/crm/.kosh/schema.d.ts +799 -0
- package/dist/crm/demo.d.ts +6 -0
- package/dist/crm/demo.js +45 -0
- package/dist/crm/index.d.ts +3 -0
- package/dist/crm/index.js +361 -0
- package/dist/crm/module.d.ts +1843 -0
- package/dist/crm/module.js +207 -0
- package/dist/event/.kosh/bindings.d.ts +22 -0
- package/dist/event/.kosh/bindings.js +15 -0
- package/dist/event/.kosh/schema.d.ts +843 -0
- package/dist/event/demo.d.ts +6 -0
- package/dist/event/demo.js +65 -0
- package/dist/event/index.d.ts +14 -0
- package/dist/event/index.js +333 -0
- package/dist/event/mails.d.ts +15 -0
- package/dist/event/mails.js +112 -0
- package/dist/event/module.d.ts +2800 -0
- package/dist/event/module.js +323 -0
- package/dist/event/web/CheckInBox.d.ts +9 -0
- package/dist/event/web/CheckInBox.js +87 -0
- package/dist/event/web/index.d.ts +11 -0
- package/dist/event/web/index.js +16 -0
- package/dist/event/web/messages.d.ts +2 -0
- package/dist/event/web/messages.js +23 -0
- package/dist/event_account/.kosh/bindings.d.ts +22 -0
- package/dist/event_account/.kosh/bindings.js +15 -0
- package/dist/event_account/.kosh/schema.d.ts +1639 -0
- package/dist/event_account/index.d.ts +3 -0
- package/dist/event_account/index.js +28 -0
- package/dist/event_account/module.d.ts +6968 -0
- package/dist/event_account/module.js +90 -0
- package/dist/hr/.kosh/bindings.d.ts +22 -0
- package/dist/hr/.kosh/bindings.js +15 -0
- package/dist/hr/.kosh/schema.d.ts +878 -0
- package/dist/hr/demo.d.ts +6 -0
- package/dist/hr/demo.js +60 -0
- package/dist/hr/index.d.ts +3 -0
- package/dist/hr/index.js +483 -0
- package/dist/hr/module.d.ts +2611 -0
- package/dist/hr/module.js +350 -0
- package/dist/integrations/index.js +12 -0
- package/dist/integrations/web/OAuthAppsPage.d.ts +9 -0
- package/dist/integrations/web/OAuthAppsPage.js +79 -0
- package/dist/integrations/web/index.d.ts +1 -0
- package/dist/integrations/web/index.js +3 -0
- package/dist/integrations/web/messages.js +58 -0
- package/dist/l10n_bd/.kosh/bindings.js +1 -1
- package/dist/l10n_bd/.kosh/schema.d.ts +181 -3
- package/dist/l10n_bd/module.d.ts +359 -6
- package/dist/l10n_in/.kosh/bindings.js +1 -1
- package/dist/l10n_in/.kosh/schema.d.ts +181 -3
- package/dist/l10n_in/module.d.ts +380 -7
- package/dist/maintenance/.kosh/bindings.d.ts +22 -0
- package/dist/maintenance/.kosh/bindings.js +15 -0
- package/dist/maintenance/.kosh/schema.d.ts +911 -0
- package/dist/maintenance/demo.d.ts +6 -0
- package/dist/maintenance/demo.js +60 -0
- package/dist/maintenance/index.d.ts +3 -0
- package/dist/maintenance/index.js +422 -0
- package/dist/maintenance/module.d.ts +3545 -0
- package/dist/maintenance/module.js +446 -0
- package/dist/maintenance_stock/.kosh/bindings.d.ts +22 -0
- package/dist/maintenance_stock/.kosh/bindings.js +15 -0
- package/dist/maintenance_stock/.kosh/schema.d.ts +1371 -0
- package/dist/maintenance_stock/index.d.ts +9 -0
- package/dist/maintenance_stock/index.js +52 -0
- package/dist/maintenance_stock/module.d.ts +6953 -0
- package/dist/maintenance_stock/module.js +192 -0
- package/dist/messaging/web/index.js +1 -1
- package/dist/modules-sdk/index.d.ts +1 -1
- package/dist/modules-sdk/manifest.d.ts +70 -0
- package/dist/modules-sdk/manifest.js +35 -1
- package/dist/mrp/.kosh/bindings.d.ts +22 -0
- package/dist/mrp/.kosh/bindings.js +15 -0
- package/dist/mrp/.kosh/schema.d.ts +1378 -0
- package/dist/mrp/demo.d.ts +7 -0
- package/dist/mrp/demo.js +42 -0
- package/dist/mrp/index.d.ts +3 -0
- package/dist/mrp/index.js +392 -0
- package/dist/mrp/module.d.ts +8608 -0
- package/dist/mrp/module.js +695 -0
- package/dist/mrp_account/.kosh/bindings.d.ts +22 -0
- package/dist/mrp_account/.kosh/bindings.js +15 -0
- package/dist/mrp_account/.kosh/schema.d.ts +2106 -0
- package/dist/mrp_account/index.d.ts +3 -0
- package/dist/mrp_account/index.js +22 -0
- package/dist/mrp_account/module.d.ts +20840 -0
- package/dist/mrp_account/module.js +123 -0
- package/dist/notify/web/index.js +1 -1
- package/dist/payment/.kosh/bindings.js +1 -1
- package/dist/payment/.kosh/schema.d.ts +181 -3
- package/dist/payment/module.d.ts +359 -6
- package/dist/portal/.kosh/bindings.js +1 -1
- package/dist/portal/.kosh/schema.d.ts +205 -3
- package/dist/portal/module.d.ts +166 -2
- package/dist/pos/.kosh/bindings.d.ts +22 -0
- package/dist/pos/.kosh/bindings.js +15 -0
- package/dist/pos/.kosh/schema.d.ts +1690 -0
- package/dist/pos/accounting.d.ts +10 -0
- package/dist/pos/accounting.js +149 -0
- package/dist/pos/demo.d.ts +6 -0
- package/dist/pos/demo.js +90 -0
- package/dist/pos/index.d.ts +9 -0
- package/dist/pos/index.js +349 -0
- package/dist/pos/module.d.ts +9636 -0
- package/dist/pos/module.js +423 -0
- package/dist/pos/routes.d.ts +39 -0
- package/dist/pos/routes.js +368 -0
- package/dist/pos/web/Register.d.ts +9 -0
- package/dist/pos/web/Register.js +252 -0
- package/dist/pos/web/index.d.ts +11 -0
- package/dist/pos/web/index.js +16 -0
- package/dist/pos/web/messages.d.ts +2 -0
- package/dist/pos/web/messages.js +87 -0
- package/dist/pos_stock/.kosh/bindings.d.ts +22 -0
- package/dist/pos_stock/.kosh/bindings.js +15 -0
- package/dist/pos_stock/.kosh/schema.d.ts +2015 -0
- package/dist/pos_stock/index.d.ts +9 -0
- package/dist/pos_stock/index.js +35 -0
- package/dist/pos_stock/module.d.ts +11935 -0
- package/dist/pos_stock/module.js +115 -0
- package/dist/product/.kosh/bindings.js +1 -1
- package/dist/product/.kosh/schema.d.ts +178 -1
- package/dist/product/index.js +81 -4
- package/dist/product/module.d.ts +43 -1
- package/dist/product/module.js +9 -2
- package/dist/project/.kosh/bindings.js +1 -1
- package/dist/project/.kosh/schema.d.ts +181 -3
- package/dist/project/index.js +147 -7
- package/dist/project/module.d.ts +210 -2
- package/dist/project/module.js +52 -5
- package/dist/project_portal/.kosh/bindings.js +1 -1
- package/dist/project_portal/.kosh/schema.d.ts +181 -3
- package/dist/project_portal/module.d.ts +353 -4
- package/dist/purchase/.kosh/bindings.js +1 -1
- package/dist/purchase/.kosh/schema.d.ts +181 -3
- package/dist/purchase/index.js +163 -3
- package/dist/purchase/module.d.ts +215 -3
- package/dist/purchase/module.js +21 -3
- package/dist/purchase_account/.kosh/bindings.js +1 -1
- package/dist/purchase_account/.kosh/schema.d.ts +182 -3
- package/dist/purchase_account/index.js +26 -1
- package/dist/purchase_account/module.d.ts +393 -7
- package/dist/purchase_portal/.kosh/bindings.js +1 -1
- package/dist/purchase_portal/.kosh/schema.d.ts +181 -3
- package/dist/purchase_portal/module.d.ts +366 -5
- package/dist/purchase_stock/.kosh/bindings.d.ts +22 -0
- package/dist/purchase_stock/.kosh/bindings.js +15 -0
- package/dist/purchase_stock/.kosh/schema.d.ts +1310 -0
- package/dist/purchase_stock/index.d.ts +7 -0
- package/dist/purchase_stock/index.js +91 -0
- package/dist/purchase_stock/module.d.ts +6763 -0
- package/dist/purchase_stock/module.js +144 -0
- package/dist/purchase_stock_account/.kosh/bindings.d.ts +22 -0
- package/dist/purchase_stock_account/.kosh/bindings.js +15 -0
- package/dist/purchase_stock_account/.kosh/schema.d.ts +1959 -0
- package/dist/purchase_stock_account/index.d.ts +3 -0
- package/dist/purchase_stock_account/index.js +24 -0
- package/dist/purchase_stock_account/module.d.ts +35366 -0
- package/dist/purchase_stock_account/module.js +162 -0
- package/dist/recruitment/.kosh/bindings.d.ts +22 -0
- package/dist/recruitment/.kosh/bindings.js +15 -0
- package/dist/recruitment/.kosh/schema.d.ts +990 -0
- package/dist/recruitment/demo.d.ts +6 -0
- package/dist/recruitment/demo.js +45 -0
- package/dist/recruitment/index.d.ts +3 -0
- package/dist/recruitment/index.js +385 -0
- package/dist/recruitment/module.d.ts +3247 -0
- package/dist/recruitment/module.js +193 -0
- package/dist/recruitment/web/HireButton.d.ts +9 -0
- package/dist/recruitment/web/HireButton.js +32 -0
- package/dist/recruitment/web/index.d.ts +11 -0
- package/dist/recruitment/web/index.js +16 -0
- package/dist/recruitment/web/messages.d.ts +2 -0
- package/dist/recruitment/web/messages.js +19 -0
- package/dist/reporting/documents.d.ts +5 -0
- package/dist/reporting/documents.js +28 -1
- package/dist/reporting/index.d.ts +1 -1
- package/dist/reporting/index.js +4 -2
- package/dist/reporting/models.js +9 -0
- package/dist/reporting/overviews.d.ts +16 -0
- package/dist/reporting/overviews.js +56 -0
- package/dist/reporting/routes-documents.js +1 -1
- package/dist/reporting/routes.js +138 -80
- package/dist/reporting/web/DashboardView.d.ts +11 -0
- package/dist/reporting/web/DashboardView.js +2 -2
- package/dist/reporting/web/DesignerView.js +5 -0
- package/dist/reporting/web/Overview.d.ts +9 -0
- package/dist/reporting/web/Overview.js +53 -0
- package/dist/reporting/web/index.d.ts +1 -0
- package/dist/reporting/web/index.js +4 -1
- package/dist/reporting/web/messages.js +9 -0
- package/dist/sale_account/.kosh/bindings.js +1 -1
- package/dist/sale_account/.kosh/schema.d.ts +186 -3
- package/dist/sale_account/index.js +22 -1
- package/dist/sale_account/module.d.ts +461 -7
- package/dist/sale_portal/.kosh/bindings.js +1 -1
- package/dist/sale_portal/.kosh/schema.d.ts +185 -3
- package/dist/sale_portal/module.d.ts +434 -5
- package/dist/sale_project/.kosh/bindings.js +1 -1
- package/dist/sale_project/.kosh/schema.d.ts +187 -3
- package/dist/sale_project/module.d.ts +476 -6
- package/dist/sale_stock/.kosh/bindings.d.ts +22 -0
- package/dist/sale_stock/.kosh/bindings.js +15 -0
- package/dist/sale_stock/.kosh/schema.d.ts +1228 -0
- package/dist/sale_stock/index.d.ts +7 -0
- package/dist/sale_stock/index.js +84 -0
- package/dist/sale_stock/module.d.ts +6791 -0
- package/dist/sale_stock/module.js +146 -0
- package/dist/sales/.kosh/bindings.js +1 -1
- package/dist/sales/.kosh/schema.d.ts +185 -3
- package/dist/sales/index.js +191 -4
- package/dist/sales/module.d.ts +355 -3
- package/dist/sales/module.js +78 -3
- package/dist/server/api/metadata.d.ts +5 -0
- package/dist/server/api/metadata.js +5 -0
- package/dist/server/api/rest.js +64 -1
- package/dist/server/api/views.js +1 -0
- package/dist/server/app-settings.d.ts +7 -0
- package/dist/server/app-settings.js +10 -0
- package/dist/server/auth/base.js +85 -0
- package/dist/server/auth/oauth.d.ts +21 -0
- package/dist/server/auth/oauth.js +579 -0
- package/dist/server/auth/service.d.ts +4 -0
- package/dist/server/auth/service.js +17 -0
- package/dist/server/identity.d.ts +2 -0
- package/dist/server/images.d.ts +12 -0
- package/dist/server/images.js +139 -0
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +2 -0
- package/dist/server/server.d.ts +4 -0
- package/dist/server/server.js +9 -1
- package/dist/settings/app-settings.d.ts +2 -0
- package/dist/settings/app-settings.js +518 -0
- package/dist/settings/company.js +3 -1
- package/dist/settings/index.d.ts +1 -0
- package/dist/settings/index.js +15 -0
- package/dist/settings/people.js +32 -6
- package/dist/settings/web/AppSettingsPage.d.ts +11 -0
- package/dist/settings/web/AppSettingsPage.js +210 -0
- package/dist/settings/web/CompaniesPage.js +2 -2
- package/dist/settings/web/CompanyPage.js +13 -2
- package/dist/settings/web/ConnectedAppsPage.d.ts +7 -0
- package/dist/settings/web/ConnectedAppsPage.js +33 -0
- package/dist/settings/web/OverviewPage.js +28 -4
- package/dist/settings/web/PeoplePage.js +23 -18
- package/dist/settings/web/ProfilePage.js +9 -7
- package/dist/settings/web/index.d.ts +2 -0
- package/dist/settings/web/index.js +8 -0
- package/dist/settings/web/messages.js +146 -2
- package/dist/stock/.kosh/bindings.d.ts +22 -0
- package/dist/stock/.kosh/bindings.js +15 -0
- package/dist/stock/.kosh/schema.d.ts +1147 -0
- package/dist/stock/demo.d.ts +7 -0
- package/dist/stock/demo.js +55 -0
- package/dist/stock/index.d.ts +3 -0
- package/dist/stock/index.js +674 -0
- package/dist/stock/module.d.ts +6330 -0
- package/dist/stock/module.js +1325 -0
- package/dist/stock/web/ScanBox.d.ts +8 -0
- package/dist/stock/web/ScanBox.js +90 -0
- package/dist/stock/web/index.d.ts +11 -0
- package/dist/stock/web/index.js +16 -0
- package/dist/stock/web/messages.d.ts +2 -0
- package/dist/stock/web/messages.js +25 -0
- package/dist/stock_account/.kosh/bindings.d.ts +22 -0
- package/dist/stock_account/.kosh/bindings.js +15 -0
- package/dist/stock_account/.kosh/schema.d.ts +1875 -0
- package/dist/stock_account/index.d.ts +3 -0
- package/dist/stock_account/index.js +130 -0
- package/dist/stock_account/module.d.ts +10655 -0
- package/dist/stock_account/module.js +339 -0
- package/dist/storage/attachments.d.ts +5 -0
- package/dist/storage/attachments.js +12 -1
- package/dist/storage/models.js +3 -0
- package/dist/storage/routes.js +13 -2
- package/dist/todo/.kosh/bindings.d.ts +22 -0
- package/dist/todo/.kosh/bindings.js +15 -0
- package/dist/todo/.kosh/schema.d.ts +732 -0
- package/dist/todo/demo.d.ts +6 -0
- package/dist/todo/demo.js +35 -0
- package/dist/todo/index.d.ts +3 -0
- package/dist/todo/index.js +187 -0
- package/dist/todo/module.d.ts +1180 -0
- package/dist/todo/module.js +130 -0
- package/dist/trade_finance/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance/.kosh/bindings.js +15 -0
- package/dist/trade_finance/.kosh/schema.d.ts +884 -0
- package/dist/trade_finance/demo.d.ts +6 -0
- package/dist/trade_finance/demo.js +63 -0
- package/dist/trade_finance/index.d.ts +3 -0
- package/dist/trade_finance/index.js +413 -0
- package/dist/trade_finance/module.d.ts +3779 -0
- package/dist/trade_finance/module.js +505 -0
- package/dist/trade_finance_account/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance_account/.kosh/bindings.js +15 -0
- package/dist/trade_finance_account/.kosh/schema.d.ts +1711 -0
- package/dist/trade_finance_account/index.d.ts +3 -0
- package/dist/trade_finance_account/index.js +92 -0
- package/dist/trade_finance_account/module.d.ts +7928 -0
- package/dist/trade_finance_account/module.js +244 -0
- package/dist/trade_finance_purchase/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance_purchase/.kosh/bindings.js +15 -0
- package/dist/trade_finance_purchase/.kosh/schema.d.ts +1153 -0
- package/dist/trade_finance_purchase/index.d.ts +8 -0
- package/dist/trade_finance_purchase/index.js +50 -0
- package/dist/trade_finance_purchase/module.d.ts +4252 -0
- package/dist/trade_finance_purchase/module.js +44 -0
- package/dist/trade_finance_sale/.kosh/bindings.d.ts +22 -0
- package/dist/trade_finance_sale/.kosh/bindings.js +15 -0
- package/dist/trade_finance_sale/.kosh/schema.d.ts +1071 -0
- package/dist/trade_finance_sale/index.d.ts +8 -0
- package/dist/trade_finance_sale/index.js +50 -0
- package/dist/trade_finance_sale/module.d.ts +4280 -0
- package/dist/trade_finance_sale/module.js +44 -0
- package/dist/ui/api.d.ts +6 -1
- package/dist/ui/api.js +2 -1
- package/dist/ui/app.js +51 -17
- package/dist/ui/components/AppIcon.js +33 -1
- package/dist/ui/components/Avatar.d.ts +21 -0
- package/dist/ui/components/Avatar.js +71 -0
- package/dist/ui/components/primitives.d.ts +2 -0
- package/dist/ui/components/primitives.js +7 -1
- package/dist/ui/i18n/messages.d.ts +53 -0
- package/dist/ui/i18n/messages.js +98 -0
- package/dist/ui/index.d.ts +2 -0
- package/dist/ui/index.js +2 -0
- package/dist/ui/offline/queue.d.ts +5 -1
- package/dist/ui/offline/queue.js +2 -0
- package/dist/ui/registry.d.ts +12 -1
- package/dist/ui/registry.js +10 -1
- package/dist/ui/search/ViewTabs.d.ts +14 -0
- package/dist/ui/search/ViewTabs.js +42 -0
- package/dist/ui/shell/Consent.d.ts +5 -0
- package/dist/ui/shell/Consent.js +35 -0
- package/dist/ui/shell/Home.js +136 -16
- package/dist/ui/shell/Sidebar.js +15 -22
- package/dist/ui/views/FormView.js +98 -17
- package/dist/ui/views/KanbanView.d.ts +2 -0
- package/dist/ui/views/KanbanView.js +32 -3
- package/dist/ui/views/ListView.js +155 -31
- package/dist/ui/views/StatsRow.d.ts +14 -0
- package/dist/ui/views/StatsRow.js +88 -0
- package/dist/ui/views/widgets.d.ts +2 -1
- package/dist/ui/views/widgets.js +194 -1
- package/dist/views/builders.d.ts +19 -2
- package/dist/views/builders.js +21 -1
- package/dist/views/condition.d.ts +6 -0
- package/dist/views/condition.js +20 -0
- package/dist/views/documents.d.ts +8 -0
- package/dist/views/extend.d.ts +2 -2
- package/dist/views/extend.js +11 -1
- package/dist/views/index.d.ts +2 -2
- package/dist/views/index.js +2 -2
- package/dist/views/types.d.ts +52 -5
- package/dist/views/validate.js +39 -1
- package/dist/website/.kosh/bindings.d.ts +22 -0
- package/dist/website/.kosh/bindings.js +15 -0
- package/dist/website/.kosh/schema.d.ts +881 -0
- package/dist/website/demo.d.ts +6 -0
- package/dist/website/demo.js +115 -0
- package/dist/website/index.d.ts +9 -0
- package/dist/website/index.js +250 -0
- package/dist/website/module.d.ts +2987 -0
- package/dist/website/module.js +281 -0
- package/dist/website/render.d.ts +45 -0
- package/dist/website/render.js +102 -0
- package/dist/website/routes.d.ts +12 -0
- package/dist/website/routes.js +246 -0
- package/dist/website/web/PagePreview.d.ts +7 -0
- package/dist/website/web/PagePreview.js +33 -0
- package/dist/website/web/index.d.ts +11 -0
- package/dist/website/web/index.js +16 -0
- package/dist/website/web/messages.d.ts +2 -0
- package/dist/website/web/messages.js +15 -0
- package/dist/website_crm/.kosh/bindings.d.ts +22 -0
- package/dist/website_crm/.kosh/bindings.js +15 -0
- package/dist/website_crm/.kosh/schema.d.ts +966 -0
- package/dist/website_crm/index.d.ts +3 -0
- package/dist/website_crm/index.js +39 -0
- package/dist/website_crm/module.d.ts +2955 -0
- package/dist/website_crm/module.js +44 -0
- package/package.json +118 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The data layer of the Trade Finance ↔ Sales bridge (RFC 0021, Trade Finance step 3): an export LC received for a sales order takes its customer, currency and amount; the order lists its LCs.
|
|
3
|
+
*/
|
|
4
|
+
import { salesShutro } from '../sales/index.js';
|
|
5
|
+
import { orm } from '../server/orm.js';
|
|
6
|
+
import { tradeFinanceShutro } from '../trade_finance/index.js';
|
|
7
|
+
import { defineModule, extendModel, f, onchange } from './.kosh/bindings.js';
|
|
8
|
+
export const LcSale = extendModel('trade.lc', {
|
|
9
|
+
fields: {
|
|
10
|
+
saleOrder: f.many2one('sale.order', { ondelete: 'set null', label: 'Sales order' }),
|
|
11
|
+
},
|
|
12
|
+
onchange: {
|
|
13
|
+
// The order's counterparty, currency and total; the LC's direction and source document.
|
|
14
|
+
saleOrder: onchange('trade.lc', async (d) => {
|
|
15
|
+
const order = await d.record('saleOrder');
|
|
16
|
+
if (order === null || order === undefined)
|
|
17
|
+
return;
|
|
18
|
+
d.set('direction', 'export');
|
|
19
|
+
d.set('partner', order.partner.id);
|
|
20
|
+
d.set('currency', order.currency.id);
|
|
21
|
+
d.set('amount', order.amountTotal?.amount.toString() ?? null);
|
|
22
|
+
d.set('origin', order.name);
|
|
23
|
+
}),
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
export const SaleLcs = extendModel('sale.order', {
|
|
27
|
+
fields: {
|
|
28
|
+
lcs: f.one2many('trade.lc', 'saleOrder', { label: 'Letters of credit' }),
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
export const tradeFinanceSaleShutro = defineModule({
|
|
32
|
+
name: 'trade_finance_sale',
|
|
33
|
+
version: '0.1.0',
|
|
34
|
+
depends: [tradeFinanceShutro, salesShutro],
|
|
35
|
+
groups: [
|
|
36
|
+
orm.defineGroup({
|
|
37
|
+
code: 'trade_finance_sale.user',
|
|
38
|
+
label: 'Export LCs for sales orders',
|
|
39
|
+
implies: ['trade_finance.user', 'sales.user'],
|
|
40
|
+
}),
|
|
41
|
+
],
|
|
42
|
+
models: [],
|
|
43
|
+
extensions: [LcSale, SaleLcs],
|
|
44
|
+
});
|
package/dist/ui/api.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export interface ViewField {
|
|
|
36
36
|
readonly label: string;
|
|
37
37
|
readonly required: boolean;
|
|
38
38
|
readonly readonly: boolean;
|
|
39
|
+
readonly help?: string;
|
|
39
40
|
readonly target?: string;
|
|
40
41
|
readonly options?: readonly {
|
|
41
42
|
readonly value: string;
|
|
@@ -74,6 +75,10 @@ export interface CurrentUser {
|
|
|
74
75
|
readonly administratorNeedsSecondFactor: boolean;
|
|
75
76
|
/** The policy requires two-factor sign-in of everyone and it is not on (RFC 0019 §3.5); older servers send none. */
|
|
76
77
|
readonly twoFactorSetupRequired?: boolean;
|
|
78
|
+
/** The user's photo version, or null (older servers send none). */
|
|
79
|
+
readonly avatar?: string | null;
|
|
80
|
+
/** Each company's logo version, when it has one. */
|
|
81
|
+
readonly logos?: Readonly<Record<string, string>>;
|
|
77
82
|
}
|
|
78
83
|
export interface AppEntry {
|
|
79
84
|
readonly code: string;
|
|
@@ -186,7 +191,7 @@ export interface KoshApi {
|
|
|
186
191
|
readonly filename: string;
|
|
187
192
|
}>;
|
|
188
193
|
/** Sends a file as the raw body (`Content-Type` from the file, `X-Filename` URI-encoded); the JSON answer. */
|
|
189
|
-
upload<T>(path: string, file: Blob, filename: string): Promise<T>;
|
|
194
|
+
upload<T>(path: string, file: Blob, filename: string, extra?: Readonly<Record<string, string>>): Promise<T>;
|
|
190
195
|
/**
|
|
191
196
|
* POSTs `body` and reads the server-sent events of the answer (`event:` and JSON `data:` lines), calling
|
|
192
197
|
* `onEvent` for each (`message` when the event has no name) until the stream ends. @throws {ApiError} when the
|
package/dist/ui/api.js
CHANGED
|
@@ -226,8 +226,9 @@ export function createKoshApi(options = {}) {
|
|
|
226
226
|
if (buffered.trim() !== '')
|
|
227
227
|
flush(buffered);
|
|
228
228
|
},
|
|
229
|
-
async upload(path, file, filename) {
|
|
229
|
+
async upload(path, file, filename, extra = {}) {
|
|
230
230
|
const headers = {
|
|
231
|
+
...extra,
|
|
231
232
|
'content-type': file.type === '' ? 'application/octet-stream' : file.type,
|
|
232
233
|
'x-filename': encodeURIComponent(filename),
|
|
233
234
|
};
|
package/dist/ui/app.js
CHANGED
|
@@ -12,11 +12,12 @@ import { compileSearch, decodeSearch, encodeSearch } from '../search/index.js';
|
|
|
12
12
|
import { onlineManager, QueryClient, QueryClientProvider, useQuery, useQueryClient, } from '@tanstack/react-query';
|
|
13
13
|
import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client';
|
|
14
14
|
import { createRootRoute, createRoute, createRouter, Link, Navigate, Outlet, RouterProvider, useNavigate, useParams, useSearch, } from '@tanstack/react-router';
|
|
15
|
-
import { CalendarDays, ChartColumn, ChartGantt, ChevronRight, ChevronsUpDown, Columns3, Grid2x2, Languages, LayoutDashboard, List, LogOut, Map as MapIcon, Monitor, Moon, Network, PanelLeft, Plus, Search, Sheet, Sun, Table2, } from 'lucide-react';
|
|
15
|
+
import { CalendarDays, ChartColumn, ChartGantt, ChevronDown, ChevronRight, ChevronsUpDown, Columns3, Grid2x2, House, Languages, LayoutDashboard, List, LogOut, Map as MapIcon, Monitor, Moon, Network, PanelLeft, Plus, Search, Sheet, Sun, Table2, } from 'lucide-react';
|
|
16
16
|
import { Direction } from 'radix-ui';
|
|
17
17
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react';
|
|
18
18
|
import { I18nextProvider, useTranslation } from 'react-i18next';
|
|
19
19
|
import { ApiError } from './api.js';
|
|
20
|
+
import { Avatar, imageUrl } from './components/Avatar.js';
|
|
20
21
|
import { Alert, Button, Menu, MenuCheckboxItem, MenuContent, MenuItem, MenuLabel, MenuSeparator, MenuTrigger, Spinner, } from './components/primitives.js';
|
|
21
22
|
import { KoshProvider, useKosh } from './context.js';
|
|
22
23
|
import { DesignProvider } from './design.js';
|
|
@@ -29,8 +30,10 @@ import { setPrefill, takePrefill } from './prefill.js';
|
|
|
29
30
|
import { onAppUpdate, registerServiceWorker } from './pwa/register.js';
|
|
30
31
|
import { createRealtimeClient } from './realtime.js';
|
|
31
32
|
import { SearchBar } from './search/SearchBar.js';
|
|
33
|
+
import { ViewTabs } from './search/ViewTabs.js';
|
|
32
34
|
import { Activate } from './shell/Activate.js';
|
|
33
35
|
import { CommandPalette, PaletteContext } from './shell/CommandPalette.js';
|
|
36
|
+
import { Consent } from './shell/Consent.js';
|
|
34
37
|
import { Home } from './shell/Home.js';
|
|
35
38
|
import { Login } from './shell/Login.js';
|
|
36
39
|
import { OfflineMenu } from './shell/OfflineMenu.js';
|
|
@@ -142,7 +145,8 @@ function CompanyBlock({ me, company, collapsed, onChange, }) {
|
|
|
142
145
|
enabled: me.companies.length > 0,
|
|
143
146
|
});
|
|
144
147
|
const name = names.data?.find((c) => c.id === company)?.displayName ?? t('app.name');
|
|
145
|
-
|
|
148
|
+
// The company's logo, else its initial.
|
|
149
|
+
const badge = (_jsx(Avatar, { name: name.slice(0, 1), src: imageUrl('company', company, me.logos?.[String(company)]), shape: "square", className: "font-semibold" }));
|
|
146
150
|
if (collapsed)
|
|
147
151
|
return _jsx("span", { title: name, children: badge });
|
|
148
152
|
if (me.companies.length < 2)
|
|
@@ -153,19 +157,16 @@ function CompanyBlock({ me, company, collapsed, onChange, }) {
|
|
|
153
157
|
onChange(c.id);
|
|
154
158
|
}, children: _jsxs("span", { className: "flex items-center gap-2", children: [_jsx("span", { "aria-hidden": true, className: "grid size-5 place-items-center rounded bg-muted text-[10px] font-semibold", children: c.displayName.slice(0, 1).toUpperCase() }), c.displayName] }) }, c.id)))] })] }));
|
|
155
159
|
}
|
|
156
|
-
|
|
157
|
-
function UserMenu({ user, collapsed, }) {
|
|
160
|
+
function UserMenu({ user, collapsed, corner, }) {
|
|
158
161
|
const { t } = useTranslation();
|
|
159
|
-
const { api, registry, persister } = useServices();
|
|
162
|
+
const { api, registry, persister, lang, setLang } = useServices();
|
|
163
|
+
const { theme, setTheme, density, setDensity } = useKosh();
|
|
160
164
|
const queryClient = useQueryClient();
|
|
161
165
|
const navigate = useNavigate();
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
.slice(0, 2)
|
|
167
|
-
.toUpperCase();
|
|
168
|
-
return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": user.name, className: cn('flex w-full min-w-0 items-center gap-2 rounded-md px-1.5 py-1.5 text-sm hover:bg-muted', collapsed && 'justify-center'), children: [_jsx("span", { "aria-hidden": true, className: "grid size-7 shrink-0 place-items-center rounded-full bg-muted text-xs font-medium", children: initials }), collapsed ? null : _jsx("span", { className: "truncate", children: user.name })] }) }), _jsxs(MenuContent, { align: "start", side: "top", children: [_jsx(MenuLabel, { children: user.name }), registry.userPages().map((p) => (_jsx(MenuItem, { onSelect: () => void navigate({ to: '/me/$page', params: { page: p.name } }), children: t(p.label) }, p.name))), _jsxs(MenuItem, { onSelect: async () => {
|
|
166
|
+
return (_jsxs(Menu, { children: [_jsx(MenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", "aria-label": user.name, "data-kosh-account": corner === undefined ? 'sidebar' : 'corner', className: cn('flex min-w-0 items-center gap-2 rounded-md px-1.5 py-1.5 text-sm hover:bg-muted', corner === undefined ? 'w-full' : 'rounded-full border bg-surface pe-3', collapsed && 'justify-center'), children: [_jsx(Avatar, { name: user.name, src: imageUrl('user', user.id, user.avatar) }), collapsed ? null : _jsx("span", { className: "truncate", children: user.name }), corner === undefined ? null : (_jsx(ChevronDown, { className: "size-3.5 text-muted-foreground", "aria-hidden": true }))] }) }), _jsxs(MenuContent, { align: corner === undefined ? 'start' : 'end', side: corner === undefined ? 'top' : 'bottom', className: corner === undefined ? undefined : 'max-h-[80vh] w-64 overflow-y-auto', children: [_jsxs(MenuLabel, { children: [user.name, _jsx("span", { className: "block text-xs font-normal text-muted-foreground", children: user.login })] }), registry.userPages().map((p) => (_jsx(MenuItem, { onSelect: () => void navigate({ to: '/me/$page', params: { page: p.name } }), children: t(p.label) }, p.name))), corner === undefined ? null : (_jsxs(_Fragment, { children: [corner.companies.length < 2 ? null : (_jsxs(_Fragment, { children: [_jsx(MenuSeparator, {}), _jsx(MenuLabel, { children: t('shell.company') }), corner.companies.map((c) => (_jsx(MenuCheckboxItem, { checked: c.id === corner.current, onCheckedChange: () => {
|
|
167
|
+
if (c.id !== corner.current)
|
|
168
|
+
corner.onSwitch(c.id);
|
|
169
|
+
}, children: c.name }, c.id)))] })), _jsx(MenuSeparator, {}), _jsx(MenuLabel, { children: t('shell.language') }), LANGUAGES.map((l) => (_jsx(MenuCheckboxItem, { checked: lang === l.code, onCheckedChange: () => setLang(l.code), children: _jsx("span", { lang: l.code.replace('_', '-'), children: l.name }) }, l.code))), _jsx(MenuSeparator, {}), _jsx(MenuLabel, { children: t('shell.theme') }), ['light', 'dark', 'system'].map((choice) => (_jsx(MenuCheckboxItem, { checked: theme === choice, onCheckedChange: () => setTheme(choice), children: t(`shell.${choice}`) }, choice))), _jsx(MenuLabel, { children: t('shell.density') }), ['comfortable', 'compact'].map((choice) => (_jsx(MenuCheckboxItem, { checked: density === choice, onCheckedChange: () => setDensity(choice), children: t(`views.${choice}`) }, choice)))] })), _jsx(MenuSeparator, {}), _jsxs(MenuItem, { onSelect: async () => {
|
|
169
170
|
await api.logout();
|
|
170
171
|
queryClient.clear();
|
|
171
172
|
await dropOfflineCache(persister);
|
|
@@ -247,11 +248,21 @@ function Shell() {
|
|
|
247
248
|
setDrawer(false);
|
|
248
249
|
setPalette(true);
|
|
249
250
|
}, top: _jsx(CompanyBlock, { me: user, company: current, collapsed: inDrawer ? false : collapsed, onChange: switchCompany }), bottom: _jsx(UserMenu, { user: user, collapsed: inDrawer ? false : collapsed }), onNavigate: inDrawer ? () => setDrawer(false) : undefined }));
|
|
250
|
-
return (_jsx(KoshProvider, { api: api, registry: registry, uid: user.id, companyId: current, lang: lang, tz: Intl.DateTimeFormat().resolvedOptions().timeZone, grouping: user.groupings?.[String(current)] ?? 'international', accent: user.accents?.[String(current)] ?? null, ...(realtime === undefined ? {} : { realtime }), ...(offline === undefined ? {} : { offline }), switchCompany: switchCompany, children: _jsx(ToastProvider, { children: _jsxs(DesignProvider, { children: [_jsx(OfflineAgent, { uid: user.id }), _jsxs(PaletteContext.Provider, { value: openPalette, children: [_jsx("a", { href: "#content", className: "sr-only focus:not-sr-only focus:absolute focus:start-2 focus:top-2 focus:z-50 focus:rounded focus:bg-surface focus:px-3 focus:py-2", children: t('shell.skip') }), _jsxs("div", { className: "flex min-h-screen", children: [
|
|
251
|
+
return (_jsx(KoshProvider, { api: api, registry: registry, uid: user.id, companyId: current, lang: lang, tz: Intl.DateTimeFormat().resolvedOptions().timeZone, grouping: user.groupings?.[String(current)] ?? 'international', accent: user.accents?.[String(current)] ?? null, ...(realtime === undefined ? {} : { realtime }), ...(offline === undefined ? {} : { offline }), switchCompany: switchCompany, children: _jsx(ToastProvider, { children: _jsxs(DesignProvider, { children: [_jsx(OfflineAgent, { uid: user.id }), _jsxs(PaletteContext.Provider, { value: openPalette, children: [_jsx("a", { href: "#content", className: "sr-only focus:not-sr-only focus:absolute focus:start-2 focus:top-2 focus:z-50 focus:rounded focus:bg-surface focus:px-3 focus:py-2", children: t('shell.skip') }), params.app === undefined ? (_jsxs("div", { className: "flex min-h-screen flex-col", children: [_jsxs("div", { className: "flex h-14 items-center gap-2 px-4 lg:px-6", children: [userPage === undefined ? null : (_jsxs(Link, { to: "/", className: "flex items-center gap-1.5 rounded-md px-2 py-1 text-sm text-muted-foreground hover:bg-muted hover:text-foreground", children: [_jsx(House, { className: "size-4", "aria-hidden": true }), t('shell.home')] })), _jsx("span", { className: "flex-1" }), registry
|
|
252
|
+
.headerItems()
|
|
253
|
+
.filter((item) => item.home)
|
|
254
|
+
.map(({ name, component: Item }) => (_jsx(Item, { api: api }, name))), _jsx(UserMenu, { user: user, collapsed: false, corner: {
|
|
255
|
+
companies: (companyNames.data ?? []).map((c) => ({
|
|
256
|
+
id: c.id,
|
|
257
|
+
name: c.displayName,
|
|
258
|
+
})),
|
|
259
|
+
current,
|
|
260
|
+
onSwitch: switchCompany,
|
|
261
|
+
} })] }), user.administratorNeedsSecondFactor ? (_jsx("div", { className: "px-4 pt-3", children: _jsx(Alert, { tone: "info", children: t('auth.adminNeeds2fa') }) })) : null, _jsx("main", { id: "content", className: "mx-auto w-full max-w-[100rem] flex-1 p-4 lg:p-6", children: _jsx(Outlet, {}) })] })) : (_jsxs("div", { className: "flex min-h-screen", children: [_jsx("aside", { "aria-label": t('shell.sidebar'), className: cn('sticky top-0 hidden h-screen shrink-0 border-e bg-surface transition-[width] lg:block', collapsed ? 'w-14' : 'w-60'), children: sidebar(false) }), _jsx(SidebarDrawer, { open: drawer, onOpenChange: setDrawer, children: sidebar(true) }), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col", children: [_jsx("header", { className: "sticky top-0 z-30 border-b bg-surface", children: _jsxs("div", { className: "flex h-12 items-center gap-2 px-3 lg:px-4", children: [_jsx(Button, { variant: "ghost", size: "icon", className: "lg:hidden", "aria-label": t('shell.openMenu'), onClick: () => setDrawer(true), children: _jsx(PanelLeft, { className: "rtl:rotate-180", "aria-hidden": true }) }), _jsx("nav", { "aria-label": t('shell.location'), className: "flex min-w-0 flex-1 items-center gap-1.5 text-sm", children: app === undefined ? (_jsx("span", { className: "font-semibold", children: userPage !== undefined
|
|
251
262
|
? t(userPage.label)
|
|
252
263
|
: params.app === undefined
|
|
253
264
|
? t('shell.home')
|
|
254
|
-
: t('app.name') })) : (_jsxs(_Fragment, { children: [_jsx(Link, { to: "/$app", params: { app: app.code }, className: "font-semibold hover:underline", children: app.label }), section === undefined ? null : (_jsxs(_Fragment, { children: [_jsx(ChevronRight, { className: "size-3.5 text-muted-foreground rtl:rotate-180", "aria-hidden": true }), _jsx(Link, { to: "/$app/a/$action", params: { app: app.code, action: params.action ?? '' }, className: "truncate text-muted-foreground hover:text-foreground hover:underline", children: section })] }))] })) }), _jsx(Button, { variant: "ghost", size: "icon", className: "lg:hidden", "aria-label": t('palette.button'), onClick: () => setPalette(true), children: _jsx(Search, { "aria-hidden": true }) }), _jsx(OfflineMenu, {}), registry.headerItems().map(({ name, component: Item }) => (_jsx(Item, { api: api }, name))), _jsx(LanguageMenu, {}), _jsx(ThemeMenu, {})] }) }), user.administratorNeedsSecondFactor ? (_jsx("div", { className: "px-4 pt-3", children: _jsx(Alert, { tone: "info", children: t('auth.adminNeeds2fa') }) })) : null, _jsx("main", { id: "content", className: "mx-auto w-full max-w-[100rem] flex-1 p-4 lg:p-6", children: _jsx(Outlet, {}) })] })] }), _jsx(CommandPalette, { open: palette, onOpenChange: setPalette, api: api, onShortcuts: openHelp, companies: (companyNames.data ?? [])
|
|
265
|
+
: t('app.name') })) : (_jsxs(_Fragment, { children: [_jsx(Link, { to: "/$app", params: { app: app.code }, className: "font-semibold hover:underline", children: app.label }), section === undefined ? null : (_jsxs(_Fragment, { children: [_jsx(ChevronRight, { className: "size-3.5 text-muted-foreground rtl:rotate-180", "aria-hidden": true }), _jsx(Link, { to: "/$app/a/$action", params: { app: app.code, action: params.action ?? '' }, className: "truncate text-muted-foreground hover:text-foreground hover:underline", children: section })] }))] })) }), _jsx(Button, { variant: "ghost", size: "icon", className: "lg:hidden", "aria-label": t('palette.button'), onClick: () => setPalette(true), children: _jsx(Search, { "aria-hidden": true }) }), _jsx(OfflineMenu, {}), registry.headerItems().map(({ name, component: Item }) => (_jsx(Item, { api: api }, name))), _jsx(LanguageMenu, {}), _jsx(ThemeMenu, {})] }) }), user.administratorNeedsSecondFactor ? (_jsx("div", { className: "px-4 pt-3", children: _jsx(Alert, { tone: "info", children: t('auth.adminNeeds2fa') }) })) : null, _jsx("main", { id: "content", className: "mx-auto w-full max-w-[100rem] flex-1 p-4 lg:p-6", children: _jsx(Outlet, {}) })] })] })), _jsx(CommandPalette, { open: palette, onOpenChange: setPalette, api: api, onShortcuts: openHelp, companies: (companyNames.data ?? [])
|
|
255
266
|
.filter((c) => c.id !== current)
|
|
256
267
|
.map((c) => ({ id: c.id, name: c.displayName })), onSwitchCompany: switchCompany }), _jsx(ShortcutsHelp, { open: help, onOpenChange: setHelp })] })] }) }) }));
|
|
257
268
|
}
|
|
@@ -291,10 +302,11 @@ function useServedView(action, type) {
|
|
|
291
302
|
function ClientPage({ action }) {
|
|
292
303
|
const { registry, api } = useServices();
|
|
293
304
|
const { t } = useTranslation();
|
|
305
|
+
const params = useParams({ strict: false });
|
|
294
306
|
const Page = registry.page(action.page);
|
|
295
307
|
if (Page === undefined)
|
|
296
308
|
return _jsx(Alert, { children: t('app.unknownPage', { page: action.page }) });
|
|
297
|
-
return _jsx(Page, { api: api, ...(action.label === undefined ? {} : { label: action.label }) });
|
|
309
|
+
return (_jsx(Page, { api: api, ...(action.label === undefined ? {} : { label: action.label }), ...(params.app === undefined ? {} : { app: params.app }), ...(params.action === undefined ? {} : { action: params.action }) }));
|
|
298
310
|
}
|
|
299
311
|
function ActionPage() {
|
|
300
312
|
const { action: actionId } = useParams({ strict: false });
|
|
@@ -390,7 +402,7 @@ function WindowActionPage() {
|
|
|
390
402
|
params: { app, action: actionId },
|
|
391
403
|
search: { ...search, view: v },
|
|
392
404
|
}), children: _jsx(Icon, { "aria-hidden": true }) }, v));
|
|
393
|
-
})] })) : null] }), _jsx(SearchBar, { model: model, viewType: type, spec: spec.data, state: state, onChange: setState, labels: labels }), Renderer === undefined ? (_jsx(Alert, { children: `No renderer for '${served.data.view.component ?? type}'.` })) : (_jsx(Renderer, { served: served.data, where: compiled.where, groupBy: compiled.groupBy, order: compiled.order, onOrder: (order) => setState({ ...state, order }), onOpen: open, onDrill: drill }))] }));
|
|
405
|
+
})] })) : null] }), _jsx(ViewTabs, { model: model, viewType: type, state: state, onChange: setState }), _jsx(SearchBar, { model: model, viewType: type, spec: spec.data, state: state, onChange: setState, labels: labels }), Renderer === undefined ? (_jsx(Alert, { children: `No renderer for '${served.data.view.component ?? type}'.` })) : (_jsx(Renderer, { served: served.data, where: compiled.where, groupBy: compiled.groupBy, order: compiled.order, onOrder: (order) => setState({ ...state, order }), onOpen: open, onDrill: drill }))] }));
|
|
394
406
|
}
|
|
395
407
|
/** What the record page needs from the shell: the API, the user, the apps (for breadcrumbs). */
|
|
396
408
|
function useRecordContext() {
|
|
@@ -481,9 +493,24 @@ function ActivatePage() {
|
|
|
481
493
|
const navigate = useNavigate();
|
|
482
494
|
return _jsx(Activate, { api: useServices().api, onSignedIn: () => void navigate({ to: '/' }) });
|
|
483
495
|
}
|
|
496
|
+
/** Where to go after signing in: `?next=` when it is a path of this site (never another site), else home. */
|
|
497
|
+
function nextPath() {
|
|
498
|
+
const next = new URLSearchParams(window.location.search).get('next') ?? '';
|
|
499
|
+
return next.startsWith('/') && !next.startsWith('//') && !next.startsWith('/\\') ? next : '/';
|
|
500
|
+
}
|
|
484
501
|
function LoginPage() {
|
|
485
502
|
const navigate = useNavigate();
|
|
486
|
-
return _jsx(Login, { api: useServices().api, onSignedIn: () =>
|
|
503
|
+
return (_jsx(Login, { api: useServices().api, onSignedIn: () => {
|
|
504
|
+
const next = nextPath();
|
|
505
|
+
// A page outside the router's own paths with its query (the OAuth consent) loads as an address.
|
|
506
|
+
if (next.startsWith('/oauth/'))
|
|
507
|
+
window.location.assign(next);
|
|
508
|
+
else
|
|
509
|
+
void navigate({ to: next });
|
|
510
|
+
} }));
|
|
511
|
+
}
|
|
512
|
+
function ConsentPage() {
|
|
513
|
+
return _jsx(Consent, { api: useServices().api });
|
|
487
514
|
}
|
|
488
515
|
// ------------------------------------------------------------------------------------------------- router
|
|
489
516
|
const rootRoute = createRootRoute({ component: Outlet });
|
|
@@ -497,6 +524,12 @@ const activateRoute = createRoute({
|
|
|
497
524
|
path: '/activate',
|
|
498
525
|
component: ActivatePage,
|
|
499
526
|
});
|
|
527
|
+
/** The OAuth consent page (RFC 0017 §5.2): outside the shell, like sign-in. */
|
|
528
|
+
const consentRoute = createRoute({
|
|
529
|
+
getParentRoute: () => rootRoute,
|
|
530
|
+
path: '/oauth/authorize',
|
|
531
|
+
component: ConsentPage,
|
|
532
|
+
});
|
|
500
533
|
const shellRoute = createRoute({ getParentRoute: () => rootRoute, id: 'shell', component: Shell });
|
|
501
534
|
const indexRoute = createRoute({
|
|
502
535
|
getParentRoute: () => shellRoute,
|
|
@@ -546,6 +579,7 @@ const userPageRoute = createRoute({
|
|
|
546
579
|
const routeTree = rootRoute.addChildren([
|
|
547
580
|
loginRoute,
|
|
548
581
|
activateRoute,
|
|
582
|
+
consentRoute,
|
|
549
583
|
shellRoute.addChildren([
|
|
550
584
|
indexRoute,
|
|
551
585
|
openRoute,
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
3
3
|
* App icons by name (`app.icon` in a module manifest): the launcher's tiles and the App manager's cards. Names
|
|
4
4
|
* follow Lucide; an unknown name shows a neutral grid, never a broken image.
|
|
5
5
|
*/
|
|
6
|
-
import { BookOpen, Boxes, Briefcase, ChartColumn, FolderKanban, LayoutGrid, Mail, MessageSquare, MessagesSquare, Package, PencilRuler, Receipt, Ruler, Settings, ShieldCheck, ShoppingCart, Users, Wallet, Warehouse, Workflow, } from 'lucide-react';
|
|
6
|
+
import { BookOpen, Boxes, Briefcase, Building, Building2, CalendarDays, ChartColumn, Factory, FolderKanban, Globe, Landmark, LayoutGrid, ListChecks, LockKeyhole, Mail, MessageSquare, MessagesSquare, Package, PencilRuler, Plug, Receipt, Ruler, ScrollText, Settings, ShieldCheck, ShoppingCart, Sparkles, Store, Ticket, TrendingUp, UserPlus, Users, Wallet, Warehouse, Workflow, Wrench, } from 'lucide-react';
|
|
7
7
|
import { cn } from '../lib/cn.js';
|
|
8
8
|
const ICONS = {
|
|
9
9
|
settings: Settings,
|
|
@@ -37,6 +37,38 @@ const ICONS = {
|
|
|
37
37
|
shield: ShieldCheck,
|
|
38
38
|
permissions: ShieldCheck,
|
|
39
39
|
briefcase: Briefcase,
|
|
40
|
+
plug: Plug,
|
|
41
|
+
focus: ListChecks,
|
|
42
|
+
agenda: CalendarDays,
|
|
43
|
+
pipeline: TrendingUp,
|
|
44
|
+
workforce: Briefcase,
|
|
45
|
+
hiring: UserPlus,
|
|
46
|
+
stockroom: Warehouse,
|
|
47
|
+
production: Factory,
|
|
48
|
+
assetcare: Wrench,
|
|
49
|
+
tradefinance: Landmark,
|
|
50
|
+
eventhub: Ticket,
|
|
51
|
+
sites: Globe,
|
|
52
|
+
counter: Store,
|
|
53
|
+
event: Ticket,
|
|
54
|
+
trade_finance: Landmark,
|
|
55
|
+
maintenance: Wrench,
|
|
56
|
+
mrp: Factory,
|
|
57
|
+
stock: Warehouse,
|
|
58
|
+
recruitment: UserPlus,
|
|
59
|
+
hr: Briefcase,
|
|
60
|
+
crm: TrendingUp,
|
|
61
|
+
calendar: CalendarDays,
|
|
62
|
+
todo: ListChecks,
|
|
63
|
+
integrations: Plug,
|
|
64
|
+
cart: ShoppingCart,
|
|
65
|
+
company: Building2,
|
|
66
|
+
companies: Building,
|
|
67
|
+
security: LockKeyhole,
|
|
68
|
+
audit: ScrollText,
|
|
69
|
+
apps: LayoutGrid,
|
|
70
|
+
assistant: Sparkles,
|
|
71
|
+
sparkles: Sparkles,
|
|
40
72
|
};
|
|
41
73
|
/** A tinted square with the app's icon (decorative: the app's name is always shown next to it). */
|
|
42
74
|
export function AppIcon({ name, className, }) {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ReactElement } from 'react';
|
|
2
|
+
/** The URL of a photo (`user`) or logo (`company`), or null when there is none. */
|
|
3
|
+
export declare function imageUrl(kind: 'user' | 'company', id: number, version: string | null | undefined): string | null;
|
|
4
|
+
/** Up to two initials of a name. */
|
|
5
|
+
export declare function initialsOf(name: string): string;
|
|
6
|
+
export declare function Avatar({ name, src, shape, className, }: {
|
|
7
|
+
readonly name: string;
|
|
8
|
+
readonly src: string | null;
|
|
9
|
+
/** People are round; companies square. */
|
|
10
|
+
readonly shape?: 'circle' | 'square';
|
|
11
|
+
readonly className?: string;
|
|
12
|
+
}): ReactElement;
|
|
13
|
+
export declare function ImagePicker({ name, src, fit, onChoose, onRemove, busy, }: {
|
|
14
|
+
readonly name: string;
|
|
15
|
+
readonly src: string | null;
|
|
16
|
+
readonly fit: 'photo' | 'logo';
|
|
17
|
+
/** The image as a `data:` URL, already small. */
|
|
18
|
+
readonly onChoose: (dataUrl: string) => void;
|
|
19
|
+
readonly onRemove: () => void;
|
|
20
|
+
readonly busy?: boolean;
|
|
21
|
+
}): ReactElement;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Photos and logos: `Avatar` shows a person's photo or a company's logo (the server's image route, versioned so the
|
|
4
|
+
* browser caches it), else the initials on a tint. `ImagePicker` chooses a file and makes it small in the browser —
|
|
5
|
+
* a 256 px square, cropped to the centre for photos, fitted whole for logos — before anything is sent.
|
|
6
|
+
*/
|
|
7
|
+
import { ImageUp, Trash2 } from 'lucide-react';
|
|
8
|
+
import { useId, useRef, useState } from 'react';
|
|
9
|
+
import { useTranslation } from 'react-i18next';
|
|
10
|
+
import { cn } from '../lib/cn.js';
|
|
11
|
+
import { Button } from './primitives.js';
|
|
12
|
+
/** The URL of a photo (`user`) or logo (`company`), or null when there is none. */
|
|
13
|
+
export function imageUrl(kind, id, version) {
|
|
14
|
+
return version === null || version === undefined
|
|
15
|
+
? null
|
|
16
|
+
: `/api/v1/images/${kind}/${id}?v=${version}`;
|
|
17
|
+
}
|
|
18
|
+
/** Up to two initials of a name. */
|
|
19
|
+
export function initialsOf(name) {
|
|
20
|
+
return name
|
|
21
|
+
.split(/\s+/)
|
|
22
|
+
.filter((w) => w !== '')
|
|
23
|
+
.map((w) => w.slice(0, 1))
|
|
24
|
+
.join('')
|
|
25
|
+
.slice(0, 2)
|
|
26
|
+
.toUpperCase();
|
|
27
|
+
}
|
|
28
|
+
export function Avatar({ name, src, shape = 'circle', className, }) {
|
|
29
|
+
const [failed, setFailed] = useState(false);
|
|
30
|
+
const round = shape === 'circle' ? 'rounded-full' : 'rounded-md';
|
|
31
|
+
if (src !== null && !failed)
|
|
32
|
+
return (_jsx("img", { src: src, alt: "", "aria-hidden": true, onError: () => setFailed(true), className: cn('size-7 shrink-0 bg-surface object-cover', shape === 'square' && 'object-contain', round, className) }));
|
|
33
|
+
return (_jsx("span", { "aria-hidden": true, className: cn('grid size-7 shrink-0 place-items-center bg-primary text-xs font-medium text-primary-foreground', round, className), children: initialsOf(name) }));
|
|
34
|
+
}
|
|
35
|
+
const SIZE = 256;
|
|
36
|
+
/** Draws `file` on a 256 px square: covered and centred (`photo`) or fitted whole (`logo`). */
|
|
37
|
+
async function shrink(file, fit) {
|
|
38
|
+
const bitmap = await createImageBitmap(file);
|
|
39
|
+
const canvas = document.createElement('canvas');
|
|
40
|
+
canvas.width = SIZE;
|
|
41
|
+
canvas.height = SIZE;
|
|
42
|
+
const ctx = canvas.getContext('2d');
|
|
43
|
+
if (ctx === null)
|
|
44
|
+
throw new Error('No canvas');
|
|
45
|
+
const scale = fit === 'photo'
|
|
46
|
+
? Math.max(SIZE / bitmap.width, SIZE / bitmap.height)
|
|
47
|
+
: Math.min(SIZE / bitmap.width, SIZE / bitmap.height);
|
|
48
|
+
const w = bitmap.width * scale;
|
|
49
|
+
const h = bitmap.height * scale;
|
|
50
|
+
ctx.imageSmoothingQuality = 'high';
|
|
51
|
+
ctx.drawImage(bitmap, (SIZE - w) / 2, (SIZE - h) / 2, w, h);
|
|
52
|
+
bitmap.close();
|
|
53
|
+
// Photos as WebP (small); logos as PNG (they keep transparency and sharp edges).
|
|
54
|
+
return fit === 'photo' ? canvas.toDataURL('image/webp', 0.86) : canvas.toDataURL('image/png');
|
|
55
|
+
}
|
|
56
|
+
export function ImagePicker({ name, src, fit, onChoose, onRemove, busy, }) {
|
|
57
|
+
const { t } = useTranslation();
|
|
58
|
+
const id = useId();
|
|
59
|
+
const input = useRef(null);
|
|
60
|
+
const [error, setError] = useState(null);
|
|
61
|
+
return (_jsxs("div", { className: "flex flex-wrap items-center gap-4", children: [_jsx(Avatar, { name: name, src: src, shape: fit === 'photo' ? 'circle' : 'square', className: "size-16 text-lg" }), _jsxs("div", { className: "grid gap-1.5", children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx("input", { ref: input, id: id, type: "file", accept: "image/png,image/jpeg,image/webp", className: "sr-only", onChange: (e) => {
|
|
62
|
+
const file = e.target.files?.[0];
|
|
63
|
+
e.target.value = '';
|
|
64
|
+
if (file === undefined)
|
|
65
|
+
return;
|
|
66
|
+
setError(null);
|
|
67
|
+
shrink(file, fit)
|
|
68
|
+
.then(onChoose)
|
|
69
|
+
.catch(() => setError(t('image.unreadable')));
|
|
70
|
+
} }), _jsxs(Button, { size: "sm", busy: busy === true, onClick: () => input.current?.click(), children: [_jsx(ImageUp, { "aria-hidden": true }), fit === 'photo' ? t('image.choosePhoto') : t('image.chooseLogo')] }), src === null ? null : (_jsxs(Button, { size: "sm", variant: "ghost", onClick: onRemove, children: [_jsx(Trash2, { "aria-hidden": true }), t('image.remove')] }))] }), _jsx("p", { className: "text-xs text-muted-foreground", children: error ?? t('image.hint') })] })] }));
|
|
71
|
+
}
|
|
@@ -76,3 +76,5 @@ export declare function EmptyState({ icon, title, description, action, }: {
|
|
|
76
76
|
readonly description?: string;
|
|
77
77
|
readonly action?: ReactNode;
|
|
78
78
|
}): ReactElement;
|
|
79
|
+
/** The classes of a button, for elements that must look like one (a file picker's label). */
|
|
80
|
+
export declare function buttonClass(variant?: ButtonProps['variant'], size?: ButtonProps['size']): string;
|
|
@@ -23,7 +23,9 @@ const buttonVariants = cva('inline-flex items-center justify-center gap-2 whites
|
|
|
23
23
|
});
|
|
24
24
|
export function Button({ className, variant, size, asChild, busy, children, disabled, ...props }) {
|
|
25
25
|
const Comp = asChild === true ? Slot.Root : 'button';
|
|
26
|
-
return (_jsxs(Comp
|
|
26
|
+
return (_jsxs(Comp
|
|
27
|
+
// A button inside a record's form must not save it: only `type="submit"` submits.
|
|
28
|
+
, { ...(asChild === true ? {} : { type: 'button' }), className: cn(buttonVariants({ variant, size }), className), disabled: disabled === true || busy === true, "aria-busy": busy === true ? true : undefined, ...props, children: [busy === true ? _jsx(Loader2, { className: "animate-spin", "aria-hidden": true }) : null, children] }));
|
|
27
29
|
}
|
|
28
30
|
const field = 'w-full rounded-md border border-input bg-surface px-3 text-sm text-surface-foreground placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-60 read-only:bg-muted aria-invalid:border-danger';
|
|
29
31
|
export function Input({ className, ...props }) {
|
|
@@ -120,3 +122,7 @@ export function Skeleton({ className, ...props }) {
|
|
|
120
122
|
export function EmptyState({ icon, title, description, action, }) {
|
|
121
123
|
return (_jsxs("div", { className: "flex flex-col items-center gap-2 px-6 py-12 text-center", children: [icon === undefined ? null : (_jsx("div", { className: "mb-1 flex size-12 items-center justify-center rounded-full bg-muted text-muted-foreground [&_svg]:size-6", children: icon })), _jsx("p", { className: "font-medium", children: title }), description === undefined ? null : (_jsx("p", { className: "max-w-md text-sm text-muted-foreground", children: description })), action === undefined ? null : _jsx("div", { className: "mt-2", children: action })] }));
|
|
122
124
|
}
|
|
125
|
+
/** The classes of a button, for elements that must look like one (a file picker's label). */
|
|
126
|
+
export function buttonClass(variant = 'primary', size = 'md') {
|
|
127
|
+
return buttonVariants({ variant, size });
|
|
128
|
+
}
|
|
@@ -88,8 +88,12 @@ export declare const en: {
|
|
|
88
88
|
export: string;
|
|
89
89
|
exportCsv: string;
|
|
90
90
|
exportXlsx: string;
|
|
91
|
+
exportPdf: string;
|
|
91
92
|
exportFailed: string;
|
|
92
93
|
columns: string;
|
|
94
|
+
pinned: string;
|
|
95
|
+
resetWidths: string;
|
|
96
|
+
resize: string;
|
|
93
97
|
density: string;
|
|
94
98
|
statusOf: string;
|
|
95
99
|
nextState: string;
|
|
@@ -113,6 +117,7 @@ export declare const en: {
|
|
|
113
117
|
compact: string;
|
|
114
118
|
comfortable: string;
|
|
115
119
|
emptyTitle: string;
|
|
120
|
+
emptyNamed: string;
|
|
116
121
|
emptyHint: string;
|
|
117
122
|
emptyFiltered: string;
|
|
118
123
|
moved: string;
|
|
@@ -166,6 +171,23 @@ export declare const en: {
|
|
|
166
171
|
none: string;
|
|
167
172
|
lines: string;
|
|
168
173
|
addLine: string;
|
|
174
|
+
nextStep: {
|
|
175
|
+
ready: string;
|
|
176
|
+
before: string;
|
|
177
|
+
missing_one: string;
|
|
178
|
+
missing_other: string;
|
|
179
|
+
};
|
|
180
|
+
daysLeft: {
|
|
181
|
+
today: string;
|
|
182
|
+
in: string;
|
|
183
|
+
late: string;
|
|
184
|
+
};
|
|
185
|
+
image: {
|
|
186
|
+
choose: string;
|
|
187
|
+
remove: string;
|
|
188
|
+
none: string;
|
|
189
|
+
saveFirst: string;
|
|
190
|
+
};
|
|
169
191
|
removeLine: string;
|
|
170
192
|
choose: string;
|
|
171
193
|
pasted_one: string;
|
|
@@ -234,11 +256,37 @@ export declare const en: {
|
|
|
234
256
|
done: string;
|
|
235
257
|
recent: string;
|
|
236
258
|
apps: string;
|
|
259
|
+
today: string;
|
|
260
|
+
approve: string;
|
|
261
|
+
review: string;
|
|
262
|
+
dueToday: string;
|
|
263
|
+
todo: string;
|
|
264
|
+
meetings: string;
|
|
265
|
+
mentioned: string;
|
|
266
|
+
saved: string;
|
|
237
267
|
};
|
|
238
268
|
toast: {
|
|
239
269
|
region: string;
|
|
240
270
|
close: string;
|
|
241
271
|
};
|
|
272
|
+
image: {
|
|
273
|
+
choosePhoto: string;
|
|
274
|
+
chooseLogo: string;
|
|
275
|
+
remove: string;
|
|
276
|
+
hint: string;
|
|
277
|
+
unreadable: string;
|
|
278
|
+
};
|
|
279
|
+
oauth: {
|
|
280
|
+
title: string;
|
|
281
|
+
as: string;
|
|
282
|
+
may: string;
|
|
283
|
+
limits: string;
|
|
284
|
+
back: string;
|
|
285
|
+
homepage: string;
|
|
286
|
+
already: string;
|
|
287
|
+
allow: string;
|
|
288
|
+
cancel: string;
|
|
289
|
+
};
|
|
242
290
|
palette: {
|
|
243
291
|
switchCompany: string;
|
|
244
292
|
profile: string;
|
|
@@ -292,6 +340,11 @@ export declare const en: {
|
|
|
292
340
|
custom: string;
|
|
293
341
|
remove: string;
|
|
294
342
|
or: string;
|
|
343
|
+
savedViews: string;
|
|
344
|
+
allRecords: string;
|
|
345
|
+
moreViews_one: string;
|
|
346
|
+
moreViews_other: string;
|
|
347
|
+
shared: string;
|
|
295
348
|
};
|
|
296
349
|
};
|
|
297
350
|
type Messages = typeof en;
|