@open-mercato/core 0.8.1-develop.7454.1.010095490f → 0.8.1-develop.7455.1.8f7e53badb

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.
Files changed (155) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/customer_dictionary_entry/index.js +2 -0
  3. package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
  4. package/dist/generated/entities/customer_interaction/index.js +2 -0
  5. package/dist/generated/entities/customer_interaction/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +2 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/customers/api/activity-types/route.js +151 -0
  9. package/dist/modules/customers/api/activity-types/route.js.map +7 -0
  10. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
  11. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
  12. package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
  13. package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
  14. package/dist/modules/customers/api/interactions/route.js +3 -0
  15. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  16. package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
  17. package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
  18. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
  19. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
  20. package/dist/modules/customers/calendar-event-types.js +548 -0
  21. package/dist/modules/customers/calendar-event-types.js.map +7 -0
  22. package/dist/modules/customers/commands/dictionaries.js +70 -10
  23. package/dist/modules/customers/commands/dictionaries.js.map +2 -2
  24. package/dist/modules/customers/commands/interactions.js +187 -22
  25. package/dist/modules/customers/commands/interactions.js.map +3 -3
  26. package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
  27. package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
  28. package/dist/modules/customers/components/DictionarySettings.js +3 -1
  29. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  30. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
  31. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
  32. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
  33. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  34. package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
  35. package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
  36. package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
  37. package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
  38. package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
  39. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
  40. package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
  41. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  42. package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
  43. package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
  44. package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
  45. package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
  46. package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
  47. package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
  48. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
  49. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
  50. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
  51. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
  52. package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
  53. package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
  54. package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
  55. package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
  56. package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
  57. package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
  58. package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
  59. package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
  60. package/dist/modules/customers/components/calendar/types.js +1 -0
  61. package/dist/modules/customers/components/calendar/types.js.map +2 -2
  62. package/dist/modules/customers/data/entities.js +6 -0
  63. package/dist/modules/customers/data/entities.js.map +2 -2
  64. package/dist/modules/customers/data/validators.js +39 -10
  65. package/dist/modules/customers/data/validators.js.map +2 -2
  66. package/dist/modules/customers/di.js +20 -1
  67. package/dist/modules/customers/di.js.map +2 -2
  68. package/dist/modules/customers/extension-points.js +13 -0
  69. package/dist/modules/customers/extension-points.js.map +2 -2
  70. package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
  71. package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
  72. package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
  73. package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
  74. package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
  75. package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
  76. package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
  77. package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
  78. package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
  79. package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
  80. package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
  81. package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
  82. package/dist/modules/customers/lib/calendar/timezone.js +111 -0
  83. package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
  84. package/dist/modules/customers/lib/interactionReadModel.js +1 -0
  85. package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
  86. package/dist/modules/customers/lib/todoCompatibility.js +1 -0
  87. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  88. package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
  89. package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
  90. package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
  91. package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
  92. package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
  93. package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
  94. package/dist/modules/planner/lib/availabilityMerge.js +101 -38
  95. package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
  96. package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
  97. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
  98. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  99. package/generated/entities/customer_dictionary_entry/index.ts +1 -0
  100. package/generated/entities/customer_interaction/index.ts +1 -0
  101. package/generated/entity-fields-registry.ts +2 -0
  102. package/package.json +7 -7
  103. package/src/modules/customers/api/activity-types/route.ts +154 -0
  104. package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
  105. package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
  106. package/src/modules/customers/api/interactions/route.ts +4 -0
  107. package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
  108. package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
  109. package/src/modules/customers/calendar-event-types.ts +674 -0
  110. package/src/modules/customers/commands/dictionaries.ts +84 -8
  111. package/src/modules/customers/commands/interactions.ts +244 -21
  112. package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
  113. package/src/modules/customers/components/DictionarySettings.tsx +5 -3
  114. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
  115. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
  116. package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
  117. package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
  118. package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
  119. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
  120. package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
  121. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
  122. package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
  123. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
  124. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
  125. package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
  126. package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
  127. package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
  128. package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
  129. package/src/modules/customers/components/calendar/types.ts +1 -0
  130. package/src/modules/customers/components/detail/types.ts +1 -0
  131. package/src/modules/customers/data/entities.ts +8 -1
  132. package/src/modules/customers/data/validators.ts +42 -7
  133. package/src/modules/customers/di.ts +21 -0
  134. package/src/modules/customers/extension-points.ts +13 -0
  135. package/src/modules/customers/i18n/de.json +83 -0
  136. package/src/modules/customers/i18n/en.json +83 -0
  137. package/src/modules/customers/i18n/es.json +83 -0
  138. package/src/modules/customers/i18n/ko.json +83 -0
  139. package/src/modules/customers/i18n/pl.json +83 -0
  140. package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
  141. package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
  142. package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
  143. package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
  144. package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
  145. package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
  146. package/src/modules/customers/lib/calendar/timezone.ts +117 -0
  147. package/src/modules/customers/lib/interactionReadModel.ts +1 -0
  148. package/src/modules/customers/lib/todoCompatibility.ts +2 -0
  149. package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
  150. package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
  151. package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
  152. package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
  153. package/src/modules/planner/lib/availabilityMerge.ts +106 -41
  154. package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
  155. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customers/migrations/Migration20260928175058_customers.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260928175058_customers extends Migration {\n\n override name = 'Migration20260928175058_customers';\n\n override up(): void | Promise<void> {\n this.addSql(`alter table \"customer_dictionary_entries\" add \"activity_type_behavior\" jsonb null;`);\n }\n\n override down(): void | Promise<void> {\n this.addSql(`alter table \"customer_dictionary_entries\" drop column \"activity_type_behavior\";`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,0CAA0C,UAAU;AAAA,EAA1D;AAAA;AAEL,SAAS,OAAO;AAAA;AAAA,EAEP,KAA2B;AAClC,SAAK,OAAO,oFAAoF;AAAA,EAClG;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,iFAAiF;AAAA,EAC/F;AAEF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,17 @@
1
+ import { Migration } from "@mikro-orm/migrations";
2
+ class Migration20260930110235_customers extends Migration {
3
+ constructor() {
4
+ super(...arguments);
5
+ this.name = "Migration20260930110235_customers";
6
+ }
7
+ up() {
8
+ this.addSql(`alter table "customer_interactions" add "timezone" text null;`);
9
+ }
10
+ down() {
11
+ this.addSql(`alter table "customer_interactions" drop column "timezone";`);
12
+ }
13
+ }
14
+ export {
15
+ Migration20260930110235_customers
16
+ };
17
+ //# sourceMappingURL=Migration20260930110235_customers.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/modules/customers/migrations/Migration20260930110235_customers.ts"],
4
+ "sourcesContent": ["import { Migration } from '@mikro-orm/migrations';\n\nexport class Migration20260930110235_customers extends Migration {\n\n override name = 'Migration20260930110235_customers';\n\n override up(): void | Promise<void> {\n this.addSql(`alter table \"customer_interactions\" add \"timezone\" text null;`);\n }\n\n override down(): void | Promise<void> {\n this.addSql(`alter table \"customer_interactions\" drop column \"timezone\";`);\n }\n\n}\n"],
5
+ "mappings": "AAAA,SAAS,iBAAiB;AAEnB,MAAM,0CAA0C,UAAU;AAAA,EAA1D;AAAA;AAEL,SAAS,OAAO;AAAA;AAAA,EAEP,KAA2B;AAClC,SAAK,OAAO,+DAA+D;AAAA,EAC7E;AAAA,EAES,OAA6B;AACpC,SAAK,OAAO,6DAA6D;AAAA,EAC3E;AAEF;",
6
+ "names": []
7
+ }
@@ -72,59 +72,68 @@ function DictionaryTable({
72
72
  onEdit(entry);
73
73
  } : void 0;
74
74
  return /* @__PURE__ */ jsx(
75
- DataTable,
75
+ "div",
76
76
  {
77
- title: translations.title,
78
- titleHeadingLevel: 2,
79
- actions,
80
- columns,
81
- data: paginated,
82
- embedded: true,
83
- sortable: true,
84
- sorting,
85
- onSortingChange: setSorting,
86
- searchValue: search,
87
- onSearchChange: (value) => {
88
- setSearch(value);
89
- setPage(1);
90
- },
91
- searchPlaceholder: translations.searchPlaceholder,
92
- isLoading: loading,
93
- emptyState: /* @__PURE__ */ jsx("p", { className: "py-10 text-center text-sm text-muted-foreground", children: translations.emptyLabel }),
94
- pagination: {
95
- page,
96
- pageSize,
97
- total: filtered.length,
98
- totalPages,
99
- onPageChange: setPage
100
- },
101
- refreshButton: onRefresh ? {
102
- label: translations.refreshLabel,
103
- onRefresh,
104
- isRefreshing: loading
105
- } : void 0,
106
- onRowClick: handleRowClick,
107
- rowActions: canManage ? (entry) => {
108
- if (!entry) return null;
109
- if (entry.isInherited) return null;
110
- const items = [];
111
- if (onEdit) {
112
- items.push({
113
- id: "edit",
114
- label: translations.editLabel,
115
- onSelect: () => onEdit(entry)
116
- });
77
+ role: "region",
78
+ "aria-label": translations.title,
79
+ tabIndex: 0,
80
+ className: "w-full min-w-0 max-w-full overflow-x-auto focus-visible:outline-none focus-visible:shadow-focus",
81
+ children: /* @__PURE__ */ jsx(
82
+ DataTable,
83
+ {
84
+ title: translations.title,
85
+ titleHeadingLevel: 2,
86
+ actions,
87
+ columns,
88
+ data: paginated,
89
+ embedded: true,
90
+ sortable: true,
91
+ sorting,
92
+ onSortingChange: setSorting,
93
+ searchValue: search,
94
+ onSearchChange: (value) => {
95
+ setSearch(value);
96
+ setPage(1);
97
+ },
98
+ searchPlaceholder: translations.searchPlaceholder,
99
+ isLoading: loading,
100
+ emptyState: /* @__PURE__ */ jsx("p", { className: "py-10 text-center text-sm text-muted-foreground", children: translations.emptyLabel }),
101
+ pagination: {
102
+ page,
103
+ pageSize,
104
+ total: filtered.length,
105
+ totalPages,
106
+ onPageChange: setPage
107
+ },
108
+ refreshButton: onRefresh ? {
109
+ label: translations.refreshLabel,
110
+ onRefresh,
111
+ isRefreshing: loading
112
+ } : void 0,
113
+ onRowClick: handleRowClick,
114
+ rowActions: canManage ? (entry) => {
115
+ if (!entry) return null;
116
+ if (entry.isInherited) return null;
117
+ const items = [];
118
+ if (onEdit) {
119
+ items.push({
120
+ id: "edit",
121
+ label: translations.editLabel,
122
+ onSelect: () => onEdit(entry)
123
+ });
124
+ }
125
+ if (onDelete) {
126
+ items.push({
127
+ id: "delete",
128
+ label: translations.deleteLabel,
129
+ onSelect: () => onDelete(entry),
130
+ destructive: true
131
+ });
132
+ }
133
+ return items.length ? /* @__PURE__ */ jsx(RowActions, { items }) : null;
134
+ } : void 0
117
135
  }
118
- if (onDelete) {
119
- items.push({
120
- id: "delete",
121
- label: translations.deleteLabel,
122
- onSelect: () => onDelete(entry),
123
- destructive: true
124
- });
125
- }
126
- return items.length ? /* @__PURE__ */ jsx(RowActions, { items }) : null;
127
- } : void 0
136
+ )
128
137
  }
129
138
  );
130
139
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/dictionaries/components/DictionaryTable.tsx"],
4
- "sourcesContent": ["import * as React from 'react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions, type RowActionItem } from '@open-mercato/ui/backend/RowActions'\nimport { renderDictionaryColor, renderDictionaryIcon } from './dictionaryAppearance'\n\nexport type DictionaryTableEntry = {\n id: string\n value: string\n label: string\n color: string | null\n icon: string | null\n usageCount?: number\n organizationId?: string | null\n tenantId?: string | null\n isInherited?: boolean\n createdAt?: string | null\n updatedAt?: string | null\n}\n\nexport type DictionaryTableTranslations = {\n title: string\n valueColumn: string\n labelColumn: string\n appearanceColumn: string\n addLabel: string\n editLabel: string\n deleteLabel: string\n refreshLabel: string\n inheritedLabel: string\n inheritedTooltip: string\n emptyLabel: string\n usageCountLabel?: string\n searchPlaceholder?: string\n}\n\ntype DictionaryTableProps = {\n entries: DictionaryTableEntry[]\n loading?: boolean\n canManage?: boolean\n onCreate?: () => void\n onEdit?: (entry: DictionaryTableEntry) => void\n onDelete?: (entry: DictionaryTableEntry) => void\n onRefresh?: () => void\n translations: DictionaryTableTranslations\n}\n\nexport function DictionaryTable({\n entries,\n loading = false,\n canManage = false,\n onCreate,\n onEdit,\n onDelete,\n onRefresh,\n translations,\n}: DictionaryTableProps) {\n const [search, setSearch] = React.useState('')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'value', desc: false }])\n const [page, setPage] = React.useState(1)\n const pageSize = 50\n\n const filtered = React.useMemo(() => {\n if (!search.trim()) return entries\n const term = search.trim().toLowerCase()\n return entries.filter((entry) => {\n return (\n entry.value.toLowerCase().includes(term) ||\n (entry.label ?? '').toLowerCase().includes(term)\n )\n })\n }, [entries, search])\n\n const paginated = React.useMemo(() => {\n const start = (page - 1) * pageSize\n return filtered.slice(start, start + pageSize)\n }, [filtered, page])\n\n const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize))\n\n const columns = React.useMemo<ColumnDef<DictionaryTableEntry>[]>(() => [\n {\n accessorKey: 'value',\n header: translations.valueColumn,\n meta: { priority: 1 },\n cell: ({ getValue, row }) => (\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium\">{String(getValue())}</span>\n {row.original.isInherited ? (\n <span className=\"rounded bg-muted px-2 py-0.5 text-xs text-muted-foreground\" title={translations.inheritedTooltip}>\n {translations.inheritedLabel}\n </span>\n ) : null}\n </div>\n {typeof row.original.usageCount === 'number' && row.original.usageCount > 0 && translations.usageCountLabel ? (\n <div className=\"text-xs text-muted-foreground\">\n {translations.usageCountLabel.replace('{{count}}', String(row.original.usageCount))}\n </div>\n ) : null}\n </div>\n ),\n },\n {\n accessorKey: 'label',\n header: translations.labelColumn,\n meta: { priority: 2 },\n cell: ({ getValue }) => <span>{String(getValue() ?? '')}</span>,\n },\n {\n id: 'appearance',\n header: translations.appearanceColumn,\n meta: { priority: 3 },\n cell: ({ row }) => {\n const { color, icon } = row.original\n if (!color && !icon) return <span className=\"text-muted-foreground\">\u2014</span>\n return (\n <div className=\"flex items-center gap-2\">\n {color ? renderDictionaryColor(color, 'h-4 w-4 rounded-full border border-border') : null}\n {icon ? renderDictionaryIcon(icon, 'h-4 w-4') : null}\n </div>\n )\n },\n },\n ], [translations.appearanceColumn, translations.inheritedLabel, translations.inheritedTooltip, translations.labelColumn, translations.valueColumn])\n\n const actions = React.useMemo(() => {\n if (!canManage || !onCreate) return null\n return (\n <Button size=\"sm\" onClick={onCreate}>\n {translations.addLabel}\n </Button>\n )\n }, [canManage, onCreate, translations.addLabel])\n\n const handleRowClick = canManage && onEdit\n ? (entry: DictionaryTableEntry) => {\n if (entry.isInherited) return\n onEdit(entry)\n }\n : undefined\n\n return (\n <DataTable<DictionaryTableEntry>\n title={translations.title}\n titleHeadingLevel={2}\n actions={actions}\n columns={columns}\n data={paginated}\n embedded\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={translations.searchPlaceholder}\n isLoading={loading}\n emptyState={<p className=\"py-10 text-center text-sm text-muted-foreground\">{translations.emptyLabel}</p>}\n pagination={{\n page,\n pageSize,\n total: filtered.length,\n totalPages,\n onPageChange: setPage,\n }}\n refreshButton={onRefresh ? {\n label: translations.refreshLabel,\n onRefresh,\n isRefreshing: loading,\n } : undefined}\n onRowClick={handleRowClick}\n rowActions={\n canManage\n ? (entry) => {\n if (!entry) return null\n if (entry.isInherited) return null\n const items: RowActionItem[] = []\n if (onEdit) {\n items.push({\n id: 'edit',\n label: translations.editLabel,\n onSelect: () => onEdit(entry),\n })\n }\n if (onDelete) {\n items.push({\n id: 'delete',\n label: translations.deleteLabel,\n onSelect: () => onDelete(entry),\n destructive: true,\n })\n }\n return items.length ? <RowActions items={items} /> : null\n }\n : undefined\n }\n />\n )\n}\n"],
5
- "mappings": "AAyFU,SACE,KADF;AAzFV,YAAY,WAAW;AAGvB,SAAS,iBAAiB;AAC1B,SAAS,cAAc;AACvB,SAAS,kBAAsC;AAC/C,SAAS,uBAAuB,4BAA4B;AA2CrD,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,UAAU;AAAA,EACV,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,CAAC,QAAQ,SAAS,IAAI,MAAM,SAAS,EAAE;AAC7C,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAuB,CAAC,EAAE,IAAI,SAAS,MAAM,MAAM,CAAC,CAAC;AACzF,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,CAAC;AACxC,QAAM,WAAW;AAEjB,QAAM,WAAW,MAAM,QAAQ,MAAM;AACnC,QAAI,CAAC,OAAO,KAAK,EAAG,QAAO;AAC3B,UAAM,OAAO,OAAO,KAAK,EAAE,YAAY;AACvC,WAAO,QAAQ,OAAO,CAAC,UAAU;AAC/B,aACE,MAAM,MAAM,YAAY,EAAE,SAAS,IAAI,MACtC,MAAM,SAAS,IAAI,YAAY,EAAE,SAAS,IAAI;AAAA,IAEnD,CAAC;AAAA,EACH,GAAG,CAAC,SAAS,MAAM,CAAC;AAEpB,QAAM,YAAY,MAAM,QAAQ,MAAM;AACpC,UAAM,SAAS,OAAO,KAAK;AAC3B,WAAO,SAAS,MAAM,OAAO,QAAQ,QAAQ;AAAA,EAC/C,GAAG,CAAC,UAAU,IAAI,CAAC;AAEnB,QAAM,aAAa,KAAK,IAAI,GAAG,KAAK,KAAK,SAAS,SAAS,QAAQ,CAAC;AAEpE,QAAM,UAAU,MAAM,QAA2C,MAAM;AAAA,IACrE;AAAA,MACE,aAAa;AAAA,MACb,QAAQ,aAAa;AAAA,MACrB,MAAM,EAAE,UAAU,EAAE;AAAA,MACpB,MAAM,CAAC,EAAE,UAAU,IAAI,MACrB,qBAAC,SAAI,WAAU,aACb;AAAA,6BAAC,SAAI,WAAU,2BACb;AAAA,8BAAC,UAAK,WAAU,eAAe,iBAAO,SAAS,CAAC,GAAE;AAAA,UACjD,IAAI,SAAS,cACZ,oBAAC,UAAK,WAAU,8DAA6D,OAAO,aAAa,kBAC9F,uBAAa,gBAChB,IACE;AAAA,WACN;AAAA,QACC,OAAO,IAAI,SAAS,eAAe,YAAY,IAAI,SAAS,aAAa,KAAK,aAAa,kBAC1F,oBAAC,SAAI,WAAU,iCACZ,uBAAa,gBAAgB,QAAQ,aAAa,OAAO,IAAI,SAAS,UAAU,CAAC,GACpF,IACE;AAAA,SACN;AAAA,IAEJ;AAAA,IACA;AAAA,MACE,aAAa;AAAA,MACb,QAAQ,aAAa;AAAA,MACrB,MAAM,EAAE,UAAU,EAAE;AAAA,MACpB,MAAM,CAAC,EAAE,SAAS,MAAM,oBAAC,UAAM,iBAAO,SAAS,KAAK,EAAE,GAAE;AAAA,IAC1D;AAAA,IACA;AAAA,MACE,IAAI;AAAA,MACJ,QAAQ,aAAa;AAAA,MACrB,MAAM,EAAE,UAAU,EAAE;AAAA,MACpB,MAAM,CAAC,EAAE,IAAI,MAAM;AACjB,cAAM,EAAE,OAAO,KAAK,IAAI,IAAI;AAC5B,YAAI,CAAC,SAAS,CAAC,KAAM,QAAO,oBAAC,UAAK,WAAU,yBAAwB,oBAAC;AACrE,eACE,qBAAC,SAAI,WAAU,2BACZ;AAAA,kBAAQ,sBAAsB,OAAO,2CAA2C,IAAI;AAAA,UACpF,OAAO,qBAAqB,MAAM,SAAS,IAAI;AAAA,WAClD;AAAA,MAEJ;AAAA,IACF;AAAA,EACF,GAAG,CAAC,aAAa,kBAAkB,aAAa,gBAAgB,aAAa,kBAAkB,aAAa,aAAa,aAAa,WAAW,CAAC;AAElJ,QAAM,UAAU,MAAM,QAAQ,MAAM;AAClC,QAAI,CAAC,aAAa,CAAC,SAAU,QAAO;AACpC,WACE,oBAAC,UAAO,MAAK,MAAK,SAAS,UACxB,uBAAa,UAChB;AAAA,EAEJ,GAAG,CAAC,WAAW,UAAU,aAAa,QAAQ,CAAC;AAE/C,QAAM,iBAAiB,aAAa,SAChC,CAAC,UAAgC;AAC/B,QAAI,MAAM,YAAa;AACvB,WAAO,KAAK;AAAA,EACd,IACA;AAEJ,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,aAAa;AAAA,MACpB,mBAAmB;AAAA,MACnB;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN,UAAQ;AAAA,MACR,UAAQ;AAAA,MACR;AAAA,MACA,iBAAiB;AAAA,MACjB,aAAa;AAAA,MACb,gBAAgB,CAAC,UAAU;AACzB,kBAAU,KAAK;AACf,gBAAQ,CAAC;AAAA,MACX;AAAA,MACA,mBAAmB,aAAa;AAAA,MAChC,WAAW;AAAA,MACX,YAAY,oBAAC,OAAE,WAAU,mDAAmD,uBAAa,YAAW;AAAA,MACpG,YAAY;AAAA,QACV;AAAA,QACA;AAAA,QACA,OAAO,SAAS;AAAA,QAChB;AAAA,QACA,cAAc;AAAA,MAChB;AAAA,MACA,eAAe,YAAY;AAAA,QACzB,OAAO,aAAa;AAAA,QACpB;AAAA,QACA,cAAc;AAAA,MAChB,IAAI;AAAA,MACJ,YAAY;AAAA,MACZ,YACE,YACI,CAAC,UAAU;AACT,YAAI,CAAC,MAAO,QAAO;AACnB,YAAI,MAAM,YAAa,QAAO;AAC9B,cAAM,QAAyB,CAAC;AAChC,YAAI,QAAQ;AACV,gBAAM,KAAK;AAAA,YACT,IAAI;AAAA,YACJ,OAAO,aAAa;AAAA,YACpB,UAAU,MAAM,OAAO,KAAK;AAAA,UAC9B,CAAC;AAAA,QACH;AACA,YAAI,UAAU;AACZ,gBAAM,KAAK;AAAA,YACT,IAAI;AAAA,YACJ,OAAO,aAAa;AAAA,YACpB,UAAU,MAAM,SAAS,KAAK;AAAA,YAC9B,aAAa;AAAA,UACf,CAAC;AAAA,QACH;AACA,eAAO,MAAM,SAAS,oBAAC,cAAW,OAAc,IAAK;AAAA,MACvD,IACA;AAAA;AAAA,EAER;AAEJ;",
4
+ "sourcesContent": ["import * as React from 'react'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { SortingState } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions, type RowActionItem } from '@open-mercato/ui/backend/RowActions'\nimport { renderDictionaryColor, renderDictionaryIcon } from './dictionaryAppearance'\n\nexport type DictionaryTableEntry = {\n id: string\n value: string\n label: string\n color: string | null\n icon: string | null\n usageCount?: number\n organizationId?: string | null\n tenantId?: string | null\n isInherited?: boolean\n createdAt?: string | null\n updatedAt?: string | null\n}\n\nexport type DictionaryTableTranslations = {\n title: string\n valueColumn: string\n labelColumn: string\n appearanceColumn: string\n addLabel: string\n editLabel: string\n deleteLabel: string\n refreshLabel: string\n inheritedLabel: string\n inheritedTooltip: string\n emptyLabel: string\n usageCountLabel?: string\n searchPlaceholder?: string\n}\n\ntype DictionaryTableProps = {\n entries: DictionaryTableEntry[]\n loading?: boolean\n canManage?: boolean\n onCreate?: () => void\n onEdit?: (entry: DictionaryTableEntry) => void\n onDelete?: (entry: DictionaryTableEntry) => void\n onRefresh?: () => void\n translations: DictionaryTableTranslations\n}\n\nexport function DictionaryTable({\n entries,\n loading = false,\n canManage = false,\n onCreate,\n onEdit,\n onDelete,\n onRefresh,\n translations,\n}: DictionaryTableProps) {\n const [search, setSearch] = React.useState('')\n const [sorting, setSorting] = React.useState<SortingState>([{ id: 'value', desc: false }])\n const [page, setPage] = React.useState(1)\n const pageSize = 50\n\n const filtered = React.useMemo(() => {\n if (!search.trim()) return entries\n const term = search.trim().toLowerCase()\n return entries.filter((entry) => {\n return (\n entry.value.toLowerCase().includes(term) ||\n (entry.label ?? '').toLowerCase().includes(term)\n )\n })\n }, [entries, search])\n\n const paginated = React.useMemo(() => {\n const start = (page - 1) * pageSize\n return filtered.slice(start, start + pageSize)\n }, [filtered, page])\n\n const totalPages = Math.max(1, Math.ceil(filtered.length / pageSize))\n\n const columns = React.useMemo<ColumnDef<DictionaryTableEntry>[]>(() => [\n {\n accessorKey: 'value',\n header: translations.valueColumn,\n meta: { priority: 1 },\n cell: ({ getValue, row }) => (\n <div className=\"space-y-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium\">{String(getValue())}</span>\n {row.original.isInherited ? (\n <span className=\"rounded bg-muted px-2 py-0.5 text-xs text-muted-foreground\" title={translations.inheritedTooltip}>\n {translations.inheritedLabel}\n </span>\n ) : null}\n </div>\n {typeof row.original.usageCount === 'number' && row.original.usageCount > 0 && translations.usageCountLabel ? (\n <div className=\"text-xs text-muted-foreground\">\n {translations.usageCountLabel.replace('{{count}}', String(row.original.usageCount))}\n </div>\n ) : null}\n </div>\n ),\n },\n {\n accessorKey: 'label',\n header: translations.labelColumn,\n meta: { priority: 2 },\n cell: ({ getValue }) => <span>{String(getValue() ?? '')}</span>,\n },\n {\n id: 'appearance',\n header: translations.appearanceColumn,\n meta: { priority: 3 },\n cell: ({ row }) => {\n const { color, icon } = row.original\n if (!color && !icon) return <span className=\"text-muted-foreground\">\u2014</span>\n return (\n <div className=\"flex items-center gap-2\">\n {color ? renderDictionaryColor(color, 'h-4 w-4 rounded-full border border-border') : null}\n {icon ? renderDictionaryIcon(icon, 'h-4 w-4') : null}\n </div>\n )\n },\n },\n ], [translations.appearanceColumn, translations.inheritedLabel, translations.inheritedTooltip, translations.labelColumn, translations.valueColumn])\n\n const actions = React.useMemo(() => {\n if (!canManage || !onCreate) return null\n return (\n <Button size=\"sm\" onClick={onCreate}>\n {translations.addLabel}\n </Button>\n )\n }, [canManage, onCreate, translations.addLabel])\n\n const handleRowClick = canManage && onEdit\n ? (entry: DictionaryTableEntry) => {\n if (entry.isInherited) return\n onEdit(entry)\n }\n : undefined\n\n return (\n <div\n role=\"region\"\n aria-label={translations.title}\n tabIndex={0}\n className=\"w-full min-w-0 max-w-full overflow-x-auto focus-visible:outline-none focus-visible:shadow-focus\"\n >\n <DataTable<DictionaryTableEntry>\n title={translations.title}\n titleHeadingLevel={2}\n actions={actions}\n columns={columns}\n data={paginated}\n embedded\n sortable\n sorting={sorting}\n onSortingChange={setSorting}\n searchValue={search}\n onSearchChange={(value) => {\n setSearch(value)\n setPage(1)\n }}\n searchPlaceholder={translations.searchPlaceholder}\n isLoading={loading}\n emptyState={<p className=\"py-10 text-center text-sm text-muted-foreground\">{translations.emptyLabel}</p>}\n pagination={{\n page,\n pageSize,\n total: filtered.length,\n totalPages,\n onPageChange: setPage,\n }}\n refreshButton={onRefresh ? {\n label: translations.refreshLabel,\n onRefresh,\n isRefreshing: loading,\n } : undefined}\n onRowClick={handleRowClick}\n rowActions={\n canManage\n ? (entry) => {\n if (!entry) return null\n if (entry.isInherited) return null\n const items: RowActionItem[] = []\n if (onEdit) {\n items.push({\n id: 'edit',\n label: translations.editLabel,\n onSelect: () => onEdit(entry),\n })\n }\n if (onDelete) {\n items.push({\n id: 'delete',\n label: translations.deleteLabel,\n onSelect: () => onDelete(entry),\n destructive: true,\n })\n }\n return items.length ? <RowActions items={items} /> : null\n }\n : undefined\n }\n />\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,45 @@
1
1
  const DAY_MS = 24 * 60 * 60 * 1e3;
2
+ const timezoneFormatters = /* @__PURE__ */ new Map();
3
+ function zonedParts(instant, timezone) {
4
+ let formatter = timezoneFormatters.get(timezone);
5
+ if (!formatter) {
6
+ formatter = new Intl.DateTimeFormat("en-US", {
7
+ timeZone: timezone,
8
+ year: "numeric",
9
+ month: "2-digit",
10
+ day: "2-digit",
11
+ hour: "2-digit",
12
+ minute: "2-digit",
13
+ second: "2-digit",
14
+ hourCycle: "h23"
15
+ });
16
+ if (timezoneFormatters.size >= 64) timezoneFormatters.delete(timezoneFormatters.keys().next().value);
17
+ timezoneFormatters.set(timezone, formatter);
18
+ }
19
+ const parts = formatter.formatToParts(instant);
20
+ const number = (kind) => Number(parts.find((part) => part.type === kind)?.value);
21
+ return { year: number("year"), month: number("month"), day: number("day"), hour: number("hour"), minute: number("minute"), second: number("second") };
22
+ }
23
+ function zonedWallToInstant(wall, timezone) {
24
+ const target = wall.getTime();
25
+ let candidate = target;
26
+ for (let attempt = 0; attempt < 4; attempt += 1) {
27
+ const parts = zonedParts(new Date(candidate), timezone);
28
+ const displayed = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
29
+ const next = candidate + target - displayed;
30
+ if (next === candidate) return new Date(candidate);
31
+ candidate = next;
32
+ }
33
+ return null;
34
+ }
35
+ function zonedStartOfDay(dayKey, timezone) {
36
+ return zonedWallToInstant(/* @__PURE__ */ new Date(`${dayKey}T00:00:00Z`), timezone);
37
+ }
38
+ function dayKeyForRule(instant, timezone) {
39
+ if (!timezone || timezone === "UTC" || timezone === "Etc/UTC" || timezone === "Etc/GMT") return toDayKey(instant);
40
+ const parts = zonedParts(instant, timezone);
41
+ return `${parts.year}-${String(parts.month).padStart(2, "0")}-${String(parts.day).padStart(2, "0")}`;
42
+ }
2
43
  function parseRrule(rule) {
3
44
  const dtStartMatch = rule.match(/DTSTART[:=](\d{8}T\d{6}Z?)/);
4
45
  const durationMatch = rule.match(/DURATION:PT(?:(\d+)H)?(?:(\d+)M)?/);
@@ -39,10 +80,10 @@ function buildExdateSets(exdates) {
39
80
  });
40
81
  return { dateOnly, dateTime };
41
82
  }
42
- function shouldExcludeOccurrence(startAt, exdates) {
83
+ function shouldExcludeOccurrence(startAt, exdates, timezone) {
43
84
  const { dateOnly, dateTime } = buildExdateSets(exdates);
44
85
  if (dateTime.has(startAt.getTime())) return true;
45
- const dayKey = startAt.toISOString().slice(0, 10);
86
+ const dayKey = dayKeyForRule(startAt, timezone);
46
87
  return dateOnly.has(dayKey);
47
88
  }
48
89
  function toDayKey(value) {
@@ -51,15 +92,46 @@ function toDayKey(value) {
51
92
  function startOfDay(value) {
52
93
  return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()));
53
94
  }
54
- function expandRule(rule, parsed, range) {
95
+ function expandZonedRule(rule, parsed, range, weeklyScheduleTemplate) {
96
+ const timezone = rule.timezone;
97
+ const anchor = zonedParts(parsed.startAt, timezone);
98
+ const anchorWall = Date.UTC(anchor.year, anchor.month - 1, anchor.day, anchor.hour, anchor.minute, anchor.second);
99
+ const step = parsed.freq === "WEEKLY" ? 7 * DAY_MS : DAY_MS;
100
+ const duration = parsed.durationMinutes * 6e4;
101
+ const windows = [];
102
+ const rangeAnchor = zonedParts(range.start, timezone);
103
+ const rangeStartWall = Date.UTC(rangeAnchor.year, rangeAnchor.month - 1, rangeAnchor.day, rangeAnchor.hour, rangeAnchor.minute, rangeAnchor.second);
104
+ const rangeOffset = Math.floor((rangeStartWall - duration - DAY_MS - anchorWall) / step);
105
+ const skipped = weeklyScheduleTemplate && parsed.freq === "WEEKLY" && parsed.count === void 0 ? rangeOffset : Math.max(0, rangeOffset);
106
+ let wall = new Date(anchorWall + skipped * step);
107
+ let remaining = (parsed.count ?? Number.POSITIVE_INFINITY) - skipped;
108
+ while (wall.getTime() <= range.end.getTime() + DAY_MS && remaining > 0) {
109
+ const start = zonedWallToInstant(wall, timezone);
110
+ if (start && !shouldExcludeOccurrence(start, rule.exdates, timezone)) {
111
+ const end = new Date(start.getTime() + duration);
112
+ if (end > range.start && start < range.end) windows.push({ start, end, ruleId: rule.id });
113
+ }
114
+ wall = new Date(wall.getTime() + step);
115
+ remaining -= 1;
116
+ }
117
+ return windows;
118
+ }
119
+ function expandRule(rule, parsed, range, weeklyScheduleTemplate) {
55
120
  const { startAt, durationMinutes, freq, count, repeat } = parsed;
56
121
  if (repeat === "once") {
57
- if (shouldExcludeOccurrence(startAt, rule.exdates)) return [];
58
- const start = startOfDay(startAt);
59
- const end = new Date(start.getTime() + DAY_MS);
122
+ if (shouldExcludeOccurrence(startAt, rule.exdates, rule.timezone)) return [];
123
+ const dayKey = dayKeyForRule(startAt, rule.timezone);
124
+ const start = rule.timezone ? zonedStartOfDay(dayKey, rule.timezone) : startOfDay(startAt);
125
+ if (!start) return [];
126
+ const nextDay = new Date((/* @__PURE__ */ new Date(`${dayKey}T00:00:00Z`)).getTime() + DAY_MS).toISOString().slice(0, 10);
127
+ const end = rule.timezone ? zonedStartOfDay(nextDay, rule.timezone) : new Date(start.getTime() + DAY_MS);
128
+ if (!end) return [];
60
129
  if (end <= range.start || start >= range.end) return [];
61
130
  return [{ start, end, ruleId: rule.id }];
62
131
  }
132
+ if (rule.timezone && !["UTC", "Etc/UTC", "Etc/GMT"].includes(rule.timezone)) {
133
+ return expandZonedRule(rule, parsed, range, weeklyScheduleTemplate);
134
+ }
63
135
  const durationMs = durationMinutes * 6e4;
64
136
  const windows = [];
65
137
  const addWindow = (start) => {
@@ -68,37 +140,23 @@ function expandRule(rule, parsed, range) {
68
140
  if (end <= range.start || start >= range.end) return;
69
141
  windows.push({ start, end, ruleId: rule.id });
70
142
  };
71
- if (freq === "DAILY") {
72
- let cursor2 = new Date(startAt);
73
- let remaining2 = count ?? Number.POSITIVE_INFINITY;
74
- while (cursor2 < range.end && remaining2 > 0) {
75
- addWindow(new Date(cursor2));
76
- cursor2 = new Date(cursor2.getTime() + DAY_MS);
77
- remaining2 -= 1;
78
- }
79
- return windows;
80
- }
81
- let cursor = new Date(startAt);
82
- let remaining = count ?? Number.POSITIVE_INFINITY;
83
- if (cursor < range.start) {
84
- const diffDays = Math.floor((range.start.getTime() - cursor.getTime()) / DAY_MS);
85
- const weeksToAdd = Math.floor(diffDays / 7);
86
- if (weeksToAdd > 0) {
87
- cursor = new Date(cursor.getTime() + weeksToAdd * 7 * DAY_MS);
88
- }
89
- }
143
+ const step = freq === "WEEKLY" ? 7 * DAY_MS : DAY_MS;
144
+ const rangeOffset = Math.floor((range.start.getTime() - durationMs - startAt.getTime()) / step);
145
+ const skipped = weeklyScheduleTemplate && freq === "WEEKLY" && count === void 0 ? rangeOffset : Math.max(0, rangeOffset);
146
+ let cursor = new Date(startAt.getTime() + skipped * step);
147
+ let remaining = (count ?? Number.POSITIVE_INFINITY) - skipped;
90
148
  while (cursor < range.end && remaining > 0) {
91
149
  addWindow(new Date(cursor));
92
- cursor = new Date(cursor.getTime() + 7 * DAY_MS);
150
+ cursor = new Date(cursor.getTime() + step);
93
151
  remaining -= 1;
94
152
  }
95
153
  return windows;
96
154
  }
97
- function expandRules(rules, range) {
155
+ function expandRules(rules, range, weeklyScheduleTemplate) {
98
156
  const expanded = rules.flatMap((rule) => {
99
157
  const parsed = parseRrule(rule.rrule);
100
158
  if (!parsed) return [];
101
- return expandRule(rule, parsed, range);
159
+ return expandRule(rule, parsed, range, weeklyScheduleTemplate);
102
160
  });
103
161
  return expanded.sort((a, b) => a.start.getTime() - b.start.getTime());
104
162
  }
@@ -123,32 +181,37 @@ function subtractWindow(window, blockers) {
123
181
  return segments;
124
182
  }
125
183
  function getMergedAvailabilityWindows(params) {
126
- const parsedRules = params.rules.map((rule) => {
184
+ const rules = params.respectTimezone ? params.rules : params.rules.map((rule) => ({ ...rule, timezone: void 0 }));
185
+ const parsedRules = rules.map((rule) => {
127
186
  const parsed = parseRrule(rule.rrule);
128
187
  if (!parsed) return null;
129
188
  return { rule, parsed };
130
189
  }).filter((entry) => entry !== null);
131
190
  const overrideDays = /* @__PURE__ */ new Map();
132
191
  parsedRules.filter(({ parsed }) => parsed.repeat === "once").forEach(({ rule, parsed }) => {
133
- const dayKey = toDayKey(parsed.startAt);
192
+ const dayKey = dayKeyForRule(parsed.startAt, rule.timezone);
193
+ const overrideKey = `${rule.timezone ?? "UTC"}|${dayKey}`;
134
194
  const kind = rule.kind === "unavailability" ? "unavailability" : "availability";
135
195
  if (kind === "unavailability") {
136
- overrideDays.set(dayKey, "unavailability");
137
- } else if (!overrideDays.has(dayKey)) {
138
- overrideDays.set(dayKey, "availability");
196
+ overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone });
197
+ } else if (!overrideDays.has(overrideKey)) {
198
+ overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone });
139
199
  }
140
200
  });
141
201
  const availabilityRules = parsedRules.filter(({ parsed, rule }) => parsed.repeat !== "once" && rule.kind !== "unavailability").map(({ rule }) => rule);
142
202
  const unavailabilityRules = parsedRules.filter(({ parsed, rule }) => parsed.repeat !== "once" && rule.kind === "unavailability").map(({ rule }) => rule);
143
- const availabilityWindows = expandRules(availabilityRules, params.range).filter((window) => !overrideDays.has(toDayKey(window.start)));
144
- const unavailabilityWindows = expandRules(unavailabilityRules, params.range);
203
+ const availabilityWindows = availabilityRules.flatMap((rule) => expandRules([rule], params.range, params.weeklyScheduleTemplate === true).filter((window) => !overrideDays.has(`${rule.timezone ?? "UTC"}|${dayKeyForRule(window.start, rule.timezone)}`)));
204
+ const unavailabilityWindows = expandRules(unavailabilityRules, params.range, params.weeklyScheduleTemplate === true);
145
205
  const merged = unavailabilityWindows.length === 0 ? availabilityWindows : availabilityWindows.flatMap((window) => subtractWindow(window, unavailabilityWindows));
146
206
  const overrideWindows = [];
147
- overrideDays.forEach((kind, dayKey) => {
207
+ overrideDays.forEach(({ kind, dayKey, timezone }) => {
148
208
  if (kind !== "availability") return;
149
- const start = /* @__PURE__ */ new Date(`${dayKey}T00:00:00Z`);
209
+ const start = timezone ? zonedStartOfDay(dayKey, timezone) : /* @__PURE__ */ new Date(`${dayKey}T00:00:00Z`);
210
+ if (!start) return;
150
211
  if (Number.isNaN(start.getTime())) return;
151
- const end = new Date(start.getTime() + DAY_MS);
212
+ const nextDay = new Date((/* @__PURE__ */ new Date(`${dayKey}T00:00:00Z`)).getTime() + DAY_MS).toISOString().slice(0, 10);
213
+ const end = timezone ? zonedStartOfDay(nextDay, timezone) : new Date(start.getTime() + DAY_MS);
214
+ if (!end) return;
152
215
  if (end <= params.range.start || start >= params.range.end) return;
153
216
  overrideWindows.push({ start, end });
154
217
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/planner/lib/availabilityMerge.ts"],
4
- "sourcesContent": ["type AvailabilityKind = 'availability' | 'unavailability'\n\nexport type AvailabilityRuleLike = {\n id?: string\n rrule: string\n exdates?: string[]\n kind?: AvailabilityKind\n note?: string | null\n}\n\nexport type AvailabilityRange = {\n start: Date\n end: Date\n}\n\nexport type AvailabilityWindow = {\n start: Date\n end: Date\n ruleId?: string\n}\n\ntype ParsedRule = {\n startAt: Date\n durationMinutes: number\n freq: 'DAILY' | 'WEEKLY'\n repeat: 'once' | 'daily' | 'weekly'\n count?: number\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\n\nfunction parseRrule(rule: string): ParsedRule | null {\n const dtStartMatch = rule.match(/DTSTART[:=](\\d{8}T\\d{6}Z?)/)\n const durationMatch = rule.match(/DURATION:PT(?:(\\d+)H)?(?:(\\d+)M)?/)\n const freqMatch = rule.match(/FREQ=([A-Z]+)/)\n if (!dtStartMatch?.[1] || !durationMatch || !freqMatch?.[1]) return null\n const raw = dtStartMatch[1].replace(/Z$/, '')\n const parts = raw.match(/(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})/)\n if (!parts) return null\n const [, year, month, day, hour, minute, second] = parts\n const iso = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`\n const startAt = new Date(iso)\n if (Number.isNaN(startAt.getTime())) return null\n\n const hours = durationMatch[1] ? Number(durationMatch[1]) : 0\n const minutes = durationMatch[2] ? Number(durationMatch[2]) : 0\n const durationMinutes = Math.max(1, hours * 60 + minutes)\n const freq = freqMatch[1]\n if (freq !== 'DAILY' && freq !== 'WEEKLY') return null\n\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const count = countMatch?.[1] ? Number(countMatch[1]) : undefined\n const repeat = freq === 'WEEKLY'\n ? 'weekly'\n : freq === 'DAILY' && count === 1\n ? 'once'\n : 'daily'\n return { startAt, durationMinutes, freq, count, repeat }\n}\n\nfunction buildExdateSets(exdates?: string[]) {\n const dateOnly = new Set<string>()\n const dateTime = new Set<number>()\n ;(exdates ?? []).forEach((value) => {\n if (typeof value !== 'string') return\n const trimmed = value.trim()\n if (!trimmed) return\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n dateOnly.add(trimmed)\n return\n }\n const parsed = new Date(trimmed)\n if (!Number.isNaN(parsed.getTime())) {\n dateTime.add(parsed.getTime())\n }\n })\n return { dateOnly, dateTime }\n}\n\nfunction shouldExcludeOccurrence(startAt: Date, exdates?: string[]): boolean {\n const { dateOnly, dateTime } = buildExdateSets(exdates)\n if (dateTime.has(startAt.getTime())) return true\n const dayKey = startAt.toISOString().slice(0, 10)\n return dateOnly.has(dayKey)\n}\n\nfunction toDayKey(value: Date): string {\n return value.toISOString().slice(0, 10)\n}\n\nfunction startOfDay(value: Date): Date {\n return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()))\n}\n\nfunction expandRule(rule: AvailabilityRuleLike, parsed: ParsedRule, range: AvailabilityRange): AvailabilityWindow[] {\n const { startAt, durationMinutes, freq, count, repeat } = parsed\n if (repeat === 'once') {\n if (shouldExcludeOccurrence(startAt, rule.exdates)) return []\n const start = startOfDay(startAt)\n const end = new Date(start.getTime() + DAY_MS)\n if (end <= range.start || start >= range.end) return []\n return [{ start, end, ruleId: rule.id }]\n }\n const durationMs = durationMinutes * 60000\n const windows: AvailabilityWindow[] = []\n const addWindow = (start: Date) => {\n if (shouldExcludeOccurrence(start, rule.exdates)) return\n const end = new Date(start.getTime() + durationMs)\n if (end <= range.start || start >= range.end) return\n windows.push({ start, end, ruleId: rule.id })\n }\n\n if (freq === 'DAILY') {\n let cursor = new Date(startAt)\n let remaining = count ?? Number.POSITIVE_INFINITY\n while (cursor < range.end && remaining > 0) {\n addWindow(new Date(cursor))\n cursor = new Date(cursor.getTime() + DAY_MS)\n remaining -= 1\n }\n return windows\n }\n\n let cursor = new Date(startAt)\n let remaining = count ?? Number.POSITIVE_INFINITY\n if (cursor < range.start) {\n const diffDays = Math.floor((range.start.getTime() - cursor.getTime()) / DAY_MS)\n const weeksToAdd = Math.floor(diffDays / 7)\n if (weeksToAdd > 0) {\n cursor = new Date(cursor.getTime() + weeksToAdd * 7 * DAY_MS)\n }\n }\n while (cursor < range.end && remaining > 0) {\n addWindow(new Date(cursor))\n cursor = new Date(cursor.getTime() + 7 * DAY_MS)\n remaining -= 1\n }\n return windows\n}\n\nfunction expandRules(rules: AvailabilityRuleLike[], range: AvailabilityRange): AvailabilityWindow[] {\n const expanded = rules.flatMap((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return []\n return expandRule(rule, parsed, range)\n })\n return expanded.sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n\nfunction subtractWindow(window: AvailabilityWindow, blockers: AvailabilityWindow[]): AvailabilityWindow[] {\n let segments = [window]\n blockers.forEach((blocker) => {\n const next: AvailabilityWindow[] = []\n segments.forEach((segment) => {\n if (blocker.end <= segment.start || blocker.start >= segment.end) {\n next.push(segment)\n return\n }\n if (blocker.start > segment.start) {\n next.push({ ...segment, end: new Date(blocker.start) })\n }\n if (blocker.end < segment.end) {\n next.push({ ...segment, start: new Date(blocker.end) })\n }\n })\n segments = next\n })\n return segments\n}\n\nexport function getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n}): AvailabilityWindow[] {\n const parsedRules = params.rules\n .map((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return null\n return { rule, parsed }\n })\n .filter((entry): entry is { rule: AvailabilityRuleLike; parsed: ParsedRule } => entry !== null)\n\n const overrideDays = new Map<string, AvailabilityKind>()\n parsedRules\n .filter(({ parsed }) => parsed.repeat === 'once')\n .forEach(({ rule, parsed }) => {\n const dayKey = toDayKey(parsed.startAt)\n const kind = rule.kind === 'unavailability' ? 'unavailability' : 'availability'\n if (kind === 'unavailability') {\n overrideDays.set(dayKey, 'unavailability')\n } else if (!overrideDays.has(dayKey)) {\n overrideDays.set(dayKey, 'availability')\n }\n })\n\n const availabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind !== 'unavailability')\n .map(({ rule }) => rule)\n const unavailabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind === 'unavailability')\n .map(({ rule }) => rule)\n\n const availabilityWindows = expandRules(availabilityRules, params.range)\n .filter((window) => !overrideDays.has(toDayKey(window.start)))\n const unavailabilityWindows = expandRules(unavailabilityRules, params.range)\n\n const merged = unavailabilityWindows.length === 0\n ? availabilityWindows\n : availabilityWindows.flatMap((window) => subtractWindow(window, unavailabilityWindows))\n\n const overrideWindows: AvailabilityWindow[] = []\n overrideDays.forEach((kind, dayKey) => {\n if (kind !== 'availability') return\n const start = new Date(`${dayKey}T00:00:00Z`)\n if (Number.isNaN(start.getTime())) return\n const end = new Date(start.getTime() + DAY_MS)\n if (end <= params.range.start || start >= params.range.end) return\n overrideWindows.push({ start, end })\n })\n\n return [...merged, ...overrideWindows].sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n"],
5
- "mappings": "AA6BA,MAAM,SAAS,KAAK,KAAK,KAAK;AAE9B,SAAS,WAAW,MAAiC;AACnD,QAAM,eAAe,KAAK,MAAM,4BAA4B;AAC5D,QAAM,gBAAgB,KAAK,MAAM,mCAAmC;AACpE,QAAM,YAAY,KAAK,MAAM,eAAe;AAC5C,MAAI,CAAC,eAAe,CAAC,KAAK,CAAC,iBAAiB,CAAC,YAAY,CAAC,EAAG,QAAO;AACpE,QAAM,MAAM,aAAa,CAAC,EAAE,QAAQ,MAAM,EAAE;AAC5C,QAAM,QAAQ,IAAI,MAAM,6CAA6C;AACrE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,CAAC,EAAE,MAAM,OAAO,KAAK,MAAM,QAAQ,MAAM,IAAI;AACnD,QAAM,MAAM,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG,IAAI,IAAI,IAAI,MAAM,IAAI,MAAM;AAC/D,QAAM,UAAU,IAAI,KAAK,GAAG;AAC5B,MAAI,OAAO,MAAM,QAAQ,QAAQ,CAAC,EAAG,QAAO;AAE5C,QAAM,QAAQ,cAAc,CAAC,IAAI,OAAO,cAAc,CAAC,CAAC,IAAI;AAC5D,QAAM,UAAU,cAAc,CAAC,IAAI,OAAO,cAAc,CAAC,CAAC,IAAI;AAC9D,QAAM,kBAAkB,KAAK,IAAI,GAAG,QAAQ,KAAK,OAAO;AACxD,QAAM,OAAO,UAAU,CAAC;AACxB,MAAI,SAAS,WAAW,SAAS,SAAU,QAAO;AAElD,QAAM,aAAa,KAAK,MAAM,aAAa;AAC3C,QAAM,QAAQ,aAAa,CAAC,IAAI,OAAO,WAAW,CAAC,CAAC,IAAI;AACxD,QAAM,SAAS,SAAS,WACpB,WACA,SAAS,WAAW,UAAU,IAC5B,SACA;AACN,SAAO,EAAE,SAAS,iBAAiB,MAAM,OAAO,OAAO;AACzD;AAEA,SAAS,gBAAgB,SAAoB;AAC3C,QAAM,WAAW,oBAAI,IAAY;AACjC,QAAM,WAAW,oBAAI,IAAY;AAChC,GAAC,WAAW,CAAC,GAAG,QAAQ,CAAC,UAAU;AAClC,QAAI,OAAO,UAAU,SAAU;AAC/B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,CAAC,QAAS;AACd,QAAI,sBAAsB,KAAK,OAAO,GAAG;AACvC,eAAS,IAAI,OAAO;AACpB;AAAA,IACF;AACA,UAAM,SAAS,IAAI,KAAK,OAAO;AAC/B,QAAI,CAAC,OAAO,MAAM,OAAO,QAAQ,CAAC,GAAG;AACnC,eAAS,IAAI,OAAO,QAAQ,CAAC;AAAA,IAC/B;AAAA,EACF,CAAC;AACD,SAAO,EAAE,UAAU,SAAS;AAC9B;AAEA,SAAS,wBAAwB,SAAe,SAA6B;AAC3E,QAAM,EAAE,UAAU,SAAS,IAAI,gBAAgB,OAAO;AACtD,MAAI,SAAS,IAAI,QAAQ,QAAQ,CAAC,EAAG,QAAO;AAC5C,QAAM,SAAS,QAAQ,YAAY,EAAE,MAAM,GAAG,EAAE;AAChD,SAAO,SAAS,IAAI,MAAM;AAC5B;AAEA,SAAS,SAAS,OAAqB;AACrC,SAAO,MAAM,YAAY,EAAE,MAAM,GAAG,EAAE;AACxC;AAEA,SAAS,WAAW,OAAmB;AACrC,SAAO,IAAI,KAAK,KAAK,IAAI,MAAM,eAAe,GAAG,MAAM,YAAY,GAAG,MAAM,WAAW,CAAC,CAAC;AAC3F;AAEA,SAAS,WAAW,MAA4B,QAAoB,OAAgD;AAClH,QAAM,EAAE,SAAS,iBAAiB,MAAM,OAAO,OAAO,IAAI;AAC1D,MAAI,WAAW,QAAQ;AACrB,QAAI,wBAAwB,SAAS,KAAK,OAAO,EAAG,QAAO,CAAC;AAC5D,UAAM,QAAQ,WAAW,OAAO;AAChC,UAAM,MAAM,IAAI,KAAK,MAAM,QAAQ,IAAI,MAAM;AAC7C,QAAI,OAAO,MAAM,SAAS,SAAS,MAAM,IAAK,QAAO,CAAC;AACtD,WAAO,CAAC,EAAE,OAAO,KAAK,QAAQ,KAAK,GAAG,CAAC;AAAA,EACzC;AACA,QAAM,aAAa,kBAAkB;AACrC,QAAM,UAAgC,CAAC;AACvC,QAAM,YAAY,CAAC,UAAgB;AACjC,QAAI,wBAAwB,OAAO,KAAK,OAAO,EAAG;AAClD,UAAM,MAAM,IAAI,KAAK,MAAM,QAAQ,IAAI,UAAU;AACjD,QAAI,OAAO,MAAM,SAAS,SAAS,MAAM,IAAK;AAC9C,YAAQ,KAAK,EAAE,OAAO,KAAK,QAAQ,KAAK,GAAG,CAAC;AAAA,EAC9C;AAEA,MAAI,SAAS,SAAS;AACpB,QAAIA,UAAS,IAAI,KAAK,OAAO;AAC7B,QAAIC,aAAY,SAAS,OAAO;AAChC,WAAOD,UAAS,MAAM,OAAOC,aAAY,GAAG;AAC1C,gBAAU,IAAI,KAAKD,OAAM,CAAC;AAC1B,MAAAA,UAAS,IAAI,KAAKA,QAAO,QAAQ,IAAI,MAAM;AAC3C,MAAAC,cAAa;AAAA,IACf;AACA,WAAO;AAAA,EACT;AAEA,MAAI,SAAS,IAAI,KAAK,OAAO;AAC7B,MAAI,YAAY,SAAS,OAAO;AAChC,MAAI,SAAS,MAAM,OAAO;AACxB,UAAM,WAAW,KAAK,OAAO,MAAM,MAAM,QAAQ,IAAI,OAAO,QAAQ,KAAK,MAAM;AAC/E,UAAM,aAAa,KAAK,MAAM,WAAW,CAAC;AAC1C,QAAI,aAAa,GAAG;AAClB,eAAS,IAAI,KAAK,OAAO,QAAQ,IAAI,aAAa,IAAI,MAAM;AAAA,IAC9D;AAAA,EACF;AACA,SAAO,SAAS,MAAM,OAAO,YAAY,GAAG;AAC1C,cAAU,IAAI,KAAK,MAAM,CAAC;AAC1B,aAAS,IAAI,KAAK,OAAO,QAAQ,IAAI,IAAI,MAAM;AAC/C,iBAAa;AAAA,EACf;AACA,SAAO;AACT;AAEA,SAAS,YAAY,OAA+B,OAAgD;AAClG,QAAM,WAAW,MAAM,QAAQ,CAAC,SAAS;AACvC,UAAM,SAAS,WAAW,KAAK,KAAK;AACpC,QAAI,CAAC,OAAQ,QAAO,CAAC;AACrB,WAAO,WAAW,MAAM,QAAQ,KAAK;AAAA,EACvC,CAAC;AACD,SAAO,SAAS,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,QAAQ,IAAI,EAAE,MAAM,QAAQ,CAAC;AACtE;AAEA,SAAS,eAAe,QAA4B,UAAsD;AACxG,MAAI,WAAW,CAAC,MAAM;AACtB,WAAS,QAAQ,CAAC,YAAY;AAC5B,UAAM,OAA6B,CAAC;AACpC,aAAS,QAAQ,CAAC,YAAY;AAC5B,UAAI,QAAQ,OAAO,QAAQ,SAAS,QAAQ,SAAS,QAAQ,KAAK;AAChE,aAAK,KAAK,OAAO;AACjB;AAAA,MACF;AACA,UAAI,QAAQ,QAAQ,QAAQ,OAAO;AACjC,aAAK,KAAK,EAAE,GAAG,SAAS,KAAK,IAAI,KAAK,QAAQ,KAAK,EAAE,CAAC;AAAA,MACxD;AACA,UAAI,QAAQ,MAAM,QAAQ,KAAK;AAC7B,aAAK,KAAK,EAAE,GAAG,SAAS,OAAO,IAAI,KAAK,QAAQ,GAAG,EAAE,CAAC;AAAA,MACxD;AAAA,IACF,CAAC;AACD,eAAW;AAAA,EACb,CAAC;AACD,SAAO;AACT;AAEO,SAAS,6BAA6B,QAGpB;AACvB,QAAM,cAAc,OAAO,MACxB,IAAI,CAAC,SAAS;AACb,UAAM,SAAS,WAAW,KAAK,KAAK;AACpC,QAAI,CAAC,OAAQ,QAAO;AACpB,WAAO,EAAE,MAAM,OAAO;AAAA,EACxB,CAAC,EACA,OAAO,CAAC,UAAuE,UAAU,IAAI;AAEhG,QAAM,eAAe,oBAAI,IAA8B;AACvD,cACG,OAAO,CAAC,EAAE,OAAO,MAAM,OAAO,WAAW,MAAM,EAC/C,QAAQ,CAAC,EAAE,MAAM,OAAO,MAAM;AAC7B,UAAM,SAAS,SAAS,OAAO,OAAO;AACtC,UAAM,OAAO,KAAK,SAAS,mBAAmB,mBAAmB;AACjE,QAAI,SAAS,kBAAkB;AAC7B,mBAAa,IAAI,QAAQ,gBAAgB;AAAA,IAC3C,WAAW,CAAC,aAAa,IAAI,MAAM,GAAG;AACpC,mBAAa,IAAI,QAAQ,cAAc;AAAA,IACzC;AAAA,EACF,CAAC;AAEH,QAAM,oBAAoB,YACvB,OAAO,CAAC,EAAE,QAAQ,KAAK,MAAM,OAAO,WAAW,UAAU,KAAK,SAAS,gBAAgB,EACvF,IAAI,CAAC,EAAE,KAAK,MAAM,IAAI;AACzB,QAAM,sBAAsB,YACzB,OAAO,CAAC,EAAE,QAAQ,KAAK,MAAM,OAAO,WAAW,UAAU,KAAK,SAAS,gBAAgB,EACvF,IAAI,CAAC,EAAE,KAAK,MAAM,IAAI;AAEzB,QAAM,sBAAsB,YAAY,mBAAmB,OAAO,KAAK,EACpE,OAAO,CAAC,WAAW,CAAC,aAAa,IAAI,SAAS,OAAO,KAAK,CAAC,CAAC;AAC/D,QAAM,wBAAwB,YAAY,qBAAqB,OAAO,KAAK;AAE3E,QAAM,SAAS,sBAAsB,WAAW,IAC5C,sBACA,oBAAoB,QAAQ,CAAC,WAAW,eAAe,QAAQ,qBAAqB,CAAC;AAEzF,QAAM,kBAAwC,CAAC;AAC/C,eAAa,QAAQ,CAAC,MAAM,WAAW;AACrC,QAAI,SAAS,eAAgB;AAC7B,UAAM,QAAQ,oBAAI,KAAK,GAAG,MAAM,YAAY;AAC5C,QAAI,OAAO,MAAM,MAAM,QAAQ,CAAC,EAAG;AACnC,UAAM,MAAM,IAAI,KAAK,MAAM,QAAQ,IAAI,MAAM;AAC7C,QAAI,OAAO,OAAO,MAAM,SAAS,SAAS,OAAO,MAAM,IAAK;AAC5D,oBAAgB,KAAK,EAAE,OAAO,IAAI,CAAC;AAAA,EACrC,CAAC;AAED,SAAO,CAAC,GAAG,QAAQ,GAAG,eAAe,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,QAAQ,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC7F;",
6
- "names": ["cursor", "remaining"]
4
+ "sourcesContent": ["type AvailabilityKind = 'availability' | 'unavailability'\n\nexport type AvailabilityRuleLike = {\n id?: string\n rrule: string\n timezone?: string\n exdates?: string[]\n kind?: AvailabilityKind\n note?: string | null\n}\n\nexport type AvailabilityRange = {\n start: Date\n end: Date\n}\n\nexport type AvailabilityWindow = {\n start: Date\n end: Date\n ruleId?: string\n}\n\ntype ParsedRule = {\n startAt: Date\n durationMinutes: number\n freq: 'DAILY' | 'WEEKLY'\n repeat: 'once' | 'daily' | 'weekly'\n count?: number\n}\n\nconst DAY_MS = 24 * 60 * 60 * 1000\nconst timezoneFormatters = new Map<string, Intl.DateTimeFormat>()\n\nfunction zonedParts(instant: Date, timezone: string): { year: number; month: number; day: number; hour: number; minute: number; second: number } {\n let formatter = timezoneFormatters.get(timezone)\n if (!formatter) {\n formatter = new Intl.DateTimeFormat('en-US', {\n timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit',\n hour: '2-digit', minute: '2-digit', second: '2-digit', hourCycle: 'h23',\n })\n if (timezoneFormatters.size >= 64) timezoneFormatters.delete(timezoneFormatters.keys().next().value!)\n timezoneFormatters.set(timezone, formatter)\n }\n const parts = formatter.formatToParts(instant)\n const number = (kind: string) => Number(parts.find((part) => part.type === kind)?.value)\n return { year: number('year'), month: number('month'), day: number('day'), hour: number('hour'), minute: number('minute'), second: number('second') }\n}\n\nfunction zonedWallToInstant(wall: Date, timezone: string): Date | null {\n const target = wall.getTime()\n let candidate = target\n for (let attempt = 0; attempt < 4; attempt += 1) {\n const parts = zonedParts(new Date(candidate), timezone)\n const displayed = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second)\n const next = candidate + target - displayed\n if (next === candidate) return new Date(candidate)\n candidate = next\n }\n return null\n}\n\nfunction zonedStartOfDay(dayKey: string, timezone: string): Date | null {\n return zonedWallToInstant(new Date(`${dayKey}T00:00:00Z`), timezone)\n}\n\nfunction dayKeyForRule(instant: Date, timezone?: string): string {\n if (!timezone || timezone === 'UTC' || timezone === 'Etc/UTC' || timezone === 'Etc/GMT') return toDayKey(instant)\n const parts = zonedParts(instant, timezone)\n return `${parts.year}-${String(parts.month).padStart(2, '0')}-${String(parts.day).padStart(2, '0')}`\n}\n\nfunction parseRrule(rule: string): ParsedRule | null {\n const dtStartMatch = rule.match(/DTSTART[:=](\\d{8}T\\d{6}Z?)/)\n const durationMatch = rule.match(/DURATION:PT(?:(\\d+)H)?(?:(\\d+)M)?/)\n const freqMatch = rule.match(/FREQ=([A-Z]+)/)\n if (!dtStartMatch?.[1] || !durationMatch || !freqMatch?.[1]) return null\n const raw = dtStartMatch[1].replace(/Z$/, '')\n const parts = raw.match(/(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})/)\n if (!parts) return null\n const [, year, month, day, hour, minute, second] = parts\n const iso = `${year}-${month}-${day}T${hour}:${minute}:${second}Z`\n const startAt = new Date(iso)\n if (Number.isNaN(startAt.getTime())) return null\n\n const hours = durationMatch[1] ? Number(durationMatch[1]) : 0\n const minutes = durationMatch[2] ? Number(durationMatch[2]) : 0\n const durationMinutes = Math.max(1, hours * 60 + minutes)\n const freq = freqMatch[1]\n if (freq !== 'DAILY' && freq !== 'WEEKLY') return null\n\n const countMatch = rule.match(/COUNT=(\\d+)/)\n const count = countMatch?.[1] ? Number(countMatch[1]) : undefined\n const repeat = freq === 'WEEKLY'\n ? 'weekly'\n : freq === 'DAILY' && count === 1\n ? 'once'\n : 'daily'\n return { startAt, durationMinutes, freq, count, repeat }\n}\n\nfunction buildExdateSets(exdates?: string[]) {\n const dateOnly = new Set<string>()\n const dateTime = new Set<number>()\n ;(exdates ?? []).forEach((value) => {\n if (typeof value !== 'string') return\n const trimmed = value.trim()\n if (!trimmed) return\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(trimmed)) {\n dateOnly.add(trimmed)\n return\n }\n const parsed = new Date(trimmed)\n if (!Number.isNaN(parsed.getTime())) {\n dateTime.add(parsed.getTime())\n }\n })\n return { dateOnly, dateTime }\n}\n\nfunction shouldExcludeOccurrence(startAt: Date, exdates?: string[], timezone?: string): boolean {\n const { dateOnly, dateTime } = buildExdateSets(exdates)\n if (dateTime.has(startAt.getTime())) return true\n const dayKey = dayKeyForRule(startAt, timezone)\n return dateOnly.has(dayKey)\n}\n\nfunction toDayKey(value: Date): string {\n return value.toISOString().slice(0, 10)\n}\n\nfunction startOfDay(value: Date): Date {\n return new Date(Date.UTC(value.getUTCFullYear(), value.getUTCMonth(), value.getUTCDate()))\n}\n\nfunction expandZonedRule(rule: AvailabilityRuleLike, parsed: ParsedRule, range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const timezone = rule.timezone!\n const anchor = zonedParts(parsed.startAt, timezone)\n const anchorWall = Date.UTC(anchor.year, anchor.month - 1, anchor.day, anchor.hour, anchor.minute, anchor.second)\n const step = parsed.freq === 'WEEKLY' ? 7 * DAY_MS : DAY_MS\n const duration = parsed.durationMinutes * 60000\n const windows: AvailabilityWindow[] = []\n const rangeAnchor = zonedParts(range.start, timezone)\n const rangeStartWall = Date.UTC(rangeAnchor.year, rangeAnchor.month - 1, rangeAnchor.day, rangeAnchor.hour, rangeAnchor.minute, rangeAnchor.second)\n const rangeOffset = Math.floor((rangeStartWall - duration - DAY_MS - anchorWall) / step)\n const skipped = weeklyScheduleTemplate && parsed.freq === 'WEEKLY' && parsed.count === undefined ? rangeOffset : Math.max(0, rangeOffset)\n let wall = new Date(anchorWall + skipped * step)\n let remaining = (parsed.count ?? Number.POSITIVE_INFINITY) - skipped\n while (wall.getTime() <= range.end.getTime() + DAY_MS && remaining > 0) {\n const start = zonedWallToInstant(wall, timezone)\n if (start && !shouldExcludeOccurrence(start, rule.exdates, timezone)) {\n const end = new Date(start.getTime() + duration)\n if (end > range.start && start < range.end) windows.push({ start, end, ruleId: rule.id })\n }\n wall = new Date(wall.getTime() + step)\n remaining -= 1\n }\n return windows\n}\n\nfunction expandRule(rule: AvailabilityRuleLike, parsed: ParsedRule, range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const { startAt, durationMinutes, freq, count, repeat } = parsed\n if (repeat === 'once') {\n if (shouldExcludeOccurrence(startAt, rule.exdates, rule.timezone)) return []\n const dayKey = dayKeyForRule(startAt, rule.timezone)\n const start = rule.timezone ? zonedStartOfDay(dayKey, rule.timezone) : startOfDay(startAt)\n if (!start) return []\n const nextDay = new Date(new Date(`${dayKey}T00:00:00Z`).getTime() + DAY_MS).toISOString().slice(0, 10)\n const end = rule.timezone ? zonedStartOfDay(nextDay, rule.timezone) : new Date(start.getTime() + DAY_MS)\n if (!end) return []\n if (end <= range.start || start >= range.end) return []\n return [{ start, end, ruleId: rule.id }]\n }\n if (rule.timezone && !['UTC', 'Etc/UTC', 'Etc/GMT'].includes(rule.timezone)) {\n return expandZonedRule(rule, parsed, range, weeklyScheduleTemplate)\n }\n const durationMs = durationMinutes * 60000\n const windows: AvailabilityWindow[] = []\n const addWindow = (start: Date) => {\n if (shouldExcludeOccurrence(start, rule.exdates)) return\n const end = new Date(start.getTime() + durationMs)\n if (end <= range.start || start >= range.end) return\n windows.push({ start, end, ruleId: rule.id })\n }\n\n const step = freq === 'WEEKLY' ? 7 * DAY_MS : DAY_MS\n const rangeOffset = Math.floor((range.start.getTime() - durationMs - startAt.getTime()) / step)\n const skipped = weeklyScheduleTemplate && freq === 'WEEKLY' && count === undefined ? rangeOffset : Math.max(0, rangeOffset)\n let cursor = new Date(startAt.getTime() + skipped * step)\n let remaining = (count ?? Number.POSITIVE_INFINITY) - skipped\n while (cursor < range.end && remaining > 0) {\n addWindow(new Date(cursor))\n cursor = new Date(cursor.getTime() + step)\n remaining -= 1\n }\n return windows\n}\n\nfunction expandRules(rules: AvailabilityRuleLike[], range: AvailabilityRange, weeklyScheduleTemplate: boolean): AvailabilityWindow[] {\n const expanded = rules.flatMap((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return []\n return expandRule(rule, parsed, range, weeklyScheduleTemplate)\n })\n return expanded.sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n\nfunction subtractWindow(window: AvailabilityWindow, blockers: AvailabilityWindow[]): AvailabilityWindow[] {\n let segments = [window]\n blockers.forEach((blocker) => {\n const next: AvailabilityWindow[] = []\n segments.forEach((segment) => {\n if (blocker.end <= segment.start || blocker.start >= segment.end) {\n next.push(segment)\n return\n }\n if (blocker.start > segment.start) {\n next.push({ ...segment, end: new Date(blocker.start) })\n }\n if (blocker.end < segment.end) {\n next.push({ ...segment, start: new Date(blocker.end) })\n }\n })\n segments = next\n })\n return segments\n}\n\nexport function getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n respectTimezone?: boolean\n weeklyScheduleTemplate?: boolean\n}): AvailabilityWindow[] {\n const rules = params.respectTimezone ? params.rules : params.rules.map((rule) => ({ ...rule, timezone: undefined }))\n const parsedRules = rules\n .map((rule) => {\n const parsed = parseRrule(rule.rrule)\n if (!parsed) return null\n return { rule, parsed }\n })\n .filter((entry): entry is { rule: AvailabilityRuleLike; parsed: ParsedRule } => entry !== null)\n\n const overrideDays = new Map<string, { kind: AvailabilityKind; dayKey: string; timezone?: string }>()\n parsedRules\n .filter(({ parsed }) => parsed.repeat === 'once')\n .forEach(({ rule, parsed }) => {\n const dayKey = dayKeyForRule(parsed.startAt, rule.timezone)\n const overrideKey = `${rule.timezone ?? 'UTC'}|${dayKey}`\n const kind = rule.kind === 'unavailability' ? 'unavailability' : 'availability'\n if (kind === 'unavailability') {\n overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone })\n } else if (!overrideDays.has(overrideKey)) {\n overrideDays.set(overrideKey, { kind, dayKey, timezone: rule.timezone })\n }\n })\n\n const availabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind !== 'unavailability')\n .map(({ rule }) => rule)\n const unavailabilityRules = parsedRules\n .filter(({ parsed, rule }) => parsed.repeat !== 'once' && rule.kind === 'unavailability')\n .map(({ rule }) => rule)\n\n const availabilityWindows = availabilityRules.flatMap((rule) =>\n expandRules([rule], params.range, params.weeklyScheduleTemplate === true).filter((window) =>\n !overrideDays.has(`${rule.timezone ?? 'UTC'}|${dayKeyForRule(window.start, rule.timezone)}`)))\n const unavailabilityWindows = expandRules(unavailabilityRules, params.range, params.weeklyScheduleTemplate === true)\n\n const merged = unavailabilityWindows.length === 0\n ? availabilityWindows\n : availabilityWindows.flatMap((window) => subtractWindow(window, unavailabilityWindows))\n\n const overrideWindows: AvailabilityWindow[] = []\n overrideDays.forEach(({ kind, dayKey, timezone }) => {\n if (kind !== 'availability') return\n const start = timezone ? zonedStartOfDay(dayKey, timezone) : new Date(`${dayKey}T00:00:00Z`)\n if (!start) return\n if (Number.isNaN(start.getTime())) return\n const nextDay = new Date(new Date(`${dayKey}T00:00:00Z`).getTime() + DAY_MS).toISOString().slice(0, 10)\n const end = timezone ? zonedStartOfDay(nextDay, timezone) : new Date(start.getTime() + DAY_MS)\n if (!end) return\n if (end <= params.range.start || start >= params.range.end) return\n overrideWindows.push({ start, end })\n })\n\n return [...merged, ...overrideWindows].sort((a, b) => a.start.getTime() - b.start.getTime())\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/planner/services/plannerAvailabilityService.ts"],
4
- "sourcesContent": ["import type { AvailabilityRange, AvailabilityRuleLike, AvailabilityWindow } from '../lib/availabilityMerge'\nimport { getMergedAvailabilityWindows } from '../lib/availabilityMerge'\n\nexport type { AvailabilityRange, AvailabilityRuleLike, AvailabilityWindow }\n\nexport interface PlannerAvailabilityService {\n getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n }): AvailabilityWindow[]\n}\n\nexport class DefaultPlannerAvailabilityService implements PlannerAvailabilityService {\n getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n }): AvailabilityWindow[] {\n return getMergedAvailabilityWindows(params)\n }\n}\n"],
5
- "mappings": "AACA,SAAS,oCAAoC;AAWtC,MAAM,kCAAwE;AAAA,EACnF,6BAA6B,QAGJ;AACvB,WAAO,6BAA6B,MAAM;AAAA,EAC5C;AACF;",
4
+ "sourcesContent": ["import type { AvailabilityRange, AvailabilityRuleLike, AvailabilityWindow } from '../lib/availabilityMerge'\nimport { getMergedAvailabilityWindows } from '../lib/availabilityMerge'\n\nexport type { AvailabilityRange, AvailabilityRuleLike, AvailabilityWindow }\n\nexport interface PlannerAvailabilityService {\n getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n respectTimezone?: boolean\n weeklyScheduleTemplate?: boolean\n }): AvailabilityWindow[]\n}\n\nexport class DefaultPlannerAvailabilityService implements PlannerAvailabilityService {\n getMergedAvailabilityWindows(params: {\n rules: AvailabilityRuleLike[]\n range: AvailabilityRange\n respectTimezone?: boolean\n weeklyScheduleTemplate?: boolean\n }): AvailabilityWindow[] {\n return getMergedAvailabilityWindows(params)\n }\n}\n"],
5
+ "mappings": "AACA,SAAS,oCAAoC;AAatC,MAAM,kCAAwE;AAAA,EACnF,6BAA6B,QAKJ;AACvB,WAAO,6BAA6B,MAAM;AAAA,EAC5C;AACF;",
6
6
  "names": []
7
7
  }
@@ -71,7 +71,9 @@ function TimesheetProjectDetailPage({ params }) {
71
71
  const [loading, setLoading] = React.useState(true);
72
72
  const [error, setError] = React.useState(null);
73
73
  const [isNotFound, setIsNotFound] = React.useState(false);
74
- const [accessDenied, setAccessDenied] = React.useState(false);
74
+ const [accessDeniedProjectId, setAccessDeniedProjectId] = React.useState(null);
75
+ const accessDenied = accessDeniedProjectId !== null && accessDeniedProjectId === projectId;
76
+ const [settledProjectId, setSettledProjectId] = React.useState(null);
75
77
  const [employees, setEmployees] = React.useState([]);
76
78
  const [employeesLoading, setEmployeesLoading] = React.useState(false);
77
79
  const [expandedCards, setExpandedCards] = React.useState(/* @__PURE__ */ new Set());
@@ -232,16 +234,16 @@ function TimesheetProjectDetailPage({ params }) {
232
234
  setLoading(true);
233
235
  setError(null);
234
236
  setIsNotFound(false);
235
- setAccessDenied(false);
236
237
  try {
237
238
  const queryParams = new URLSearchParams({ page: "1", pageSize: "1", ids: projectId });
238
239
  const call = await apiCall(
239
240
  `/api/staff/timesheets/time-projects?${queryParams.toString()}`
240
241
  );
241
242
  if (call.status === 404 && readDenialReason(call.result) === NO_PROJECT_ACCESS_REASON) {
242
- if (!cancelled) setAccessDenied(true);
243
+ if (!cancelled) setAccessDeniedProjectId(projectId ?? null);
243
244
  return;
244
245
  }
246
+ if (!cancelled) setAccessDeniedProjectId(null);
245
247
  if (!call.ok) {
246
248
  throw new Error(t("staff.timesheets.projects.errors.load", "Failed to load project."));
247
249
  }
@@ -257,7 +259,10 @@ function TimesheetProjectDetailPage({ params }) {
257
259
  setError(loadError instanceof Error ? loadError.message : t("staff.timesheets.projects.errors.load", "Failed to load project."));
258
260
  }
259
261
  } finally {
260
- if (!cancelled) setLoading(false);
262
+ if (!cancelled) {
263
+ setLoading(false);
264
+ setSettledProjectId(projectId ?? null);
265
+ }
261
266
  }
262
267
  }
263
268
  loadProject();
@@ -450,12 +455,12 @@ function TimesheetProjectDetailPage({ params }) {
450
455
  void handleAddEmployee();
451
456
  }
452
457
  }, [handleAddEmployee]);
453
- if (loading) {
454
- return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(LoadingMessage, { label: t("staff.timesheets.projects.loading", "Loading project...") }) }) });
455
- }
456
458
  if (accessDenied) {
457
459
  return /* @__PURE__ */ jsx(NoProjectAccess, { timeProjectId: projectId });
458
460
  }
461
+ if (loading || settledProjectId !== (projectId ?? null)) {
462
+ return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(LoadingMessage, { label: t("staff.timesheets.projects.loading", "Loading project...") }) }) });
463
+ }
459
464
  if (isNotFound) {
460
465
  return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(PageBody, { children: /* @__PURE__ */ jsx(
461
466
  RecordNotFoundState,