@open-mercato/core 0.6.6-develop.6094.1.28b081ea16 → 0.6.6-develop.6126.1.0b9392fdae
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/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/catalog_product/index.js +52 -0
- package/dist/generated/entities/catalog_product/index.js.map +2 -2
- package/dist/generated/entities/catalog_product_variant/index.js +4 -0
- package/dist/generated/entities/catalog_product_variant/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +28 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/api/products/route.js +103 -33
- package/dist/modules/catalog/api/products/route.js.map +2 -2
- package/dist/modules/catalog/api/variants/route.js +4 -0
- package/dist/modules/catalog/api/variants/route.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js +46 -2
- package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +4 -0
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js +2 -0
- package/dist/modules/catalog/backend/catalog/products/[productId]/variants/create/page.js.map +2 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js +42 -2
- package/dist/modules/catalog/backend/catalog/products/create/page.js.map +2 -2
- package/dist/modules/catalog/commands/products.js +251 -14
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/catalog/commands/variants.js +72 -1
- package/dist/modules/catalog/commands/variants.js.map +2 -2
- package/dist/modules/catalog/components/products/ProductComplianceSection.js +500 -0
- package/dist/modules/catalog/components/products/ProductComplianceSection.js.map +7 -0
- package/dist/modules/catalog/components/products/VariantBuilder.js +39 -1
- package/dist/modules/catalog/components/products/VariantBuilder.js.map +2 -2
- package/dist/modules/catalog/components/products/productForm.js +164 -2
- package/dist/modules/catalog/components/products/productForm.js.map +2 -2
- package/dist/modules/catalog/components/products/variantForm.js +2 -0
- package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
- package/dist/modules/catalog/data/entities.js +93 -0
- package/dist/modules/catalog/data/entities.js.map +2 -2
- package/dist/modules/catalog/data/types.js +28 -0
- package/dist/modules/catalog/data/types.js.map +2 -2
- package/dist/modules/catalog/data/validators.js +100 -5
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/lib/gtin.js +61 -0
- package/dist/modules/catalog/lib/gtin.js.map +7 -0
- package/dist/modules/catalog/migrations/Migration20260611090000.js +17 -0
- package/dist/modules/catalog/migrations/Migration20260611090000.js.map +7 -0
- package/dist/modules/catalog/translations.js +1 -1
- package/dist/modules/catalog/translations.js.map +2 -2
- package/dist/modules/configs/api/cache/route.js +60 -10
- package/dist/modules/configs/api/cache/route.js.map +3 -3
- package/dist/modules/configs/components/CachePanel.js +52 -29
- package/dist/modules/configs/components/CachePanel.js.map +2 -2
- package/dist/modules/currencies/api/currencies/options/route.js +18 -4
- package/dist/modules/currencies/api/currencies/options/route.js.map +2 -2
- package/dist/modules/currencies/services/rateFetchingService.js +35 -18
- package/dist/modules/currencies/services/rateFetchingService.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +16 -16
- package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +1 -1
- package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
- package/dist/modules/customers/backend/calendar/page.js +10 -0
- package/dist/modules/customers/backend/calendar/page.js.map +7 -0
- package/dist/modules/customers/backend/calendar/page.meta.js +25 -0
- package/dist/modules/customers/backend/calendar/page.meta.js.map +7 -0
- package/dist/modules/customers/cli.js +1 -0
- package/dist/modules/customers/cli.js.map +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js +182 -0
- package/dist/modules/customers/components/calendar/AgendaList.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +331 -0
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarFooter.js +48 -0
- package/dist/modules/customers/components/calendar/CalendarFooter.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarHeader.js +36 -0
- package/dist/modules/customers/components/calendar/CalendarHeader.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarScreen.js +516 -0
- package/dist/modules/customers/components/calendar/CalendarScreen.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +163 -0
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarTabs.js +60 -0
- package/dist/modules/customers/components/calendar/CalendarTabs.js.map +7 -0
- package/dist/modules/customers/components/calendar/CalendarToolbar.js +262 -0
- package/dist/modules/customers/components/calendar/CalendarToolbar.js.map +7 -0
- package/dist/modules/customers/components/calendar/EventBlock.js +149 -0
- package/dist/modules/customers/components/calendar/EventBlock.js.map +7 -0
- package/dist/modules/customers/components/calendar/EventPeekPopover.js +72 -0
- package/dist/modules/customers/components/calendar/EventPeekPopover.js.map +7 -0
- package/dist/modules/customers/components/calendar/MonthGrid.js +214 -0
- package/dist/modules/customers/components/calendar/MonthGrid.js.map +7 -0
- package/dist/modules/customers/components/calendar/ShortcutsDialog.js +44 -0
- package/dist/modules/customers/components/calendar/ShortcutsDialog.js.map +7 -0
- package/dist/modules/customers/components/calendar/TimeGrid.js +410 -0
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +7 -0
- package/dist/modules/customers/components/calendar/UpcomingCards.js +175 -0
- package/dist/modules/customers/components/calendar/UpcomingCards.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/CategoryField.js +75 -0
- package/dist/modules/customers/components/calendar/editor/CategoryField.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js +30 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +135 -0
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js +218 -0
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/RepeatField.js +119 -0
- package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +83 -0
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +44 -0
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/hooks.js +132 -0
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/inputs.js +160 -0
- package/dist/modules/customers/components/calendar/editor/inputs.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/lookups.js +107 -0
- package/dist/modules/customers/components/calendar/editor/lookups.js.map +7 -0
- package/dist/modules/customers/components/calendar/types.js +30 -0
- package/dist/modules/customers/components/calendar/types.js.map +7 -0
- package/dist/modules/customers/components/calendar/useCalendarItems.js +131 -0
- package/dist/modules/customers/components/calendar/useCalendarItems.js.map +7 -0
- package/dist/modules/customers/components/calendar/useCalendarPreferences.js +41 -0
- package/dist/modules/customers/components/calendar/useCalendarPreferences.js.map +7 -0
- package/dist/modules/customers/lib/calendar/categories.js +28 -0
- package/dist/modules/customers/lib/calendar/categories.js.map +7 -0
- package/dist/modules/customers/lib/calendar/conflicts.js +72 -0
- package/dist/modules/customers/lib/calendar/conflicts.js.map +7 -0
- package/dist/modules/customers/lib/calendar/editorPayload.js +271 -0
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +7 -0
- package/dist/modules/customers/lib/calendar/grid.js +48 -0
- package/dist/modules/customers/lib/calendar/grid.js.map +7 -0
- package/dist/modules/customers/lib/calendar/layout.js +39 -0
- package/dist/modules/customers/lib/calendar/layout.js.map +7 -0
- package/dist/modules/customers/lib/calendar/mapItem.js +79 -0
- package/dist/modules/customers/lib/calendar/mapItem.js.map +7 -0
- package/dist/modules/customers/lib/calendar/preferences.js +71 -0
- package/dist/modules/customers/lib/calendar/preferences.js.map +7 -0
- package/dist/modules/customers/lib/calendar/range.js +46 -0
- package/dist/modules/customers/lib/calendar/range.js.map +7 -0
- package/dist/modules/customers/lib/calendar/recurrence.js +113 -0
- package/dist/modules/customers/lib/calendar/recurrence.js.map +7 -0
- package/dist/modules/dashboards/services/widgetDataService.js +5 -5
- package/dist/modules/dashboards/services/widgetDataService.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +61 -1
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js +33 -1
- package/dist/modules/dictionaries/api/[dictionaryId]/entries/route.js.map +2 -2
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js +61 -1
- package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
- package/dist/modules/dictionaries/api/route.js +33 -1
- package/dist/modules/dictionaries/api/route.js.map +2 -2
- package/dist/modules/feature_toggles/lib/queries.js +36 -52
- package/dist/modules/feature_toggles/lib/queries.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js +32 -1
- package/dist/modules/perspectives/api/[tableId]/[perspectiveId]/route.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js +32 -1
- package/dist/modules/perspectives/api/[tableId]/roles/[roleId]/route.js.map +2 -2
- package/dist/modules/perspectives/api/[tableId]/route.js +73 -2
- package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
- package/dist/modules/perspectives/services/perspectiveService.js +5 -3
- package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
- package/dist/modules/planner/api/access.js +9 -0
- package/dist/modules/planner/api/access.js.map +2 -2
- package/dist/modules/planner/api/availability-date-specific.js +22 -1
- package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/api/availability-weekly.js +22 -1
- package/dist/modules/planner/api/availability-weekly.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/assign/route.js +39 -0
- package/dist/modules/resources/api/resources/tags/assign/route.js.map +2 -2
- package/dist/modules/resources/api/resources/tags/unassign/route.js +39 -0
- package/dist/modules/resources/api/resources/tags/unassign/route.js.map +2 -2
- package/dist/modules/sales/api/settings/document-numbers/route.js +33 -1
- package/dist/modules/sales/api/settings/document-numbers/route.js.map +2 -2
- package/dist/modules/sales/api/settings/order-editing/route.js +35 -3
- package/dist/modules/sales/api/settings/order-editing/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/accept/route.js +41 -0
- package/dist/modules/staff/api/leave-requests/accept/route.js.map +2 -2
- package/dist/modules/staff/api/leave-requests/reject/route.js +41 -0
- package/dist/modules/staff/api/leave-requests/reject/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/self/route.js +40 -0
- package/dist/modules/staff/api/team-members/self/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/assign/route.js +41 -0
- package/dist/modules/staff/api/team-members/tags/assign/route.js.map +2 -2
- package/dist/modules/staff/api/team-members/tags/unassign/route.js +41 -0
- package/dist/modules/staff/api/team-members/tags/unassign/route.js.map +2 -2
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js +61 -1
- package/dist/modules/translations/api/[entityType]/[entityId]/route.js.map +2 -2
- package/dist/modules/translations/api/context.js +7 -0
- package/dist/modules/translations/api/context.js.map +2 -2
- package/dist/modules/translations/api/put/locales.js +33 -1
- package/dist/modules/translations/api/put/locales.js.map +2 -2
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js +6 -7
- package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
- package/dist/modules/workflows/backend/instances/[id]/page.js.map +1 -1
- package/dist/modules/workflows/components/EdgeEditDialog.js.map +1 -1
- package/dist/modules/workflows/components/EdgeEditDialogCrudForm.js.map +1 -1
- package/dist/modules/workflows/components/NodeEditDialog.js.map +1 -1
- package/dist/modules/workflows/components/NodeEditDialogCrudForm.js.map +1 -1
- package/dist/modules/workflows/components/WorkflowGraph.js.map +1 -1
- package/dist/modules/workflows/components/WorkflowGraphImpl.js +17 -9
- package/dist/modules/workflows/components/WorkflowGraphImpl.js.map +2 -2
- package/dist/modules/workflows/components/mobile/MobileVisualEditor.js.map +1 -1
- package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js +8 -0
- package/dist/modules/workflows/frontend/checkout-demo/deriveCurrentStep.js.map +7 -0
- package/dist/modules/workflows/frontend/checkout-demo/page.js +2 -20
- package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
- package/dist/modules/workflows/lib/graph-utils.js +8 -0
- package/dist/modules/workflows/lib/graph-utils.js.map +2 -2
- package/generated/entities/catalog_product/index.ts +26 -0
- package/generated/entities/catalog_product_variant/index.ts +2 -0
- package/generated/entity-fields-registry.ts +28 -0
- package/package.json +7 -7
- package/src/modules/catalog/api/products/route.ts +123 -39
- package/src/modules/catalog/api/variants/route.ts +4 -0
- package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +43 -0
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +14 -0
- package/src/modules/catalog/backend/catalog/products/[productId]/variants/create/page.tsx +2 -0
- package/src/modules/catalog/backend/catalog/products/create/page.tsx +41 -0
- package/src/modules/catalog/commands/products.ts +307 -2
- package/src/modules/catalog/commands/variants.ts +90 -1
- package/src/modules/catalog/components/products/ProductComplianceSection.tsx +497 -0
- package/src/modules/catalog/components/products/VariantBuilder.tsx +43 -0
- package/src/modules/catalog/components/products/productForm.ts +262 -0
- package/src/modules/catalog/components/products/variantForm.ts +4 -0
- package/src/modules/catalog/data/entities.ts +97 -0
- package/src/modules/catalog/data/types.ts +36 -0
- package/src/modules/catalog/data/validators.ts +159 -3
- package/src/modules/catalog/i18n/de.json +64 -0
- package/src/modules/catalog/i18n/en.json +64 -0
- package/src/modules/catalog/i18n/es.json +64 -0
- package/src/modules/catalog/i18n/pl.json +64 -0
- package/src/modules/catalog/lib/gtin.ts +66 -0
- package/src/modules/catalog/migrations/.snapshot-open-mercato.json +457 -0
- package/src/modules/catalog/migrations/Migration20260611090000.ts +17 -0
- package/src/modules/catalog/translations.ts +1 -1
- package/src/modules/configs/api/cache/route.ts +72 -10
- package/src/modules/configs/components/CachePanel.tsx +59 -29
- package/src/modules/currencies/api/currencies/options/route.ts +22 -4
- package/src/modules/currencies/services/rateFetchingService.ts +63 -26
- package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +18 -18
- package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +1 -1
- package/src/modules/customer_accounts/i18n/de.json +1 -1
- package/src/modules/customer_accounts/i18n/en.json +1 -1
- package/src/modules/customer_accounts/i18n/es.json +1 -1
- package/src/modules/customer_accounts/i18n/pl.json +1 -1
- package/src/modules/customers/backend/calendar/page.meta.ts +23 -0
- package/src/modules/customers/backend/calendar/page.tsx +12 -0
- package/src/modules/customers/cli.ts +1 -0
- package/src/modules/customers/components/calendar/AgendaList.tsx +219 -0
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +386 -0
- package/src/modules/customers/components/calendar/CalendarFooter.tsx +61 -0
- package/src/modules/customers/components/calendar/CalendarHeader.tsx +40 -0
- package/src/modules/customers/components/calendar/CalendarScreen.tsx +590 -0
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +207 -0
- package/src/modules/customers/components/calendar/CalendarTabs.tsx +63 -0
- package/src/modules/customers/components/calendar/CalendarToolbar.tsx +295 -0
- package/src/modules/customers/components/calendar/EventBlock.tsx +205 -0
- package/src/modules/customers/components/calendar/EventPeekPopover.tsx +100 -0
- package/src/modules/customers/components/calendar/MonthGrid.tsx +236 -0
- package/src/modules/customers/components/calendar/ShortcutsDialog.tsx +57 -0
- package/src/modules/customers/components/calendar/TimeGrid.tsx +510 -0
- package/src/modules/customers/components/calendar/UpcomingCards.tsx +220 -0
- package/src/modules/customers/components/calendar/editor/CategoryField.tsx +79 -0
- package/src/modules/customers/components/calendar/editor/LocationField.tsx +37 -0
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +148 -0
- package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +242 -0
- package/src/modules/customers/components/calendar/editor/RepeatField.tsx +132 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +110 -0
- package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +50 -0
- package/src/modules/customers/components/calendar/editor/hooks.ts +162 -0
- package/src/modules/customers/components/calendar/editor/inputs.tsx +158 -0
- package/src/modules/customers/components/calendar/editor/lookups.ts +160 -0
- package/src/modules/customers/components/calendar/types.ts +163 -0
- package/src/modules/customers/components/calendar/useCalendarItems.ts +162 -0
- package/src/modules/customers/components/calendar/useCalendarPreferences.ts +52 -0
- package/src/modules/customers/i18n/de.json +195 -0
- package/src/modules/customers/i18n/en.json +195 -0
- package/src/modules/customers/i18n/es.json +195 -0
- package/src/modules/customers/i18n/pl.json +195 -0
- package/src/modules/customers/lib/calendar/categories.ts +26 -0
- package/src/modules/customers/lib/calendar/conflicts.ts +117 -0
- package/src/modules/customers/lib/calendar/editorPayload.ts +388 -0
- package/src/modules/customers/lib/calendar/grid.ts +50 -0
- package/src/modules/customers/lib/calendar/layout.ts +47 -0
- package/src/modules/customers/lib/calendar/mapItem.ts +94 -0
- package/src/modules/customers/lib/calendar/preferences.ts +95 -0
- package/src/modules/customers/lib/calendar/range.ts +45 -0
- package/src/modules/customers/lib/calendar/recurrence.ts +137 -0
- package/src/modules/dashboards/services/widgetDataService.ts +8 -6
- package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +61 -1
- package/src/modules/dictionaries/api/[dictionaryId]/entries/route.ts +33 -1
- package/src/modules/dictionaries/api/[dictionaryId]/route.ts +65 -1
- package/src/modules/dictionaries/api/route.ts +36 -1
- package/src/modules/feature_toggles/lib/queries.ts +41 -60
- package/src/modules/perspectives/api/[tableId]/[perspectiveId]/route.ts +34 -1
- package/src/modules/perspectives/api/[tableId]/roles/[roleId]/route.ts +34 -1
- package/src/modules/perspectives/api/[tableId]/route.ts +107 -32
- package/src/modules/perspectives/services/perspectiveService.ts +9 -5
- package/src/modules/planner/api/access.ts +9 -0
- package/src/modules/planner/api/availability-date-specific.ts +22 -1
- package/src/modules/planner/api/availability-weekly.ts +22 -1
- package/src/modules/resources/api/resources/tags/assign/route.ts +42 -0
- package/src/modules/resources/api/resources/tags/unassign/route.ts +42 -0
- package/src/modules/sales/api/settings/document-numbers/route.ts +35 -1
- package/src/modules/sales/api/settings/order-editing/route.ts +37 -3
- package/src/modules/staff/api/leave-requests/accept/route.ts +45 -0
- package/src/modules/staff/api/leave-requests/reject/route.ts +45 -0
- package/src/modules/staff/api/team-members/self/route.ts +43 -0
- package/src/modules/staff/api/team-members/tags/assign/route.ts +45 -0
- package/src/modules/staff/api/team-members/tags/unassign/route.ts +45 -0
- package/src/modules/translations/api/[entityType]/[entityId]/route.ts +65 -1
- package/src/modules/translations/api/context.ts +7 -0
- package/src/modules/translations/api/put/locales.ts +35 -1
- package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +11 -9
- package/src/modules/workflows/backend/instances/[id]/page.tsx +1 -1
- package/src/modules/workflows/components/EdgeEditDialog.tsx +1 -1
- package/src/modules/workflows/components/EdgeEditDialogCrudForm.tsx +1 -1
- package/src/modules/workflows/components/NodeEditDialog.tsx +1 -1
- package/src/modules/workflows/components/NodeEditDialogCrudForm.tsx +1 -1
- package/src/modules/workflows/components/WorkflowGraph.tsx +2 -2
- package/src/modules/workflows/components/WorkflowGraphImpl.tsx +27 -13
- package/src/modules/workflows/components/mobile/MobileVisualEditor.tsx +3 -3
- package/src/modules/workflows/frontend/checkout-demo/deriveCurrentStep.ts +12 -0
- package/src/modules/workflows/frontend/checkout-demo/page.tsx +3 -23
- package/src/modules/workflows/lib/graph-utils.ts +21 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { endOfDay } from 'date-fns/endOfDay'
|
|
5
|
+
import { startOfDay } from 'date-fns/startOfDay'
|
|
6
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
|
+
import { computeDurationMinutes, type EditorFormState, type EditorKindConfig } from '../../../lib/calendar/editorPayload'
|
|
8
|
+
import { findEditorConflictItems } from '../../../lib/calendar/conflicts'
|
|
9
|
+
import type { ConflictScope } from '../../../lib/calendar/preferences'
|
|
10
|
+
import { mapInteractionToCalendarItem } from '../../../lib/calendar/mapItem'
|
|
11
|
+
import { expandOccurrences } from '../../../lib/calendar/recurrence'
|
|
12
|
+
import { getFetchWindow } from '../../../lib/calendar/range'
|
|
13
|
+
import type { CalendarItem } from '../types'
|
|
14
|
+
import { fetchInteractionWindow } from '../useCalendarItems'
|
|
15
|
+
import { fetchDealById, fetchRelatedEntityById, findStaffMemberName } from './lookups'
|
|
16
|
+
|
|
17
|
+
// Edit-mode prefill stores ids only (parseItemToFormState is pure); resolve the
|
|
18
|
+
// human labels for the related entity, the deal chip and the task assignee.
|
|
19
|
+
export function useEditorLabelResolution(
|
|
20
|
+
open: boolean,
|
|
21
|
+
form: EditorFormState,
|
|
22
|
+
update: (patch: Partial<EditorFormState>) => void,
|
|
23
|
+
): void {
|
|
24
|
+
React.useEffect(() => {
|
|
25
|
+
if (!open) return
|
|
26
|
+
const controller = new AbortController()
|
|
27
|
+
let cancelled = false
|
|
28
|
+
async function resolveLabels() {
|
|
29
|
+
if (form.relatedTo && form.relatedTo.kind === 'unknown') {
|
|
30
|
+
const resolved = await fetchRelatedEntityById(form.relatedTo.id, controller.signal)
|
|
31
|
+
if (!cancelled && resolved) update({ relatedTo: { id: resolved.id, kind: resolved.kind, label: resolved.label } })
|
|
32
|
+
}
|
|
33
|
+
if (form.dealId && !form.dealLabel) {
|
|
34
|
+
const deal = await fetchDealById(form.dealId, controller.signal)
|
|
35
|
+
if (!cancelled && deal) update({ dealLabel: deal.label })
|
|
36
|
+
}
|
|
37
|
+
if (form.assigneeUserId && !form.assigneeName) {
|
|
38
|
+
const name = await findStaffMemberName(form.assigneeUserId, controller.signal)
|
|
39
|
+
if (!cancelled && name) update({ assigneeName: name })
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
resolveLabels().catch(() => {
|
|
43
|
+
if (cancelled || controller.signal.aborted || !form.relatedTo || form.relatedTo.kind !== 'unknown' || form.relatedTo.label) {
|
|
44
|
+
return
|
|
45
|
+
}
|
|
46
|
+
update({ relatedTo: { ...form.relatedTo, label: form.relatedTo.id } })
|
|
47
|
+
})
|
|
48
|
+
return () => {
|
|
49
|
+
cancelled = true
|
|
50
|
+
controller.abort()
|
|
51
|
+
}
|
|
52
|
+
}, [open, form.relatedTo, form.dealId, form.dealLabel, form.assigneeUserId, form.assigneeName, update])
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatClock(date: Date): string {
|
|
56
|
+
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Debounced save-time conflict probe. Detection uses the SAME `findConflicts`
|
|
60
|
+
// logic the grid uses (overlap + shared owner/participant) against a freshly
|
|
61
|
+
// fetched ±1-day window, so the editor warning is always consistent with the
|
|
62
|
+
// conflict badges/rings the user sees on the calendar. The warning is
|
|
63
|
+
// informational and never blocks saving.
|
|
64
|
+
export function useConflictProbe(
|
|
65
|
+
open: boolean,
|
|
66
|
+
form: EditorFormState,
|
|
67
|
+
config: EditorKindConfig,
|
|
68
|
+
excludeId: string | null,
|
|
69
|
+
draftOwnerUserId: string | null,
|
|
70
|
+
scope: ConflictScope,
|
|
71
|
+
currentUserId: string | null,
|
|
72
|
+
): string | null {
|
|
73
|
+
const t = useT()
|
|
74
|
+
const [conflict, setConflict] = React.useState<string | null>(null)
|
|
75
|
+
const participantsKey = form.participants.map((participant) => participant.userId).join(',')
|
|
76
|
+
React.useEffect(() => {
|
|
77
|
+
if (!open || !form.date) {
|
|
78
|
+
setConflict(null)
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
// Resolve the draft's [start, end] span exactly as the grid will after save:
|
|
82
|
+
// all-day spans startOfDay..endOfDay (matching mapInteractionToCalendarItem),
|
|
83
|
+
// timed events use the start + computed duration (multi-day endDate included).
|
|
84
|
+
const isAllDay = config.hasAllDay && form.allDay
|
|
85
|
+
let start: Date
|
|
86
|
+
let end: Date
|
|
87
|
+
if (isAllDay) {
|
|
88
|
+
const dayDate = new Date(`${form.date}T00:00:00`)
|
|
89
|
+
if (Number.isNaN(dayDate.getTime())) {
|
|
90
|
+
setConflict(null)
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
start = startOfDay(dayDate)
|
|
94
|
+
end = endOfDay(dayDate)
|
|
95
|
+
} else {
|
|
96
|
+
if (!form.startTime) {
|
|
97
|
+
setConflict(null)
|
|
98
|
+
return
|
|
99
|
+
}
|
|
100
|
+
start = new Date(`${form.date}T${form.startTime}:00`)
|
|
101
|
+
if (Number.isNaN(start.getTime())) {
|
|
102
|
+
setConflict(null)
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
const durationMinutes = config.hasEnd ? computeDurationMinutes(form) ?? 30 : 30
|
|
106
|
+
end = new Date(start.getTime() + durationMinutes * 60_000)
|
|
107
|
+
}
|
|
108
|
+
// The same padded window + recurrence expansion the grid uses, so the probe
|
|
109
|
+
// sees the exact candidate set behind the grid's conflict badges/rings. The
|
|
110
|
+
// window pads ±1 day around the FULL draft span so multi-day drafts still
|
|
111
|
+
// catch neighbours near their end, not just their start day.
|
|
112
|
+
const dayStart = startOfDay(start)
|
|
113
|
+
dayStart.setDate(dayStart.getDate() - 1)
|
|
114
|
+
const dayEnd = endOfDay(end)
|
|
115
|
+
dayEnd.setDate(dayEnd.getDate() + 1)
|
|
116
|
+
const fetchWindow = getFetchWindow({ from: dayStart, to: dayEnd })
|
|
117
|
+
const controller = new AbortController()
|
|
118
|
+
let active = true
|
|
119
|
+
const timer = setTimeout(async () => {
|
|
120
|
+
try {
|
|
121
|
+
const { payloads } = await fetchInteractionWindow(fetchWindow, controller.signal)
|
|
122
|
+
if (!active) return
|
|
123
|
+
const others: CalendarItem[] = []
|
|
124
|
+
for (const payload of payloads) {
|
|
125
|
+
const item = mapInteractionToCalendarItem(payload, {})
|
|
126
|
+
if (!item) continue
|
|
127
|
+
others.push(...expandOccurrences(item, fetchWindow))
|
|
128
|
+
}
|
|
129
|
+
const conflictItems = findEditorConflictItems(
|
|
130
|
+
{
|
|
131
|
+
start,
|
|
132
|
+
end,
|
|
133
|
+
ownerUserId: draftOwnerUserId,
|
|
134
|
+
participants: form.participants.map((participant) => ({ userId: participant.userId, name: participant.name })),
|
|
135
|
+
status: form.status,
|
|
136
|
+
},
|
|
137
|
+
others,
|
|
138
|
+
excludeId,
|
|
139
|
+
{ scope, currentUserId },
|
|
140
|
+
)
|
|
141
|
+
if (!active) return
|
|
142
|
+
if (conflictItems.length === 0) {
|
|
143
|
+
setConflict(null)
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
const summary = conflictItems
|
|
147
|
+
.map((item) => `${formatClock(item.start)}–${formatClock(item.end)}: ${item.title || t('customers.calendar.grid.untitled', 'Untitled')}`)
|
|
148
|
+
.join(', ')
|
|
149
|
+
setConflict(summary ? t('customers.calendar.editor.conflictWarning', 'Overlaps with: {items}', { items: summary }) : null)
|
|
150
|
+
} catch {
|
|
151
|
+
if (active) setConflict(null)
|
|
152
|
+
}
|
|
153
|
+
}, 500)
|
|
154
|
+
return () => {
|
|
155
|
+
active = false
|
|
156
|
+
clearTimeout(timer)
|
|
157
|
+
controller.abort()
|
|
158
|
+
}
|
|
159
|
+
// Re-probe when schedule-relevant inputs change (time window, all-day, attendees, owner, status, scope).
|
|
160
|
+
}, [open, form.date, form.startTime, form.endDate, form.endTime, form.allDay, form.status, participantsKey, config.hasAllDay, config.hasEnd, excludeId, draftOwnerUserId, scope, currentUserId, t]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
161
|
+
return conflict
|
|
162
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { ChevronDown, Plus } from 'lucide-react'
|
|
5
|
+
import { cn } from '@open-mercato/shared/lib/utils'
|
|
6
|
+
import { Avatar } from '@open-mercato/ui/primitives/avatar'
|
|
7
|
+
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
8
|
+
import { Input } from '@open-mercato/ui/primitives/input'
|
|
9
|
+
import { Switch } from '@open-mercato/ui/primitives/switch'
|
|
10
|
+
|
|
11
|
+
export const CONTROL_BORDER = 'border border-input'
|
|
12
|
+
export const CONTROL_TEXT = 'text-sm text-foreground placeholder:text-muted-foreground'
|
|
13
|
+
export const LABEL_CLASS = 'text-xs font-medium text-muted-foreground'
|
|
14
|
+
export const DROPDOWN_PANEL_CLASS =
|
|
15
|
+
'absolute z-50 mt-1 max-h-56 w-full overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md'
|
|
16
|
+
|
|
17
|
+
export function Field({ label, children, error }: { label: string; children: React.ReactNode; error?: string | null }) {
|
|
18
|
+
return (
|
|
19
|
+
<div className="flex w-full flex-col gap-1.5">
|
|
20
|
+
<span className={LABEL_CLASS}>{label}</span>
|
|
21
|
+
{children}
|
|
22
|
+
{error ? <p className="text-xs text-status-error-text">{error}</p> : null}
|
|
23
|
+
</div>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function UppercaseBadge({ style, className, children }: { style?: React.CSSProperties; className?: string; children: React.ReactNode }) {
|
|
28
|
+
return (
|
|
29
|
+
<span
|
|
30
|
+
className={cn(
|
|
31
|
+
'inline-flex shrink-0 items-center justify-center rounded-full bg-muted px-2 py-0.5 text-overline font-medium uppercase text-muted-foreground',
|
|
32
|
+
className,
|
|
33
|
+
)}
|
|
34
|
+
style={style}
|
|
35
|
+
>
|
|
36
|
+
{children}
|
|
37
|
+
</span>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Mockup tint colors are illustrative; runtime pills derive from the
|
|
42
|
+
// dictionary entry color using the appearanceColor hex-alpha tint technique
|
|
43
|
+
// (see EventBlock.softTintStyle) with a darkened text shade of the same hue.
|
|
44
|
+
export function categoryPillStyle(color: string | null | undefined): React.CSSProperties {
|
|
45
|
+
if (typeof color === 'string' && /^#[0-9a-fA-F]{6}$/.test(color)) {
|
|
46
|
+
return { backgroundColor: `${color}59`, color: `color-mix(in srgb, ${color} 55%, black)` }
|
|
47
|
+
}
|
|
48
|
+
if (typeof color === 'string' && color.length > 0) {
|
|
49
|
+
return {
|
|
50
|
+
backgroundColor: `color-mix(in srgb, ${color} 35%, white)`,
|
|
51
|
+
color: `color-mix(in srgb, ${color} 55%, black)`,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function AllDayToggle({ checked, onCheckedChange, label }: { checked: boolean; onCheckedChange(next: boolean): void; label: string }) {
|
|
58
|
+
return (
|
|
59
|
+
<Switch
|
|
60
|
+
checked={checked}
|
|
61
|
+
aria-label={label}
|
|
62
|
+
onCheckedChange={onCheckedChange}
|
|
63
|
+
className="h-6 w-10"
|
|
64
|
+
/>
|
|
65
|
+
)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function formatDisplayDate(value: string, locale: string): string {
|
|
69
|
+
const parsed = new Date(`${value}T00:00:00`)
|
|
70
|
+
if (Number.isNaN(parsed.getTime())) return value
|
|
71
|
+
return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' }).format(parsed)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function DateControl({
|
|
75
|
+
value,
|
|
76
|
+
onChange,
|
|
77
|
+
ariaLabel,
|
|
78
|
+
locale,
|
|
79
|
+
className,
|
|
80
|
+
}: {
|
|
81
|
+
value: string
|
|
82
|
+
onChange(next: string): void
|
|
83
|
+
ariaLabel: string
|
|
84
|
+
locale: string
|
|
85
|
+
className?: string
|
|
86
|
+
}) {
|
|
87
|
+
return (
|
|
88
|
+
<div
|
|
89
|
+
className={cn(
|
|
90
|
+
'relative flex h-9 w-full items-center justify-between rounded-md bg-background px-3',
|
|
91
|
+
CONTROL_BORDER,
|
|
92
|
+
className,
|
|
93
|
+
)}
|
|
94
|
+
>
|
|
95
|
+
<span className="truncate text-sm text-foreground">{formatDisplayDate(value, locale)}</span>
|
|
96
|
+
<span aria-hidden className="h-px w-2 shrink-0" />
|
|
97
|
+
<ChevronDown aria-hidden className="size-4 shrink-0 opacity-60" />
|
|
98
|
+
<Input
|
|
99
|
+
type="date"
|
|
100
|
+
value={value}
|
|
101
|
+
onChange={(event) => { if (event.target.value) onChange(event.target.value) }}
|
|
102
|
+
aria-label={ariaLabel}
|
|
103
|
+
className="absolute inset-0 h-full w-full cursor-pointer border-0 bg-transparent p-0 opacity-0 shadow-none hover:bg-transparent"
|
|
104
|
+
inputClassName="h-full cursor-pointer"
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function TimeControl({ value, onChange, ariaLabel }: { value: string; onChange(next: string): void; ariaLabel: string }) {
|
|
111
|
+
return (
|
|
112
|
+
<Input
|
|
113
|
+
type="time"
|
|
114
|
+
value={value}
|
|
115
|
+
onChange={(event) => { if (event.target.value) onChange(event.target.value) }}
|
|
116
|
+
aria-label={ariaLabel}
|
|
117
|
+
className="h-9 w-32 shrink-0"
|
|
118
|
+
/>
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function PersonChip({
|
|
123
|
+
name,
|
|
124
|
+
badge,
|
|
125
|
+
compact,
|
|
126
|
+
onRemove,
|
|
127
|
+
removeLabel,
|
|
128
|
+
}: {
|
|
129
|
+
name: string
|
|
130
|
+
badge?: React.ReactNode
|
|
131
|
+
compact?: boolean
|
|
132
|
+
onRemove?: () => void
|
|
133
|
+
removeLabel?: string
|
|
134
|
+
}) {
|
|
135
|
+
return (
|
|
136
|
+
<span
|
|
137
|
+
className={cn(
|
|
138
|
+
'inline-flex shrink-0 items-center gap-1.5 rounded-full bg-muted pl-1 pr-2',
|
|
139
|
+
compact ? 'py-0.5' : 'py-1',
|
|
140
|
+
)}
|
|
141
|
+
>
|
|
142
|
+
<Avatar size="xs" label={name} />
|
|
143
|
+
<span className="max-w-40 truncate text-xs font-medium text-foreground">{name}</span>
|
|
144
|
+
{badge}
|
|
145
|
+
{onRemove ? (
|
|
146
|
+
<IconButton
|
|
147
|
+
variant="ghost"
|
|
148
|
+
size="xs"
|
|
149
|
+
onClick={(event) => { event.stopPropagation(); onRemove() }}
|
|
150
|
+
aria-label={removeLabel}
|
|
151
|
+
className="size-5 shrink-0"
|
|
152
|
+
>
|
|
153
|
+
<Plus aria-hidden className="size-3.5 rotate-45 opacity-50" />
|
|
154
|
+
</IconButton>
|
|
155
|
+
) : null}
|
|
156
|
+
</span>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
2
|
+
import { fetchAssignableStaffMembers } from '../../detail/assignableStaff'
|
|
3
|
+
|
|
4
|
+
export type RelatedEntityKind = 'person' | 'company'
|
|
5
|
+
|
|
6
|
+
export type RelatedEntityOption = {
|
|
7
|
+
id: string
|
|
8
|
+
kind: RelatedEntityKind
|
|
9
|
+
label: string
|
|
10
|
+
subtitle: string | null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type DealOption = {
|
|
14
|
+
id: string
|
|
15
|
+
label: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type PersonOption = {
|
|
19
|
+
userId: string
|
|
20
|
+
name: string
|
|
21
|
+
email: string | null
|
|
22
|
+
isCustomer: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readNonEmptyString(value: unknown): string | null {
|
|
26
|
+
if (typeof value !== 'string') return null
|
|
27
|
+
const trimmed = value.trim()
|
|
28
|
+
return trimmed.length > 0 ? trimmed : null
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function extractRelatedEntityOption(item: Record<string, unknown>, kind: RelatedEntityKind): RelatedEntityOption | null {
|
|
32
|
+
const id = typeof item.id === 'string' ? item.id : null
|
|
33
|
+
if (!id) return null
|
|
34
|
+
const displayName = readNonEmptyString(item.displayName) ?? readNonEmptyString(item.display_name)
|
|
35
|
+
const detail = kind === 'person'
|
|
36
|
+
? readNonEmptyString(item.primaryEmail) ?? readNonEmptyString(item.primary_email)
|
|
37
|
+
: readNonEmptyString(item.domain)
|
|
38
|
+
?? readNonEmptyString(item.websiteUrl)
|
|
39
|
+
?? readNonEmptyString(item.website_url)
|
|
40
|
+
const label = displayName ?? detail ?? id
|
|
41
|
+
return { id, kind, label, subtitle: detail && detail !== label ? detail : null }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Same lookup source as the deal-association pattern in DealForm
|
|
45
|
+
// (useDealAssociationLookups): the people/companies CRUD list APIs with
|
|
46
|
+
// `search` + name sorting, merged into a single related-to picker.
|
|
47
|
+
export async function searchRelatedEntities(
|
|
48
|
+
kind: RelatedEntityKind,
|
|
49
|
+
query: string,
|
|
50
|
+
signal: AbortSignal,
|
|
51
|
+
): Promise<RelatedEntityOption[]> {
|
|
52
|
+
const params = new URLSearchParams({ page: '1', pageSize: '20', sortField: 'name', sortDir: 'asc' })
|
|
53
|
+
if (query.length) params.set('search', query)
|
|
54
|
+
const endpoint = kind === 'person'
|
|
55
|
+
? `/api/customers/people?${params.toString()}`
|
|
56
|
+
: `/api/customers/companies?${params.toString()}`
|
|
57
|
+
const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(endpoint, { signal })
|
|
58
|
+
const items = Array.isArray(data?.items) ? data.items : []
|
|
59
|
+
return items
|
|
60
|
+
.map((item) => (item && typeof item === 'object' ? extractRelatedEntityOption(item, kind) : null))
|
|
61
|
+
.filter((option): option is RelatedEntityOption => option !== null)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function fetchRelatedEntityOfKind(
|
|
65
|
+
kind: RelatedEntityKind,
|
|
66
|
+
entityId: string,
|
|
67
|
+
signal: AbortSignal,
|
|
68
|
+
): Promise<RelatedEntityOption | null> {
|
|
69
|
+
const params = new URLSearchParams({ page: '1', pageSize: '1', ids: entityId })
|
|
70
|
+
const endpoint = kind === 'person'
|
|
71
|
+
? `/api/customers/people?${params.toString()}`
|
|
72
|
+
: `/api/customers/companies?${params.toString()}`
|
|
73
|
+
try {
|
|
74
|
+
const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(endpoint, { signal })
|
|
75
|
+
const item = Array.isArray(data?.items) ? data.items[0] : null
|
|
76
|
+
return item && typeof item === 'object' ? extractRelatedEntityOption(item, kind) : null
|
|
77
|
+
} catch {
|
|
78
|
+
return null
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export async function fetchRelatedEntityById(entityId: string, signal: AbortSignal): Promise<RelatedEntityOption | null> {
|
|
83
|
+
const [person, company] = await Promise.all([
|
|
84
|
+
fetchRelatedEntityOfKind('person', entityId, signal),
|
|
85
|
+
fetchRelatedEntityOfKind('company', entityId, signal),
|
|
86
|
+
])
|
|
87
|
+
return person ?? company
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function extractDealOption(item: Record<string, unknown>): DealOption | null {
|
|
91
|
+
const id = typeof item.id === 'string' ? item.id : null
|
|
92
|
+
if (!id) return null
|
|
93
|
+
const label = readNonEmptyString(item.title) ?? id
|
|
94
|
+
return { id, label }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function fetchDealsForEntity(
|
|
98
|
+
entity: { id: string; kind: RelatedEntityKind },
|
|
99
|
+
signal: AbortSignal,
|
|
100
|
+
): Promise<DealOption[]> {
|
|
101
|
+
const params = new URLSearchParams({ page: '1', pageSize: '20' })
|
|
102
|
+
params.set(entity.kind === 'person' ? 'personId' : 'companyId', entity.id)
|
|
103
|
+
const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(
|
|
104
|
+
`/api/customers/deals?${params.toString()}`,
|
|
105
|
+
{ signal },
|
|
106
|
+
)
|
|
107
|
+
const items = Array.isArray(data?.items) ? data.items : []
|
|
108
|
+
return items
|
|
109
|
+
.map((item) => (item && typeof item === 'object' ? extractDealOption(item) : null))
|
|
110
|
+
.filter((option): option is DealOption => option !== null)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function fetchDealById(dealId: string, signal: AbortSignal): Promise<DealOption | null> {
|
|
114
|
+
try {
|
|
115
|
+
const params = new URLSearchParams({ page: '1', pageSize: '1', ids: dealId })
|
|
116
|
+
const data = await readApiResultOrThrow<{ items?: Array<Record<string, unknown>> }>(
|
|
117
|
+
`/api/customers/deals?${params.toString()}`,
|
|
118
|
+
{ signal },
|
|
119
|
+
)
|
|
120
|
+
const item = Array.isArray(data?.items) ? data.items[0] : null
|
|
121
|
+
return item && typeof item === 'object' ? extractDealOption(item) : null
|
|
122
|
+
} catch {
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function searchPeopleOptions(
|
|
128
|
+
query: string,
|
|
129
|
+
options: { includeCustomers: boolean; signal: AbortSignal },
|
|
130
|
+
): Promise<PersonOption[]> {
|
|
131
|
+
const [staff, customers] = await Promise.all([
|
|
132
|
+
fetchAssignableStaffMembers(query, { pageSize: 10, signal: options.signal }),
|
|
133
|
+
options.includeCustomers
|
|
134
|
+
? searchRelatedEntities('person', query, options.signal)
|
|
135
|
+
: Promise.resolve<RelatedEntityOption[]>([]),
|
|
136
|
+
])
|
|
137
|
+
const staffOptions: PersonOption[] = staff.map((member) => ({
|
|
138
|
+
userId: member.userId,
|
|
139
|
+
name: member.displayName,
|
|
140
|
+
email: member.email,
|
|
141
|
+
isCustomer: false,
|
|
142
|
+
}))
|
|
143
|
+
const customerOptions: PersonOption[] = customers.map((person) => ({
|
|
144
|
+
userId: person.id,
|
|
145
|
+
name: person.label,
|
|
146
|
+
email: person.subtitle,
|
|
147
|
+
isCustomer: true,
|
|
148
|
+
}))
|
|
149
|
+
return [...staffOptions, ...customerOptions]
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export async function findStaffMemberName(userId: string, signal: AbortSignal): Promise<string | null> {
|
|
153
|
+
try {
|
|
154
|
+
const members = await fetchAssignableStaffMembers('', { pageSize: 100, signal })
|
|
155
|
+
const match = members.find((member) => member.userId === userId)
|
|
156
|
+
return match ? match.displayName : null
|
|
157
|
+
} catch {
|
|
158
|
+
return null
|
|
159
|
+
}
|
|
160
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
|
|
3
|
+
export type CalendarView = 'day' | 'week' | 'month' | 'agenda'
|
|
4
|
+
|
|
5
|
+
export type CalendarCategory = 'meeting' | 'event' | 'task' | 'other'
|
|
6
|
+
|
|
7
|
+
export type CalendarItemStatus = 'planned' | 'done' | 'canceled'
|
|
8
|
+
|
|
9
|
+
export type CalendarPlatform = 'zoom' | 'meet' | 'slack' | 'teams'
|
|
10
|
+
|
|
11
|
+
export type CalendarLocationKind = 'url' | 'venue' | 'platform'
|
|
12
|
+
|
|
13
|
+
export type CalendarTab = 'all' | 'meetings' | 'events'
|
|
14
|
+
|
|
15
|
+
export type CalendarRangePreset = 'thisWeek' | 'next7' | 'thisMonth' | 'next30'
|
|
16
|
+
|
|
17
|
+
export type CalendarRange = { from: Date; to: Date }
|
|
18
|
+
|
|
19
|
+
const calendarParticipantSchema = z
|
|
20
|
+
.object({
|
|
21
|
+
userId: z.string(),
|
|
22
|
+
name: z.string().optional(),
|
|
23
|
+
email: z.string().optional(),
|
|
24
|
+
})
|
|
25
|
+
.passthrough()
|
|
26
|
+
|
|
27
|
+
export const calendarInteractionPayloadSchema = z
|
|
28
|
+
.object({
|
|
29
|
+
id: z.string(),
|
|
30
|
+
interactionType: z.string(),
|
|
31
|
+
title: z.string().nullable().optional(),
|
|
32
|
+
status: z.string(),
|
|
33
|
+
scheduledAt: z.string().nullable().optional(),
|
|
34
|
+
occurredAt: z.string().nullable().optional(),
|
|
35
|
+
durationMinutes: z.number().nullable().optional(),
|
|
36
|
+
allDay: z.boolean().nullable().optional(),
|
|
37
|
+
location: z.string().nullable().optional(),
|
|
38
|
+
participants: z.array(calendarParticipantSchema).nullable().optional(),
|
|
39
|
+
recurrenceRule: z.string().nullable().optional(),
|
|
40
|
+
recurrenceEnd: z.string().nullable().optional(),
|
|
41
|
+
appearanceIcon: z.string().nullable().optional(),
|
|
42
|
+
appearanceColor: z.string().nullable().optional(),
|
|
43
|
+
ownerUserId: z.string().nullable().optional(),
|
|
44
|
+
entityId: z.string().nullable().optional(),
|
|
45
|
+
dealId: z.string().nullable().optional(),
|
|
46
|
+
updatedAt: z.string().nullable().optional(),
|
|
47
|
+
})
|
|
48
|
+
.passthrough()
|
|
49
|
+
|
|
50
|
+
export type CalendarInteractionPayload = z.infer<typeof calendarInteractionPayloadSchema>
|
|
51
|
+
|
|
52
|
+
export type CalendarParticipant = { userId: string; name?: string; email?: string }
|
|
53
|
+
|
|
54
|
+
export interface CalendarItem {
|
|
55
|
+
id: string
|
|
56
|
+
title: string
|
|
57
|
+
interactionType: string
|
|
58
|
+
category: CalendarCategory
|
|
59
|
+
status: CalendarItemStatus
|
|
60
|
+
start: Date
|
|
61
|
+
end: Date
|
|
62
|
+
allDay: boolean
|
|
63
|
+
location: string | null
|
|
64
|
+
platform: CalendarPlatform | null
|
|
65
|
+
locationKind: CalendarLocationKind | null
|
|
66
|
+
participants: CalendarParticipant[]
|
|
67
|
+
ownerUserId: string | null
|
|
68
|
+
entityId: string | null
|
|
69
|
+
dealId: string | null
|
|
70
|
+
color: string | null
|
|
71
|
+
isRecurringOccurrence: boolean
|
|
72
|
+
updatedAt: string | null
|
|
73
|
+
raw: CalendarInteractionPayload
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface CalendarFiltersValue {
|
|
77
|
+
types: string[]
|
|
78
|
+
status: string | null
|
|
79
|
+
ownerUserId: string | null
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface TimeGridProps {
|
|
83
|
+
days: 1 | 7
|
|
84
|
+
anchor: Date
|
|
85
|
+
items: CalendarItem[]
|
|
86
|
+
conflictIds: Set<string>
|
|
87
|
+
showWeekends: boolean
|
|
88
|
+
showConflicts: boolean
|
|
89
|
+
aiSummaries: boolean
|
|
90
|
+
highlightItemId?: string | null
|
|
91
|
+
onItemClick(item: CalendarItem): void
|
|
92
|
+
onJoin(item: CalendarItem): void
|
|
93
|
+
onNavigate(deltaDays: number): void
|
|
94
|
+
onCreate?: () => void
|
|
95
|
+
onCreateRange?(start: Date, end: Date): void
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface MonthGridProps {
|
|
99
|
+
anchor: Date
|
|
100
|
+
items: CalendarItem[]
|
|
101
|
+
onItemClick(item: CalendarItem): void
|
|
102
|
+
onDayOpen(date: Date): void
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface AgendaListProps {
|
|
106
|
+
anchor: Date
|
|
107
|
+
horizonDays: number
|
|
108
|
+
items: CalendarItem[]
|
|
109
|
+
typeLabels?: Record<string, string>
|
|
110
|
+
onItemClick(item: CalendarItem): void
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface UpcomingCard {
|
|
114
|
+
item: CalendarItem
|
|
115
|
+
kind: 'today' | 'conflicted' | 'cancelled' | 'future'
|
|
116
|
+
conflictCount: number
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export interface UpcomingCardsProps {
|
|
120
|
+
cards: UpcomingCard[]
|
|
121
|
+
canManage?: boolean
|
|
122
|
+
onJoin(item: CalendarItem): void
|
|
123
|
+
onSeeConflict(item: CalendarItem): void
|
|
124
|
+
onOpen(item: CalendarItem): void
|
|
125
|
+
onEdit(item: CalendarItem): void
|
|
126
|
+
onCancel(item: CalendarItem): void
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface CalendarHeaderProps {
|
|
130
|
+
view: CalendarView
|
|
131
|
+
anchor: Date
|
|
132
|
+
onNewEvent?: () => void
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface CalendarToolbarProps {
|
|
136
|
+
view: CalendarView
|
|
137
|
+
anchor: Date
|
|
138
|
+
range: CalendarRange
|
|
139
|
+
preset: CalendarRangePreset | null
|
|
140
|
+
search: string
|
|
141
|
+
filters: CalendarFiltersValue
|
|
142
|
+
typeOptions: Array<{ value: string; label: string }>
|
|
143
|
+
ownerOptions: Array<{ value: string; label: string }>
|
|
144
|
+
onToday(): void
|
|
145
|
+
onPresetChange(preset: CalendarRangePreset): void
|
|
146
|
+
onAnchorChange(date: Date): void
|
|
147
|
+
onSearchChange(value: string): void
|
|
148
|
+
onFiltersChange(value: CalendarFiltersValue): void
|
|
149
|
+
onOpenSettings(): void
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export interface CalendarTabsProps {
|
|
153
|
+
tab: CalendarTab
|
|
154
|
+
counts: { all: number; meetings: number; events: number }
|
|
155
|
+
view: CalendarView
|
|
156
|
+
onTabChange(tab: CalendarTab): void
|
|
157
|
+
onViewChange(view: CalendarView): void
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface CalendarFooterProps {
|
|
161
|
+
timezoneLabel: string
|
|
162
|
+
onOpenShortcuts?: () => void
|
|
163
|
+
}
|