@fayz-ai/admin 0.29.0 → 0.30.0

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 (72) hide show
  1. package/dist/ScheduleEditor-QC6OYBUE.js +7 -0
  2. package/dist/{ScheduleEditor-KU4UUG6Q.js.map → ScheduleEditor-QC6OYBUE.js.map} +1 -1
  3. package/dist/SubscriptionPage-TVAEOFNC.js +9 -0
  4. package/dist/{SubscriptionPage-2JPALG53.js.map → SubscriptionPage-TVAEOFNC.js.map} +1 -1
  5. package/dist/app/AdminShell.d.ts.map +1 -1
  6. package/dist/app/admin-app.d.ts.map +1 -1
  7. package/dist/app/bottom-nav.d.ts.map +1 -1
  8. package/dist/app/rail-preset.d.ts +40 -2
  9. package/dist/app/rail-preset.d.ts.map +1 -1
  10. package/dist/app/useAppPreset.d.ts +27 -0
  11. package/dist/app/useAppPreset.d.ts.map +1 -1
  12. package/dist/chunk-3SREEFHN.js +1685 -0
  13. package/dist/chunk-3SREEFHN.js.map +1 -0
  14. package/dist/{chunk-R4HQ2HS2.js → chunk-KSUIF56I.js} +7 -2
  15. package/dist/chunk-KSUIF56I.js.map +1 -0
  16. package/dist/{chunk-OVVTLRDD.js → chunk-U6LV4GIH.js} +3 -3
  17. package/dist/{chunk-OVVTLRDD.js.map → chunk-U6LV4GIH.js.map} +1 -1
  18. package/dist/{chunk-XJB76FHA.js → chunk-ZUM3Y7VB.js} +50 -17
  19. package/dist/chunk-ZUM3Y7VB.js.map +1 -0
  20. package/dist/components/notifications/NotificationInbox.d.ts.map +1 -1
  21. package/dist/components/settings/SettingsPage.d.ts.map +1 -1
  22. package/dist/components/shared/PersonLink.d.ts.map +1 -1
  23. package/dist/components/shared/person-actions.d.ts +48 -0
  24. package/dist/components/shared/person-actions.d.ts.map +1 -0
  25. package/dist/crud/CrudDetailPage.d.ts +6 -1
  26. package/dist/crud/CrudDetailPage.d.ts.map +1 -1
  27. package/dist/crud/CrudListView.d.ts.map +1 -1
  28. package/dist/crud/archetype-lookup.d.ts +24 -0
  29. package/dist/crud/archetype-lookup.d.ts.map +1 -1
  30. package/dist/crud/archetypes/AddressesTab.d.ts.map +1 -1
  31. package/dist/crud/archetypes/EntityLink.d.ts.map +1 -1
  32. package/dist/crud/archetypes/KanbanBoard.d.ts +11 -1
  33. package/dist/crud/archetypes/KanbanBoard.d.ts.map +1 -1
  34. package/dist/crud/archetypes/OrderDetailView.d.ts +3 -0
  35. package/dist/crud/archetypes/OrderDetailView.d.ts.map +1 -1
  36. package/dist/crud/archetypes/PersonDetailTabs.d.ts +22 -0
  37. package/dist/crud/archetypes/PersonDetailTabs.d.ts.map +1 -1
  38. package/dist/crud/archetypes/ScheduleDateExceptions.d.ts +6 -0
  39. package/dist/crud/archetypes/ScheduleDateExceptions.d.ts.map +1 -0
  40. package/dist/crud/archetypes/ScheduleEditor.d.ts +8 -1
  41. package/dist/crud/archetypes/ScheduleEditor.d.ts.map +1 -1
  42. package/dist/crud/archetypes/ScheduleGridPanel.d.ts +10 -0
  43. package/dist/crud/archetypes/ScheduleGridPanel.d.ts.map +1 -0
  44. package/dist/hooks/useNotifications.d.ts.map +1 -1
  45. package/dist/index.d.ts +5 -0
  46. package/dist/index.d.ts.map +1 -1
  47. package/dist/index.js +1470 -1142
  48. package/dist/index.js.map +1 -1
  49. package/dist/lib/dedup.d.ts.map +1 -1
  50. package/dist/lib/schedule-config.d.ts +15 -0
  51. package/dist/lib/schedule-config.d.ts.map +1 -1
  52. package/dist/lib/schedule-service.d.ts +92 -0
  53. package/dist/lib/schedule-service.d.ts.map +1 -1
  54. package/dist/org/OrgMark.d.ts.map +1 -1
  55. package/dist/org/WorkspaceUnitSheet.d.ts +15 -0
  56. package/dist/org/WorkspaceUnitSheet.d.ts.map +1 -0
  57. package/dist/org/context.d.ts.map +1 -1
  58. package/dist/org/index.js +2 -2
  59. package/dist/units/UnitMenuSection.d.ts.map +1 -1
  60. package/dist/units/UnitPickerDialog.d.ts +4 -0
  61. package/dist/units/UnitPickerDialog.d.ts.map +1 -0
  62. package/dist/units/index.d.ts +2 -0
  63. package/dist/units/index.d.ts.map +1 -1
  64. package/dist/units/picker.store.d.ts +10 -0
  65. package/dist/units/picker.store.d.ts.map +1 -0
  66. package/package.json +6 -6
  67. package/dist/ScheduleEditor-KU4UUG6Q.js +0 -7
  68. package/dist/SubscriptionPage-2JPALG53.js +0 -9
  69. package/dist/chunk-NXFFJY2X.js +0 -1077
  70. package/dist/chunk-NXFFJY2X.js.map +0 -1
  71. package/dist/chunk-R4HQ2HS2.js.map +0 -1
  72. package/dist/chunk-XJB76FHA.js.map +0 -1
@@ -1,1077 +0,0 @@
1
- import { getFayzSupabaseClient } from './chunk-XJB76FHA.js';
2
- import { useOrganizationStore } from './chunk-W5IBKM3K.js';
3
- import { useState, useSyncExternalStore, useEffect, useCallback, useRef } from 'react';
4
- import { Clock, Save, Check, Plus, CalendarOff, CalendarPlus, Trash2, X, Copy, Settings, ChevronDown } from 'lucide-react';
5
- import { toast, Skeleton, Card, CardContent, Button, TooltipProvider, Tooltip, TooltipTrigger, TooltipContent, Modal, ModalContent, ModalHeader, ModalTitle, ModalDescription, ModalBody, DatePicker, Tabs, TabsList, TabsTrigger, TimePicker, ModalFooter, Badge } from '@fayz-ai/ui';
6
- import { useTranslation } from '@fayz-ai/core';
7
- import { createPortal } from 'react-dom';
8
- import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
-
10
- // src/lib/schedule-service.ts
11
- function parseBlockSettings(metadata) {
12
- return {
13
- bufferMinutes: typeof metadata.bufferMinutes === "number" ? metadata.bufferMinutes : void 0,
14
- allowedServiceIds: Array.isArray(metadata.allowedServiceIds) ? metadata.allowedServiceIds : void 0,
15
- maxConcurrent: typeof metadata.maxConcurrent === "number" ? metadata.maxConcurrent : void 0,
16
- minAdvanceHours: typeof metadata.minAdvanceHours === "number" ? metadata.minAdvanceHours : void 0,
17
- maxAdvanceDays: typeof metadata.maxAdvanceDays === "number" ? metadata.maxAdvanceDays : void 0,
18
- label: typeof metadata.label === "string" ? metadata.label : void 0,
19
- locationId: typeof metadata.locationId === "string" ? metadata.locationId : void 0
20
- };
21
- }
22
- function mergeBlockSettings(metadata, settings) {
23
- const merged = { ...metadata };
24
- for (const [key, value] of Object.entries(settings)) {
25
- if (value !== void 0 && value !== null && !(Array.isArray(value) && value.length === 0)) {
26
- merged[key] = value;
27
- } else {
28
- delete merged[key];
29
- }
30
- }
31
- return merged;
32
- }
33
- function getTenantId() {
34
- const id = useOrganizationStore.getState().currentOrg?.id;
35
- if (!id) throw new Error("No active tenant");
36
- return id;
37
- }
38
- function mapRow(row) {
39
- const r = {};
40
- for (const [key, value] of Object.entries(row)) {
41
- r[key.replace(/_([a-z])/g, (_, c) => c.toUpperCase())] = value;
42
- }
43
- return r;
44
- }
45
- async function getSchedules(assigneeId, kind = "working_hours") {
46
- const core = getFayzSupabaseClient();
47
- const { data, error } = await core.from("schedules").select("*").eq("kind", kind).eq("assignee_id", assigneeId).order("day_of_week").order("starts_at");
48
- if (error) throw error;
49
- return (data ?? []).map(mapRow);
50
- }
51
- async function saveSchedule(input) {
52
- const core = getFayzSupabaseClient();
53
- const tenantId = getTenantId();
54
- const { data, error } = await core.from("schedules").insert({
55
- tenant_id: tenantId,
56
- kind: input.kind ?? "working_hours",
57
- assignee_id: input.assigneeId,
58
- location_id: input.locationId ?? null,
59
- day_of_week: input.dayOfWeek ?? null,
60
- specific_date: input.specificDate ?? null,
61
- starts_at: input.startsAt,
62
- ends_at: input.endsAt,
63
- is_active: input.isActive ?? true
64
- }).select("*").single();
65
- if (error) throw error;
66
- return mapRow(data);
67
- }
68
- async function deleteSchedule(id) {
69
- const core = getFayzSupabaseClient();
70
- const { error } = await core.from("schedules").delete().eq("id", id);
71
- if (error) throw error;
72
- }
73
- async function replaceWeeklySchedules(assigneeId, week, kind = "working_hours") {
74
- const core = getFayzSupabaseClient();
75
- const tenantId = getTenantId();
76
- const { error: delErr } = await core.from("schedules").delete().eq("assignee_id", assigneeId).eq("kind", kind).not("day_of_week", "is", null).is("specific_date", null);
77
- if (delErr) throw delErr;
78
- const rows = [];
79
- for (const [dayStr, day] of Object.entries(week)) {
80
- if (!day.enabled) continue;
81
- for (const period of day.periods) {
82
- const metadata = period.settings ? mergeBlockSettings({}, period.settings) : {};
83
- rows.push({
84
- tenant_id: tenantId,
85
- kind,
86
- assignee_id: assigneeId,
87
- day_of_week: Number(dayStr),
88
- specific_date: null,
89
- starts_at: period.startsAt,
90
- ends_at: period.endsAt,
91
- is_active: true,
92
- metadata
93
- });
94
- }
95
- }
96
- if (rows.length > 0) {
97
- const { error: insErr } = await core.from("schedules").insert(rows);
98
- if (insErr) throw insErr;
99
- }
100
- }
101
- function eachDate(startDate, endDate) {
102
- const dates = [];
103
- const current = /* @__PURE__ */ new Date(startDate + "T00:00:00Z");
104
- const end = /* @__PURE__ */ new Date(endDate + "T00:00:00Z");
105
- while (current <= end) {
106
- dates.push(current.toISOString().slice(0, 10));
107
- current.setUTCDate(current.getUTCDate() + 1);
108
- }
109
- return dates;
110
- }
111
- async function saveException(input) {
112
- const core = getFayzSupabaseClient();
113
- const tenantId = getTenantId();
114
- const kind = input.kind ?? "working_hours";
115
- const dates = eachDate(input.startDate, input.endDate);
116
- const isRange = dates.length > 1;
117
- const meta = mergeBlockSettings(
118
- {
119
- ...input.notes ? { notes: input.notes } : {},
120
- ...isRange ? { rangeStart: input.startDate, rangeEnd: input.endDate } : {},
121
- exceptionType: input.type
122
- },
123
- input.settings ?? {}
124
- );
125
- const { error: delErr } = await core.from("schedules").delete().eq("assignee_id", input.assigneeId).eq("kind", kind).in("specific_date", dates);
126
- if (delErr) throw delErr;
127
- const rows = [];
128
- if (input.type === "unavailable") {
129
- for (const date of dates) {
130
- rows.push({
131
- tenant_id: tenantId,
132
- kind,
133
- assignee_id: input.assigneeId,
134
- day_of_week: null,
135
- specific_date: date,
136
- starts_at: "00:00",
137
- ends_at: "00:00",
138
- is_active: false,
139
- metadata: meta
140
- });
141
- }
142
- } else {
143
- for (const date of dates) {
144
- for (const p of input.periods) {
145
- rows.push({
146
- tenant_id: tenantId,
147
- kind,
148
- assignee_id: input.assigneeId,
149
- day_of_week: null,
150
- specific_date: date,
151
- starts_at: p.startsAt,
152
- ends_at: p.endsAt,
153
- is_active: true,
154
- metadata: meta
155
- });
156
- }
157
- }
158
- }
159
- if (rows.length > 0) {
160
- const { error } = await core.from("schedules").insert(rows);
161
- if (error) throw error;
162
- }
163
- }
164
- async function deleteException(assigneeId, startDate, endDate, kind = "working_hours") {
165
- const core = getFayzSupabaseClient();
166
- const dates = eachDate(startDate, endDate ?? startDate);
167
- const { error } = await core.from("schedules").delete().eq("assignee_id", assigneeId).eq("kind", kind).in("specific_date", dates);
168
- if (error) throw error;
169
- }
170
-
171
- // src/lib/schedule-config.ts
172
- var globalConfig = null;
173
- var listeners = /* @__PURE__ */ new Set();
174
- function setScheduleBlockConfig(config) {
175
- globalConfig = config;
176
- listeners.forEach((fn) => fn());
177
- }
178
- function getScheduleBlockConfig() {
179
- return globalConfig;
180
- }
181
- function subscribeScheduleBlockConfig(listener) {
182
- listeners.add(listener);
183
- return () => {
184
- listeners.delete(listener);
185
- };
186
- }
187
- function BlockSettingsPopover({
188
- settings,
189
- onChange,
190
- config,
191
- triggerRef,
192
- open,
193
- onClose
194
- }) {
195
- const t = useTranslation();
196
- const popoverRef = useRef(null);
197
- const [pos, setPos] = useState({ top: 0, left: 0 });
198
- useEffect(() => {
199
- if (!open || !triggerRef.current) return;
200
- const rect = triggerRef.current.getBoundingClientRect();
201
- const popW = 280;
202
- let left = rect.left + rect.width / 2 - popW / 2;
203
- if (left + popW > window.innerWidth - 12) left = window.innerWidth - popW - 12;
204
- if (left < 8) left = 8;
205
- setPos({ top: rect.bottom + 6, left });
206
- }, [open, triggerRef]);
207
- useEffect(() => {
208
- if (!open) return;
209
- const handler = (e) => {
210
- if (popoverRef.current?.contains(e.target)) return;
211
- if (triggerRef.current?.contains(e.target)) return;
212
- onClose();
213
- };
214
- document.addEventListener("mousedown", handler);
215
- return () => document.removeEventListener("mousedown", handler);
216
- }, [open, onClose, triggerRef]);
217
- if (!open) return null;
218
- const showServices = config.showServices !== false && config.services && config.services.length > 0;
219
- const showConcurrent = config.showConcurrent !== false;
220
- const showBookingWindow = config.showBookingWindow !== false;
221
- const showLocations = config.showLocations === true && config.locations && config.locations.length > 0;
222
- function update(patch) {
223
- onChange({ ...settings, ...patch });
224
- }
225
- return createPortal(
226
- /* @__PURE__ */ jsxs(
227
- "div",
228
- {
229
- ref: popoverRef,
230
- style: { position: "fixed", top: pos.top, left: pos.left, zIndex: 9999 },
231
- className: "fayz-glass-surface w-[280px] rounded-xl border bg-popover shadow-xl",
232
- children: [
233
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-3 py-2.5 border-b", children: [
234
- /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold", children: t("schedule.blockSettings.title") }),
235
- /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, className: "p-0.5 rounded text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" }) })
236
- ] }),
237
- /* @__PURE__ */ jsxs("div", { className: "p-3 space-y-3", children: [
238
- showLocations && /* @__PURE__ */ jsx(
239
- LocationSelect,
240
- {
241
- locations: config.locations,
242
- value: settings.locationId,
243
- onChange: (id) => update({ locationId: id || void 0 })
244
- }
245
- ),
246
- showServices && /* @__PURE__ */ jsx(
247
- ServiceSelect,
248
- {
249
- services: config.services,
250
- selected: settings.allowedServiceIds ?? [],
251
- onChange: (ids) => update({ allowedServiceIds: ids.length > 0 ? ids : void 0 })
252
- }
253
- ),
254
- showConcurrent && /* @__PURE__ */ jsxs("div", { children: [
255
- /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("schedule.blockSettings.maxConcurrent") }),
256
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-1", children: [
257
- /* @__PURE__ */ jsx(
258
- "button",
259
- {
260
- type: "button",
261
- onClick: () => update({ maxConcurrent: Math.max(1, (settings.maxConcurrent ?? config.defaults.maxConcurrent) - 1) }),
262
- className: "flex h-7 w-7 items-center justify-center rounded-md border text-sm hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors",
263
- children: "-"
264
- }
265
- ),
266
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium w-6 text-center", children: settings.maxConcurrent ?? config.defaults.maxConcurrent }),
267
- /* @__PURE__ */ jsx(
268
- "button",
269
- {
270
- type: "button",
271
- onClick: () => update({ maxConcurrent: (settings.maxConcurrent ?? config.defaults.maxConcurrent) + 1 }),
272
- className: "flex h-7 w-7 items-center justify-center rounded-md border text-sm hover:bg-muted bg-card shadow-button active:shadow-button-inset transition-colors",
273
- children: "+"
274
- }
275
- )
276
- ] })
277
- ] }),
278
- showBookingWindow && /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-2", children: [
279
- /* @__PURE__ */ jsxs("div", { children: [
280
- /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("schedule.blockSettings.minAdvance") }),
281
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 mt-1", children: [
282
- /* @__PURE__ */ jsx(
283
- "input",
284
- {
285
- type: "number",
286
- min: 0,
287
- value: settings.minAdvanceHours ?? config.defaults.minAdvanceHours,
288
- onChange: (e) => update({ minAdvanceHours: Number(e.target.value) || 0 }),
289
- className: "w-full rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2 py-1 text-xs tabular-nums"
290
- }
291
- ),
292
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground shrink-0", children: t("schedule.blockSettings.hrs") })
293
- ] })
294
- ] }),
295
- /* @__PURE__ */ jsxs("div", { children: [
296
- /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("schedule.blockSettings.maxAdvance") }),
297
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 mt-1", children: [
298
- /* @__PURE__ */ jsx(
299
- "input",
300
- {
301
- type: "number",
302
- min: 1,
303
- value: settings.maxAdvanceDays ?? config.defaults.maxAdvanceDays,
304
- onChange: (e) => update({ maxAdvanceDays: Number(e.target.value) || 30 }),
305
- className: "w-full rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2 py-1 text-xs tabular-nums"
306
- }
307
- ),
308
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground shrink-0", children: t("schedule.blockSettings.days") })
309
- ] })
310
- ] })
311
- ] }),
312
- /* @__PURE__ */ jsxs("div", { children: [
313
- /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("schedule.blockSettings.label") }),
314
- /* @__PURE__ */ jsx(
315
- "input",
316
- {
317
- type: "text",
318
- value: settings.label ?? "",
319
- onChange: (e) => update({ label: e.target.value || void 0 }),
320
- placeholder: t("schedule.blockSettings.labelPlaceholder"),
321
- className: "mt-1 w-full rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2 py-1 text-xs placeholder:text-muted-foreground/40"
322
- }
323
- )
324
- ] })
325
- ] })
326
- ]
327
- }
328
- ),
329
- document.body
330
- );
331
- }
332
- function LocationSelect({
333
- locations,
334
- value,
335
- onChange
336
- }) {
337
- const t = useTranslation();
338
- const [open, setOpen] = useState(false);
339
- const ref = useRef(null);
340
- useEffect(() => {
341
- if (!open) return;
342
- const handler = (e) => {
343
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
344
- };
345
- document.addEventListener("mousedown", handler);
346
- return () => document.removeEventListener("mousedown", handler);
347
- }, [open]);
348
- const current = value ? locations.find((l) => l.id === value) : null;
349
- const label = current ? current.name : t("schedule.blockSettings.noLocation");
350
- return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
351
- /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("schedule.blockSettings.location") }),
352
- /* @__PURE__ */ jsxs(
353
- "button",
354
- {
355
- type: "button",
356
- onClick: () => setOpen((p) => !p),
357
- className: "mt-1 flex items-center justify-between w-full rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2 py-1.5 text-xs hover:bg-muted/30 transition-colors",
358
- children: [
359
- /* @__PURE__ */ jsx("span", { className: current ? "" : "text-muted-foreground", children: label }),
360
- /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 text-muted-foreground" })
361
- ]
362
- }
363
- ),
364
- open && /* @__PURE__ */ jsxs("div", { className: "fayz-glass-surface absolute top-full left-0 z-10 mt-1 w-full max-h-40 overflow-y-auto rounded-lg border bg-popover shadow-lg py-1", children: [
365
- /* @__PURE__ */ jsx(
366
- "button",
367
- {
368
- type: "button",
369
- onClick: () => {
370
- onChange("");
371
- setOpen(false);
372
- },
373
- className: `block w-full px-2.5 py-1.5 text-xs text-left transition-colors ${!value ? "bg-primary/10 text-primary font-medium" : "hover:bg-muted/50"}`,
374
- children: t("schedule.blockSettings.noLocation")
375
- }
376
- ),
377
- locations.map((l) => /* @__PURE__ */ jsx(
378
- "button",
379
- {
380
- type: "button",
381
- onClick: () => {
382
- onChange(l.id);
383
- setOpen(false);
384
- },
385
- className: `block w-full px-2.5 py-1.5 text-xs text-left transition-colors ${l.id === value ? "bg-primary/10 text-primary font-medium" : "hover:bg-muted/50"}`,
386
- children: l.name
387
- },
388
- l.id
389
- ))
390
- ] })
391
- ] });
392
- }
393
- function ServiceSelect({
394
- services,
395
- selected,
396
- onChange
397
- }) {
398
- const t = useTranslation();
399
- const [open, setOpen] = useState(false);
400
- const ref = useRef(null);
401
- useEffect(() => {
402
- if (!open) return;
403
- const handler = (e) => {
404
- if (ref.current && !ref.current.contains(e.target)) setOpen(false);
405
- };
406
- document.addEventListener("mousedown", handler);
407
- return () => document.removeEventListener("mousedown", handler);
408
- }, [open]);
409
- const allSelected = selected.length === 0;
410
- const label = allSelected ? t("schedule.blockSettings.allServices") : selected.length === 1 ? services.find((s) => s.id === selected[0])?.name ?? t("schedule.blockSettings.oneService") : t("schedule.blockSettings.nServices", { count: selected.length });
411
- function toggle(id) {
412
- onChange(selected.includes(id) ? selected.filter((s) => s !== id) : [...selected, id]);
413
- }
414
- return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
415
- /* @__PURE__ */ jsx("label", { className: "text-[11px] font-medium text-muted-foreground", children: t("schedule.blockSettings.allowedServices") }),
416
- /* @__PURE__ */ jsxs(
417
- "button",
418
- {
419
- type: "button",
420
- onClick: () => setOpen((p) => !p),
421
- className: "mt-1 flex items-center justify-between w-full rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-2 py-1.5 text-xs hover:bg-muted/30 transition-colors",
422
- children: [
423
- /* @__PURE__ */ jsx("span", { className: allSelected ? "text-muted-foreground" : "", children: label }),
424
- /* @__PURE__ */ jsx(ChevronDown, { className: "h-3 w-3 text-muted-foreground" })
425
- ]
426
- }
427
- ),
428
- open && /* @__PURE__ */ jsxs("div", { className: "fayz-glass-surface absolute top-full left-0 z-10 mt-1 w-full max-h-40 overflow-y-auto rounded-lg border bg-popover shadow-lg py-1", children: [
429
- /* @__PURE__ */ jsx(
430
- "button",
431
- {
432
- type: "button",
433
- onClick: () => onChange([]),
434
- className: `block w-full px-2.5 py-1.5 text-xs text-left transition-colors ${allSelected ? "bg-primary/10 text-primary font-medium" : "hover:bg-muted/50"}`,
435
- children: t("schedule.blockSettings.allServices")
436
- }
437
- ),
438
- services.map((s) => {
439
- const checked = selected.includes(s.id);
440
- return /* @__PURE__ */ jsxs(
441
- "button",
442
- {
443
- type: "button",
444
- onClick: () => toggle(s.id),
445
- className: `flex items-center gap-2 w-full px-2.5 py-1.5 text-xs text-left transition-colors ${checked ? "bg-primary/5" : "hover:bg-muted/50"}`,
446
- children: [
447
- /* @__PURE__ */ jsx("span", { className: `flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-sm border ${checked ? "border-primary bg-primary text-primary-foreground" : "border-muted-foreground/30"}`, children: checked && /* @__PURE__ */ jsx("svg", { className: "h-2.5 w-2.5", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M2 6l3 3 5-5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
448
- /* @__PURE__ */ jsx("span", { className: "truncate", children: s.name })
449
- ]
450
- },
451
- s.id
452
- );
453
- })
454
- ] })
455
- ] });
456
- }
457
- var DAY_KEYS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
458
- var WEEK_ORDER = [1, 2, 3, 4, 5, 6, 0];
459
- function schedulesToWeek(schedules) {
460
- const week = {};
461
- for (let i = 0; i < 7; i++) {
462
- week[i] = { enabled: false, periods: [] };
463
- }
464
- for (const s of schedules) {
465
- if (s.dayOfWeek == null || s.specificDate != null) continue;
466
- const day = week[s.dayOfWeek];
467
- if (s.isActive) {
468
- day.enabled = true;
469
- const settings = parseBlockSettings(s.metadata ?? {});
470
- const hasSettings = Object.values(settings).some((v) => v !== void 0);
471
- day.periods.push({
472
- id: s.id,
473
- startsAt: s.startsAt,
474
- endsAt: s.endsAt,
475
- settings: hasSettings ? settings : void 0
476
- });
477
- }
478
- }
479
- for (const day of Object.values(week)) {
480
- day.periods.sort((a, b) => a.startsAt.localeCompare(b.startsAt));
481
- }
482
- return week;
483
- }
484
- function schedulesToExceptions(schedules) {
485
- const map = /* @__PURE__ */ new Map();
486
- for (const s of schedules) {
487
- if (!s.specificDate) continue;
488
- const meta = s.metadata ?? {};
489
- const rangeStart = meta.rangeStart ?? s.specificDate;
490
- const rangeEnd = meta.rangeEnd ?? s.specificDate;
491
- const key = `${rangeStart}__${rangeEnd}`;
492
- const exType = meta.exceptionType === "available" ? "available" : "unavailable";
493
- const notes = meta.notes ?? "";
494
- const existing = map.get(key);
495
- if (!existing) {
496
- map.set(key, {
497
- startDate: rangeStart,
498
- endDate: rangeEnd,
499
- type: exType,
500
- periods: s.isActive ? [{ startsAt: s.startsAt, endsAt: s.endsAt }] : [],
501
- notes
502
- });
503
- } else if (s.isActive && s.specificDate === rangeStart) {
504
- existing.periods.push({ startsAt: s.startsAt, endsAt: s.endsAt });
505
- }
506
- }
507
- return Array.from(map.values()).sort((a, b) => a.startDate.localeCompare(b.startDate));
508
- }
509
- function formatDate(dateStr) {
510
- const d = /* @__PURE__ */ new Date(dateStr + "T00:00:00");
511
- return d.toLocaleDateString(void 0, { month: "short", day: "numeric", year: "numeric" });
512
- }
513
- function PeriodRow({
514
- period,
515
- blockConfig,
516
- dayOfWeek,
517
- settingsOpen,
518
- onUpdateTime,
519
- onUpdateBuffer,
520
- onUpdateSettings,
521
- onRemove,
522
- onCopyToAll,
523
- onToggleSettings,
524
- onCloseSettings,
525
- onAddBlock
526
- }) {
527
- const t = useTranslation();
528
- const gearRef = useRef(null);
529
- const bufferValue = period.settings?.bufferMinutes ?? blockConfig?.defaults.bufferMinutes ?? 0;
530
- const hasCustomSettings = period.settings && Object.entries(period.settings).some(
531
- ([k, v]) => k !== "bufferMinutes" && v !== void 0
532
- );
533
- const dayName = t(`schedule.days.${DAY_KEYS[dayOfWeek]}`);
534
- return /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5 group", children: [
535
- period.settings?.label && /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium text-primary bg-primary/10 rounded-full px-2 py-0.5 shrink-0", children: period.settings.label }),
536
- /* @__PURE__ */ jsx(TimePicker, { value: period.startsAt, onChange: (v) => onUpdateTime("startsAt", v), interval: 30, className: "w-[90px]" }),
537
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground/50", children: "-" }),
538
- /* @__PURE__ */ jsx(TimePicker, { value: period.endsAt, onChange: (v) => onUpdateTime("endsAt", v), interval: 30, min: period.startsAt, className: "w-[90px]" }),
539
- /* @__PURE__ */ jsxs(Tooltip, { children: [
540
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-full border bg-muted/30 px-2 py-1 shrink-0", children: [
541
- /* @__PURE__ */ jsx(
542
- "input",
543
- {
544
- type: "number",
545
- min: 0,
546
- max: 120,
547
- value: bufferValue,
548
- onChange: (e) => onUpdateBuffer(Number(e.target.value) || 0),
549
- className: "w-8 bg-transparent text-xs text-center tabular-nums outline-none"
550
- }
551
- ),
552
- /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground", children: t("schedule.editor.min") })
553
- ] }) }),
554
- /* @__PURE__ */ jsx(TooltipContent, { children: t("schedule.editor.bufferTooltip") })
555
- ] }),
556
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-0.5 shrink-0 ml-auto", children: [
557
- /* @__PURE__ */ jsxs(Tooltip, { children: [
558
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
559
- "button",
560
- {
561
- type: "button",
562
- onClick: onRemove,
563
- className: "p-1.5 rounded-md text-muted-foreground/30 hover:text-destructive hover:bg-destructive/5 transition-colors",
564
- children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
565
- }
566
- ) }),
567
- /* @__PURE__ */ jsx(TooltipContent, { children: t("schedule.editor.removeInterval") })
568
- ] }),
569
- /* @__PURE__ */ jsxs(Tooltip, { children: [
570
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
571
- "button",
572
- {
573
- type: "button",
574
- onClick: onAddBlock,
575
- className: "p-1.5 rounded-md text-muted-foreground/30 hover:text-foreground hover:bg-muted transition-colors",
576
- children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" })
577
- }
578
- ) }),
579
- /* @__PURE__ */ jsx(TooltipContent, { children: t("schedule.editor.newInterval", { day: dayName }) })
580
- ] }),
581
- /* @__PURE__ */ jsxs(Tooltip, { children: [
582
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
583
- "button",
584
- {
585
- type: "button",
586
- onClick: onCopyToAll,
587
- className: "p-1.5 rounded-md text-muted-foreground/30 hover:text-foreground hover:bg-muted transition-colors",
588
- children: /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
589
- }
590
- ) }),
591
- /* @__PURE__ */ jsx(TooltipContent, { children: t("schedule.editor.copyToAll") })
592
- ] }),
593
- blockConfig && /* @__PURE__ */ jsxs(Tooltip, { children: [
594
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
595
- "button",
596
- {
597
- ref: gearRef,
598
- type: "button",
599
- onClick: onToggleSettings,
600
- className: `p-1.5 rounded-md transition-colors ${hasCustomSettings ? "text-primary bg-primary/5" : "text-muted-foreground/30 hover:text-foreground hover:bg-muted"}`,
601
- children: /* @__PURE__ */ jsx(Settings, { className: "h-3.5 w-3.5" })
602
- }
603
- ) }),
604
- /* @__PURE__ */ jsx(TooltipContent, { children: t("schedule.blockSettings.title") })
605
- ] })
606
- ] }),
607
- blockConfig && /* @__PURE__ */ jsx(
608
- BlockSettingsPopover,
609
- {
610
- settings: period.settings ?? {},
611
- onChange: onUpdateSettings,
612
- config: blockConfig,
613
- triggerRef: gearRef,
614
- open: settingsOpen,
615
- onClose: onCloseSettings
616
- }
617
- )
618
- ] }) });
619
- }
620
- function ScheduleEditor({ assigneeId }) {
621
- const t = useTranslation();
622
- const [week, setWeek] = useState({});
623
- const [exceptions, setExceptions] = useState([]);
624
- const [loading, setLoading] = useState(true);
625
- const [saving, setSaving] = useState(false);
626
- const [showAddException, setShowAddException] = useState(false);
627
- const blockConfig = useSyncExternalStore(subscribeScheduleBlockConfig, getScheduleBlockConfig);
628
- const [settingsPopover, setSettingsPopover] = useState(null);
629
- const [weekDirty, setWeekDirty] = useState(false);
630
- useEffect(() => {
631
- if (blockConfig?.showLocations && (!blockConfig.locations || blockConfig.locations.length === 0) && blockConfig.fetchLocations) {
632
- blockConfig.fetchLocations().catch(() => {
633
- });
634
- }
635
- }, [blockConfig?.showLocations, blockConfig?.locations?.length]);
636
- const [newExStartDate, setNewExStartDate] = useState("");
637
- const [newExEndDate, setNewExEndDate] = useState("");
638
- const [newExType, setNewExType] = useState("unavailable");
639
- const [newExPeriods, setNewExPeriods] = useState([{ startsAt: "09:00", endsAt: "17:00" }]);
640
- const [newExNotes, setNewExNotes] = useState("");
641
- const [savingException, setSavingException] = useState(false);
642
- const loadData = useCallback(async () => {
643
- setLoading(true);
644
- try {
645
- const data = await getSchedules(assigneeId);
646
- setWeek(schedulesToWeek(data));
647
- setExceptions(schedulesToExceptions(data));
648
- setWeekDirty(false);
649
- } catch {
650
- toast.error(t("schedule.editor.failedToLoad"));
651
- }
652
- setLoading(false);
653
- }, [assigneeId]);
654
- useEffect(() => {
655
- loadData();
656
- }, [loadData]);
657
- function markDirty() {
658
- setWeekDirty(true);
659
- }
660
- function toggleDay(dayOfWeek) {
661
- markDirty();
662
- setWeek((prev) => ({
663
- ...prev,
664
- [dayOfWeek]: {
665
- ...prev[dayOfWeek],
666
- enabled: !prev[dayOfWeek]?.enabled,
667
- periods: prev[dayOfWeek]?.enabled ? [] : [{ startsAt: "09:00", endsAt: "18:00" }]
668
- }
669
- }));
670
- }
671
- function updatePeriod(dayOfWeek, idx, field, value) {
672
- markDirty();
673
- setWeek((prev) => {
674
- const day = { ...prev[dayOfWeek] };
675
- const periods = [...day.periods];
676
- periods[idx] = { ...periods[idx], [field]: value };
677
- return { ...prev, [dayOfWeek]: { ...day, periods } };
678
- });
679
- }
680
- function addPeriod(dayOfWeek) {
681
- markDirty();
682
- setWeek((prev) => {
683
- const day = { ...prev[dayOfWeek] };
684
- const last = day.periods[day.periods.length - 1];
685
- day.periods = [...day.periods, { startsAt: last?.endsAt ?? "13:00", endsAt: "18:00" }];
686
- return { ...prev, [dayOfWeek]: day };
687
- });
688
- }
689
- function removePeriod(dayOfWeek, idx) {
690
- markDirty();
691
- setWeek((prev) => {
692
- const day = { ...prev[dayOfWeek] };
693
- day.periods = day.periods.filter((_, i) => i !== idx);
694
- if (day.periods.length === 0) day.enabled = false;
695
- return { ...prev, [dayOfWeek]: day };
696
- });
697
- }
698
- function updatePeriodSettings(dayOfWeek, idx, settings) {
699
- markDirty();
700
- setWeek((prev) => {
701
- const day = { ...prev[dayOfWeek] };
702
- const periods = [...day.periods];
703
- periods[idx] = { ...periods[idx], settings };
704
- return { ...prev, [dayOfWeek]: { ...day, periods } };
705
- });
706
- }
707
- function updateBufferMinutes(dayOfWeek, idx, value) {
708
- markDirty();
709
- setWeek((prev) => {
710
- const day = { ...prev[dayOfWeek] };
711
- const periods = [...day.periods];
712
- const existing = periods[idx].settings ?? {};
713
- periods[idx] = { ...periods[idx], settings: { ...existing, bufferMinutes: value || void 0 } };
714
- return { ...prev, [dayOfWeek]: { ...day, periods } };
715
- });
716
- }
717
- function copyToAll(sourceDayOfWeek) {
718
- markDirty();
719
- const source = week[sourceDayOfWeek];
720
- if (!source) return;
721
- setWeek((prev) => {
722
- const next = { ...prev };
723
- for (let i = 0; i < 7; i++) {
724
- if (i === sourceDayOfWeek) continue;
725
- next[i] = {
726
- enabled: source.enabled,
727
- periods: source.periods.map((p) => ({ startsAt: p.startsAt, endsAt: p.endsAt, settings: p.settings ? { ...p.settings } : void 0 }))
728
- };
729
- }
730
- return next;
731
- });
732
- }
733
- async function handleSaveWeekly() {
734
- setSaving(true);
735
- try {
736
- await replaceWeeklySchedules(assigneeId, week);
737
- toast.success(t("schedule.editor.saved"));
738
- loadData();
739
- } catch (err) {
740
- toast.error(t("schedule.editor.failedToSave"), { description: err?.message });
741
- }
742
- setSaving(false);
743
- }
744
- async function handleAddException() {
745
- if (!newExStartDate) {
746
- toast.error(t("schedule.editor.selectStartDate"));
747
- return;
748
- }
749
- const endDate = newExEndDate || newExStartDate;
750
- if (endDate < newExStartDate) {
751
- toast.error(t("schedule.editor.endDateAfterStart"));
752
- return;
753
- }
754
- setSavingException(true);
755
- try {
756
- await saveException({
757
- assigneeId,
758
- startDate: newExStartDate,
759
- endDate,
760
- type: newExType,
761
- periods: newExType === "unavailable" ? [] : newExPeriods,
762
- notes: newExNotes || void 0
763
- });
764
- toast.success(t("schedule.editor.exceptionAdded"));
765
- setShowAddException(false);
766
- setNewExStartDate("");
767
- setNewExEndDate("");
768
- setNewExType("unavailable");
769
- setNewExPeriods([{ startsAt: "09:00", endsAt: "17:00" }]);
770
- setNewExNotes("");
771
- loadData();
772
- } catch (err) {
773
- toast.error(t("schedule.editor.failedToSaveException"), { description: err?.message });
774
- }
775
- setSavingException(false);
776
- }
777
- async function handleDeleteException(startDate, endDate) {
778
- try {
779
- await deleteException(assigneeId, startDate, endDate);
780
- toast.success(t("schedule.editor.exceptionRemoved"));
781
- loadData();
782
- } catch (err) {
783
- toast.error(t("schedule.editor.failedToDelete"), { description: err?.message });
784
- }
785
- }
786
- if (loading) {
787
- return /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-6", children: [
788
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
789
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
790
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
791
- /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-9 rounded-lg" }),
792
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
793
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-36" }),
794
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-48" })
795
- ] })
796
- ] }),
797
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-20 rounded-md" })
798
- ] }),
799
- /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "p-0 divide-y", children: Array.from({ length: 7 }).map((_, i) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
800
- /* @__PURE__ */ jsx(Skeleton, { className: "h-5 w-5 rounded-full" }),
801
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-8" }),
802
- i < 5 ? /* @__PURE__ */ jsxs(Fragment, { children: [
803
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-24 rounded-lg" }),
804
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-3" }),
805
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-24 rounded-lg" })
806
- ] }) : /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-20" })
807
- ] }, i)) }) })
808
- ] }),
809
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
810
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
811
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
812
- /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-9 rounded-lg" }),
813
- /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
814
- /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-32" }),
815
- /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-44" })
816
- ] })
817
- ] }),
818
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-32 rounded-md" })
819
- ] }),
820
- /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-3 w-48" }) }) })
821
- ] })
822
- ] });
823
- }
824
- return /* @__PURE__ */ jsxs("div", { className: "fz-fields fz-fields-2 gap-6", children: [
825
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
826
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
827
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
828
- /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-primary/10", children: /* @__PURE__ */ jsx(Clock, { className: "h-4 w-4 text-primary" }) }),
829
- /* @__PURE__ */ jsxs("div", { children: [
830
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t("schedule.editor.weeklyTitle") }),
831
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("schedule.editor.weeklyDescription") })
832
- ] })
833
- ] }),
834
- weekDirty && /* @__PURE__ */ jsxs(Button, { size: "sm", onClick: handleSaveWeekly, disabled: saving, children: [
835
- /* @__PURE__ */ jsx(Save, { className: "h-4 w-4 mr-1.5" }),
836
- saving ? t("common.saving") : t("common.save")
837
- ] })
838
- ] }),
839
- /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "p-0 divide-y", children: WEEK_ORDER.map((dayOfWeek) => {
840
- const day = week[dayOfWeek] ?? { enabled: false, periods: [] };
841
- return /* @__PURE__ */ jsxs(
842
- "div",
843
- {
844
- className: `flex items-start gap-4 px-4 py-3 transition-colors ${day.enabled ? "" : "bg-muted/20"}`,
845
- children: [
846
- /* @__PURE__ */ jsxs(
847
- "button",
848
- {
849
- type: "button",
850
- onClick: () => toggleDay(dayOfWeek),
851
- className: "flex items-center gap-2.5 w-20 shrink-0 pt-1.5",
852
- children: [
853
- /* @__PURE__ */ jsx("span", { className: `flex h-5 w-5 shrink-0 items-center justify-center rounded-full transition-all ${day.enabled ? "bg-primary text-primary-foreground shadow-sm" : "border-2 border-muted-foreground/25 hover:border-muted-foreground/40"}`, children: day.enabled && /* @__PURE__ */ jsx(Check, { className: "h-3 w-3", strokeWidth: 3 }) }),
854
- /* @__PURE__ */ jsx("span", { className: `text-sm font-medium ${day.enabled ? "" : "text-muted-foreground/40"}`, children: t(`schedule.days.${DAY_KEYS[dayOfWeek]}`) })
855
- ]
856
- }
857
- ),
858
- day.enabled ? /* @__PURE__ */ jsx("div", { className: "flex-1 space-y-2", children: day.periods.map((period, idx) => /* @__PURE__ */ jsx(
859
- PeriodRow,
860
- {
861
- period,
862
- dayOfWeek,
863
- blockConfig,
864
- settingsOpen: settingsPopover?.dayOfWeek === dayOfWeek && settingsPopover?.idx === idx,
865
- onUpdateTime: (field, v) => updatePeriod(dayOfWeek, idx, field, v),
866
- onUpdateBuffer: (v) => updateBufferMinutes(dayOfWeek, idx, v),
867
- onUpdateSettings: (s) => updatePeriodSettings(dayOfWeek, idx, s),
868
- onRemove: () => removePeriod(dayOfWeek, idx),
869
- onAddBlock: () => addPeriod(dayOfWeek),
870
- onCopyToAll: () => copyToAll(dayOfWeek),
871
- onToggleSettings: () => setSettingsPopover(
872
- settingsPopover?.dayOfWeek === dayOfWeek && settingsPopover?.idx === idx ? null : { dayOfWeek, idx }
873
- ),
874
- onCloseSettings: () => setSettingsPopover(null)
875
- },
876
- idx
877
- )) }) : /* @__PURE__ */ jsx(TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 pt-1.5", children: [
878
- /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground/40", children: t("schedule.editor.unavailable") }),
879
- /* @__PURE__ */ jsxs(Tooltip, { children: [
880
- /* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
881
- "button",
882
- {
883
- type: "button",
884
- onClick: () => toggleDay(dayOfWeek),
885
- className: "flex h-6 w-6 items-center justify-center rounded-full border border-muted-foreground/25 text-muted-foreground/40 hover:border-foreground/40 hover:text-foreground hover:bg-muted transition-colors",
886
- children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" })
887
- }
888
- ) }),
889
- /* @__PURE__ */ jsx(TooltipContent, { children: t("schedule.editor.addHoursFor", { day: t(`schedule.days.${DAY_KEYS[dayOfWeek]}`) }) })
890
- ] })
891
- ] }) })
892
- ]
893
- },
894
- dayOfWeek
895
- );
896
- }) }) })
897
- ] }),
898
- /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
899
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
900
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
901
- /* @__PURE__ */ jsx("div", { className: "flex h-9 w-9 items-center justify-center rounded-lg bg-orange-500/10", children: /* @__PURE__ */ jsx(CalendarOff, { className: "h-4 w-4 text-orange-600" }) }),
902
- /* @__PURE__ */ jsxs("div", { children: [
903
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold", children: t("schedule.editor.exceptionsTitle") }),
904
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("schedule.editor.exceptionsDescription") })
905
- ] })
906
- ] }),
907
- /* @__PURE__ */ jsxs(
908
- Button,
909
- {
910
- size: "sm",
911
- variant: "outline",
912
- onClick: () => setShowAddException(true),
913
- children: [
914
- /* @__PURE__ */ jsx(CalendarPlus, { className: "h-4 w-4 mr-1.5" }),
915
- t("schedule.editor.addException")
916
- ]
917
- }
918
- )
919
- ] }),
920
- /* @__PURE__ */ jsx(Modal, { open: showAddException, onOpenChange: setShowAddException, children: /* @__PURE__ */ jsxs(ModalContent, { size: "md", children: [
921
- /* @__PURE__ */ jsxs(ModalHeader, { children: [
922
- /* @__PURE__ */ jsx(ModalTitle, { children: t("schedule.editor.addException") }),
923
- /* @__PURE__ */ jsx(ModalDescription, { children: t("schedule.editor.exceptionsDescription") })
924
- ] }),
925
- /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-5", children: [
926
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
927
- /* @__PURE__ */ jsxs("div", { children: [
928
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground mb-1.5 block", children: t("schedule.editor.startDate") }),
929
- /* @__PURE__ */ jsx(
930
- DatePicker,
931
- {
932
- value: newExStartDate,
933
- onChange: (v) => {
934
- setNewExStartDate(v);
935
- if (!newExEndDate || v > newExEndDate) setNewExEndDate(v);
936
- }
937
- }
938
- )
939
- ] }),
940
- /* @__PURE__ */ jsxs("div", { children: [
941
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground mb-1.5 block", children: t("schedule.editor.endDate") }),
942
- /* @__PURE__ */ jsx(
943
- DatePicker,
944
- {
945
- value: newExEndDate || newExStartDate,
946
- onChange: setNewExEndDate
947
- }
948
- )
949
- ] })
950
- ] }),
951
- /* @__PURE__ */ jsxs("div", { children: [
952
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground mb-1.5 block", children: t("schedule.editor.type") }),
953
- /* @__PURE__ */ jsx(Tabs, { value: newExType, onValueChange: (v) => setNewExType(v), children: /* @__PURE__ */ jsxs(TabsList, { className: "h-9 w-full", children: [
954
- /* @__PURE__ */ jsxs(TabsTrigger, { value: "unavailable", className: "text-xs gap-1.5 flex-1", children: [
955
- /* @__PURE__ */ jsx(CalendarOff, { className: "h-3.5 w-3.5" }),
956
- t("schedule.editor.unavailable")
957
- ] }),
958
- /* @__PURE__ */ jsxs(TabsTrigger, { value: "available", className: "text-xs gap-1.5 flex-1", children: [
959
- /* @__PURE__ */ jsx(Clock, { className: "h-3.5 w-3.5" }),
960
- t("schedule.editor.customHours")
961
- ] })
962
- ] }) })
963
- ] }),
964
- newExType === "available" && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
965
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("schedule.editor.workingHours") }),
966
- newExPeriods.map((period, idx) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
967
- /* @__PURE__ */ jsx(
968
- TimePicker,
969
- {
970
- value: period.startsAt,
971
- onChange: (v) => {
972
- const updated = [...newExPeriods];
973
- updated[idx] = { ...updated[idx], startsAt: v };
974
- setNewExPeriods(updated);
975
- },
976
- interval: 30,
977
- className: "flex-1"
978
- }
979
- ),
980
- /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground shrink-0", children: t("schedule.editor.to") }),
981
- /* @__PURE__ */ jsx(
982
- TimePicker,
983
- {
984
- value: period.endsAt,
985
- onChange: (v) => {
986
- const updated = [...newExPeriods];
987
- updated[idx] = { ...updated[idx], endsAt: v };
988
- setNewExPeriods(updated);
989
- },
990
- interval: 30,
991
- min: period.startsAt,
992
- className: "flex-1"
993
- }
994
- ),
995
- newExPeriods.length > 1 && /* @__PURE__ */ jsx(
996
- "button",
997
- {
998
- type: "button",
999
- onClick: () => setNewExPeriods(newExPeriods.filter((_, i) => i !== idx)),
1000
- className: "p-1.5 rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/5 transition-colors shrink-0",
1001
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
1002
- }
1003
- )
1004
- ] }, idx)),
1005
- /* @__PURE__ */ jsxs(
1006
- "button",
1007
- {
1008
- type: "button",
1009
- onClick: () => {
1010
- const last = newExPeriods[newExPeriods.length - 1];
1011
- setNewExPeriods([...newExPeriods, { startsAt: last?.endsAt ?? "13:00", endsAt: "18:00" }]);
1012
- },
1013
- className: "flex items-center gap-1 text-xs text-primary hover:underline",
1014
- children: [
1015
- /* @__PURE__ */ jsx(Plus, { className: "h-3 w-3" }),
1016
- " ",
1017
- t("schedule.editor.addTimeBlock")
1018
- ]
1019
- }
1020
- )
1021
- ] }),
1022
- /* @__PURE__ */ jsxs("div", { children: [
1023
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground mb-1.5 block", children: t("schedule.editor.notes") }),
1024
- /* @__PURE__ */ jsx(
1025
- "input",
1026
- {
1027
- type: "text",
1028
- value: newExNotes,
1029
- onChange: (e) => setNewExNotes(e.target.value),
1030
- placeholder: t("schedule.editor.notesPlaceholder"),
1031
- className: "block w-full rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm placeholder:text-muted-foreground/40 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-1"
1032
- }
1033
- )
1034
- ] })
1035
- ] }),
1036
- /* @__PURE__ */ jsxs(ModalFooter, { children: [
1037
- /* @__PURE__ */ jsx(Button, { variant: "ghost", onClick: () => setShowAddException(false), children: t("common.cancel") }),
1038
- /* @__PURE__ */ jsxs(Button, { onClick: handleAddException, disabled: savingException, children: [
1039
- /* @__PURE__ */ jsx(Save, { className: "h-3.5 w-3.5 mr-1.5" }),
1040
- savingException ? t("common.saving") : t("schedule.editor.saveException")
1041
- ] })
1042
- ] })
1043
- ] }) }),
1044
- exceptions.length > 0 ? /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "p-0 divide-y", children: exceptions.map((ex) => {
1045
- const isRange = ex.startDate !== ex.endDate;
1046
- const dateLabel = isRange ? `${formatDate(ex.startDate)} \u2014 ${formatDate(ex.endDate)}` : formatDate(ex.startDate);
1047
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-3 group", children: [
1048
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 min-w-0", children: [
1049
- /* @__PURE__ */ jsx("span", { className: `h-2.5 w-2.5 shrink-0 rounded-full ${ex.type === "unavailable" ? "bg-destructive" : "bg-success"}` }),
1050
- /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
1051
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
1052
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: dateLabel }),
1053
- /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "text-[10px]", children: ex.type === "unavailable" ? t("schedule.editor.unavailable") : t("schedule.editor.customHours") }),
1054
- ex.type === "available" && ex.periods.length > 0 && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: ex.periods.map((p) => `${p.startsAt} \u2014 ${p.endsAt}`).join(", ") })
1055
- ] }),
1056
- ex.notes && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-0.5 truncate", children: ex.notes })
1057
- ] })
1058
- ] }),
1059
- /* @__PURE__ */ jsx(
1060
- "button",
1061
- {
1062
- type: "button",
1063
- onClick: () => handleDeleteException(ex.startDate, ex.endDate),
1064
- className: "p-1.5 rounded-md text-muted-foreground hover:text-destructive hover:bg-destructive/5 transition-colors shrink-0 ml-2",
1065
- title: t("schedule.editor.removeException"),
1066
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
1067
- }
1068
- )
1069
- ] }, ex.startDate + ex.endDate);
1070
- }) }) }) : !showAddException && /* @__PURE__ */ jsx(Card, { children: /* @__PURE__ */ jsx(CardContent, { className: "flex items-center justify-center py-8 text-sm text-muted-foreground", children: t("schedule.editor.noExceptions") }) })
1071
- ] })
1072
- ] });
1073
- }
1074
-
1075
- export { ScheduleEditor, deleteSchedule, getScheduleBlockConfig, getSchedules, mergeBlockSettings, parseBlockSettings, replaceWeeklySchedules, saveSchedule, setScheduleBlockConfig, subscribeScheduleBlockConfig };
1076
- //# sourceMappingURL=chunk-NXFFJY2X.js.map
1077
- //# sourceMappingURL=chunk-NXFFJY2X.js.map