@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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/customer_dictionary_entry/index.js +2 -0
- package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
- package/dist/generated/entities/customer_interaction/index.js +2 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/customers/api/activity-types/route.js +151 -0
- package/dist/modules/customers/api/activity-types/route.js.map +7 -0
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +3 -0
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
- package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
- package/dist/modules/customers/calendar-event-types.js +548 -0
- package/dist/modules/customers/calendar-event-types.js.map +7 -0
- package/dist/modules/customers/commands/dictionaries.js +70 -10
- package/dist/modules/customers/commands/dictionaries.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +187 -22
- package/dist/modules/customers/commands/interactions.js.map +3 -3
- package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
- package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
- package/dist/modules/customers/components/DictionarySettings.js +3 -1
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
- package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
- package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
- package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
- package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
- package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
- package/dist/modules/customers/components/calendar/types.js +1 -0
- package/dist/modules/customers/components/calendar/types.js.map +2 -2
- package/dist/modules/customers/data/entities.js +6 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +39 -10
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/di.js +20 -1
- package/dist/modules/customers/di.js.map +2 -2
- package/dist/modules/customers/extension-points.js +13 -0
- package/dist/modules/customers/extension-points.js.map +2 -2
- package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
- package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
- package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
- package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
- package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
- package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
- package/dist/modules/customers/lib/calendar/timezone.js +111 -0
- package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
- package/dist/modules/customers/lib/interactionReadModel.js +1 -0
- package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +1 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
- package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
- package/dist/modules/planner/lib/availabilityMerge.js +101 -38
- package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
- package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/generated/entities/customer_dictionary_entry/index.ts +1 -0
- package/generated/entities/customer_interaction/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +7 -7
- package/src/modules/customers/api/activity-types/route.ts +154 -0
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
- package/src/modules/customers/api/interactions/route.ts +4 -0
- package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
- package/src/modules/customers/calendar-event-types.ts +674 -0
- package/src/modules/customers/commands/dictionaries.ts +84 -8
- package/src/modules/customers/commands/interactions.ts +244 -21
- package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
- package/src/modules/customers/components/DictionarySettings.tsx +5 -3
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
- package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
- package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
- package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
- package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
- package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
- package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
- package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
- package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
- package/src/modules/customers/components/calendar/types.ts +1 -0
- package/src/modules/customers/components/detail/types.ts +1 -0
- package/src/modules/customers/data/entities.ts +8 -1
- package/src/modules/customers/data/validators.ts +42 -7
- package/src/modules/customers/di.ts +21 -0
- package/src/modules/customers/extension-points.ts +13 -0
- package/src/modules/customers/i18n/de.json +83 -0
- package/src/modules/customers/i18n/en.json +83 -0
- package/src/modules/customers/i18n/es.json +83 -0
- package/src/modules/customers/i18n/ko.json +83 -0
- package/src/modules/customers/i18n/pl.json +83 -0
- package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
- package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
- package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
- package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
- package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
- package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
- package/src/modules/customers/lib/calendar/timezone.ts +117 -0
- package/src/modules/customers/lib/interactionReadModel.ts +1 -0
- package/src/modules/customers/lib/todoCompatibility.ts +2 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
- package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
- package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
- package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
- package/src/modules/planner/lib/availabilityMerge.ts +106 -41
- package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
- 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
|
-
|
|
75
|
+
"div",
|
|
76
76
|
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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
|
|
5
|
-
"mappings": "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
|
|
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
|
|
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
|
|
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
|
|
59
|
-
const
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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() +
|
|
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
|
|
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 =
|
|
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,
|
|
137
|
-
} else if (!overrideDays.has(
|
|
138
|
-
overrideDays.set(dayKey,
|
|
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(
|
|
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
|
|
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": "
|
|
6
|
-
"names": [
|
|
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;
|
|
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 [
|
|
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)
|
|
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)
|
|
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,
|