@open-mercato/core 0.9.0 → 0.9.1-develop.7459.1.99e3accd30
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/customer_dictionary_entry/index.js +2 -0
- package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
- package/dist/generated/entities/customer_interaction/index.js +2 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +1 -1
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/productDeleteChildren.js +389 -0
- package/dist/modules/catalog/commands/productDeleteChildren.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +52 -17
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +47 -11
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +3 -3
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -1
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
- package/dist/modules/communication_channels/lib/inbound-message-origin.js +1 -1
- package/dist/modules/communication_channels/lib/inbound-message-origin.js.map +2 -2
- package/dist/modules/communication_channels/lib/oauth-state.js +4 -1
- package/dist/modules/communication_channels/lib/oauth-state.js.map +2 -2
- package/dist/modules/communication_channels/lib/use-connect-channel.js +10 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js.map +2 -2
- package/dist/modules/customers/api/activity-types/route.js +151 -0
- package/dist/modules/customers/api/activity-types/route.js.map +7 -0
- package/dist/modules/customers/api/deals/[id]/people/route.js +58 -5
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +3 -0
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
- package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +0 -23
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -17
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/calendar-event-types.js +548 -0
- package/dist/modules/customers/calendar-event-types.js.map +7 -0
- package/dist/modules/customers/commands/dictionaries.js +70 -10
- package/dist/modules/customers/commands/dictionaries.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +187 -22
- package/dist/modules/customers/commands/interactions.js.map +3 -3
- package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
- package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
- package/dist/modules/customers/components/DictionarySettings.js +3 -1
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
- package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
- package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
- package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
- package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
- package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
- package/dist/modules/customers/components/calendar/types.js +1 -0
- package/dist/modules/customers/components/calendar/types.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +78 -343
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/detail/CreatePersonDialog.js +12 -11
- package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +7 -3
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealPeopleSection.js +206 -0
- package/dist/modules/customers/components/detail/DealPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js +342 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/PersonCard.js +5 -2
- package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
- package/dist/modules/customers/components/linking/adapters/personAdapter.js +3 -0
- package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +2 -2
- package/dist/modules/customers/data/entities.js +6 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +39 -10
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/di.js +20 -1
- package/dist/modules/customers/di.js.map +2 -2
- package/dist/modules/customers/extension-points.js +13 -0
- package/dist/modules/customers/extension-points.js.map +2 -2
- package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
- package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
- package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
- package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
- package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
- package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
- package/dist/modules/customers/lib/calendar/timezone.js +111 -0
- package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
- package/dist/modules/customers/lib/interactionReadModel.js +1 -0
- package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +1 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
- package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
- package/dist/modules/messages/subscribers/message-notification.js +13 -1
- package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +201 -53
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +37 -11
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/planner/lib/availabilityMerge.js +101 -38
- package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
- package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +159 -81
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js +7 -0
- package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +28 -25
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/generated/entities/customer_dictionary_entry/index.ts +1 -0
- package/generated/entities/customer_interaction/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +9 -8
- package/src/modules/auth/i18n/de.json +3 -0
- package/src/modules/auth/i18n/en.json +3 -0
- package/src/modules/auth/i18n/es.json +3 -0
- package/src/modules/auth/i18n/ko.json +3 -0
- package/src/modules/auth/i18n/pl.json +3 -0
- package/src/modules/catalog/commands/categories.ts +1 -1
- package/src/modules/catalog/commands/productDeleteChildren.ts +557 -0
- package/src/modules/catalog/commands/products.ts +58 -17
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +46 -10
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
- package/src/modules/communication_channels/i18n/de.json +4 -0
- package/src/modules/communication_channels/i18n/en.json +4 -0
- package/src/modules/communication_channels/i18n/es.json +4 -0
- package/src/modules/communication_channels/i18n/ko.json +4 -0
- package/src/modules/communication_channels/i18n/pl.json +4 -0
- package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
- package/src/modules/communication_channels/lib/oauth-state.ts +4 -1
- package/src/modules/communication_channels/lib/use-connect-channel.ts +10 -0
- package/src/modules/customers/api/activity-types/route.ts +154 -0
- package/src/modules/customers/api/deals/[id]/people/route.ts +82 -3
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
- package/src/modules/customers/api/interactions/route.ts +4 -0
- package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +0 -29
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -17
- package/src/modules/customers/calendar-event-types.ts +674 -0
- package/src/modules/customers/commands/dictionaries.ts +84 -8
- package/src/modules/customers/commands/interactions.ts +244 -21
- package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
- package/src/modules/customers/components/DictionarySettings.tsx +5 -3
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
- package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
- package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
- package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
- package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
- package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
- package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
- package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
- package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
- package/src/modules/customers/components/calendar/types.ts +1 -0
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +94 -388
- package/src/modules/customers/components/detail/CreatePersonDialog.tsx +39 -19
- package/src/modules/customers/components/detail/DealForm.tsx +7 -3
- package/src/modules/customers/components/detail/DealPeopleSection.tsx +256 -0
- package/src/modules/customers/components/detail/LinkedPeopleSection.tsx +474 -0
- package/src/modules/customers/components/detail/PersonCard.tsx +12 -4
- package/src/modules/customers/components/detail/types.ts +1 -0
- package/src/modules/customers/components/linking/adapters/personAdapter.tsx +4 -0
- package/src/modules/customers/data/entities.ts +8 -1
- package/src/modules/customers/data/validators.ts +42 -7
- package/src/modules/customers/di.ts +21 -0
- package/src/modules/customers/extension-points.ts +13 -0
- package/src/modules/customers/i18n/de.json +104 -8
- package/src/modules/customers/i18n/en.json +104 -8
- package/src/modules/customers/i18n/es.json +104 -8
- package/src/modules/customers/i18n/ko.json +104 -8
- package/src/modules/customers/i18n/pl.json +104 -8
- package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
- package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
- package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
- package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
- package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
- package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
- package/src/modules/customers/lib/calendar/timezone.ts +117 -0
- package/src/modules/customers/lib/interactionReadModel.ts +1 -0
- package/src/modules/customers/lib/todoCompatibility.ts +2 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
- package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
- package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
- package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
- package/src/modules/messages/subscribers/message-notification.ts +13 -3
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +243 -62
- package/src/modules/payment_gateways/lib/gateway-service.ts +39 -12
- package/src/modules/planner/lib/availabilityMerge.ts +106 -41
- package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +194 -111
- package/src/modules/sales/components/documents/lineItemUtils.ts +18 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
- package/src/modules/sync_excel/lib/adapters/customers.ts +33 -27
|
@@ -15,7 +15,8 @@ function PriorityField({
|
|
|
15
15
|
value,
|
|
16
16
|
labels,
|
|
17
17
|
ariaLabel,
|
|
18
|
-
onChange
|
|
18
|
+
onChange,
|
|
19
|
+
disabled
|
|
19
20
|
}) {
|
|
20
21
|
const [open, setOpen] = React.useState(false);
|
|
21
22
|
const close = React.useCallback(() => setOpen(false), []);
|
|
@@ -39,6 +40,7 @@ function PriorityField({
|
|
|
39
40
|
"aria-haspopup": "listbox",
|
|
40
41
|
"aria-expanded": open,
|
|
41
42
|
"aria-label": ariaLabel,
|
|
43
|
+
disabled,
|
|
42
44
|
onClick: () => setOpen((previous) => !previous),
|
|
43
45
|
className: cn("h-9 w-full justify-between bg-background px-3 font-normal shadow-none", CONTROL_BORDER),
|
|
44
46
|
children: [
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/calendar/editor/PriorityField.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Check, ChevronDown, ChevronsDown, ChevronsUp, Equal } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport type { EditorPriority } from '../../../lib/calendar/editorPayload'\nimport { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, useDropdownDismiss } from './inputs'\n\n// Jira/Linear-style priority glyphs: filled double chevrons for the extremes,\n// an equals bar for the middle. Colours stay on DS status tokens (no amber):\n// blue = low urgency, muted = medium, red = high.\nconst PRIORITY_META: Record<EditorPriority, { Icon: React.ComponentType<{ className?: string }>; color: string }> = {\n low: { Icon: ChevronsDown, color: 'text-status-info-text' },\n medium: { Icon: Equal, color: 'text-muted-foreground' },\n high: { Icon: ChevronsUp, color: 'text-status-error-text' },\n}\n\nconst PRIORITY_ORDER: EditorPriority[] = ['high', 'medium', 'low']\n\nexport function PriorityField({\n value,\n labels,\n ariaLabel,\n onChange,\n}: {\n value: EditorPriority\n labels: Record<EditorPriority, string>\n ariaLabel: string\n onChange(next: EditorPriority): void\n}) {\n const [open, setOpen] = React.useState(false)\n const close = React.useCallback(() => setOpen(false), [])\n const rootRef = useDropdownDismiss(open, close)\n const selected = PRIORITY_META[value]\n const SelectedIcon = selected.Icon\n\n return (\n <div\n ref={rootRef}\n className=\"relative w-full\"\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setOpen(false)\n }}\n >\n <Button\n type=\"button\"\n variant=\"outline\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label={ariaLabel}\n onClick={() => setOpen((previous) => !previous)}\n className={cn('h-9 w-full justify-between bg-background px-3 font-normal shadow-none', CONTROL_BORDER)}\n >\n <span className=\"flex min-w-0 items-center gap-2\">\n <SelectedIcon aria-hidden className={cn('size-4 shrink-0', selected.color)} />\n <span className=\"truncate text-sm text-foreground\">{labels[value]}</span>\n </span>\n <ChevronDown aria-hidden className=\"size-4 shrink-0 opacity-60\" />\n </Button>\n {open ? (\n <div role=\"listbox\" aria-label={ariaLabel} className={DROPDOWN_PANEL_CLASS}>\n {PRIORITY_ORDER.map((priority) => {\n const meta = PRIORITY_META[priority]\n const Icon = meta.Icon\n const active = priority === value\n return (\n <Button\n key={priority}\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={active}\n onClick={() => {\n onChange(priority)\n setOpen(false)\n }}\n className={cn('h-auto w-full justify-start gap-2 px-2 py-1.5 text-left text-sm font-normal', active && 'bg-muted')}\n >\n <Icon aria-hidden className={cn('size-4 shrink-0', meta.color)} />\n <span className=\"min-w-0 flex-1 truncate text-foreground\">{labels[priority]}</span>\n <Check aria-hidden className={cn('size-4 shrink-0', active ? 'opacity-100' : 'opacity-0')} />\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Check, ChevronDown, ChevronsDown, ChevronsUp, Equal } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport type { EditorPriority } from '../../../lib/calendar/editorPayload'\nimport { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, useDropdownDismiss } from './inputs'\n\n// Jira/Linear-style priority glyphs: filled double chevrons for the extremes,\n// an equals bar for the middle. Colours stay on DS status tokens (no amber):\n// blue = low urgency, muted = medium, red = high.\nconst PRIORITY_META: Record<EditorPriority, { Icon: React.ComponentType<{ className?: string }>; color: string }> = {\n low: { Icon: ChevronsDown, color: 'text-status-info-text' },\n medium: { Icon: Equal, color: 'text-muted-foreground' },\n high: { Icon: ChevronsUp, color: 'text-status-error-text' },\n}\n\nconst PRIORITY_ORDER: EditorPriority[] = ['high', 'medium', 'low']\n\nexport function PriorityField({\n value,\n labels,\n ariaLabel,\n onChange,\n disabled,\n}: {\n value: EditorPriority\n labels: Record<EditorPriority, string>\n ariaLabel: string\n onChange(next: EditorPriority): void\n disabled?: boolean\n}) {\n const [open, setOpen] = React.useState(false)\n const close = React.useCallback(() => setOpen(false), [])\n const rootRef = useDropdownDismiss(open, close)\n const selected = PRIORITY_META[value]\n const SelectedIcon = selected.Icon\n\n return (\n <div\n ref={rootRef}\n className=\"relative w-full\"\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setOpen(false)\n }}\n >\n <Button\n type=\"button\"\n variant=\"outline\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label={ariaLabel}\n disabled={disabled}\n onClick={() => setOpen((previous) => !previous)}\n className={cn('h-9 w-full justify-between bg-background px-3 font-normal shadow-none', CONTROL_BORDER)}\n >\n <span className=\"flex min-w-0 items-center gap-2\">\n <SelectedIcon aria-hidden className={cn('size-4 shrink-0', selected.color)} />\n <span className=\"truncate text-sm text-foreground\">{labels[value]}</span>\n </span>\n <ChevronDown aria-hidden className=\"size-4 shrink-0 opacity-60\" />\n </Button>\n {open ? (\n <div role=\"listbox\" aria-label={ariaLabel} className={DROPDOWN_PANEL_CLASS}>\n {PRIORITY_ORDER.map((priority) => {\n const meta = PRIORITY_META[priority]\n const Icon = meta.Icon\n const active = priority === value\n return (\n <Button\n key={priority}\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={active}\n onClick={() => {\n onChange(priority)\n setOpen(false)\n }}\n className={cn('h-auto w-full justify-start gap-2 px-2 py-1.5 text-left text-sm font-normal', active && 'bg-muted')}\n >\n <Icon aria-hidden className={cn('size-4 shrink-0', meta.color)} />\n <span className=\"min-w-0 flex-1 truncate text-foreground\">{labels[priority]}</span>\n <Check aria-hidden className={cn('size-4 shrink-0', active ? 'opacity-100' : 'opacity-0')} />\n </Button>\n )\n })}\n </div>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAyDQ,SACE,KADF;AAvDR,YAAY,WAAW;AACvB,SAAS,OAAO,aAAa,cAAc,YAAY,aAAa;AACpE,SAAS,UAAU;AACnB,SAAS,cAAc;AAEvB,SAAS,gBAAgB,sBAAsB,0BAA0B;AAKzE,MAAM,gBAA8G;AAAA,EAClH,KAAK,EAAE,MAAM,cAAc,OAAO,wBAAwB;AAAA,EAC1D,QAAQ,EAAE,MAAM,OAAO,OAAO,wBAAwB;AAAA,EACtD,MAAM,EAAE,MAAM,YAAY,OAAO,yBAAyB;AAC5D;AAEA,MAAM,iBAAmC,CAAC,QAAQ,UAAU,KAAK;AAE1D,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,KAAK;AAC5C,QAAM,QAAQ,MAAM,YAAY,MAAM,QAAQ,KAAK,GAAG,CAAC,CAAC;AACxD,QAAM,UAAU,mBAAmB,MAAM,KAAK;AAC9C,QAAM,WAAW,cAAc,KAAK;AACpC,QAAM,eAAe,SAAS;AAE9B,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAU;AAAA,MACV,QAAQ,CAAC,UAAU;AACjB,YAAI,CAAC,MAAM,cAAc,SAAS,MAAM,aAA4B,EAAG,SAAQ,KAAK;AAAA,MACtF;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,iBAAc;AAAA,YACd,iBAAe;AAAA,YACf,cAAY;AAAA,YACZ;AAAA,YACA,SAAS,MAAM,QAAQ,CAAC,aAAa,CAAC,QAAQ;AAAA,YAC9C,WAAW,GAAG,yEAAyE,cAAc;AAAA,YAErG;AAAA,mCAAC,UAAK,WAAU,mCACd;AAAA,oCAAC,gBAAa,eAAW,MAAC,WAAW,GAAG,mBAAmB,SAAS,KAAK,GAAG;AAAA,gBAC5E,oBAAC,UAAK,WAAU,oCAAoC,iBAAO,KAAK,GAAE;AAAA,iBACpE;AAAA,cACA,oBAAC,eAAY,eAAW,MAAC,WAAU,8BAA6B;AAAA;AAAA;AAAA,QAClE;AAAA,QACC,OACC,oBAAC,SAAI,MAAK,WAAU,cAAY,WAAW,WAAW,sBACnD,yBAAe,IAAI,CAAC,aAAa;AAChC,gBAAM,OAAO,cAAc,QAAQ;AACnC,gBAAM,OAAO,KAAK;AAClB,gBAAM,SAAS,aAAa;AAC5B,iBACE;AAAA,YAAC;AAAA;AAAA,cAEC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,iBAAe;AAAA,cACf,SAAS,MAAM;AACb,yBAAS,QAAQ;AACjB,wBAAQ,KAAK;AAAA,cACf;AAAA,cACA,WAAW,GAAG,+EAA+E,UAAU,UAAU;AAAA,cAEjH;AAAA,oCAAC,QAAK,eAAW,MAAC,WAAW,GAAG,mBAAmB,KAAK,KAAK,GAAG;AAAA,gBAChE,oBAAC,UAAK,WAAU,2CAA2C,iBAAO,QAAQ,GAAE;AAAA,gBAC5E,oBAAC,SAAM,eAAW,MAAC,WAAW,GAAG,mBAAmB,SAAS,gBAAgB,WAAW,GAAG;AAAA;AAAA;AAAA,YAbtF;AAAA,UAcP;AAAA,QAEJ,CAAC,GACH,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -22,6 +22,8 @@ function RepeatField({
|
|
|
22
22
|
endType,
|
|
23
23
|
count,
|
|
24
24
|
untilDate,
|
|
25
|
+
untilError,
|
|
26
|
+
disabled,
|
|
25
27
|
onFreqChange,
|
|
26
28
|
onToggleDay,
|
|
27
29
|
onEndTypeChange,
|
|
@@ -37,6 +39,7 @@ function RepeatField({
|
|
|
37
39
|
{
|
|
38
40
|
value: freq,
|
|
39
41
|
onValueChange: (value) => onFreqChange(value),
|
|
42
|
+
disabled,
|
|
40
43
|
children: [
|
|
41
44
|
/* @__PURE__ */ jsx(SelectTrigger, { "aria-label": freqLabel, children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
42
45
|
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
@@ -56,6 +59,7 @@ function RepeatField({
|
|
|
56
59
|
variant: isActive ? "default" : "outline",
|
|
57
60
|
"aria-pressed": isActive,
|
|
58
61
|
"aria-label": t(day.ariaKey, day.ariaFallback),
|
|
62
|
+
disabled,
|
|
59
63
|
onClick: () => onToggleDay(index),
|
|
60
64
|
className: cn(
|
|
61
65
|
"size-9 px-0 text-xs font-medium",
|
|
@@ -74,6 +78,7 @@ function RepeatField({
|
|
|
74
78
|
ariaLabel: t("customers.calendar.editor.repeat.ends", "Ends"),
|
|
75
79
|
value: endType,
|
|
76
80
|
onChange: onEndTypeChange,
|
|
81
|
+
disabled,
|
|
77
82
|
options: [
|
|
78
83
|
{ value: "never", label: t("customers.calendar.editor.repeat.never", "Never") },
|
|
79
84
|
{ value: "date", label: t("customers.calendar.editor.repeat.onDate", "On date") },
|
|
@@ -87,9 +92,11 @@ function RepeatField({
|
|
|
87
92
|
className: "w-44",
|
|
88
93
|
value: untilDate || "",
|
|
89
94
|
onChange: onUntilDateChange,
|
|
90
|
-
ariaLabel: t("customers.calendar.editor.repeat.onDate", "On date")
|
|
95
|
+
ariaLabel: t("customers.calendar.editor.repeat.onDate", "On date"),
|
|
96
|
+
disabled
|
|
91
97
|
}
|
|
92
98
|
) : null,
|
|
99
|
+
endType === "date" && untilError ? /* @__PURE__ */ jsx("p", { className: "w-full text-xs text-status-error-text", children: untilError }) : null,
|
|
93
100
|
endType === "count" ? /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
94
101
|
/* @__PURE__ */ jsx(
|
|
95
102
|
Input,
|
|
@@ -103,6 +110,7 @@ function RepeatField({
|
|
|
103
110
|
if (Number.isInteger(next) && next >= 1) onCountChange(next);
|
|
104
111
|
},
|
|
105
112
|
"aria-label": t("customers.calendar.editor.repeat.after", "After"),
|
|
113
|
+
disabled,
|
|
106
114
|
className: "h-9 w-20"
|
|
107
115
|
}
|
|
108
116
|
),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/calendar/editor/RepeatField.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'\nimport type { EditorRepeatEndType, EditorRepeatFreq } from '../../../lib/calendar/editorPayload'\nimport { DateControl, LABEL_CLASS } from './inputs'\nimport { SegmentGroup } from './SegmentGroup'\n\nconst DAY_LABEL_KEYS = [\n { letterKey: 'customers.calendar.editor.repeat.days.mon', letterFallback: 'M', ariaKey: 'customers.calendar.day.mon', ariaFallback: 'MON' },\n { letterKey: 'customers.calendar.editor.repeat.days.tue', letterFallback: 'T', ariaKey: 'customers.calendar.day.tue', ariaFallback: 'TUE' },\n { letterKey: 'customers.calendar.editor.repeat.days.wed', letterFallback: 'W', ariaKey: 'customers.calendar.day.wed', ariaFallback: 'WED' },\n { letterKey: 'customers.calendar.editor.repeat.days.thu', letterFallback: 'T', ariaKey: 'customers.calendar.day.thu', ariaFallback: 'THU' },\n { letterKey: 'customers.calendar.editor.repeat.days.fri', letterFallback: 'F', ariaKey: 'customers.calendar.day.fri', ariaFallback: 'FRI' },\n { letterKey: 'customers.calendar.editor.repeat.days.sat', letterFallback: 'S', ariaKey: 'customers.calendar.day.sat', ariaFallback: 'SAT' },\n { letterKey: 'customers.calendar.editor.repeat.days.sun', letterFallback: 'S', ariaKey: 'customers.calendar.day.sun', ariaFallback: 'SUN' },\n]\n\nexport function RepeatField({\n freq,\n days,\n endType,\n count,\n untilDate,\n onFreqChange,\n onToggleDay,\n onEndTypeChange,\n onCountChange,\n onUntilDateChange,\n}: {\n freq: EditorRepeatFreq\n days: boolean[]\n endType: EditorRepeatEndType\n count: number\n untilDate: string\n onFreqChange(next: EditorRepeatFreq): void\n onToggleDay(index: number): void\n onEndTypeChange(next: EditorRepeatEndType): void\n onCountChange(next: number): void\n onUntilDateChange(next: string): void\n}) {\n const t = useT()\n const freqLabel = t('customers.calendar.editor.repeat.label', 'Repeat')\n return (\n <div className=\"flex w-full flex-col gap-2.5\">\n <span className={LABEL_CLASS}>{freqLabel}</span>\n <Select\n value={freq}\n onValueChange={(value) => onFreqChange(value as EditorRepeatFreq)}\n >\n <SelectTrigger aria-label={freqLabel}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"none\">{t('customers.calendar.editor.repeat.freq.none', 'Does not repeat')}</SelectItem>\n <SelectItem value=\"daily\">{t('customers.calendar.editor.repeat.freq.daily', 'Daily')}</SelectItem>\n <SelectItem value=\"weekly\">{t('customers.calendar.editor.repeat.freq.weekly', 'Weekly')}</SelectItem>\n </SelectContent>\n </Select>\n {freq === 'weekly' ? (\n <div className=\"flex items-start gap-1.5\">\n {DAY_LABEL_KEYS.map((day, index) => {\n const isActive = Boolean(days[index])\n return (\n <Button\n key={day.ariaKey}\n type=\"button\"\n variant={isActive ? 'default' : 'outline'}\n aria-pressed={isActive}\n aria-label={t(day.ariaKey, day.ariaFallback)}\n onClick={() => onToggleDay(index)}\n className={cn(\n 'size-9 px-0 text-xs font-medium',\n !isActive && 'text-muted-foreground',\n )}\n >\n {t(day.letterKey, day.letterFallback)}\n </Button>\n )\n })}\n </div>\n ) : null}\n {freq !== 'none' ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className={LABEL_CLASS}>{t('customers.calendar.editor.repeat.ends', 'Ends')}</span>\n <SegmentGroup\n ariaLabel={t('customers.calendar.editor.repeat.ends', 'Ends')}\n value={endType}\n onChange={onEndTypeChange}\n options={[\n { value: 'never', label: t('customers.calendar.editor.repeat.never', 'Never') },\n { value: 'date', label: t('customers.calendar.editor.repeat.onDate', 'On date') },\n { value: 'count', label: t('customers.calendar.editor.repeat.after', 'After') },\n ]}\n />\n {endType === 'date' ? (\n <DateControl\n className=\"w-44\"\n value={untilDate || ''}\n onChange={onUntilDateChange}\n ariaLabel={t('customers.calendar.editor.repeat.onDate', 'On date')}\n />\n ) : null}\n {endType === 'count' ? (\n <span className=\"flex items-center gap-2\">\n <Input\n type=\"number\"\n min={1}\n max={365}\n value={count}\n onChange={(event) => {\n const next = Number(event.target.value)\n if (Number.isInteger(next) && next >= 1) onCountChange(next)\n }}\n aria-label={t('customers.calendar.editor.repeat.after', 'After')}\n className=\"h-9 w-20\"\n />\n <span className={LABEL_CLASS}>{t('customers.calendar.editor.repeat.times', 'times')}</span>\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'\nimport type { EditorRepeatEndType, EditorRepeatFreq } from '../../../lib/calendar/editorPayload'\nimport { DateControl, LABEL_CLASS } from './inputs'\nimport { SegmentGroup } from './SegmentGroup'\n\nconst DAY_LABEL_KEYS = [\n { letterKey: 'customers.calendar.editor.repeat.days.mon', letterFallback: 'M', ariaKey: 'customers.calendar.day.mon', ariaFallback: 'MON' },\n { letterKey: 'customers.calendar.editor.repeat.days.tue', letterFallback: 'T', ariaKey: 'customers.calendar.day.tue', ariaFallback: 'TUE' },\n { letterKey: 'customers.calendar.editor.repeat.days.wed', letterFallback: 'W', ariaKey: 'customers.calendar.day.wed', ariaFallback: 'WED' },\n { letterKey: 'customers.calendar.editor.repeat.days.thu', letterFallback: 'T', ariaKey: 'customers.calendar.day.thu', ariaFallback: 'THU' },\n { letterKey: 'customers.calendar.editor.repeat.days.fri', letterFallback: 'F', ariaKey: 'customers.calendar.day.fri', ariaFallback: 'FRI' },\n { letterKey: 'customers.calendar.editor.repeat.days.sat', letterFallback: 'S', ariaKey: 'customers.calendar.day.sat', ariaFallback: 'SAT' },\n { letterKey: 'customers.calendar.editor.repeat.days.sun', letterFallback: 'S', ariaKey: 'customers.calendar.day.sun', ariaFallback: 'SUN' },\n]\n\nexport function RepeatField({\n freq,\n days,\n endType,\n count,\n untilDate,\n untilError,\n disabled,\n onFreqChange,\n onToggleDay,\n onEndTypeChange,\n onCountChange,\n onUntilDateChange,\n}: {\n freq: EditorRepeatFreq\n days: boolean[]\n endType: EditorRepeatEndType\n count: number\n untilDate: string\n untilError?: string | null\n disabled?: boolean\n onFreqChange(next: EditorRepeatFreq): void\n onToggleDay(index: number): void\n onEndTypeChange(next: EditorRepeatEndType): void\n onCountChange(next: number): void\n onUntilDateChange(next: string): void\n}) {\n const t = useT()\n const freqLabel = t('customers.calendar.editor.repeat.label', 'Repeat')\n return (\n <div className=\"flex w-full flex-col gap-2.5\">\n <span className={LABEL_CLASS}>{freqLabel}</span>\n <Select\n value={freq}\n onValueChange={(value) => onFreqChange(value as EditorRepeatFreq)}\n disabled={disabled}\n >\n <SelectTrigger aria-label={freqLabel}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"none\">{t('customers.calendar.editor.repeat.freq.none', 'Does not repeat')}</SelectItem>\n <SelectItem value=\"daily\">{t('customers.calendar.editor.repeat.freq.daily', 'Daily')}</SelectItem>\n <SelectItem value=\"weekly\">{t('customers.calendar.editor.repeat.freq.weekly', 'Weekly')}</SelectItem>\n </SelectContent>\n </Select>\n {freq === 'weekly' ? (\n <div className=\"flex items-start gap-1.5\">\n {DAY_LABEL_KEYS.map((day, index) => {\n const isActive = Boolean(days[index])\n return (\n <Button\n key={day.ariaKey}\n type=\"button\"\n variant={isActive ? 'default' : 'outline'}\n aria-pressed={isActive}\n aria-label={t(day.ariaKey, day.ariaFallback)}\n disabled={disabled}\n onClick={() => onToggleDay(index)}\n className={cn(\n 'size-9 px-0 text-xs font-medium',\n !isActive && 'text-muted-foreground',\n )}\n >\n {t(day.letterKey, day.letterFallback)}\n </Button>\n )\n })}\n </div>\n ) : null}\n {freq !== 'none' ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className={LABEL_CLASS}>{t('customers.calendar.editor.repeat.ends', 'Ends')}</span>\n <SegmentGroup\n ariaLabel={t('customers.calendar.editor.repeat.ends', 'Ends')}\n value={endType}\n onChange={onEndTypeChange}\n disabled={disabled}\n options={[\n { value: 'never', label: t('customers.calendar.editor.repeat.never', 'Never') },\n { value: 'date', label: t('customers.calendar.editor.repeat.onDate', 'On date') },\n { value: 'count', label: t('customers.calendar.editor.repeat.after', 'After') },\n ]}\n />\n {endType === 'date' ? (\n <DateControl\n className=\"w-44\"\n value={untilDate || ''}\n onChange={onUntilDateChange}\n ariaLabel={t('customers.calendar.editor.repeat.onDate', 'On date')}\n disabled={disabled}\n />\n ) : null}\n {endType === 'date' && untilError ? <p className=\"w-full text-xs text-status-error-text\">{untilError}</p> : null}\n {endType === 'count' ? (\n <span className=\"flex items-center gap-2\">\n <Input\n type=\"number\"\n min={1}\n max={365}\n value={count}\n onChange={(event) => {\n const next = Number(event.target.value)\n if (Number.isInteger(next) && next >= 1) onCountChange(next)\n }}\n aria-label={t('customers.calendar.editor.repeat.after', 'After')}\n disabled={disabled}\n className=\"h-9 w-20\"\n />\n <span className={LABEL_CLASS}>{t('customers.calendar.editor.repeat.times', 'times')}</span>\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -14,7 +14,8 @@ function ResourcesField({
|
|
|
14
14
|
placeholder,
|
|
15
15
|
ariaLabel,
|
|
16
16
|
value,
|
|
17
|
-
onChange
|
|
17
|
+
onChange,
|
|
18
|
+
disabled
|
|
18
19
|
}) {
|
|
19
20
|
const t = useT();
|
|
20
21
|
const [query, setQuery] = React.useState("");
|
|
@@ -109,6 +110,7 @@ function ResourcesField({
|
|
|
109
110
|
onChange(value.filter((entry) => entry.id !== resource.id));
|
|
110
111
|
},
|
|
111
112
|
"aria-label": t("customers.calendar.editor.removeResource", "Remove {name}", { name: resource.label }),
|
|
113
|
+
disabled,
|
|
112
114
|
className: "size-5 shrink-0",
|
|
113
115
|
children: /* @__PURE__ */ jsx(Plus, { "aria-hidden": true, className: "size-3.5 rotate-45 opacity-50" })
|
|
114
116
|
}
|
|
@@ -125,6 +127,7 @@ function ResourcesField({
|
|
|
125
127
|
"aria-label": ariaLabel,
|
|
126
128
|
role: "combobox",
|
|
127
129
|
"aria-expanded": open,
|
|
130
|
+
disabled,
|
|
128
131
|
className: "min-w-36 flex-1 border-0 bg-transparent px-0 shadow-none hover:bg-transparent focus-within:border-transparent focus-within:shadow-none",
|
|
129
132
|
inputClassName: "text-sm text-foreground placeholder:text-muted-foreground"
|
|
130
133
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/calendar/editor/ResourcesField.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Box, Check, ChevronDown, Plus } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport type { EditorResource } from '../../../lib/calendar/editorPayload'\nimport { fetchResourceTypes, searchResourceOptions, type ResourceOption, type ResourceType } from './lookups'\nimport { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, useDropdownDismiss } from './inputs'\n\nconst TYPE_SEARCH_THRESHOLD = 8\n\n// Multi-select of bookable resources (rooms, cars, equipment) from the\n// resources module. Rendered only when that module is loaded \u2014 the calendar\n// consumes its public list API and stores FK-id + label snapshots in\n// `linkedEntities`, never resource entities (#3552). A potentially large\n// catalog (many resources AND many types) stays navigable with a collapsible\n// resource-type filter (searchable, with counts), server-side search and a\n// \"showing N of M\" hint.\nexport function ResourcesField({\n placeholder,\n ariaLabel,\n value,\n onChange,\n}: {\n placeholder: string\n ariaLabel: string\n value: EditorResource[]\n onChange(next: EditorResource[]): void\n}) {\n const t = useT()\n const [query, setQuery] = React.useState('')\n const [open, setOpen] = React.useState(false)\n const [options, setOptions] = React.useState<ResourceOption[]>([])\n const [total, setTotal] = React.useState(0)\n const [loading, setLoading] = React.useState(false)\n const [types, setTypes] = React.useState<ResourceType[]>([])\n const [typeFilter, setTypeFilter] = React.useState<string | null>(null)\n const [typeMenuOpen, setTypeMenuOpen] = React.useState(false)\n const [typeQuery, setTypeQuery] = React.useState('')\n const close = React.useCallback(() => {\n setOpen(false)\n setTypeMenuOpen(false)\n }, [])\n const rootRef = useDropdownDismiss(open, close)\n\n // Load the type filter once the dropdown first opens.\n React.useEffect(() => {\n if (!open || types.length > 0) return\n const controller = new AbortController()\n let cancelled = false\n fetchResourceTypes(controller.signal).then((found) => {\n if (!cancelled) setTypes(found)\n })\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [open, types.length])\n\n React.useEffect(() => {\n if (!open) return\n const controller = new AbortController()\n let cancelled = false\n const timer = window.setTimeout(async () => {\n setLoading(true)\n try {\n const result = await searchResourceOptions({ query: query.trim(), resourceTypeId: typeFilter }, controller.signal)\n if (cancelled) return\n setOptions(result.items)\n setTotal(result.total)\n } catch {\n if (!cancelled) {\n setOptions([])\n setTotal(0)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }, 250)\n return () => {\n cancelled = true\n controller.abort()\n window.clearTimeout(timer)\n }\n }, [open, query, typeFilter])\n\n const selectedIds = new Set(value.map((resource) => resource.id))\n const visibleOptions = options.filter((option) => !selectedIds.has(option.id))\n const hiddenCount = Math.max(0, total - options.length)\n const activeType = typeFilter ? types.find((type) => type.id === typeFilter) ?? null : null\n const filteredTypes = typeQuery.trim().length\n ? types.filter((type) => type.name.toLowerCase().includes(typeQuery.trim().toLowerCase()))\n : types\n\n const selectType = (id: string | null) => {\n setTypeFilter(id)\n setTypeMenuOpen(false)\n setTypeQuery('')\n }\n\n return (\n <div\n ref={rootRef}\n className=\"relative w-full\"\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) close()\n }}\n >\n <div\n className={cn(\n 'flex min-h-14 w-full flex-wrap content-center items-center gap-2 rounded-md bg-background px-2.5 py-2',\n CONTROL_BORDER,\n )}\n >\n {value.map((resource) => (\n <span key={resource.id} className=\"inline-flex shrink-0 items-center gap-1.5 rounded-full bg-muted py-1 pl-2 pr-2\">\n <Box aria-hidden className=\"size-3.5 text-muted-foreground\" />\n <span className=\"max-w-40 truncate text-xs font-medium text-foreground\">{resource.label}</span>\n <IconButton\n variant=\"ghost\"\n size=\"xs\"\n onClick={(event) => {\n event.stopPropagation()\n onChange(value.filter((entry) => entry.id !== resource.id))\n }}\n aria-label={t('customers.calendar.editor.removeResource', 'Remove {name}', { name: resource.label })}\n className=\"size-5 shrink-0\"\n >\n <Plus aria-hidden className=\"size-3.5 rotate-45 opacity-50\" />\n </IconButton>\n </span>\n ))}\n <Input\n type=\"text\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onFocus={() => setOpen(true)}\n placeholder={value.length > 0 ? '' : placeholder}\n aria-label={ariaLabel}\n role=\"combobox\"\n aria-expanded={open}\n className=\"min-w-36 flex-1 border-0 bg-transparent px-0 shadow-none hover:bg-transparent focus-within:border-transparent focus-within:shadow-none\"\n inputClassName=\"text-sm text-foreground placeholder:text-muted-foreground\"\n />\n </div>\n {open ? (\n <div className={cn(DROPDOWN_PANEL_CLASS, 'flex max-h-none flex-col overflow-visible p-0')}>\n {types.length > 0 ? (\n <div className=\"border-b border-border p-1.5\">\n <Button\n type=\"button\"\n variant=\"outline\"\n aria-haspopup=\"listbox\"\n aria-expanded={typeMenuOpen}\n onClick={() => setTypeMenuOpen((previous) => !previous)}\n className={cn('h-8 w-full justify-between bg-background px-2 text-sm font-normal shadow-none', CONTROL_BORDER)}\n >\n <span className=\"truncate\">\n {activeType\n ? `${activeType.name} (${activeType.count})`\n : t('customers.calendar.editor.resourceTypes.all', 'All types')}\n </span>\n <ChevronDown aria-hidden className=\"size-4 shrink-0 opacity-60\" />\n </Button>\n </div>\n ) : null}\n\n {typeMenuOpen ? (\n <div role=\"listbox\" aria-label={t('customers.calendar.editor.resourceTypes.filterLabel', 'Resource type')} className=\"flex flex-col\">\n {types.length > TYPE_SEARCH_THRESHOLD ? (\n <div className=\"border-b border-border p-1.5\">\n <Input\n type=\"text\"\n value={typeQuery}\n onChange={(event) => setTypeQuery(event.target.value)}\n placeholder={t('customers.calendar.editor.resourceTypes.searchPlaceholder', 'Search types\u2026')}\n aria-label={t('customers.calendar.editor.resourceTypes.searchPlaceholder', 'Search types\u2026')}\n autoFocus\n className=\"h-8\"\n />\n </div>\n ) : null}\n <div className=\"max-h-56 overflow-y-auto p-1\">\n <TypeRow label={t('customers.calendar.editor.resourceTypes.all', 'All types')} active={typeFilter === null} onSelect={() => selectType(null)} />\n {filteredTypes.map((type) => (\n <TypeRow key={type.id} label={type.name} count={type.count} active={typeFilter === type.id} onSelect={() => selectType(type.id)} />\n ))}\n {filteredTypes.length === 0 ? (\n <p className=\"px-2 py-3 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.noResults', 'No results')}\n </p>\n ) : null}\n </div>\n </div>\n ) : (\n <>\n <div role=\"listbox\" aria-label={ariaLabel} className=\"max-h-56 overflow-y-auto p-1\">\n {loading ? (\n <p className=\"px-2 py-3 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.searching', 'Searching\u2026')}\n </p>\n ) : null}\n {!loading && visibleOptions.length === 0 ? (\n <p className=\"px-2 py-3 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.noResults', 'No results')}\n </p>\n ) : null}\n {!loading\n ? visibleOptions.map((option) => (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={false}\n title={option.label}\n onClick={() => {\n onChange([...value, { id: option.id, label: option.label }])\n setQuery('')\n }}\n className=\"h-auto w-full justify-start gap-2 whitespace-normal px-2 py-1.5 text-left text-sm font-normal text-foreground\"\n >\n <Box aria-hidden className=\"size-4 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{option.label}</span>\n </Button>\n ))\n : null}\n </div>\n {!loading && hiddenCount > 0 ? (\n <p className=\"border-t border-border px-2 py-1.5 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.resourceTypes.moreHint', 'Showing {shown} of {total} \u2014 keep typing to narrow', {\n shown: options.length,\n total,\n })}\n </p>\n ) : null}\n </>\n )}\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction TypeRow({ label, count, active, onSelect }: { label: string; count?: number; active: boolean; onSelect(): void }) {\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={active}\n onClick={onSelect}\n className={cn('h-auto w-full justify-start gap-2 px-2 py-1.5 text-left text-sm font-normal', active ? 'bg-muted text-foreground' : 'text-foreground')}\n >\n <Check aria-hidden className={cn('size-4 shrink-0', active ? 'opacity-100' : 'opacity-0')} />\n <span className=\"min-w-0 flex-1 truncate\">{label}</span>\n {typeof count === 'number' ? <span className=\"shrink-0 tabular-nums text-xs text-muted-foreground\">{count}</span> : null}\n </Button>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Box, Check, ChevronDown, Plus } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport type { EditorResource } from '../../../lib/calendar/editorPayload'\nimport { fetchResourceTypes, searchResourceOptions, type ResourceOption, type ResourceType } from './lookups'\nimport { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, useDropdownDismiss } from './inputs'\n\nconst TYPE_SEARCH_THRESHOLD = 8\n\n// Multi-select of bookable resources (rooms, cars, equipment) from the\n// resources module. Rendered only when that module is loaded \u2014 the calendar\n// consumes its public list API and stores FK-id + label snapshots in\n// `linkedEntities`, never resource entities (#3552). A potentially large\n// catalog (many resources AND many types) stays navigable with a collapsible\n// resource-type filter (searchable, with counts), server-side search and a\n// \"showing N of M\" hint.\nexport function ResourcesField({\n placeholder,\n ariaLabel,\n value,\n onChange,\n disabled,\n}: {\n placeholder: string\n ariaLabel: string\n value: EditorResource[]\n onChange(next: EditorResource[]): void\n disabled?: boolean\n}) {\n const t = useT()\n const [query, setQuery] = React.useState('')\n const [open, setOpen] = React.useState(false)\n const [options, setOptions] = React.useState<ResourceOption[]>([])\n const [total, setTotal] = React.useState(0)\n const [loading, setLoading] = React.useState(false)\n const [types, setTypes] = React.useState<ResourceType[]>([])\n const [typeFilter, setTypeFilter] = React.useState<string | null>(null)\n const [typeMenuOpen, setTypeMenuOpen] = React.useState(false)\n const [typeQuery, setTypeQuery] = React.useState('')\n const close = React.useCallback(() => {\n setOpen(false)\n setTypeMenuOpen(false)\n }, [])\n const rootRef = useDropdownDismiss(open, close)\n\n // Load the type filter once the dropdown first opens.\n React.useEffect(() => {\n if (!open || types.length > 0) return\n const controller = new AbortController()\n let cancelled = false\n fetchResourceTypes(controller.signal).then((found) => {\n if (!cancelled) setTypes(found)\n })\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [open, types.length])\n\n React.useEffect(() => {\n if (!open) return\n const controller = new AbortController()\n let cancelled = false\n const timer = window.setTimeout(async () => {\n setLoading(true)\n try {\n const result = await searchResourceOptions({ query: query.trim(), resourceTypeId: typeFilter }, controller.signal)\n if (cancelled) return\n setOptions(result.items)\n setTotal(result.total)\n } catch {\n if (!cancelled) {\n setOptions([])\n setTotal(0)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }, 250)\n return () => {\n cancelled = true\n controller.abort()\n window.clearTimeout(timer)\n }\n }, [open, query, typeFilter])\n\n const selectedIds = new Set(value.map((resource) => resource.id))\n const visibleOptions = options.filter((option) => !selectedIds.has(option.id))\n const hiddenCount = Math.max(0, total - options.length)\n const activeType = typeFilter ? types.find((type) => type.id === typeFilter) ?? null : null\n const filteredTypes = typeQuery.trim().length\n ? types.filter((type) => type.name.toLowerCase().includes(typeQuery.trim().toLowerCase()))\n : types\n\n const selectType = (id: string | null) => {\n setTypeFilter(id)\n setTypeMenuOpen(false)\n setTypeQuery('')\n }\n\n return (\n <div\n ref={rootRef}\n className=\"relative w-full\"\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) close()\n }}\n >\n <div\n className={cn(\n 'flex min-h-14 w-full flex-wrap content-center items-center gap-2 rounded-md bg-background px-2.5 py-2',\n CONTROL_BORDER,\n )}\n >\n {value.map((resource) => (\n <span key={resource.id} className=\"inline-flex shrink-0 items-center gap-1.5 rounded-full bg-muted py-1 pl-2 pr-2\">\n <Box aria-hidden className=\"size-3.5 text-muted-foreground\" />\n <span className=\"max-w-40 truncate text-xs font-medium text-foreground\">{resource.label}</span>\n <IconButton\n variant=\"ghost\"\n size=\"xs\"\n onClick={(event) => {\n event.stopPropagation()\n onChange(value.filter((entry) => entry.id !== resource.id))\n }}\n aria-label={t('customers.calendar.editor.removeResource', 'Remove {name}', { name: resource.label })}\n disabled={disabled}\n className=\"size-5 shrink-0\"\n >\n <Plus aria-hidden className=\"size-3.5 rotate-45 opacity-50\" />\n </IconButton>\n </span>\n ))}\n <Input\n type=\"text\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onFocus={() => setOpen(true)}\n placeholder={value.length > 0 ? '' : placeholder}\n aria-label={ariaLabel}\n role=\"combobox\"\n aria-expanded={open}\n disabled={disabled}\n className=\"min-w-36 flex-1 border-0 bg-transparent px-0 shadow-none hover:bg-transparent focus-within:border-transparent focus-within:shadow-none\"\n inputClassName=\"text-sm text-foreground placeholder:text-muted-foreground\"\n />\n </div>\n {open ? (\n <div className={cn(DROPDOWN_PANEL_CLASS, 'flex max-h-none flex-col overflow-visible p-0')}>\n {types.length > 0 ? (\n <div className=\"border-b border-border p-1.5\">\n <Button\n type=\"button\"\n variant=\"outline\"\n aria-haspopup=\"listbox\"\n aria-expanded={typeMenuOpen}\n onClick={() => setTypeMenuOpen((previous) => !previous)}\n className={cn('h-8 w-full justify-between bg-background px-2 text-sm font-normal shadow-none', CONTROL_BORDER)}\n >\n <span className=\"truncate\">\n {activeType\n ? `${activeType.name} (${activeType.count})`\n : t('customers.calendar.editor.resourceTypes.all', 'All types')}\n </span>\n <ChevronDown aria-hidden className=\"size-4 shrink-0 opacity-60\" />\n </Button>\n </div>\n ) : null}\n\n {typeMenuOpen ? (\n <div role=\"listbox\" aria-label={t('customers.calendar.editor.resourceTypes.filterLabel', 'Resource type')} className=\"flex flex-col\">\n {types.length > TYPE_SEARCH_THRESHOLD ? (\n <div className=\"border-b border-border p-1.5\">\n <Input\n type=\"text\"\n value={typeQuery}\n onChange={(event) => setTypeQuery(event.target.value)}\n placeholder={t('customers.calendar.editor.resourceTypes.searchPlaceholder', 'Search types\u2026')}\n aria-label={t('customers.calendar.editor.resourceTypes.searchPlaceholder', 'Search types\u2026')}\n autoFocus\n className=\"h-8\"\n />\n </div>\n ) : null}\n <div className=\"max-h-56 overflow-y-auto p-1\">\n <TypeRow label={t('customers.calendar.editor.resourceTypes.all', 'All types')} active={typeFilter === null} onSelect={() => selectType(null)} />\n {filteredTypes.map((type) => (\n <TypeRow key={type.id} label={type.name} count={type.count} active={typeFilter === type.id} onSelect={() => selectType(type.id)} />\n ))}\n {filteredTypes.length === 0 ? (\n <p className=\"px-2 py-3 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.noResults', 'No results')}\n </p>\n ) : null}\n </div>\n </div>\n ) : (\n <>\n <div role=\"listbox\" aria-label={ariaLabel} className=\"max-h-56 overflow-y-auto p-1\">\n {loading ? (\n <p className=\"px-2 py-3 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.searching', 'Searching\u2026')}\n </p>\n ) : null}\n {!loading && visibleOptions.length === 0 ? (\n <p className=\"px-2 py-3 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.noResults', 'No results')}\n </p>\n ) : null}\n {!loading\n ? visibleOptions.map((option) => (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={false}\n title={option.label}\n onClick={() => {\n onChange([...value, { id: option.id, label: option.label }])\n setQuery('')\n }}\n className=\"h-auto w-full justify-start gap-2 whitespace-normal px-2 py-1.5 text-left text-sm font-normal text-foreground\"\n >\n <Box aria-hidden className=\"size-4 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{option.label}</span>\n </Button>\n ))\n : null}\n </div>\n {!loading && hiddenCount > 0 ? (\n <p className=\"border-t border-border px-2 py-1.5 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.resourceTypes.moreHint', 'Showing {shown} of {total} \u2014 keep typing to narrow', {\n shown: options.length,\n total,\n })}\n </p>\n ) : null}\n </>\n )}\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction TypeRow({ label, count, active, onSelect }: { label: string; count?: number; active: boolean; onSelect(): void }) {\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={active}\n onClick={onSelect}\n className={cn('h-auto w-full justify-start gap-2 px-2 py-1.5 text-left text-sm font-normal', active ? 'bg-muted text-foreground' : 'text-foreground')}\n >\n <Check aria-hidden className={cn('size-4 shrink-0', active ? 'opacity-100' : 'opacity-0')} />\n <span className=\"min-w-0 flex-1 truncate\">{label}</span>\n {typeof count === 'number' ? <span className=\"shrink-0 tabular-nums text-xs text-muted-foreground\">{count}</span> : null}\n </Button>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -16,14 +16,17 @@ function DateTimeRow({
|
|
|
16
16
|
time,
|
|
17
17
|
showTime,
|
|
18
18
|
onDateChange,
|
|
19
|
-
onTimeChange
|
|
19
|
+
onTimeChange,
|
|
20
|
+
error,
|
|
21
|
+
disabled
|
|
20
22
|
}) {
|
|
21
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex w-full items-end gap-2.5", children: [
|
|
23
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-wrap items-end gap-x-2.5 gap-y-1", children: [
|
|
22
24
|
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1.5", children: [
|
|
23
25
|
/* @__PURE__ */ jsx("span", { className: LABEL_CLASS, children: label }),
|
|
24
|
-
/* @__PURE__ */ jsx(DateControl, { value: date, onChange: onDateChange, ariaLabel: label })
|
|
26
|
+
/* @__PURE__ */ jsx(DateControl, { value: date, onChange: onDateChange, ariaLabel: label, disabled })
|
|
25
27
|
] }),
|
|
26
|
-
showTime ? /* @__PURE__ */ jsx(TimeControl, { value: time, onChange: onTimeChange, ariaLabel: label }) : null
|
|
28
|
+
showTime ? /* @__PURE__ */ jsx(TimeControl, { value: time, onChange: onTimeChange, ariaLabel: label, disabled }) : null,
|
|
29
|
+
error ? /* @__PURE__ */ jsx("p", { className: "w-full text-xs text-status-error-text", children: error }) : null
|
|
27
30
|
] });
|
|
28
31
|
}
|
|
29
32
|
function ScheduleSection({
|
|
@@ -35,7 +38,9 @@ function ScheduleSection({
|
|
|
35
38
|
startTime,
|
|
36
39
|
endDate,
|
|
37
40
|
endTime,
|
|
41
|
+
startError,
|
|
38
42
|
endsError,
|
|
43
|
+
disabled,
|
|
39
44
|
onAllDayChange,
|
|
40
45
|
onDateChange,
|
|
41
46
|
onStartTimeChange,
|
|
@@ -48,7 +53,7 @@ function ScheduleSection({
|
|
|
48
53
|
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-2.5", children: [
|
|
49
54
|
hasAllDay ? /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-between", children: [
|
|
50
55
|
/* @__PURE__ */ jsx("span", { className: LABEL_CLASS, children: t("customers.calendar.editor.allDay", "All day") }),
|
|
51
|
-
/* @__PURE__ */ jsx(AllDayToggle, { checked: allDay, onCheckedChange: onAllDayChange, label: t("customers.calendar.editor.allDay", "All day") })
|
|
56
|
+
/* @__PURE__ */ jsx(AllDayToggle, { checked: allDay, onCheckedChange: onAllDayChange, label: t("customers.calendar.editor.allDay", "All day"), disabled })
|
|
52
57
|
] }) : null,
|
|
53
58
|
/* @__PURE__ */ jsx(
|
|
54
59
|
DateTimeRow,
|
|
@@ -58,7 +63,9 @@ function ScheduleSection({
|
|
|
58
63
|
time: startTime,
|
|
59
64
|
showTime,
|
|
60
65
|
onDateChange,
|
|
61
|
-
onTimeChange: onStartTimeChange
|
|
66
|
+
onTimeChange: onStartTimeChange,
|
|
67
|
+
error: startError,
|
|
68
|
+
disabled
|
|
62
69
|
}
|
|
63
70
|
),
|
|
64
71
|
hasEnd ? /* @__PURE__ */ jsx(
|
|
@@ -69,7 +76,8 @@ function ScheduleSection({
|
|
|
69
76
|
time: endTime,
|
|
70
77
|
showTime,
|
|
71
78
|
onDateChange: onEndDateChange,
|
|
72
|
-
onTimeChange: onEndTimeChange
|
|
79
|
+
onTimeChange: onEndTimeChange,
|
|
80
|
+
disabled
|
|
73
81
|
}
|
|
74
82
|
) : null,
|
|
75
83
|
endsError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-status-error-text", children: endsError }) : null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/calendar/editor/ScheduleSection.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { EditorDateLabel } from '../../../lib/calendar/editorPayload'\nimport { multiDayEventSpan } from '../../../lib/calendar/labels'\nimport { AllDayToggle, DateControl, LABEL_CLASS, TimeControl } from './inputs'\n\nconst DATE_LABEL_TEXT: Record<EditorDateLabel, { key: string; fallback: string }> = {\n starts: { key: 'customers.calendar.editor.dates.starts', fallback: 'Starts' },\n when: { key: 'customers.calendar.editor.dates.when', fallback: 'When' },\n sent: { key: 'customers.calendar.editor.dates.sent', fallback: 'Sent' },\n logged: { key: 'customers.calendar.editor.dates.logged', fallback: 'Logged' },\n due: { key: 'customers.calendar.editor.dates.due', fallback: 'Due' },\n}\n\nfunction DateTimeRow({\n label,\n date,\n time,\n showTime,\n onDateChange,\n onTimeChange,\n}: {\n label: string\n date: string\n time: string\n showTime: boolean\n onDateChange(next: string): void\n onTimeChange(next: string): void\n}) {\n return (\n <div className=\"flex w-full items-end gap-2.5\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-1.5\">\n <span className={LABEL_CLASS}>{label}</span>\n <DateControl value={date} onChange={onDateChange} ariaLabel={label} />\n </div>\n {showTime ? <TimeControl value={time} onChange={onTimeChange} ariaLabel={label} /> : null}\n </div>\n )\n}\n\nexport function ScheduleSection({\n dateLabel,\n hasAllDay,\n hasEnd,\n allDay,\n date,\n startTime,\n endDate,\n endTime,\n endsError,\n onAllDayChange,\n onDateChange,\n onStartTimeChange,\n onEndDateChange,\n onEndTimeChange,\n}: {\n dateLabel: EditorDateLabel\n hasAllDay: boolean\n hasEnd: boolean\n allDay: boolean\n date: string\n startTime: string\n endDate: string\n endTime: string\n endsError?: string | null\n onAllDayChange(next: boolean): void\n onDateChange(next: string): void\n onStartTimeChange(next: string): void\n onEndDateChange(next: string): void\n onEndTimeChange(next: string): void\n}) {\n const t = useT()\n const showTime = !(hasAllDay && allDay)\n const multiDaySpan = hasEnd && !endsError ? multiDayEventSpan(date, endDate) : 0\n return (\n <div className=\"flex w-full flex-col gap-2.5\">\n {hasAllDay ? (\n <div className=\"flex w-full items-center justify-between\">\n <span className={LABEL_CLASS}>{t('customers.calendar.editor.allDay', 'All day')}</span>\n <AllDayToggle checked={allDay} onCheckedChange={onAllDayChange} label={t('customers.calendar.editor.allDay', 'All day')} />\n </div>\n ) : null}\n <DateTimeRow\n label={t(DATE_LABEL_TEXT[dateLabel].key, DATE_LABEL_TEXT[dateLabel].fallback)}\n date={date}\n time={startTime}\n showTime={showTime}\n onDateChange={onDateChange}\n onTimeChange={onStartTimeChange}\n />\n {hasEnd ? (\n <DateTimeRow\n label={t('customers.calendar.editor.dates.ends', 'Ends')}\n date={endDate}\n time={endTime}\n showTime={showTime}\n onDateChange={onEndDateChange}\n onTimeChange={onEndTimeChange}\n />\n ) : null}\n {endsError ? <p className=\"text-xs text-status-error-text\">{endsError}</p> : null}\n {multiDaySpan > 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.calendar.editor.multiDayHint', 'Multi-day event \u00B7 {count} days', { count: multiDaySpan })}\n </p>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { EditorDateLabel } from '../../../lib/calendar/editorPayload'\nimport { multiDayEventSpan } from '../../../lib/calendar/labels'\nimport { AllDayToggle, DateControl, LABEL_CLASS, TimeControl } from './inputs'\n\nconst DATE_LABEL_TEXT: Record<EditorDateLabel, { key: string; fallback: string }> = {\n starts: { key: 'customers.calendar.editor.dates.starts', fallback: 'Starts' },\n when: { key: 'customers.calendar.editor.dates.when', fallback: 'When' },\n sent: { key: 'customers.calendar.editor.dates.sent', fallback: 'Sent' },\n logged: { key: 'customers.calendar.editor.dates.logged', fallback: 'Logged' },\n due: { key: 'customers.calendar.editor.dates.due', fallback: 'Due' },\n}\n\nfunction DateTimeRow({\n label,\n date,\n time,\n showTime,\n onDateChange,\n onTimeChange,\n error,\n disabled,\n}: {\n label: string\n date: string\n time: string\n showTime: boolean\n onDateChange(next: string): void\n onTimeChange(next: string): void\n error?: string | null\n disabled?: boolean\n}) {\n return (\n <div className=\"flex w-full flex-wrap items-end gap-x-2.5 gap-y-1\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-1.5\">\n <span className={LABEL_CLASS}>{label}</span>\n <DateControl value={date} onChange={onDateChange} ariaLabel={label} disabled={disabled} />\n </div>\n {showTime ? <TimeControl value={time} onChange={onTimeChange} ariaLabel={label} disabled={disabled} /> : null}\n {error ? <p className=\"w-full text-xs text-status-error-text\">{error}</p> : null}\n </div>\n )\n}\n\nexport function ScheduleSection({\n dateLabel,\n hasAllDay,\n hasEnd,\n allDay,\n date,\n startTime,\n endDate,\n endTime,\n startError,\n endsError,\n disabled,\n onAllDayChange,\n onDateChange,\n onStartTimeChange,\n onEndDateChange,\n onEndTimeChange,\n}: {\n dateLabel: EditorDateLabel\n hasAllDay: boolean\n hasEnd: boolean\n allDay: boolean\n date: string\n startTime: string\n endDate: string\n endTime: string\n startError?: string | null\n endsError?: string | null\n disabled?: boolean\n onAllDayChange(next: boolean): void\n onDateChange(next: string): void\n onStartTimeChange(next: string): void\n onEndDateChange(next: string): void\n onEndTimeChange(next: string): void\n}) {\n const t = useT()\n const showTime = !(hasAllDay && allDay)\n const multiDaySpan = hasEnd && !endsError ? multiDayEventSpan(date, endDate) : 0\n return (\n <div className=\"flex w-full flex-col gap-2.5\">\n {hasAllDay ? (\n <div className=\"flex w-full items-center justify-between\">\n <span className={LABEL_CLASS}>{t('customers.calendar.editor.allDay', 'All day')}</span>\n <AllDayToggle checked={allDay} onCheckedChange={onAllDayChange} label={t('customers.calendar.editor.allDay', 'All day')} disabled={disabled} />\n </div>\n ) : null}\n <DateTimeRow\n label={t(DATE_LABEL_TEXT[dateLabel].key, DATE_LABEL_TEXT[dateLabel].fallback)}\n date={date}\n time={startTime}\n showTime={showTime}\n onDateChange={onDateChange}\n onTimeChange={onStartTimeChange}\n error={startError}\n disabled={disabled}\n />\n {hasEnd ? (\n <DateTimeRow\n label={t('customers.calendar.editor.dates.ends', 'Ends')}\n date={endDate}\n time={endTime}\n showTime={showTime}\n onDateChange={onEndDateChange}\n onTimeChange={onEndTimeChange}\n disabled={disabled}\n />\n ) : null}\n {endsError ? <p className=\"text-xs text-status-error-text\">{endsError}</p> : null}\n {multiDaySpan > 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {t('customers.calendar.editor.multiDayHint', 'Multi-day event \u00B7 {count} days', { count: multiDaySpan })}\n </p>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAqCM,SACE,KADF;AAlCN,SAAS,YAAY;AAErB,SAAS,yBAAyB;AAClC,SAAS,cAAc,aAAa,aAAa,mBAAmB;AAEpE,MAAM,kBAA8E;AAAA,EAClF,QAAQ,EAAE,KAAK,0CAA0C,UAAU,SAAS;AAAA,EAC5E,MAAM,EAAE,KAAK,wCAAwC,UAAU,OAAO;AAAA,EACtE,MAAM,EAAE,KAAK,wCAAwC,UAAU,OAAO;AAAA,EACtE,QAAQ,EAAE,KAAK,0CAA0C,UAAU,SAAS;AAAA,EAC5E,KAAK,EAAE,KAAK,uCAAuC,UAAU,MAAM;AACrE;AAEA,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GASG;AACD,SACE,qBAAC,SAAI,WAAU,qDACb;AAAA,yBAAC,SAAI,WAAU,wCACb;AAAA,0BAAC,UAAK,WAAW,aAAc,iBAAM;AAAA,MACrC,oBAAC,eAAY,OAAO,MAAM,UAAU,cAAc,WAAW,OAAO,UAAoB;AAAA,OAC1F;AAAA,IACC,WAAW,oBAAC,eAAY,OAAO,MAAM,UAAU,cAAc,WAAW,OAAO,UAAoB,IAAK;AAAA,IACxG,QAAQ,oBAAC,OAAE,WAAU,yCAAyC,iBAAM,IAAO;AAAA,KAC9E;AAEJ;AAEO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAiBG;AACD,QAAM,IAAI,KAAK;AACf,QAAM,WAAW,EAAE,aAAa;AAChC,QAAM,eAAe,UAAU,CAAC,YAAY,kBAAkB,MAAM,OAAO,IAAI;AAC/E,SACE,qBAAC,SAAI,WAAU,gCACZ;AAAA,gBACC,qBAAC,SAAI,WAAU,4CACb;AAAA,0BAAC,UAAK,WAAW,aAAc,YAAE,oCAAoC,SAAS,GAAE;AAAA,MAChF,oBAAC,gBAAa,SAAS,QAAQ,iBAAiB,gBAAgB,OAAO,EAAE,oCAAoC,SAAS,GAAG,UAAoB;AAAA,OAC/I,IACE;AAAA,IACJ;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,gBAAgB,SAAS,EAAE,KAAK,gBAAgB,SAAS,EAAE,QAAQ;AAAA,QAC5E;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,cAAc;AAAA,QACd,OAAO;AAAA,QACP;AAAA;AAAA,IACF;AAAA,IACC,SACC;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,wCAAwC,MAAM;AAAA,QACvD,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA,cAAc;AAAA,QACd,cAAc;AAAA,QACd;AAAA;AAAA,IACF,IACE;AAAA,IACH,YAAY,oBAAC,OAAE,WAAU,kCAAkC,qBAAU,IAAO;AAAA,IAC5E,eAAe,IACd,oBAAC,OAAE,WAAU,iCACV,YAAE,0CAA0C,qCAAkC,EAAE,OAAO,aAAa,CAAC,GACxG,IACE;AAAA,KACN;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -7,7 +7,8 @@ function SegmentGroup({
|
|
|
7
7
|
value,
|
|
8
8
|
onChange,
|
|
9
9
|
size = "sm",
|
|
10
|
-
ariaLabel
|
|
10
|
+
ariaLabel,
|
|
11
|
+
disabled = false
|
|
11
12
|
}) {
|
|
12
13
|
return /* @__PURE__ */ jsx(
|
|
13
14
|
"div",
|
|
@@ -24,6 +25,7 @@ function SegmentGroup({
|
|
|
24
25
|
variant: "ghost",
|
|
25
26
|
"aria-pressed": isActive,
|
|
26
27
|
onClick: () => onChange(option.value),
|
|
28
|
+
disabled,
|
|
27
29
|
title: option.label,
|
|
28
30
|
"data-state": isActive ? "active" : "inactive",
|
|
29
31
|
className: cn(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/calendar/editor/SegmentGroup.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\n\nexport type SegmentOption<T extends string> = { value: T; label: string; icon?: React.ReactNode }\n\nexport function SegmentGroup<T extends string>({\n options,\n value,\n onChange,\n size = 'sm',\n ariaLabel,\n}: {\n options: Array<SegmentOption<T>>\n value: T\n onChange(next: T): void\n size?: 'sm' | 'md'\n ariaLabel: string\n}) {\n return (\n <div\n role=\"group\"\n aria-label={ariaLabel}\n className=\"flex w-full items-stretch overflow-hidden rounded-md border border-border bg-background\"\n >\n {options.map((option, index) => {\n const isActive = option.value === value\n return (\n <Button\n key={option.value}\n type=\"button\"\n variant=\"ghost\"\n aria-pressed={isActive}\n onClick={() => onChange(option.value)}\n title={option.label}\n data-state={isActive ? 'active' : 'inactive'}\n className={cn(\n 'h-auto min-w-0 flex-1 justify-center gap-1 rounded-none border-0 px-1.5 text-sm font-medium leading-5 shadow-none',\n size === 'md' ? 'py-2' : 'py-1.5',\n index > 0 && 'border-l border-border',\n isActive\n // Keep the indigo fill on hover in BOTH themes \u2014 the ghost Button\n // ships `dark:hover:bg-accent/50`, which would otherwise wash the\n // active segment out on hover in dark mode.\n ? 'bg-accent-indigo font-semibold text-accent-indigo-foreground hover:bg-accent-indigo hover:text-accent-indigo-foreground dark:hover:bg-accent-indigo'\n : 'bg-background text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {option.icon ? <span aria-hidden className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')}>{option.icon}</span> : null}\n <span className=\"truncate\">{option.label}</span>\n </Button>\n )\n })}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\n\nexport type SegmentOption<T extends string> = { value: T; label: string; icon?: React.ReactNode }\n\nexport function SegmentGroup<T extends string>({\n options,\n value,\n onChange,\n size = 'sm',\n ariaLabel,\n disabled = false,\n}: {\n options: Array<SegmentOption<T>>\n value: T\n onChange(next: T): void\n size?: 'sm' | 'md'\n ariaLabel: string\n disabled?: boolean\n}) {\n return (\n <div\n role=\"group\"\n aria-label={ariaLabel}\n className=\"flex w-full items-stretch overflow-hidden rounded-md border border-border bg-background\"\n >\n {options.map((option, index) => {\n const isActive = option.value === value\n return (\n <Button\n key={option.value}\n type=\"button\"\n variant=\"ghost\"\n aria-pressed={isActive}\n onClick={() => onChange(option.value)}\n disabled={disabled}\n title={option.label}\n data-state={isActive ? 'active' : 'inactive'}\n className={cn(\n 'h-auto min-w-0 flex-1 justify-center gap-1 rounded-none border-0 px-1.5 text-sm font-medium leading-5 shadow-none',\n size === 'md' ? 'py-2' : 'py-1.5',\n index > 0 && 'border-l border-border',\n isActive\n // Keep the indigo fill on hover in BOTH themes \u2014 the ghost Button\n // ships `dark:hover:bg-accent/50`, which would otherwise wash the\n // active segment out on hover in dark mode.\n ? 'bg-accent-indigo font-semibold text-accent-indigo-foreground hover:bg-accent-indigo hover:text-accent-indigo-foreground dark:hover:bg-accent-indigo'\n : 'bg-background text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {option.icon ? <span aria-hidden className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')}>{option.icon}</span> : null}\n <span className=\"truncate\">{option.label}</span>\n </Button>\n )\n })}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": ";AAgCU,SAqBiB,KArBjB;AA7BV,SAAS,UAAU;AACnB,SAAS,cAAc;AAIhB,SAAS,aAA+B;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,WAAW;AACb,GAOG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,WAAU;AAAA,MAET,kBAAQ,IAAI,CAAC,QAAQ,UAAU;AAC9B,cAAM,WAAW,OAAO,UAAU;AAClC,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,gBAAc;AAAA,YACd,SAAS,MAAM,SAAS,OAAO,KAAK;AAAA,YACpC;AAAA,YACA,OAAO,OAAO;AAAA,YACd,cAAY,WAAW,WAAW;AAAA,YAClC,WAAW;AAAA,cACT;AAAA,cACA,SAAS,OAAO,SAAS;AAAA,cACzB,QAAQ,KAAK;AAAA,cACb,WAII,wJACA;AAAA,YACN;AAAA,YAEC;AAAA,qBAAO,OAAO,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,YAAY,WAAW,gBAAgB,YAAY,GAAI,iBAAO,MAAK,IAAU;AAAA,cAC5H,oBAAC,UAAK,WAAU,YAAY,iBAAO,OAAM;AAAA;AAAA;AAAA,UArBpC,OAAO;AAAA,QAsBd;AAAA,MAEJ,CAAC;AAAA;AAAA,EACH;AAEJ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import * as React from "react";
|
|
3
|
+
import { calendarDayEndInstant, calendarDayStartInstant, calendarWallTimeToInstant } from "../../../lib/calendar/timezone.js";
|
|
3
4
|
import { endOfDay } from "date-fns/endOfDay";
|
|
4
5
|
import { startOfDay } from "date-fns/startOfDay";
|
|
5
6
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
6
7
|
import { computeDurationMinutes } from "../../../lib/calendar/editorPayload.js";
|
|
7
|
-
import {
|
|
8
|
+
import { findEditorConflicts } from "../../../lib/calendar/conflicts.js";
|
|
8
9
|
import { mapInteractionToCalendarItem } from "../../../lib/calendar/mapItem.js";
|
|
9
10
|
import { participantActorKey } from "../../../lib/calendar/participantIdentity.js";
|
|
10
11
|
import { expandOccurrences } from "../../../lib/calendar/recurrence.js";
|
|
@@ -44,12 +45,14 @@ function useEditorLabelResolution(open, form, update) {
|
|
|
44
45
|
};
|
|
45
46
|
}, [activeOrgId, open, form.relatedTo, form.dealId, form.dealLabel, form.assigneeUserId, form.assigneeName, update]);
|
|
46
47
|
}
|
|
48
|
+
const NO_DRAFT_RESOURCES = [];
|
|
47
49
|
function formatClock(date) {
|
|
48
50
|
return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
49
51
|
}
|
|
50
|
-
function useConflictProbe(open, form, config, excludeId, draftOwnerUserId, scope, currentUserId) {
|
|
52
|
+
function useConflictProbe(open, form, config, excludeId, draftOwnerUserId, scope, currentUserId, draftResources = NO_DRAFT_RESOURCES) {
|
|
51
53
|
const t = useT();
|
|
52
54
|
const [conflict, setConflict] = React.useState(null);
|
|
55
|
+
const resourcesKey = draftResources.map((resource) => resource.id).join(",");
|
|
53
56
|
const participantsKey = form.participants.map((participant) => participantActorKey(participant) ?? "").join(",");
|
|
54
57
|
React.useEffect(() => {
|
|
55
58
|
if (!open || !form.date) {
|
|
@@ -60,23 +63,29 @@ function useConflictProbe(open, form, config, excludeId, draftOwnerUserId, scope
|
|
|
60
63
|
let start;
|
|
61
64
|
let end;
|
|
62
65
|
if (isAllDay) {
|
|
63
|
-
const dayDate = /* @__PURE__ */ new Date(`${form.date}T00:00:00`);
|
|
64
|
-
if (Number.isNaN(dayDate.getTime())) {
|
|
66
|
+
const dayDate = form.timezone ? calendarDayStartInstant(form.date, form.timezone) : /* @__PURE__ */ new Date(`${form.date}T00:00:00`);
|
|
67
|
+
if (!dayDate || Number.isNaN(dayDate.getTime())) {
|
|
65
68
|
setConflict(null);
|
|
66
69
|
return;
|
|
67
70
|
}
|
|
68
|
-
start = startOfDay(dayDate);
|
|
69
|
-
|
|
71
|
+
start = form.timezone ? dayDate : startOfDay(dayDate);
|
|
72
|
+
const dayEnd2 = form.timezone ? calendarDayEndInstant(form.date, form.timezone) : endOfDay(dayDate);
|
|
73
|
+
if (!dayEnd2) {
|
|
74
|
+
setConflict(null);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
end = dayEnd2;
|
|
70
78
|
} else {
|
|
71
79
|
if (!form.startTime) {
|
|
72
80
|
setConflict(null);
|
|
73
81
|
return;
|
|
74
82
|
}
|
|
75
|
-
|
|
76
|
-
if (Number.isNaN(
|
|
83
|
+
const draftStart = form.timezone ? calendarWallTimeToInstant(form.date, form.startTime, form.timezone) : /* @__PURE__ */ new Date(`${form.date}T${form.startTime}:00`);
|
|
84
|
+
if (!draftStart || Number.isNaN(draftStart.getTime())) {
|
|
77
85
|
setConflict(null);
|
|
78
86
|
return;
|
|
79
87
|
}
|
|
88
|
+
start = draftStart;
|
|
80
89
|
const durationMinutes = config.hasEnd ? computeDurationMinutes(form) ?? 30 : 30;
|
|
81
90
|
end = new Date(start.getTime() + durationMinutes * 6e4);
|
|
82
91
|
}
|
|
@@ -97,24 +106,30 @@ function useConflictProbe(open, form, config, excludeId, draftOwnerUserId, scope
|
|
|
97
106
|
if (!item) continue;
|
|
98
107
|
others.push(...expandOccurrences(item, fetchWindow));
|
|
99
108
|
}
|
|
100
|
-
const
|
|
109
|
+
const conflicts = findEditorConflicts(
|
|
101
110
|
{
|
|
102
111
|
start,
|
|
103
112
|
end,
|
|
104
113
|
ownerUserId: draftOwnerUserId,
|
|
105
114
|
participants: form.participants.map((participant) => ({ userId: participant.userId, name: participant.name })),
|
|
106
|
-
status: form.status
|
|
115
|
+
status: form.status,
|
|
116
|
+
resources: [...draftResources],
|
|
117
|
+
ownerLabel: draftOwnerUserId && draftOwnerUserId === form.assigneeUserId ? form.assigneeName : null
|
|
107
118
|
},
|
|
108
119
|
others,
|
|
109
120
|
excludeId,
|
|
110
121
|
{ scope, currentUserId }
|
|
111
122
|
);
|
|
112
123
|
if (!active) return;
|
|
113
|
-
if (
|
|
124
|
+
if (conflicts.length === 0) {
|
|
114
125
|
setConflict(null);
|
|
115
126
|
return;
|
|
116
127
|
}
|
|
117
|
-
const summary =
|
|
128
|
+
const summary = conflicts.map(({ item, shared }) => {
|
|
129
|
+
const names = shared.map((subject) => subject.label).filter(Boolean);
|
|
130
|
+
const entry = `${formatClock(item.start)}\u2013${formatClock(item.end)}: ${item.title || t("customers.calendar.grid.untitled", "Untitled")}`;
|
|
131
|
+
return names.length ? `${entry} (${names.join(", ")})` : entry;
|
|
132
|
+
}).join(", ");
|
|
118
133
|
setConflict(summary ? t("customers.calendar.editor.conflictWarning", "Overlaps with: {items}", { items: summary }) : null);
|
|
119
134
|
} catch {
|
|
120
135
|
if (active) setConflict(null);
|
|
@@ -125,7 +140,7 @@ function useConflictProbe(open, form, config, excludeId, draftOwnerUserId, scope
|
|
|
125
140
|
clearTimeout(timer);
|
|
126
141
|
controller.abort();
|
|
127
142
|
};
|
|
128
|
-
}, [open, form.date, form.startTime, form.endDate, form.endTime, form.allDay, form.status, participantsKey, config.hasAllDay, config.hasEnd, excludeId, draftOwnerUserId, scope, currentUserId, t]);
|
|
143
|
+
}, [open, form.date, form.timezone, form.startTime, form.endDate, form.endTime, form.allDay, form.status, participantsKey, resourcesKey, config.hasAllDay, config.hasEnd, excludeId, draftOwnerUserId, scope, currentUserId, t]);
|
|
129
144
|
return conflict;
|
|
130
145
|
}
|
|
131
146
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/calendar/editor/hooks.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { endOfDay } from 'date-fns/endOfDay'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { computeDurationMinutes, type EditorFormState, type EditorKindConfig } from '../../../lib/calendar/editorPayload'\nimport {
|
|
5
|
-
"mappings": ";AAEA,YAAY,WAAW;AACvB,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AACrB,SAAS,8BAA2E;AACpF,SAAS
|
|
6
|
-
"names": []
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { calendarDayEndInstant, calendarDayStartInstant, calendarWallTimeToInstant } from '../../../lib/calendar/timezone'\nimport { endOfDay } from 'date-fns/endOfDay'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { computeDurationMinutes, type EditorFormState, type EditorKindConfig } from '../../../lib/calendar/editorPayload'\nimport { findEditorConflicts } from '../../../lib/calendar/conflicts'\nimport type { ConflictScope } from '../../../lib/calendar/preferences'\nimport { mapInteractionToCalendarItem } from '../../../lib/calendar/mapItem'\nimport { participantActorKey } from '../../../lib/calendar/participantIdentity'\nimport { expandOccurrences } from '../../../lib/calendar/recurrence'\nimport { getFetchWindow } from '../../../lib/calendar/range'\nimport type { CalendarItem } from '../types'\nimport { fetchCalendarCandidates } from '../useCalendarItems'\nimport { useCurrentOrganization } from '@open-mercato/ui/backend/BackendChromeProvider'\nimport { fetchDealById, fetchRelatedEntityById, findStaffMemberName } from './lookups'\n\n// Edit-mode prefill stores ids only (parseItemToFormState is pure); resolve the\n// human labels for the related entity, the deal chip and the task assignee.\nexport function useEditorLabelResolution(\n open: boolean,\n form: EditorFormState,\n update: (patch: Partial<EditorFormState>) => void,\n): void {\n const activeOrgId = useCurrentOrganization()?.id ?? null\n React.useEffect(() => {\n if (!open) return\n const controller = new AbortController()\n let cancelled = false\n async function resolveLabels() {\n if (form.relatedTo && form.relatedTo.kind === 'unknown') {\n const resolved = await fetchRelatedEntityById(form.relatedTo.id, controller.signal)\n if (!cancelled && resolved) update({ relatedTo: { id: resolved.id, kind: resolved.kind, label: resolved.label } })\n }\n if (form.dealId && !form.dealLabel) {\n const deal = await fetchDealById(form.dealId, controller.signal)\n if (!cancelled && deal) update({ dealLabel: deal.label })\n }\n if (form.assigneeUserId && !form.assigneeName) {\n const name = await findStaffMemberName(form.assigneeUserId, controller.signal, activeOrgId)\n if (!cancelled && name) update({ assigneeName: name })\n }\n }\n resolveLabels().catch(() => {\n if (cancelled || controller.signal.aborted || !form.relatedTo || form.relatedTo.kind !== 'unknown' || form.relatedTo.label) {\n return\n }\n update({ relatedTo: { ...form.relatedTo, label: form.relatedTo.id } })\n })\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [activeOrgId, open, form.relatedTo, form.dealId, form.dealLabel, form.assigneeUserId, form.assigneeName, update])\n}\n\nconst NO_DRAFT_RESOURCES: ReadonlyArray<{ id: string; label: string }> = []\n\nfunction formatClock(date: Date): string {\n return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n}\n\n// Debounced save-time conflict probe. Detection uses the SAME `findConflicts`\n// logic the grid uses (overlap + shared owner/participant/resource) against a freshly\n// fetched \u00B11-day window, so the editor warning is always consistent with the\n// conflict badges/rings the user sees on the calendar. The warning is\n// informational and never blocks saving.\nexport function useConflictProbe(\n open: boolean,\n form: EditorFormState,\n config: EditorKindConfig,\n excludeId: string | null,\n draftOwnerUserId: string | null,\n scope: ConflictScope,\n currentUserId: string | null,\n draftResources: ReadonlyArray<{ id: string; label: string }> = NO_DRAFT_RESOURCES,\n): string | null {\n const t = useT()\n const [conflict, setConflict] = React.useState<string | null>(null)\n const resourcesKey = draftResources.map((resource) => resource.id).join(',')\n // Keys on the canonical actor key, not the raw userId \u2014 an external guest has\n // no userId, so a userId-only key never changes when guests are added or\n // removed and the probe would keep reporting the previous attendee set.\n const participantsKey = form.participants\n .map((participant) => participantActorKey(participant) ?? '')\n .join(',')\n React.useEffect(() => {\n if (!open || !form.date) {\n setConflict(null)\n return\n }\n // Resolve the draft's [start, end] span exactly as the grid will after save:\n // all-day spans startOfDay..endOfDay (matching mapInteractionToCalendarItem),\n // timed events use the start + computed duration (multi-day endDate included).\n const isAllDay = config.hasAllDay && form.allDay\n let start: Date\n let end: Date\n if (isAllDay) {\n const dayDate = form.timezone ? calendarDayStartInstant(form.date, form.timezone) : new Date(`${form.date}T00:00:00`)\n if (!dayDate || Number.isNaN(dayDate.getTime())) {\n setConflict(null)\n return\n }\n start = form.timezone ? dayDate : startOfDay(dayDate)\n const dayEnd = form.timezone ? calendarDayEndInstant(form.date, form.timezone) : endOfDay(dayDate)\n if (!dayEnd) { setConflict(null); return }\n end = dayEnd\n } else {\n if (!form.startTime) {\n setConflict(null)\n return\n }\n const draftStart = form.timezone ? calendarWallTimeToInstant(form.date, form.startTime, form.timezone) : new Date(`${form.date}T${form.startTime}:00`)\n if (!draftStart || Number.isNaN(draftStart.getTime())) {\n setConflict(null)\n return\n }\n start = draftStart\n const durationMinutes = config.hasEnd ? computeDurationMinutes(form) ?? 30 : 30\n end = new Date(start.getTime() + durationMinutes * 60_000)\n }\n // The same padded window + recurrence expansion the grid uses, so the probe\n // sees the exact candidate set behind the grid's conflict badges/rings. The\n // window pads \u00B11 day around the FULL draft span so multi-day drafts still\n // catch neighbours near their end, not just their start day.\n const dayStart = startOfDay(start)\n dayStart.setDate(dayStart.getDate() - 1)\n const dayEnd = endOfDay(end)\n dayEnd.setDate(dayEnd.getDate() + 1)\n const fetchWindow = getFetchWindow({ from: dayStart, to: dayEnd })\n const controller = new AbortController()\n let active = true\n const timer = setTimeout(async () => {\n try {\n const { payloads } = await fetchCalendarCandidates(fetchWindow, controller.signal)\n if (!active) return\n const others: CalendarItem[] = []\n for (const payload of payloads) {\n const item = mapInteractionToCalendarItem(payload, {})\n if (!item) continue\n others.push(...expandOccurrences(item, fetchWindow))\n }\n const conflicts = findEditorConflicts(\n {\n start,\n end,\n ownerUserId: draftOwnerUserId,\n participants: form.participants.map((participant) => ({ userId: participant.userId, name: participant.name })),\n status: form.status,\n resources: [...draftResources],\n ownerLabel: draftOwnerUserId && draftOwnerUserId === form.assigneeUserId ? form.assigneeName : null,\n },\n others,\n excludeId,\n { scope, currentUserId },\n )\n if (!active) return\n if (conflicts.length === 0) {\n setConflict(null)\n return\n }\n // Name who and what is double-booked: an overlap alone does not tell the\n // user which attendee or room to change.\n const summary = conflicts\n .map(({ item, shared }) => {\n const names = shared.map((subject) => subject.label).filter(Boolean)\n const entry = `${formatClock(item.start)}\u2013${formatClock(item.end)}: ${item.title || t('customers.calendar.grid.untitled', 'Untitled')}`\n return names.length ? `${entry} (${names.join(', ')})` : entry\n })\n .join(', ')\n setConflict(summary ? t('customers.calendar.editor.conflictWarning', 'Overlaps with: {items}', { items: summary }) : null)\n } catch {\n if (active) setConflict(null)\n }\n }, 500)\n return () => {\n active = false\n clearTimeout(timer)\n controller.abort()\n }\n // Re-probe when schedule-relevant inputs change (time window, all-day, attendees, owner, status, scope).\n }, [open, form.date, form.timezone, form.startTime, form.endDate, form.endTime, form.allDay, form.status, participantsKey, resourcesKey, config.hasAllDay, config.hasEnd, excludeId, draftOwnerUserId, scope, currentUserId, t]) // eslint-disable-line react-hooks/exhaustive-deps\n return conflict\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["dayEnd"]
|
|
7
7
|
}
|