@open-mercato/core 0.6.6-develop.6205.1.109e4b6a84 → 0.6.6-develop.6227.1.2695efff30
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/modules/currencies/backend/currencies/[id]/page.js +33 -8
- package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
- package/dist/modules/currencies/backend/currencies/page.js +63 -27
- package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
- package/dist/modules/currencies/backend/exchange-rates/page.js +35 -14
- package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js +104 -41
- package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +57 -8
- package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +5 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +30 -14
- package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +29 -19
- package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +166 -64
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
- package/dist/modules/customers/components/AddressFormatSettings.js +31 -16
- package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
- package/dist/modules/customers/components/DictionarySettings.js +57 -31
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/PipelineSettings.js +156 -76
- package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/AgendaList.js +10 -4
- package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +18 -2
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js +5 -2
- package/dist/modules/customers/components/calendar/MonthGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +6 -0
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js +2 -0
- package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/lib/calendar/labels.js +33 -0
- package/dist/modules/customers/lib/calendar/labels.js.map +7 -0
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +32 -11
- package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/[id]/route.js +59 -0
- package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/mappings/route.js +44 -0
- package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
- package/dist/modules/data_sync/api/run.js +31 -0
- package/dist/modules/data_sync/api/run.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/cancel.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
- package/dist/modules/data_sync/api/runs/[id]/retry.js +31 -0
- package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/[id]/route.js +59 -3
- package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
- package/dist/modules/data_sync/api/schedules/route.js +33 -4
- package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
- package/dist/modules/directory/api/organization-branding/route.js +54 -2
- package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
- package/dist/modules/directory/backend/directory/branding/page.js +15 -9
- package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/organizations/page.js +28 -9
- package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
- package/dist/modules/directory/backend/directory/tenants/page.js +29 -13
- package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
- package/dist/modules/entities/api/definitions.batch.js +15 -0
- package/dist/modules/entities/api/definitions.batch.js.map +2 -2
- package/dist/modules/entities/api/definitions.js +26 -0
- package/dist/modules/entities/api/definitions.js.map +2 -2
- package/dist/modules/entities/api/definitions.mutation-guard.js +57 -0
- package/dist/modules/entities/api/definitions.mutation-guard.js.map +7 -0
- package/dist/modules/entities/api/definitions.restore.js +15 -0
- package/dist/modules/entities/api/definitions.restore.js.map +2 -2
- package/dist/modules/entities/api/entities.js +31 -3
- package/dist/modules/entities/api/entities.js.map +2 -2
- package/dist/modules/entities/api/records.js +18 -0
- package/dist/modules/entities/api/records.js.map +2 -2
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +28 -10
- package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
- package/dist/modules/feature_toggles/api/overrides/route.js +38 -1
- package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/emails/[id]/route.js +30 -0
- package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js +30 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js +31 -0
- package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js.map +2 -2
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +68 -39
- package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +14 -4
- package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
- package/dist/modules/payment_gateways/api/cancel/route.js +37 -0
- package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/capture/route.js +37 -0
- package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/guards.js +31 -0
- package/dist/modules/payment_gateways/api/guards.js.map +7 -0
- package/dist/modules/payment_gateways/api/refund/route.js +37 -0
- package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
- package/dist/modules/payment_gateways/api/sessions/route.js +37 -0
- package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
- package/dist/modules/planner/api/availability-date-specific.js +11 -1
- package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js +20 -3
- package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
- package/dist/modules/planner/commands/availability-date-specific.js +16 -0
- package/dist/modules/planner/commands/availability-date-specific.js.map +2 -2
- package/dist/modules/planner/components/AvailabilityRulesEditor.js +109 -40
- package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
- package/dist/modules/query_index/api/purge.js +35 -3
- package/dist/modules/query_index/api/purge.js.map +2 -2
- package/dist/modules/query_index/api/reindex.js +41 -3
- package/dist/modules/query_index/api/reindex.js.map +2 -2
- package/dist/modules/query_index/components/QueryIndexesTable.js +57 -24
- package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
- package/dist/modules/shipping_carriers/api/shipments/route.js +31 -0
- package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js +55 -0
- package/dist/modules/shipping_carriers/api/tracking/refresh/route.js.map +7 -0
- package/dist/modules/shipping_carriers/data/validators.js +6 -1
- package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +28 -8
- package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
- package/dist/modules/shipping_carriers/lib/shipping-service.js +37 -7
- package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
- package/dist/modules/shipping_carriers/workers/status-poller.js +1 -1
- package/dist/modules/shipping_carriers/workers/status-poller.js.map +2 -2
- package/dist/modules/translations/components/TranslationManager.js +49 -20
- package/dist/modules/translations/components/TranslationManager.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/currencies/backend/currencies/[id]/page.tsx +40 -10
- package/src/modules/currencies/backend/currencies/page.tsx +68 -29
- package/src/modules/currencies/backend/exchange-rates/page.tsx +40 -15
- package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +110 -41
- package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +69 -7
- package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +16 -8
- package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +32 -14
- package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +32 -20
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +171 -64
- package/src/modules/customers/components/AddressFormatSettings.tsx +39 -19
- package/src/modules/customers/components/DictionarySettings.tsx +63 -29
- package/src/modules/customers/components/PipelineSettings.tsx +165 -80
- package/src/modules/customers/components/calendar/AgendaList.tsx +13 -8
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +16 -2
- package/src/modules/customers/components/calendar/MonthGrid.tsx +5 -2
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +6 -0
- package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +2 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +7 -0
- package/src/modules/customers/i18n/de.json +11 -7
- package/src/modules/customers/i18n/en.json +6 -2
- package/src/modules/customers/i18n/es.json +11 -7
- package/src/modules/customers/i18n/pl.json +11 -7
- package/src/modules/customers/lib/calendar/labels.ts +42 -0
- package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +39 -11
- package/src/modules/data_sync/api/mappings/[id]/route.ts +63 -0
- package/src/modules/data_sync/api/mappings/route.ts +48 -0
- package/src/modules/data_sync/api/run.ts +33 -0
- package/src/modules/data_sync/api/runs/[id]/cancel.ts +34 -0
- package/src/modules/data_sync/api/runs/[id]/retry.ts +33 -0
- package/src/modules/data_sync/api/schedules/[id]/route.ts +64 -2
- package/src/modules/data_sync/api/schedules/route.ts +36 -4
- package/src/modules/directory/api/organization-branding/route.ts +61 -0
- package/src/modules/directory/backend/directory/branding/page.tsx +16 -10
- package/src/modules/directory/backend/directory/organizations/page.tsx +35 -8
- package/src/modules/directory/backend/directory/tenants/page.tsx +37 -13
- package/src/modules/entities/api/definitions.batch.ts +17 -0
- package/src/modules/entities/api/definitions.mutation-guard.ts +80 -0
- package/src/modules/entities/api/definitions.restore.ts +17 -0
- package/src/modules/entities/api/definitions.ts +30 -0
- package/src/modules/entities/api/entities.ts +35 -3
- package/src/modules/entities/api/records.ts +20 -0
- package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +33 -10
- package/src/modules/feature_toggles/api/overrides/route.ts +44 -1
- package/src/modules/inbox_ops/api/emails/[id]/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/accept-all/route.ts +32 -0
- package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +33 -0
- package/src/modules/inbox_ops/api/proposals/[id]/categorize/route.ts +33 -0
- package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +80 -42
- package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +21 -4
- package/src/modules/payment_gateways/api/cancel/route.ts +39 -0
- package/src/modules/payment_gateways/api/capture/route.ts +39 -0
- package/src/modules/payment_gateways/api/guards.ts +59 -0
- package/src/modules/payment_gateways/api/refund/route.ts +39 -0
- package/src/modules/payment_gateways/api/sessions/route.ts +40 -0
- package/src/modules/planner/api/availability-date-specific.ts +10 -0
- package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +26 -5
- package/src/modules/planner/commands/availability-date-specific.ts +24 -0
- package/src/modules/planner/components/AvailabilityRulesEditor.tsx +122 -41
- package/src/modules/query_index/api/purge.ts +37 -3
- package/src/modules/query_index/api/reindex.ts +43 -3
- package/src/modules/query_index/components/QueryIndexesTable.tsx +66 -24
- package/src/modules/shipping_carriers/api/shipments/route.ts +31 -0
- package/src/modules/shipping_carriers/api/tracking/refresh/route.ts +53 -0
- package/src/modules/shipping_carriers/data/validators.ts +5 -0
- package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +29 -8
- package/src/modules/shipping_carriers/lib/shipping-service.ts +43 -7
- package/src/modules/shipping_carriers/workers/status-poller.ts +1 -1
- package/src/modules/translations/components/TranslationManager.tsx +65 -21
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/calendar/AgendaList.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { addDays } from 'date-fns/addDays'\nimport { format } from 'date-fns/format'\nimport { isToday } from 'date-fns/isToday'\nimport { isTomorrow } from 'date-fns/isTomorrow'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { CalendarClock } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport type { AgendaListProps, CalendarCategory, CalendarItem } from './types'\n\nconst MAX_AVATARS_PER_ROW = 2\n\nconst CATEGORY_BADGE_CLASS: Record<CalendarCategory, string> = {\n meeting: 'bg-status-info-bg text-status-info-text',\n event: 'bg-status-warning-bg text-status-warning-text',\n task: 'bg-primary text-primary-foreground',\n other: 'bg-status-neutral-bg text-status-neutral-text',\n}\n\nconst CATEGORY_LABEL: Record<CalendarCategory, { key: string; fallback: string }> = {\n meeting: { key: 'customers.calendar.category.meeting', fallback: 'Meeting' },\n event: { key: 'customers.calendar.category.event', fallback: 'Event' },\n task: { key: 'customers.calendar.category.task', fallback: 'Task' },\n other: { key: 'customers.calendar.category.other', fallback: 'Other' },\n}\n\ntype AgendaDayGroup = { date: Date; items: CalendarItem[] }\n\nfunction dayKeyOf(date: Date): string {\n return format(date, 'yyyy-MM-dd')\n}\n\nfunction formatUrlHost(location: string): string {\n try {\n const url = new URL(location.startsWith('http') ? location : `https://${location}`)\n return url.hostname\n } catch {\n return location\n }\n}\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })\n}\n\nfunction groupLabelOf(date: Date): string {\n return date.toLocaleDateString(undefined, { weekday: 'long', month: 'short', day: 'numeric' })\n}\n\nfunction deriveTypeLabel(interactionType: string): string {\n const normalized = interactionType.replace(/[-_]+/g, ' ').trim()\n if (!normalized) return interactionType\n return normalized.charAt(0).toUpperCase() + normalized.slice(1)\n}\n\nfunction participantLabel(participant: CalendarItem['participants'][number]): string {\n return participant.name ?? participant.email ?? participant.userId\n}\n\nfunction buildDayGroups(anchor: Date, horizonDays: number, items: CalendarItem[]): AgendaDayGroup[] {\n const sorted = [...items].sort((first, second) => first.start.getTime() - second.start.getTime())\n const byDay = new Map<string, CalendarItem[]>()\n for (const item of sorted) {\n const key = dayKeyOf(item.start)\n const bucket = byDay.get(key)\n if (bucket) bucket.push(item)\n else byDay.set(key, [item])\n }\n const firstDay = startOfDay(anchor)\n const groups: AgendaDayGroup[] = []\n for (let offset = 0; offset <= horizonDays; offset += 1) {\n const day = addDays(firstDay, offset)\n const dayItems = byDay.get(dayKeyOf(day))\n if (dayItems && dayItems.length > 0) groups.push({ date: day, items: dayItems })\n }\n return groups\n}\n\nfunction AgendaDayHeader({ date, count }: { date: Date; count: number }) {\n const t = useT()\n const todayMarker = isToday(date)\n const tomorrowMarker = !todayMarker && isTomorrow(date)\n return (\n <div className=\"flex w-full items-center gap-2 bg-muted/50 px-3 py-2.5 sm:px-5\">\n <span className=\"text-sm font-semibold text-foreground\">{groupLabelOf(date)}</span>\n {todayMarker || tomorrowMarker ? (\n <span className={cn('text-xs font-medium', todayMarker ? 'text-foreground' : 'text-muted-foreground')}>\n {`\u00B7 ${todayMarker ? t('customers.calendar.agenda.today', 'Today') : t('customers.calendar.agenda.tomorrow', 'Tomorrow')}`}\n </span>\n ) : null}\n <span aria-hidden=\"true\" className=\"w-2.5 shrink-0\" />\n <span className=\"text-xs font-medium text-muted-foreground\"
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { addDays } from 'date-fns/addDays'\nimport { format } from 'date-fns/format'\nimport { isToday } from 'date-fns/isToday'\nimport { isTomorrow } from 'date-fns/isTomorrow'\nimport { startOfDay } from 'date-fns/startOfDay'\nimport { CalendarClock } from 'lucide-react'\nimport { useLocale, useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { eventDisplayTitle, pluralCategory } from '../../lib/calendar/labels'\nimport type { AgendaListProps, CalendarCategory, CalendarItem } from './types'\n\nconst MAX_AVATARS_PER_ROW = 2\n\nconst CATEGORY_BADGE_CLASS: Record<CalendarCategory, string> = {\n meeting: 'bg-status-info-bg text-status-info-text',\n event: 'bg-status-warning-bg text-status-warning-text',\n task: 'bg-primary text-primary-foreground',\n other: 'bg-status-neutral-bg text-status-neutral-text',\n}\n\nconst CATEGORY_LABEL: Record<CalendarCategory, { key: string; fallback: string }> = {\n meeting: { key: 'customers.calendar.category.meeting', fallback: 'Meeting' },\n event: { key: 'customers.calendar.category.event', fallback: 'Event' },\n task: { key: 'customers.calendar.category.task', fallback: 'Task' },\n other: { key: 'customers.calendar.category.other', fallback: 'Other' },\n}\n\ntype AgendaDayGroup = { date: Date; items: CalendarItem[] }\n\nfunction dayKeyOf(date: Date): string {\n return format(date, 'yyyy-MM-dd')\n}\n\nfunction formatUrlHost(location: string): string {\n try {\n const url = new URL(location.startsWith('http') ? location : `https://${location}`)\n return url.hostname\n } catch {\n return location\n }\n}\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })\n}\n\nfunction groupLabelOf(date: Date): string {\n return date.toLocaleDateString(undefined, { weekday: 'long', month: 'short', day: 'numeric' })\n}\n\nfunction deriveTypeLabel(interactionType: string): string {\n const normalized = interactionType.replace(/[-_]+/g, ' ').trim()\n if (!normalized) return interactionType\n return normalized.charAt(0).toUpperCase() + normalized.slice(1)\n}\n\nfunction participantLabel(participant: CalendarItem['participants'][number]): string {\n return participant.name ?? participant.email ?? participant.userId\n}\n\nfunction buildDayGroups(anchor: Date, horizonDays: number, items: CalendarItem[]): AgendaDayGroup[] {\n const sorted = [...items].sort((first, second) => first.start.getTime() - second.start.getTime())\n const byDay = new Map<string, CalendarItem[]>()\n for (const item of sorted) {\n const key = dayKeyOf(item.start)\n const bucket = byDay.get(key)\n if (bucket) bucket.push(item)\n else byDay.set(key, [item])\n }\n const firstDay = startOfDay(anchor)\n const groups: AgendaDayGroup[] = []\n for (let offset = 0; offset <= horizonDays; offset += 1) {\n const day = addDays(firstDay, offset)\n const dayItems = byDay.get(dayKeyOf(day))\n if (dayItems && dayItems.length > 0) groups.push({ date: day, items: dayItems })\n }\n return groups\n}\n\nfunction AgendaDayHeader({ date, count }: { date: Date; count: number }) {\n const t = useT()\n const locale = useLocale()\n const todayMarker = isToday(date)\n const tomorrowMarker = !todayMarker && isTomorrow(date)\n const countKey = `customers.calendar.agenda.eventsCount.${pluralCategory(locale, count)}`\n const resolvedCount = t(countKey, { count })\n const countLabel =\n resolvedCount === countKey\n ? t('customers.calendar.agenda.eventsCount.other', '{count} events', { count })\n : resolvedCount\n return (\n <div className=\"flex w-full items-center gap-2 bg-muted/50 px-3 py-2.5 sm:px-5\">\n <span className=\"text-sm font-semibold text-foreground\">{groupLabelOf(date)}</span>\n {todayMarker || tomorrowMarker ? (\n <span className={cn('text-xs font-medium', todayMarker ? 'text-foreground' : 'text-muted-foreground')}>\n {`\u00B7 ${todayMarker ? t('customers.calendar.agenda.today', 'Today') : t('customers.calendar.agenda.tomorrow', 'Tomorrow')}`}\n </span>\n ) : null}\n <span aria-hidden=\"true\" className=\"w-2.5 shrink-0\" />\n <span className=\"text-xs font-medium text-muted-foreground\">{countLabel}</span>\n </div>\n )\n}\n\nfunction AgendaRow({\n item,\n typeLabels,\n onItemClick,\n}: {\n item: CalendarItem\n typeLabels?: Record<string, string>\n onItemClick: (item: CalendarItem) => void\n}) {\n const t = useT()\n const canceled = item.status === 'canceled'\n const done = item.status === 'done'\n const title = eventDisplayTitle(item.title, t('customers.calendar.grid.untitled', 'Untitled'))\n const typeLabel = typeLabels?.[item.interactionType] ?? deriveTypeLabel(item.interactionType)\n const platformLabels: Record<string, string> = {\n zoom: t('customers.calendar.platformFull.zoom', 'Zoom'),\n meet: t('customers.calendar.platformFull.meet', 'Google Meet'),\n slack: t('customers.calendar.platformFull.slack', 'Slack'),\n teams: t('customers.calendar.platformFull.teams', 'Microsoft Teams'),\n }\n const locationLabel = item.platform\n ? platformLabels[item.platform]\n : item.locationKind === 'url' && item.location\n ? formatUrlHost(item.location)\n : item.location\n const startLabel = item.allDay ? t('customers.calendar.grid.allDay', 'All day') : formatTime(item.start)\n const endLabel = item.allDay ? null : formatTime(item.end)\n const ariaTime = item.allDay ? startLabel : `${startLabel} \u2013 ${endLabel}`\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={`${title} \u00B7 ${ariaTime}`}\n onClick={() => onItemClick(item)}\n className={cn(\n 'h-auto w-full justify-start whitespace-normal rounded-none bg-background px-3 py-3 text-left transition-colors hover:bg-muted/30 sm:gap-3.5 sm:px-5',\n canceled && 'opacity-60',\n )}\n >\n <div className=\"flex w-16 shrink-0 flex-col gap-0.5 sm:w-[86px]\">\n <span className={cn('truncate text-sm font-semibold leading-4', done || canceled ? 'text-muted-foreground' : 'text-foreground')}>\n {startLabel}\n </span>\n {endLabel ? <span className=\"truncate text-xs text-muted-foreground\">{endLabel}</span> : null}\n </div>\n <span\n aria-hidden=\"true\"\n className={cn('h-9 w-1 shrink-0 rounded-full', !item.color && 'bg-muted-foreground/40')}\n style={item.color ? { backgroundColor: item.color } : undefined}\n />\n <div className=\"flex min-w-0 flex-1 flex-col gap-0.5\">\n <span\n className={cn(\n 'truncate text-sm font-semibold',\n done || canceled ? 'text-muted-foreground' : 'text-foreground',\n canceled && 'line-through',\n )}\n >\n {title}\n </span>\n <span className=\"truncate text-xs text-muted-foreground\">\n {locationLabel ? `${typeLabel} \u00B7 ${locationLabel}` : typeLabel}\n </span>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n {item.participants.length > 0 ? (\n <span className=\"hidden items-center gap-0.5 sm:flex\">\n {item.participants.slice(0, MAX_AVATARS_PER_ROW).map((participant) => (\n <Avatar key={participant.userId} size=\"xs\" label={participantLabel(participant)} />\n ))}\n {item.participants.length > MAX_AVATARS_PER_ROW ? (\n <span className=\"ps-0.5 text-xs font-medium text-muted-foreground\">\n +{item.participants.length - MAX_AVATARS_PER_ROW}\n </span>\n ) : null}\n </span>\n ) : null}\n <span\n className={cn(\n 'inline-flex shrink-0 items-center rounded-full px-2 py-0.5 text-overline font-medium uppercase tracking-wide',\n CATEGORY_BADGE_CLASS[item.category],\n )}\n >\n {t(CATEGORY_LABEL[item.category].key, CATEGORY_LABEL[item.category].fallback)}\n </span>\n </div>\n </Button>\n )\n}\n\nexport function AgendaList({ anchor, horizonDays, items, typeLabels, onItemClick }: AgendaListProps) {\n const t = useT()\n const groups = React.useMemo(() => buildDayGroups(anchor, horizonDays, items), [anchor, horizonDays, items])\n if (groups.length === 0) {\n return (\n <EmptyState\n icon={<CalendarClock className=\"h-6 w-6\" aria-hidden=\"true\" />}\n title={t('customers.calendar.empty.agenda', 'Nothing scheduled in this period')}\n />\n )\n }\n return (\n <div className=\"flex w-full flex-col divide-y divide-border overflow-hidden border border-border bg-background\">\n {groups.map((group) => (\n <React.Fragment key={dayKeyOf(group.date)}>\n <AgendaDayHeader date={group.date} count={group.items.length} />\n {group.items.map((item) => (\n <AgendaRow key={item.id} item={item} typeLabels={typeLabels} onItemClick={onItemClick} />\n ))}\n </React.Fragment>\n ))}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -50,6 +50,10 @@ function CalendarSettingsModal({
|
|
|
50
50
|
{ key: "showWeekends", label: t("customers.calendar.settings.showWeekends", "Show weekends") }
|
|
51
51
|
];
|
|
52
52
|
const title = t("customers.calendar.settings.title", "Customization");
|
|
53
|
+
const removeTagLabel = React.useCallback(
|
|
54
|
+
(tag) => t("customers.calendar.settings.removeTag", "Remove {tag}", { tag }),
|
|
55
|
+
[t]
|
|
56
|
+
);
|
|
53
57
|
return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange, children: /* @__PURE__ */ jsxs(
|
|
54
58
|
DialogContent,
|
|
55
59
|
{
|
|
@@ -89,6 +93,7 @@ function CalendarSettingsModal({
|
|
|
89
93
|
placeholder: t("customers.calendar.settings.addCategory", "Add a category\u2026"),
|
|
90
94
|
value: draft.eventCategories,
|
|
91
95
|
maxTags: MAX_EVENT_CATEGORIES,
|
|
96
|
+
removeTagLabel,
|
|
92
97
|
onChange: (eventCategories) => setDraft((current) => ({ ...current, eventCategories }))
|
|
93
98
|
}
|
|
94
99
|
),
|
|
@@ -104,6 +109,7 @@ function CalendarSettingsModal({
|
|
|
104
109
|
placeholder: t("customers.calendar.settings.addType", "Add a type\u2026"),
|
|
105
110
|
value: draft.activityTypes,
|
|
106
111
|
maxTags: MAX_ACTIVITY_TYPES,
|
|
112
|
+
removeTagLabel,
|
|
107
113
|
onChange: (activityTypes) => setDraft((current) => ({ ...current, activityTypes }))
|
|
108
114
|
}
|
|
109
115
|
),
|
|
@@ -147,14 +153,24 @@ function CalendarSettingsModal({
|
|
|
147
153
|
}
|
|
148
154
|
) });
|
|
149
155
|
}
|
|
150
|
-
function SettingsTagInput({ label, maxLabel, hint, placeholder, value, maxTags, onChange }) {
|
|
156
|
+
function SettingsTagInput({ label, maxLabel, hint, placeholder, value, maxTags, removeTagLabel, onChange }) {
|
|
151
157
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
152
158
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
153
159
|
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium leading-5 text-foreground", children: label }),
|
|
154
160
|
/* @__PURE__ */ jsx("span", { className: "text-sm leading-5 text-muted-foreground", children: maxLabel }),
|
|
155
161
|
/* @__PURE__ */ jsx(SimpleTooltip, { content: hint, children: /* @__PURE__ */ jsx("span", { className: "inline-flex text-muted-foreground", tabIndex: 0, role: "img", "aria-label": hint, children: /* @__PURE__ */ jsx(Info, { "aria-hidden": true, className: "size-4" }) }) })
|
|
156
162
|
] }),
|
|
157
|
-
/* @__PURE__ */ jsx(
|
|
163
|
+
/* @__PURE__ */ jsx(
|
|
164
|
+
TagInput,
|
|
165
|
+
{
|
|
166
|
+
value,
|
|
167
|
+
onChange,
|
|
168
|
+
placeholder,
|
|
169
|
+
maxTags,
|
|
170
|
+
"aria-label": label,
|
|
171
|
+
removeTagLabel
|
|
172
|
+
}
|
|
173
|
+
)
|
|
158
174
|
] });
|
|
159
175
|
}
|
|
160
176
|
export {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/calendar/CalendarSettingsModal.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Info, X } from 'lucide-react'\nimport { VisuallyHidden } from '@radix-ui/react-visually-hidden'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { TagInput } from '@open-mercato/ui/primitives/tag-input'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\nimport {\n CalendarPreferences,\n ConflictScope,\n MAX_ACTIVITY_TYPES,\n MAX_EVENT_CATEGORIES,\n} from '../../lib/calendar/preferences'\nimport { SegmentGroup } from './editor/SegmentGroup'\n\nexport type CalendarSettingsModalProps = {\n open: boolean\n preferences: CalendarPreferences\n seedActivityTypes: string[]\n onOpenChange(open: boolean): void\n onSave(next: CalendarPreferences): void\n}\n\ntype ToggleKey = 'showCrmActivities' | 'aiSummaries' | 'conflictWarnings' | 'showWeekends'\n\n// An empty Activity Types list is an intentional floor meaning \"surface all\n// dictionary types\" (see buildEditorCategoryOptions) rather than \"surface none\" \u2014\n// it keeps the editor's category picker usable. So when the stored list is empty\n// the modal seeds the dictionary types for display; curating a non-empty subset\n// persists and filters the editor as expected.\nfunction buildDraft(preferences: CalendarPreferences, seedActivityTypes: string[]): CalendarPreferences {\n return {\n ...preferences,\n eventCategories: [...preferences.eventCategories],\n activityTypes:\n preferences.activityTypes.length > 0\n ? [...preferences.activityTypes]\n : seedActivityTypes.slice(0, MAX_ACTIVITY_TYPES),\n }\n}\n\nexport function CalendarSettingsModal({\n open,\n preferences,\n seedActivityTypes,\n onOpenChange,\n onSave,\n}: CalendarSettingsModalProps) {\n const t = useT()\n const [draft, setDraft] = React.useState<CalendarPreferences>(() => buildDraft(preferences, seedActivityTypes))\n const openRef = React.useRef(false)\n\n React.useEffect(() => {\n if (open && !openRef.current) setDraft(buildDraft(preferences, seedActivityTypes))\n openRef.current = open\n }, [open, preferences, seedActivityTypes])\n\n const handleSave = React.useCallback(() => {\n onSave(draft)\n onOpenChange(false)\n }, [draft, onOpenChange, onSave])\n\n const handleKeyDown = useDialogKeyHandler({ onConfirm: handleSave })\n\n const toggle = (key: ToggleKey) => (checked: boolean) => setDraft((current) => ({ ...current, [key]: checked }))\n\n const toggleRows: Array<{ key: ToggleKey; label: string }> = [\n { key: 'showCrmActivities', label: t('customers.calendar.settings.showCrmActivities', 'Show CRM activities on calendar') },\n { key: 'aiSummaries', label: t('customers.calendar.settings.aiSummaries', 'AI summaries & quick actions') },\n { key: 'conflictWarnings', label: t('customers.calendar.settings.conflictWarnings', 'Conflict warnings') },\n { key: 'showWeekends', label: t('customers.calendar.settings.showWeekends', 'Show weekends') },\n ]\n\n const title = t('customers.calendar.settings.title', 'Customization')\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n onKeyDown={handleKeyDown}\n aria-describedby={undefined}\n dismissible={false}\n className=\"flex w-full max-w-[400px] flex-col gap-0 overflow-hidden rounded-2xl border-0 bg-card p-0 shadow-xl\"\n >\n <VisuallyHidden>\n <DialogTitle>{title}</DialogTitle>\n </VisuallyHidden>\n <div className=\"flex shrink-0 items-start gap-3.5 border-b border-border py-4 pl-5 pr-4\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <p className=\"text-sm font-medium leading-5 text-foreground\">{title}</p>\n <p className=\"text-xs leading-4 text-muted-foreground\">\n {t('customers.calendar.settings.subtitle', 'Customise your calendar module.')}\n </p>\n </div>\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onOpenChange(false)}\n aria-label={t('customers.calendar.settings.close', 'Close')}\n className=\"shrink-0 text-muted-foreground\"\n >\n <X aria-hidden className=\"size-5\" />\n </IconButton>\n </div>\n\n <div className=\"flex flex-col gap-4 p-5\">\n <SettingsTagInput\n label={t('customers.calendar.settings.eventCategories', 'Event Categories')}\n maxLabel={t('customers.calendar.settings.max', '(max. {count})', { count: MAX_EVENT_CATEGORIES })}\n hint={t(\n 'customers.calendar.settings.eventCategoriesHint',\n 'Your own grouping labels (e.g. Team Meeting, Sales Call). Offered when creating an event.',\n )}\n placeholder={t('customers.calendar.settings.addCategory', 'Add a category\u2026')}\n value={draft.eventCategories}\n maxTags={MAX_EVENT_CATEGORIES}\n onChange={(eventCategories) => setDraft((current) => ({ ...current, eventCategories }))}\n />\n <SettingsTagInput\n label={t('customers.calendar.settings.activityTypes', 'Activity Types')}\n maxLabel={t('customers.calendar.settings.max', '(max. {count})', { count: MAX_ACTIVITY_TYPES })}\n hint={t(\n 'customers.calendar.settings.activityTypesHint',\n 'The activity types your calendar surfaces when creating an event. Seeded from your workspace dictionary.',\n )}\n placeholder={t('customers.calendar.settings.addType', 'Add a type\u2026')}\n value={draft.activityTypes}\n maxTags={MAX_ACTIVITY_TYPES}\n onChange={(activityTypes) => setDraft((current) => ({ ...current, activityTypes }))}\n />\n {toggleRows.map((row) => (\n <React.Fragment key={row.key}>\n <div className=\"flex items-center gap-2\">\n <Switch\n checked={draft[row.key]}\n onCheckedChange={toggle(row.key)}\n aria-label={row.label}\n />\n <span className=\"text-sm leading-5 text-foreground\">{row.label}</span>\n </div>\n {row.key === 'conflictWarnings' && draft.conflictWarnings ? (\n <div className=\"flex flex-col gap-1.5 pl-11\">\n <span className=\"text-xs leading-4 text-muted-foreground\">\n {t(\n 'customers.calendar.settings.conflictScopeHint',\n 'Choose whose overlaps the calendar flags as conflicts.',\n )}\n </span>\n <SegmentGroup<ConflictScope>\n ariaLabel={t('customers.calendar.settings.conflictScope', 'Conflict scope')}\n value={draft.conflictScope}\n onChange={(conflictScope) => setDraft((current) => ({ ...current, conflictScope }))}\n options={[\n { value: 'mine', label: t('customers.calendar.settings.conflictScopeMine', 'My meetings only') },\n { value: 'all', label: t('customers.calendar.settings.conflictScopeAll', 'All org meetings') },\n ]}\n />\n </div>\n ) : null}\n </React.Fragment>\n ))}\n </div>\n\n <div className=\"flex shrink-0 items-center gap-3 border-t border-border px-5 py-4\">\n <Button type=\"button\" variant=\"outline\" className=\"flex-1\" onClick={() => onOpenChange(false)}>\n {t('customers.calendar.settings.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" className=\"flex-1\" onClick={handleSave}>\n {t('customers.calendar.settings.save', 'Save Changes')}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n\ntype SettingsTagInputProps = {\n label: string\n maxLabel: string\n hint: string\n placeholder: string\n value: string[]\n maxTags: number\n onChange(value: string[]): void\n}\n\nfunction SettingsTagInput({ label, maxLabel, hint, placeholder, value, maxTags, onChange }: SettingsTagInputProps) {\n return (\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex items-center gap-1\">\n <span className=\"text-sm font-medium leading-5 text-foreground\">{label}</span>\n <span className=\"text-sm leading-5 text-muted-foreground\">{maxLabel}</span>\n <SimpleTooltip content={hint}>\n <span className=\"inline-flex text-muted-foreground\" tabIndex={0} role=\"img\" aria-label={hint}>\n <Info aria-hidden className=\"size-4\" />\n </span>\n </SimpleTooltip>\n </div>\n <TagInput
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Info, X } from 'lucide-react'\nimport { VisuallyHidden } from '@radix-ui/react-visually-hidden'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\nimport { TagInput } from '@open-mercato/ui/primitives/tag-input'\nimport { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'\nimport { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'\nimport {\n CalendarPreferences,\n ConflictScope,\n MAX_ACTIVITY_TYPES,\n MAX_EVENT_CATEGORIES,\n} from '../../lib/calendar/preferences'\nimport { SegmentGroup } from './editor/SegmentGroup'\n\nexport type CalendarSettingsModalProps = {\n open: boolean\n preferences: CalendarPreferences\n seedActivityTypes: string[]\n onOpenChange(open: boolean): void\n onSave(next: CalendarPreferences): void\n}\n\ntype ToggleKey = 'showCrmActivities' | 'aiSummaries' | 'conflictWarnings' | 'showWeekends'\n\n// An empty Activity Types list is an intentional floor meaning \"surface all\n// dictionary types\" (see buildEditorCategoryOptions) rather than \"surface none\" \u2014\n// it keeps the editor's category picker usable. So when the stored list is empty\n// the modal seeds the dictionary types for display; curating a non-empty subset\n// persists and filters the editor as expected.\nfunction buildDraft(preferences: CalendarPreferences, seedActivityTypes: string[]): CalendarPreferences {\n return {\n ...preferences,\n eventCategories: [...preferences.eventCategories],\n activityTypes:\n preferences.activityTypes.length > 0\n ? [...preferences.activityTypes]\n : seedActivityTypes.slice(0, MAX_ACTIVITY_TYPES),\n }\n}\n\nexport function CalendarSettingsModal({\n open,\n preferences,\n seedActivityTypes,\n onOpenChange,\n onSave,\n}: CalendarSettingsModalProps) {\n const t = useT()\n const [draft, setDraft] = React.useState<CalendarPreferences>(() => buildDraft(preferences, seedActivityTypes))\n const openRef = React.useRef(false)\n\n React.useEffect(() => {\n if (open && !openRef.current) setDraft(buildDraft(preferences, seedActivityTypes))\n openRef.current = open\n }, [open, preferences, seedActivityTypes])\n\n const handleSave = React.useCallback(() => {\n onSave(draft)\n onOpenChange(false)\n }, [draft, onOpenChange, onSave])\n\n const handleKeyDown = useDialogKeyHandler({ onConfirm: handleSave })\n\n const toggle = (key: ToggleKey) => (checked: boolean) => setDraft((current) => ({ ...current, [key]: checked }))\n\n const toggleRows: Array<{ key: ToggleKey; label: string }> = [\n { key: 'showCrmActivities', label: t('customers.calendar.settings.showCrmActivities', 'Show CRM activities on calendar') },\n { key: 'aiSummaries', label: t('customers.calendar.settings.aiSummaries', 'AI summaries & quick actions') },\n { key: 'conflictWarnings', label: t('customers.calendar.settings.conflictWarnings', 'Conflict warnings') },\n { key: 'showWeekends', label: t('customers.calendar.settings.showWeekends', 'Show weekends') },\n ]\n\n const title = t('customers.calendar.settings.title', 'Customization')\n const removeTagLabel = React.useCallback(\n (tag: string) => t('customers.calendar.settings.removeTag', 'Remove {tag}', { tag }),\n [t],\n )\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent\n onKeyDown={handleKeyDown}\n aria-describedby={undefined}\n dismissible={false}\n className=\"flex w-full max-w-[400px] flex-col gap-0 overflow-hidden rounded-2xl border-0 bg-card p-0 shadow-xl\"\n >\n <VisuallyHidden>\n <DialogTitle>{title}</DialogTitle>\n </VisuallyHidden>\n <div className=\"flex shrink-0 items-start gap-3.5 border-b border-border py-4 pl-5 pr-4\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-1\">\n <p className=\"text-sm font-medium leading-5 text-foreground\">{title}</p>\n <p className=\"text-xs leading-4 text-muted-foreground\">\n {t('customers.calendar.settings.subtitle', 'Customise your calendar module.')}\n </p>\n </div>\n <IconButton\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => onOpenChange(false)}\n aria-label={t('customers.calendar.settings.close', 'Close')}\n className=\"shrink-0 text-muted-foreground\"\n >\n <X aria-hidden className=\"size-5\" />\n </IconButton>\n </div>\n\n <div className=\"flex flex-col gap-4 p-5\">\n <SettingsTagInput\n label={t('customers.calendar.settings.eventCategories', 'Event Categories')}\n maxLabel={t('customers.calendar.settings.max', '(max. {count})', { count: MAX_EVENT_CATEGORIES })}\n hint={t(\n 'customers.calendar.settings.eventCategoriesHint',\n 'Your own grouping labels (e.g. Team Meeting, Sales Call). Offered when creating an event.',\n )}\n placeholder={t('customers.calendar.settings.addCategory', 'Add a category\u2026')}\n value={draft.eventCategories}\n maxTags={MAX_EVENT_CATEGORIES}\n removeTagLabel={removeTagLabel}\n onChange={(eventCategories) => setDraft((current) => ({ ...current, eventCategories }))}\n />\n <SettingsTagInput\n label={t('customers.calendar.settings.activityTypes', 'Activity Types')}\n maxLabel={t('customers.calendar.settings.max', '(max. {count})', { count: MAX_ACTIVITY_TYPES })}\n hint={t(\n 'customers.calendar.settings.activityTypesHint',\n 'The activity types your calendar surfaces when creating an event. Seeded from your workspace dictionary.',\n )}\n placeholder={t('customers.calendar.settings.addType', 'Add a type\u2026')}\n value={draft.activityTypes}\n maxTags={MAX_ACTIVITY_TYPES}\n removeTagLabel={removeTagLabel}\n onChange={(activityTypes) => setDraft((current) => ({ ...current, activityTypes }))}\n />\n {toggleRows.map((row) => (\n <React.Fragment key={row.key}>\n <div className=\"flex items-center gap-2\">\n <Switch\n checked={draft[row.key]}\n onCheckedChange={toggle(row.key)}\n aria-label={row.label}\n />\n <span className=\"text-sm leading-5 text-foreground\">{row.label}</span>\n </div>\n {row.key === 'conflictWarnings' && draft.conflictWarnings ? (\n <div className=\"flex flex-col gap-1.5 pl-11\">\n <span className=\"text-xs leading-4 text-muted-foreground\">\n {t(\n 'customers.calendar.settings.conflictScopeHint',\n 'Choose whose overlaps the calendar flags as conflicts.',\n )}\n </span>\n <SegmentGroup<ConflictScope>\n ariaLabel={t('customers.calendar.settings.conflictScope', 'Conflict scope')}\n value={draft.conflictScope}\n onChange={(conflictScope) => setDraft((current) => ({ ...current, conflictScope }))}\n options={[\n { value: 'mine', label: t('customers.calendar.settings.conflictScopeMine', 'My meetings only') },\n { value: 'all', label: t('customers.calendar.settings.conflictScopeAll', 'All org meetings') },\n ]}\n />\n </div>\n ) : null}\n </React.Fragment>\n ))}\n </div>\n\n <div className=\"flex shrink-0 items-center gap-3 border-t border-border px-5 py-4\">\n <Button type=\"button\" variant=\"outline\" className=\"flex-1\" onClick={() => onOpenChange(false)}>\n {t('customers.calendar.settings.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" className=\"flex-1\" onClick={handleSave}>\n {t('customers.calendar.settings.save', 'Save Changes')}\n </Button>\n </div>\n </DialogContent>\n </Dialog>\n )\n}\n\ntype SettingsTagInputProps = {\n label: string\n maxLabel: string\n hint: string\n placeholder: string\n value: string[]\n maxTags: number\n removeTagLabel: (tag: string) => string\n onChange(value: string[]): void\n}\n\nfunction SettingsTagInput({ label, maxLabel, hint, placeholder, value, maxTags, removeTagLabel, onChange }: SettingsTagInputProps) {\n return (\n <div className=\"flex flex-col gap-1\">\n <div className=\"flex items-center gap-1\">\n <span className=\"text-sm font-medium leading-5 text-foreground\">{label}</span>\n <span className=\"text-sm leading-5 text-muted-foreground\">{maxLabel}</span>\n <SimpleTooltip content={hint}>\n <span className=\"inline-flex text-muted-foreground\" tabIndex={0} role=\"img\" aria-label={hint}>\n <Info aria-hidden className=\"size-4\" />\n </span>\n </SimpleTooltip>\n </div>\n <TagInput\n value={value}\n onChange={onChange}\n placeholder={placeholder}\n maxTags={maxTags}\n aria-label={label}\n removeTagLabel={removeTagLabel}\n />\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -11,6 +11,7 @@ import { cn } from "@open-mercato/shared/lib/utils";
|
|
|
11
11
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
12
12
|
import { EmptyState } from "@open-mercato/ui/primitives/empty-state";
|
|
13
13
|
import { getVisibleRange } from "../../lib/calendar/range.js";
|
|
14
|
+
import { eventDisplayTitle } from "../../lib/calendar/labels.js";
|
|
14
15
|
const MAX_PILLS_PER_DAY = 2;
|
|
15
16
|
const SOFT_TINT_ALPHA = "1A";
|
|
16
17
|
function dayKeyOf(date) {
|
|
@@ -52,7 +53,9 @@ function groupItemsByDay(items) {
|
|
|
52
53
|
return byDay;
|
|
53
54
|
}
|
|
54
55
|
function MonthPill({ item, onItemClick }) {
|
|
56
|
+
const t = useT();
|
|
55
57
|
const canceled = item.status === "canceled";
|
|
58
|
+
const title = eventDisplayTitle(item.title, t("customers.calendar.grid.untitled", "Untitled"));
|
|
56
59
|
const timeLabel = item.allDay ? "" : ` \xB7 ${formatTime(item.start)} \u2013 ${formatTime(item.end)}`;
|
|
57
60
|
const tintStyle = item.color ? { backgroundColor: `${item.color}${SOFT_TINT_ALPHA}`, color: item.color } : void 0;
|
|
58
61
|
return /* @__PURE__ */ jsxs(
|
|
@@ -60,7 +63,7 @@ function MonthPill({ item, onItemClick }) {
|
|
|
60
63
|
{
|
|
61
64
|
type: "button",
|
|
62
65
|
variant: "ghost",
|
|
63
|
-
"aria-label": `${
|
|
66
|
+
"aria-label": `${title}${timeLabel}`,
|
|
64
67
|
onClick: (event) => {
|
|
65
68
|
event.stopPropagation();
|
|
66
69
|
onItemClick(item);
|
|
@@ -86,7 +89,7 @@ function MonthPill({ item, onItemClick }) {
|
|
|
86
89
|
"min-w-0 truncate text-overline font-medium uppercase tracking-wide",
|
|
87
90
|
canceled && "line-through"
|
|
88
91
|
),
|
|
89
|
-
children:
|
|
92
|
+
children: title
|
|
90
93
|
}
|
|
91
94
|
)
|
|
92
95
|
]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/components/calendar/MonthGrid.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { addDays } from 'date-fns/addDays'\nimport { format } from 'date-fns/format'\nimport { isSameMonth } from 'date-fns/isSameMonth'\nimport { isToday } from 'date-fns/isToday'\nimport { CalendarDays } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { getVisibleRange } from '../../lib/calendar/range'\nimport type { CalendarItem, MonthGridProps } from './types'\n\nconst MAX_PILLS_PER_DAY = 2\nconst SOFT_TINT_ALPHA = '1A'\n\nfunction dayKeyOf(date: Date): string {\n return format(date, 'yyyy-MM-dd')\n}\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })\n}\n\nfunction fullDateLabel(date: Date): string {\n return date.toLocaleDateString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })\n}\n\nfunction isWeekend(date: Date): boolean {\n const weekday = date.getDay()\n return weekday === 0 || weekday === 6\n}\n\nfunction buildWeeks(anchor: Date): Date[][] {\n const range = getVisibleRange('month', anchor, 0)\n const weeks: Date[][] = []\n let cursor = range.from\n while (cursor.getTime() <= range.to.getTime()) {\n const week: Date[] = []\n for (let dayIndex = 0; dayIndex < 7; dayIndex += 1) {\n week.push(cursor)\n cursor = addDays(cursor, 1)\n }\n weeks.push(week)\n }\n return weeks\n}\n\nfunction groupItemsByDay(items: CalendarItem[]): Map<string, CalendarItem[]> {\n const sorted = [...items].sort((first, second) => first.start.getTime() - second.start.getTime())\n const byDay = new Map<string, CalendarItem[]>()\n for (const item of sorted) {\n const key = dayKeyOf(item.start)\n const bucket = byDay.get(key)\n if (bucket) bucket.push(item)\n else byDay.set(key, [item])\n }\n return byDay\n}\n\nfunction MonthPill({ item, onItemClick }: { item: CalendarItem; onItemClick: (item: CalendarItem) => void }) {\n const canceled = item.status === 'canceled'\n const timeLabel = item.allDay ? '' : ` \u00B7 ${formatTime(item.start)} \u2013 ${formatTime(item.end)}`\n const tintStyle = item.color\n ? { backgroundColor: `${item.color}${SOFT_TINT_ALPHA}`, color: item.color }\n : undefined\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={`${
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { addDays } from 'date-fns/addDays'\nimport { format } from 'date-fns/format'\nimport { isSameMonth } from 'date-fns/isSameMonth'\nimport { isToday } from 'date-fns/isToday'\nimport { CalendarDays } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { EmptyState } from '@open-mercato/ui/primitives/empty-state'\nimport { getVisibleRange } from '../../lib/calendar/range'\nimport { eventDisplayTitle } from '../../lib/calendar/labels'\nimport type { CalendarItem, MonthGridProps } from './types'\n\nconst MAX_PILLS_PER_DAY = 2\nconst SOFT_TINT_ALPHA = '1A'\n\nfunction dayKeyOf(date: Date): string {\n return format(date, 'yyyy-MM-dd')\n}\n\nfunction formatTime(date: Date): string {\n return date.toLocaleTimeString(undefined, { hour: 'numeric', minute: '2-digit' })\n}\n\nfunction fullDateLabel(date: Date): string {\n return date.toLocaleDateString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })\n}\n\nfunction isWeekend(date: Date): boolean {\n const weekday = date.getDay()\n return weekday === 0 || weekday === 6\n}\n\nfunction buildWeeks(anchor: Date): Date[][] {\n const range = getVisibleRange('month', anchor, 0)\n const weeks: Date[][] = []\n let cursor = range.from\n while (cursor.getTime() <= range.to.getTime()) {\n const week: Date[] = []\n for (let dayIndex = 0; dayIndex < 7; dayIndex += 1) {\n week.push(cursor)\n cursor = addDays(cursor, 1)\n }\n weeks.push(week)\n }\n return weeks\n}\n\nfunction groupItemsByDay(items: CalendarItem[]): Map<string, CalendarItem[]> {\n const sorted = [...items].sort((first, second) => first.start.getTime() - second.start.getTime())\n const byDay = new Map<string, CalendarItem[]>()\n for (const item of sorted) {\n const key = dayKeyOf(item.start)\n const bucket = byDay.get(key)\n if (bucket) bucket.push(item)\n else byDay.set(key, [item])\n }\n return byDay\n}\n\nfunction MonthPill({ item, onItemClick }: { item: CalendarItem; onItemClick: (item: CalendarItem) => void }) {\n const t = useT()\n const canceled = item.status === 'canceled'\n const title = eventDisplayTitle(item.title, t('customers.calendar.grid.untitled', 'Untitled'))\n const timeLabel = item.allDay ? '' : ` \u00B7 ${formatTime(item.start)} \u2013 ${formatTime(item.end)}`\n const tintStyle = item.color\n ? { backgroundColor: `${item.color}${SOFT_TINT_ALPHA}`, color: item.color }\n : undefined\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={`${title}${timeLabel}`}\n onClick={(event) => {\n event.stopPropagation()\n onItemClick(item)\n }}\n className={cn(\n 'h-4 w-full min-w-0 justify-center gap-0 rounded-full p-0 pr-2',\n !item.color && 'bg-muted text-muted-foreground',\n canceled && 'opacity-60',\n )}\n style={tintStyle}\n >\n <span aria-hidden=\"true\" className=\"flex size-4 shrink-0 items-center justify-center\">\n <span\n className={cn('size-1.5 rounded-full', !item.color && 'bg-muted-foreground')}\n style={item.color ? { backgroundColor: item.color } : undefined}\n />\n </span>\n <span\n className={cn(\n 'min-w-0 truncate text-overline font-medium uppercase tracking-wide',\n canceled && 'line-through',\n )}\n >\n {title}\n </span>\n </Button>\n )\n}\n\nfunction MonthDayCell({\n day,\n anchor,\n items,\n onItemClick,\n onDayOpen,\n}: {\n day: Date\n anchor: Date\n items: CalendarItem[]\n onItemClick: (item: CalendarItem) => void\n onDayOpen: (date: Date) => void\n}) {\n const t = useT()\n const inMonth = isSameMonth(day, anchor)\n const today = isToday(day)\n const visibleItems = items.slice(0, MAX_PILLS_PER_DAY)\n const hiddenCount = items.length - visibleItems.length\n const dayLabel = fullDateLabel(day)\n return (\n <div\n className={cn(\n 'relative flex h-full min-w-0 flex-1 flex-col items-start gap-1 overflow-hidden border-r border-border p-2 text-left last:border-r-0',\n isWeekend(day) ? 'bg-muted/40' : 'bg-background',\n )}\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-label={dayLabel}\n onClick={() => onDayOpen(day)}\n className=\"absolute inset-0 z-0 h-full w-full rounded-none p-0 hover:bg-muted/50\"\n >\n <span className=\"sr-only\">{dayLabel}</span>\n </Button>\n {today ? (\n <span className=\"pointer-events-none relative z-10 flex items-center justify-center rounded-full bg-primary px-1.5 py-0.5 text-xs font-semibold text-primary-foreground\">\n {day.getDate()}\n </span>\n ) : (\n <span className={cn('pointer-events-none relative z-10 text-xs font-medium', inMonth ? 'text-foreground' : 'text-muted-foreground')}>\n {day.getDate()}\n </span>\n )}\n <span className=\"relative z-20 hidden w-full flex-col gap-1 sm:flex\">\n {visibleItems.map((item) => (\n <MonthPill key={item.id} item={item} onItemClick={onItemClick} />\n ))}\n {hiddenCount > 0 ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"h-auto self-start p-0 text-overline font-medium text-muted-foreground hover:bg-transparent hover:underline\"\n onClick={(event) => {\n event.stopPropagation()\n onDayOpen(day)\n }}\n >\n {t('customers.calendar.grid.more', '+{count} more', { count: hiddenCount })}\n </Button>\n ) : null}\n </span>\n {items.length > 0 ? (\n <span aria-hidden=\"true\" className=\"pointer-events-none relative z-10 flex w-full flex-wrap items-center gap-0.5 sm:hidden\">\n {items.slice(0, 4).map((item) => (\n <span\n key={item.id}\n className={cn('size-1.5 rounded-full', !item.color && 'bg-muted-foreground')}\n style={item.color ? { backgroundColor: item.color } : undefined}\n />\n ))}\n {items.length > 4 ? (\n <span className=\"text-overline font-medium leading-none text-muted-foreground\">\n +{items.length - 4}\n </span>\n ) : null}\n </span>\n ) : null}\n </div>\n )\n}\n\nexport function MonthGrid({ anchor, items, onItemClick, onDayOpen }: MonthGridProps) {\n const t = useT()\n const weeks = React.useMemo(() => buildWeeks(anchor), [anchor])\n const itemsByDay = React.useMemo(() => groupItemsByDay(items), [items])\n return (\n <div className=\"relative flex h-full w-full flex-col overflow-hidden border border-border bg-background\">\n <div className=\"flex h-9 w-full shrink-0 border-b border-border\">\n {weeks[0]?.map((day) => {\n const label = day.toLocaleDateString(undefined, { weekday: 'short' }).toUpperCase()\n return (\n <div\n key={dayKeyOf(day)}\n className=\"flex min-w-0 flex-1 items-center justify-center border-r border-border last:border-r-0 sm:justify-start sm:pl-2.5\"\n >\n <span className=\"truncate text-overline font-medium uppercase tracking-widest text-muted-foreground\">\n <span className=\"sm:hidden\">{label.charAt(0)}</span>\n <span className=\"hidden sm:inline\">{label}</span>\n </span>\n </div>\n )\n })}\n </div>\n {weeks.map((week) => (\n <div\n key={dayKeyOf(week[0])}\n className=\"flex w-full flex-1 border-b border-border last:border-b-0 min-h-14 sm:min-h-[86px]\"\n >\n {week.map((day) => (\n <MonthDayCell\n key={dayKeyOf(day)}\n day={day}\n anchor={anchor}\n items={itemsByDay.get(dayKeyOf(day)) ?? []}\n onItemClick={onItemClick}\n onDayOpen={onDayOpen}\n />\n ))}\n </div>\n ))}\n {items.length === 0 ? (\n <div className=\"pointer-events-none absolute inset-0 z-10 flex items-center justify-center p-6\">\n <EmptyState\n icon={<CalendarDays className=\"h-6 w-6\" aria-hidden=\"true\" />}\n title={t('customers.calendar.empty.month', 'Nothing scheduled this month')}\n className=\"bg-background/90\"\n />\n </div>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -6,6 +6,7 @@ import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
|
6
6
|
import { Avatar } from "@open-mercato/ui/primitives/avatar";
|
|
7
7
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
8
8
|
import { Input } from "@open-mercato/ui/primitives/input";
|
|
9
|
+
import { composeAccessibleName } from "../../../lib/calendar/labels.js";
|
|
9
10
|
import { searchPeopleOptions } from "./lookups.js";
|
|
10
11
|
import { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, PersonChip, UppercaseBadge } from "./inputs.js";
|
|
11
12
|
function PeopleField({
|
|
@@ -101,6 +102,11 @@ function PeopleField({
|
|
|
101
102
|
variant: "ghost",
|
|
102
103
|
role: "option",
|
|
103
104
|
"aria-selected": false,
|
|
105
|
+
"aria-label": composeAccessibleName([
|
|
106
|
+
option.name,
|
|
107
|
+
option.email,
|
|
108
|
+
option.isCustomer ? t("customers.calendar.editor.customerBadge", "Customer") : null
|
|
109
|
+
]),
|
|
104
110
|
onClick: () => {
|
|
105
111
|
const participant = {
|
|
106
112
|
userId: option.userId,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/calendar/editor/PeopleField.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 { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport type { EditorParticipant } from '../../../lib/calendar/editorPayload'\nimport { searchPeopleOptions, type PersonOption } from './lookups'\nimport { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, PersonChip, UppercaseBadge } from './inputs'\n\nexport function PeopleField({\n mode,\n placeholder,\n ariaLabel,\n value,\n onChange,\n includeCustomers,\n}: {\n mode: 'multi' | 'single'\n placeholder: string\n ariaLabel: string\n value: EditorParticipant[]\n onChange(next: EditorParticipant[]): void\n includeCustomers: 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<PersonOption[]>([])\n const [loading, setLoading] = React.useState(false)\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 results = await searchPeopleOptions(query.trim(), { includeCustomers, signal: controller.signal })\n if (cancelled) return\n setOptions(results)\n } catch {\n if (!cancelled) setOptions([])\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, includeCustomers])\n\n const selectedIds = new Set(value.map((participant) => participant.userId))\n const visibleOptions = options.filter((option) => !selectedIds.has(option.userId))\n\n const customerBadge = (\n <UppercaseBadge className=\"bg-status-info-bg text-status-info-text\">\n {t('customers.calendar.editor.customerBadge', 'Customer')}\n </UppercaseBadge>\n )\n\n return (\n <div\n className=\"relative w-full\"\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setOpen(false)\n }}\n >\n <div\n className={cn(\n 'flex w-full flex-wrap content-center items-center gap-2 rounded-md bg-background px-2.5 py-2',\n mode === 'multi' ? 'min-h-24' : 'min-h-14',\n CONTROL_BORDER,\n )}\n >\n {value.map((participant) => (\n <PersonChip\n key={participant.userId}\n name={participant.name}\n badge={participant.isCustomer ? customerBadge : undefined}\n onRemove={() => onChange(value.filter((entry) => entry.userId !== participant.userId))}\n removeLabel={t('customers.calendar.editor.removePerson', 'Remove {name}', { name: participant.name })}\n />\n ))}\n <Input\n type=\"text\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onFocus={() => setOpen(true)}\n placeholder={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 role=\"listbox\" aria-label={ariaLabel} className={DROPDOWN_PANEL_CLASS}>\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.isCustomer ? 'customer' : 'staff'}:${option.userId}`}\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={false}\n onClick={() => {\n const participant: EditorParticipant = {\n userId: option.userId,\n name: option.name,\n email: option.email ?? undefined,\n isCustomer: option.isCustomer,\n }\n onChange(mode === 'single' ? [participant] : [...value, participant])\n setQuery('')\n if (mode === 'single') setOpen(false)\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 <Avatar size=\"xs\" label={option.name} />\n <span className=\"min-w-0 flex-1 truncate\">\n {option.name}\n {option.email ? <span className=\"ml-1.5 text-xs text-muted-foreground\">{option.email}</span> : null}\n </span>\n {option.isCustomer ? customerBadge : null}\n </Button>\n ))\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 { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport type { EditorParticipant } from '../../../lib/calendar/editorPayload'\nimport { composeAccessibleName } from '../../../lib/calendar/labels'\nimport { searchPeopleOptions, type PersonOption } from './lookups'\nimport { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, PersonChip, UppercaseBadge } from './inputs'\n\nexport function PeopleField({\n mode,\n placeholder,\n ariaLabel,\n value,\n onChange,\n includeCustomers,\n}: {\n mode: 'multi' | 'single'\n placeholder: string\n ariaLabel: string\n value: EditorParticipant[]\n onChange(next: EditorParticipant[]): void\n includeCustomers: 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<PersonOption[]>([])\n const [loading, setLoading] = React.useState(false)\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 results = await searchPeopleOptions(query.trim(), { includeCustomers, signal: controller.signal })\n if (cancelled) return\n setOptions(results)\n } catch {\n if (!cancelled) setOptions([])\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, includeCustomers])\n\n const selectedIds = new Set(value.map((participant) => participant.userId))\n const visibleOptions = options.filter((option) => !selectedIds.has(option.userId))\n\n const customerBadge = (\n <UppercaseBadge className=\"bg-status-info-bg text-status-info-text\">\n {t('customers.calendar.editor.customerBadge', 'Customer')}\n </UppercaseBadge>\n )\n\n return (\n <div\n className=\"relative w-full\"\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setOpen(false)\n }}\n >\n <div\n className={cn(\n 'flex w-full flex-wrap content-center items-center gap-2 rounded-md bg-background px-2.5 py-2',\n mode === 'multi' ? 'min-h-24' : 'min-h-14',\n CONTROL_BORDER,\n )}\n >\n {value.map((participant) => (\n <PersonChip\n key={participant.userId}\n name={participant.name}\n badge={participant.isCustomer ? customerBadge : undefined}\n onRemove={() => onChange(value.filter((entry) => entry.userId !== participant.userId))}\n removeLabel={t('customers.calendar.editor.removePerson', 'Remove {name}', { name: participant.name })}\n />\n ))}\n <Input\n type=\"text\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onFocus={() => setOpen(true)}\n placeholder={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 role=\"listbox\" aria-label={ariaLabel} className={DROPDOWN_PANEL_CLASS}>\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.isCustomer ? 'customer' : 'staff'}:${option.userId}`}\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={false}\n aria-label={composeAccessibleName([\n option.name,\n option.email,\n option.isCustomer ? t('customers.calendar.editor.customerBadge', 'Customer') : null,\n ])}\n onClick={() => {\n const participant: EditorParticipant = {\n userId: option.userId,\n name: option.name,\n email: option.email ?? undefined,\n isCustomer: option.isCustomer,\n }\n onChange(mode === 'single' ? [participant] : [...value, participant])\n setQuery('')\n if (mode === 'single') setOpen(false)\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 <Avatar size=\"xs\" label={option.name} />\n <span className=\"min-w-0 flex-1 truncate\">\n {option.name}\n {option.email ? <span className=\"ml-1.5 text-xs text-muted-foreground\">{option.email}</span> : null}\n </span>\n {option.isCustomer ? customerBadge : null}\n </Button>\n ))\n : null}\n </div>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -7,6 +7,7 @@ import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
|
7
7
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
8
8
|
import { IconButton } from "@open-mercato/ui/primitives/icon-button";
|
|
9
9
|
import { Input } from "@open-mercato/ui/primitives/input";
|
|
10
|
+
import { composeAccessibleName } from "../../../lib/calendar/labels.js";
|
|
10
11
|
import {
|
|
11
12
|
fetchDealsForEntity,
|
|
12
13
|
searchRelatedEntities
|
|
@@ -153,6 +154,7 @@ function RelatedToField({
|
|
|
153
154
|
variant: "ghost",
|
|
154
155
|
role: "option",
|
|
155
156
|
"aria-selected": value?.id === option.id,
|
|
157
|
+
"aria-label": composeAccessibleName([option.label, option.subtitle, kindLabel(option.kind)]),
|
|
156
158
|
onClick: () => {
|
|
157
159
|
onChange({ id: option.id, kind: option.kind, label: option.label });
|
|
158
160
|
if (value?.id !== option.id) onDealChange(null);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/customers/components/calendar/editor/RelatedToField.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Building2, ChevronDown, User, X } 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 { EditorRelatedTo } from '../../../lib/calendar/editorPayload'\nimport {\n fetchDealsForEntity,\n searchRelatedEntities,\n type DealOption,\n type RelatedEntityOption,\n} from './lookups'\nimport { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, PersonChip, UppercaseBadge } from './inputs'\n\nconst OPTION_ROW_CLASS =\n 'h-auto w-full justify-start gap-2 whitespace-normal px-2 py-1.5 text-left text-sm font-normal text-foreground'\n\nexport function RelatedToField({\n label,\n value,\n deal,\n onChange,\n onDealChange,\n error,\n}: {\n label: string\n value: EditorRelatedTo | null\n deal: DealOption | null\n onChange(next: EditorRelatedTo | null): void\n onDealChange(next: DealOption | null): void\n error?: string | null\n}) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [query, setQuery] = React.useState('')\n const [options, setOptions] = React.useState<RelatedEntityOption[]>([])\n const [deals, setDeals] = React.useState<DealOption[]>([])\n const [loading, setLoading] = React.useState(false)\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 trimmed = query.trim()\n const [people, companies] = await Promise.all([\n searchRelatedEntities('person', trimmed, controller.signal),\n searchRelatedEntities('company', trimmed, controller.signal),\n ])\n if (cancelled) return\n setOptions([...people, ...companies])\n } catch {\n if (!cancelled) setOptions([])\n } finally {\n if (!cancelled) setLoading(false)\n }\n }, 200)\n return () => {\n cancelled = true\n controller.abort()\n window.clearTimeout(timer)\n }\n }, [open, query])\n\n React.useEffect(() => {\n if (!open || !value || value.kind === 'unknown') {\n setDeals([])\n return\n }\n const controller = new AbortController()\n let cancelled = false\n fetchDealsForEntity({ id: value.id, kind: value.kind }, controller.signal)\n .then((items) => { if (!cancelled) setDeals(items) })\n .catch(() => { if (!cancelled) setDeals([]) })\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [open, value])\n\n const kindLabel = (kind: RelatedEntityOption['kind']) =>\n kind === 'person'\n ? t('customers.deals.detail.tabs.peopleSingular', 'Person')\n : t('customers.deals.detail.tabs.companySingular', 'Company')\n\n return (\n <div\n className=\"relative w-full\"\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setOpen(false)\n }}\n >\n <div\n className={cn(\n 'flex h-10 w-full items-center rounded-md bg-background pl-2.5 pr-3',\n error ? 'border border-status-error-border' : CONTROL_BORDER,\n )}\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label={label}\n onClick={() => setOpen((previous) => !previous)}\n className=\"h-full min-w-0 flex-1 justify-between rounded-none bg-transparent p-0 text-left shadow-none hover:bg-transparent\"\n >\n <span className=\"flex min-w-0 items-center gap-2\">\n {value ? (\n <PersonChip\n compact\n name={value.label || value.id}\n />\n ) : (\n <span className=\"truncate text-sm text-muted-foreground\">\n {t('customers.calendar.editor.relatedToPlaceholder', 'Search people or companies\u2026')}\n </span>\n )}\n </span>\n <span className=\"flex shrink-0 items-center\">\n {deal ? (\n <UppercaseBadge className=\"h-7 bg-status-info-bg text-status-info-text\">\n {t('customers.calendar.editor.dealBadge', '{name} \u00B7 Deal', { name: deal.label })}\n </UppercaseBadge>\n ) : null}\n <span aria-hidden className=\"h-px w-2\" />\n <ChevronDown aria-hidden className=\"size-4 opacity-60\" />\n </span>\n </Button>\n {value ? (\n <IconButton\n variant=\"ghost\"\n size=\"xs\"\n onClick={() => {\n onChange(null)\n onDealChange(null)\n }}\n aria-label={t('customers.calendar.editor.removeRelated', 'Clear related record')}\n className=\"ms-1 shrink-0 text-muted-foreground\"\n >\n <X aria-hidden className=\"size-3.5\" />\n </IconButton>\n ) : null}\n </div>\n {open ? (\n <div role=\"listbox\" aria-label={label} className={DROPDOWN_PANEL_CLASS}>\n <Input\n type=\"text\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder={t('customers.calendar.editor.relatedToPlaceholder', 'Search people or companies\u2026')}\n aria-label={t('customers.calendar.editor.relatedToPlaceholder', 'Search people or companies\u2026')}\n autoFocus\n size=\"sm\"\n className=\"mb-1\"\n />\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 && options.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 ? options.map((option) => (\n <Button\n key={`${option.kind}:${option.id}`}\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={value?.id === option.id}\n onClick={() => {\n onChange({ id: option.id, kind: option.kind, label: option.label })\n if (value?.id !== option.id) onDealChange(null)\n setQuery('')\n }}\n className={OPTION_ROW_CLASS}\n >\n {option.kind === 'company'\n ? <Building2 aria-hidden className=\"size-3.5 shrink-0 text-muted-foreground\" />\n : <User aria-hidden className=\"size-3.5 shrink-0 text-muted-foreground\" />}\n <span className=\"min-w-0 flex-1 truncate\">\n {option.label}\n {option.subtitle ? <span className=\"ml-1.5 text-xs text-muted-foreground\">{option.subtitle}</span> : null}\n </span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">{kindLabel(option.kind)}</span>\n </Button>\n ))\n : null}\n {value && deals.length > 0 ? (\n <>\n <p className=\"px-2 pb-1 pt-2 text-overline font-medium uppercase text-muted-foreground\">\n {t('customers.calendar.editor.dealsSection', 'Deals')}\n </p>\n <Button\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={deal === null}\n onClick={() => {\n onDealChange(null)\n setOpen(false)\n }}\n className={OPTION_ROW_CLASS}\n >\n {t('customers.calendar.editor.dealNone', 'No deal')}\n </Button>\n {deals.map((option) => (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={deal?.id === option.id}\n onClick={() => {\n onDealChange(option)\n setOpen(false)\n }}\n className={OPTION_ROW_CLASS}\n >\n <span className=\"min-w-0 flex-1 truncate\">{option.label}</span>\n <UppercaseBadge className=\"bg-status-info-bg text-status-info-text\">\n {t('customers.calendar.editor.dealBadgeSuffix', 'Deal')}\n </UppercaseBadge>\n </Button>\n ))}\n </>\n ) : null}\n </div>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Building2, ChevronDown, User, X } 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 { EditorRelatedTo } from '../../../lib/calendar/editorPayload'\nimport { composeAccessibleName } from '../../../lib/calendar/labels'\nimport {\n fetchDealsForEntity,\n searchRelatedEntities,\n type DealOption,\n type RelatedEntityOption,\n} from './lookups'\nimport { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, PersonChip, UppercaseBadge } from './inputs'\n\nconst OPTION_ROW_CLASS =\n 'h-auto w-full justify-start gap-2 whitespace-normal px-2 py-1.5 text-left text-sm font-normal text-foreground'\n\nexport function RelatedToField({\n label,\n value,\n deal,\n onChange,\n onDealChange,\n error,\n}: {\n label: string\n value: EditorRelatedTo | null\n deal: DealOption | null\n onChange(next: EditorRelatedTo | null): void\n onDealChange(next: DealOption | null): void\n error?: string | null\n}) {\n const t = useT()\n const [open, setOpen] = React.useState(false)\n const [query, setQuery] = React.useState('')\n const [options, setOptions] = React.useState<RelatedEntityOption[]>([])\n const [deals, setDeals] = React.useState<DealOption[]>([])\n const [loading, setLoading] = React.useState(false)\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 trimmed = query.trim()\n const [people, companies] = await Promise.all([\n searchRelatedEntities('person', trimmed, controller.signal),\n searchRelatedEntities('company', trimmed, controller.signal),\n ])\n if (cancelled) return\n setOptions([...people, ...companies])\n } catch {\n if (!cancelled) setOptions([])\n } finally {\n if (!cancelled) setLoading(false)\n }\n }, 200)\n return () => {\n cancelled = true\n controller.abort()\n window.clearTimeout(timer)\n }\n }, [open, query])\n\n React.useEffect(() => {\n if (!open || !value || value.kind === 'unknown') {\n setDeals([])\n return\n }\n const controller = new AbortController()\n let cancelled = false\n fetchDealsForEntity({ id: value.id, kind: value.kind }, controller.signal)\n .then((items) => { if (!cancelled) setDeals(items) })\n .catch(() => { if (!cancelled) setDeals([]) })\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [open, value])\n\n const kindLabel = (kind: RelatedEntityOption['kind']) =>\n kind === 'person'\n ? t('customers.deals.detail.tabs.peopleSingular', 'Person')\n : t('customers.deals.detail.tabs.companySingular', 'Company')\n\n return (\n <div\n className=\"relative w-full\"\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setOpen(false)\n }}\n >\n <div\n className={cn(\n 'flex h-10 w-full items-center rounded-md bg-background pl-2.5 pr-3',\n error ? 'border border-status-error-border' : CONTROL_BORDER,\n )}\n >\n <Button\n type=\"button\"\n variant=\"ghost\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-label={label}\n onClick={() => setOpen((previous) => !previous)}\n className=\"h-full min-w-0 flex-1 justify-between rounded-none bg-transparent p-0 text-left shadow-none hover:bg-transparent\"\n >\n <span className=\"flex min-w-0 items-center gap-2\">\n {value ? (\n <PersonChip\n compact\n name={value.label || value.id}\n />\n ) : (\n <span className=\"truncate text-sm text-muted-foreground\">\n {t('customers.calendar.editor.relatedToPlaceholder', 'Search people or companies\u2026')}\n </span>\n )}\n </span>\n <span className=\"flex shrink-0 items-center\">\n {deal ? (\n <UppercaseBadge className=\"h-7 bg-status-info-bg text-status-info-text\">\n {t('customers.calendar.editor.dealBadge', '{name} \u00B7 Deal', { name: deal.label })}\n </UppercaseBadge>\n ) : null}\n <span aria-hidden className=\"h-px w-2\" />\n <ChevronDown aria-hidden className=\"size-4 opacity-60\" />\n </span>\n </Button>\n {value ? (\n <IconButton\n variant=\"ghost\"\n size=\"xs\"\n onClick={() => {\n onChange(null)\n onDealChange(null)\n }}\n aria-label={t('customers.calendar.editor.removeRelated', 'Clear related record')}\n className=\"ms-1 shrink-0 text-muted-foreground\"\n >\n <X aria-hidden className=\"size-3.5\" />\n </IconButton>\n ) : null}\n </div>\n {open ? (\n <div role=\"listbox\" aria-label={label} className={DROPDOWN_PANEL_CLASS}>\n <Input\n type=\"text\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder={t('customers.calendar.editor.relatedToPlaceholder', 'Search people or companies\u2026')}\n aria-label={t('customers.calendar.editor.relatedToPlaceholder', 'Search people or companies\u2026')}\n autoFocus\n size=\"sm\"\n className=\"mb-1\"\n />\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 && options.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 ? options.map((option) => (\n <Button\n key={`${option.kind}:${option.id}`}\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={value?.id === option.id}\n aria-label={composeAccessibleName([option.label, option.subtitle, kindLabel(option.kind)])}\n onClick={() => {\n onChange({ id: option.id, kind: option.kind, label: option.label })\n if (value?.id !== option.id) onDealChange(null)\n setQuery('')\n }}\n className={OPTION_ROW_CLASS}\n >\n {option.kind === 'company'\n ? <Building2 aria-hidden className=\"size-3.5 shrink-0 text-muted-foreground\" />\n : <User aria-hidden className=\"size-3.5 shrink-0 text-muted-foreground\" />}\n <span className=\"min-w-0 flex-1 truncate\">\n {option.label}\n {option.subtitle ? <span className=\"ml-1.5 text-xs text-muted-foreground\">{option.subtitle}</span> : null}\n </span>\n <span className=\"shrink-0 text-xs text-muted-foreground\">{kindLabel(option.kind)}</span>\n </Button>\n ))\n : null}\n {value && deals.length > 0 ? (\n <>\n <p className=\"px-2 pb-1 pt-2 text-overline font-medium uppercase text-muted-foreground\">\n {t('customers.calendar.editor.dealsSection', 'Deals')}\n </p>\n <Button\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={deal === null}\n onClick={() => {\n onDealChange(null)\n setOpen(false)\n }}\n className={OPTION_ROW_CLASS}\n >\n {t('customers.calendar.editor.dealNone', 'No deal')}\n </Button>\n {deals.map((option) => (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={deal?.id === option.id}\n onClick={() => {\n onDealChange(option)\n setOpen(false)\n }}\n className={OPTION_ROW_CLASS}\n >\n <span className=\"min-w-0 flex-1 truncate\">{option.label}</span>\n <UppercaseBadge className=\"bg-status-info-bg text-status-info-text\">\n {t('customers.calendar.editor.dealBadgeSuffix', 'Deal')}\n </UppercaseBadge>\n </Button>\n ))}\n </>\n ) : null}\n </div>\n ) : null}\n </div>\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
4
|
+
import { multiDayEventSpan } from "../../../lib/calendar/labels.js";
|
|
4
5
|
import { AllDayToggle, DateControl, LABEL_CLASS, TimeControl } from "./inputs.js";
|
|
5
6
|
const DATE_LABEL_TEXT = {
|
|
6
7
|
starts: { key: "customers.calendar.editor.dates.starts", fallback: "Starts" },
|
|
@@ -45,6 +46,7 @@ function ScheduleSection({
|
|
|
45
46
|
}) {
|
|
46
47
|
const t = useT();
|
|
47
48
|
const showTime = !(hasAllDay && allDay);
|
|
49
|
+
const multiDaySpan = hasEnd && !endsError ? multiDayEventSpan(date, endDate) : 0;
|
|
48
50
|
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-2.5", children: [
|
|
49
51
|
hasAllDay ? /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-between", children: [
|
|
50
52
|
/* @__PURE__ */ jsx("span", { className: LABEL_CLASS, children: t("customers.calendar.editor.allDay", "All day") }),
|
|
@@ -74,7 +76,8 @@ function ScheduleSection({
|
|
|
74
76
|
onTimeChange: onEndTimeChange
|
|
75
77
|
}
|
|
76
78
|
) : null,
|
|
77
|
-
endsError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-status-error-text", children: endsError }) : null
|
|
79
|
+
endsError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-status-error-text", children: endsError }) : null,
|
|
80
|
+
multiDaySpan > 0 ? /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("customers.calendar.editor.multiDayHint", "Multi-day event \xB7 {count} days", { count: multiDaySpan }) }) : null
|
|
78
81
|
] });
|
|
79
82
|
}
|
|
80
83
|
export {
|
|
@@ -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 { 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 locale,\n onDateChange,\n onTimeChange,\n}: {\n label: string\n date: string\n time: string\n showTime: boolean\n locale: string\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} locale={locale} />\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 locale,\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 locale: 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 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 locale={locale}\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 locale={locale}\n onDateChange={onEndDateChange}\n onTimeChange={onEndTimeChange}\n />\n ) : null}\n {endsError ? <p className=\"text-xs text-status-error-text\">{endsError}</p> : 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 locale,\n onDateChange,\n onTimeChange,\n}: {\n label: string\n date: string\n time: string\n showTime: boolean\n locale: string\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} locale={locale} />\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 locale,\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 locale: 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 locale={locale}\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 locale={locale}\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": ";AAmCM,SACE,KADF;AAhCN,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;AACF,GAQG;AACD,SACE,qBAAC,SAAI,WAAU,iCACb;AAAA,yBAAC,SAAI,WAAU,wCACb;AAAA,0BAAC,UAAK,WAAW,aAAc,iBAAM;AAAA,MACrC,oBAAC,eAAY,OAAO,MAAM,UAAU,cAAc,WAAW,OAAO,QAAgB;AAAA,OACtF;AAAA,IACC,WAAW,oBAAC,eAAY,OAAO,MAAM,UAAU,cAAc,WAAW,OAAO,IAAK;AAAA,KACvF;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;AACF,GAgBG;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;AAAA,OAC3H,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;AAAA,QACA,cAAc;AAAA;AAAA,IAChB;AAAA,IACC,SACC;AAAA,MAAC;AAAA;AAAA,QACC,OAAO,EAAE,wCAAwC,MAAM;AAAA,QACvD,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,cAAc;AAAA,QACd,cAAc;AAAA;AAAA,IAChB,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
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const MS_PER_DAY = 24 * 60 * 60 * 1e3;
|
|
2
|
+
function pluralCategory(locale, count) {
|
|
3
|
+
try {
|
|
4
|
+
return new Intl.PluralRules(locale).select(count);
|
|
5
|
+
} catch {
|
|
6
|
+
return count === 1 ? "one" : "other";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
function eventDisplayTitle(title, fallback) {
|
|
10
|
+
return title && title.trim().length > 0 ? title : fallback;
|
|
11
|
+
}
|
|
12
|
+
function composeAccessibleName(parts, separator = ", ") {
|
|
13
|
+
return parts.map((part) => typeof part === "string" ? part.trim() : "").filter((part) => part.length > 0).join(separator);
|
|
14
|
+
}
|
|
15
|
+
function parseDateInput(value) {
|
|
16
|
+
const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00`);
|
|
17
|
+
return parsed.getTime();
|
|
18
|
+
}
|
|
19
|
+
function multiDayEventSpan(startDate, endDate) {
|
|
20
|
+
const start = parseDateInput(startDate);
|
|
21
|
+
const end = parseDateInput(endDate);
|
|
22
|
+
if (Number.isNaN(start) || Number.isNaN(end)) return 0;
|
|
23
|
+
const dayDiff = Math.round((end - start) / MS_PER_DAY);
|
|
24
|
+
if (dayDiff < 1) return 0;
|
|
25
|
+
return dayDiff + 1;
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
composeAccessibleName,
|
|
29
|
+
eventDisplayTitle,
|
|
30
|
+
multiDayEventSpan,
|
|
31
|
+
pluralCategory
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=labels.js.map
|