@open-mercato/core 0.6.6-develop.6519.1.bdc91fb755 → 0.6.6-develop.6520.1.55df6df792

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 (70) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/customers/backend/calendar/page.js +9 -1
  3. package/dist/modules/customers/backend/calendar/page.js.map +2 -2
  4. package/dist/modules/customers/components/calendar/AgendaList.js +0 -11
  5. package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
  6. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -235
  7. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +2 -2
  8. package/dist/modules/customers/components/calendar/CalendarScreen.js +5 -5
  9. package/dist/modules/customers/components/calendar/CalendarScreen.js.map +2 -2
  10. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +2 -2
  11. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  12. package/dist/modules/customers/components/calendar/MonthGrid.js +1 -11
  13. package/dist/modules/customers/components/calendar/MonthGrid.js.map +2 -2
  14. package/dist/modules/customers/components/calendar/TimeGrid.js +162 -176
  15. package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
  16. package/dist/modules/customers/components/calendar/editor/LocationField.js +61 -14
  17. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
  18. package/dist/modules/customers/components/calendar/editor/PeopleField.js +10 -5
  19. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  20. package/dist/modules/customers/components/calendar/editor/PriorityField.js +85 -0
  21. package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +7 -0
  22. package/dist/modules/customers/components/calendar/editor/RelatedToField.js +8 -3
  23. package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +2 -2
  24. package/dist/modules/customers/components/calendar/editor/ResourcesField.js +225 -0
  25. package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +7 -0
  26. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +12 -7
  27. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
  28. package/dist/modules/customers/components/calendar/editor/inputs.js +68 -57
  29. package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
  30. package/dist/modules/customers/components/calendar/editor/lookups.js +82 -2
  31. package/dist/modules/customers/components/calendar/editor/lookups.js.map +2 -2
  32. package/dist/modules/customers/components/calendar/useCalendarItems.js +6 -1
  33. package/dist/modules/customers/components/calendar/useCalendarItems.js.map +2 -2
  34. package/dist/modules/customers/data/validators.js +3 -1
  35. package/dist/modules/customers/data/validators.js.map +2 -2
  36. package/dist/modules/customers/lib/calendar/editorPayload.js +53 -22
  37. package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
  38. package/dist/modules/entities/backend/entities/user/[entityId]/page.js +1 -1
  39. package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
  40. package/package.json +7 -7
  41. package/src/modules/customers/backend/calendar/page.tsx +8 -1
  42. package/src/modules/customers/components/calendar/AgendaList.tsx +0 -10
  43. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +444 -261
  44. package/src/modules/customers/components/calendar/CalendarScreen.tsx +12 -5
  45. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +6 -6
  46. package/src/modules/customers/components/calendar/MonthGrid.tsx +0 -11
  47. package/src/modules/customers/components/calendar/TimeGrid.tsx +1 -24
  48. package/src/modules/customers/components/calendar/editor/LocationField.tsx +64 -2
  49. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +11 -4
  50. package/src/modules/customers/components/calendar/editor/PriorityField.tsx +90 -0
  51. package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +8 -3
  52. package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +264 -0
  53. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +14 -6
  54. package/src/modules/customers/components/calendar/editor/inputs.tsx +91 -50
  55. package/src/modules/customers/components/calendar/editor/lookups.ts +115 -2
  56. package/src/modules/customers/components/calendar/useCalendarItems.ts +8 -1
  57. package/src/modules/customers/data/validators.ts +3 -1
  58. package/src/modules/customers/i18n/de.json +12 -2
  59. package/src/modules/customers/i18n/en.json +12 -2
  60. package/src/modules/customers/i18n/es.json +12 -2
  61. package/src/modules/customers/i18n/pl.json +12 -2
  62. package/src/modules/customers/lib/calendar/editorPayload.ts +94 -37
  63. package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +1 -1
  64. package/src/modules/entities/i18n/de.json +120 -0
  65. package/src/modules/entities/i18n/en.json +120 -0
  66. package/src/modules/entities/i18n/es.json +120 -0
  67. package/src/modules/entities/i18n/pl.json +120 -0
  68. package/dist/modules/customers/components/calendar/editor/CategoryField.js +0 -75
  69. package/dist/modules/customers/components/calendar/editor/CategoryField.js.map +0 -7
  70. package/src/modules/customers/components/calendar/editor/CategoryField.tsx +0 -79
@@ -0,0 +1,225 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { Box, Check, ChevronDown, Plus } from "lucide-react";
5
+ import { cn } from "@open-mercato/shared/lib/utils";
6
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
7
+ import { Button } from "@open-mercato/ui/primitives/button";
8
+ import { IconButton } from "@open-mercato/ui/primitives/icon-button";
9
+ import { Input } from "@open-mercato/ui/primitives/input";
10
+ import { fetchResourceTypes, searchResourceOptions } from "./lookups.js";
11
+ import { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, useDropdownDismiss } from "./inputs.js";
12
+ const TYPE_SEARCH_THRESHOLD = 8;
13
+ function ResourcesField({
14
+ placeholder,
15
+ ariaLabel,
16
+ value,
17
+ onChange
18
+ }) {
19
+ const t = useT();
20
+ const [query, setQuery] = React.useState("");
21
+ const [open, setOpen] = React.useState(false);
22
+ const [options, setOptions] = React.useState([]);
23
+ const [total, setTotal] = React.useState(0);
24
+ const [loading, setLoading] = React.useState(false);
25
+ const [types, setTypes] = React.useState([]);
26
+ const [typeFilter, setTypeFilter] = React.useState(null);
27
+ const [typeMenuOpen, setTypeMenuOpen] = React.useState(false);
28
+ const [typeQuery, setTypeQuery] = React.useState("");
29
+ const close = React.useCallback(() => {
30
+ setOpen(false);
31
+ setTypeMenuOpen(false);
32
+ }, []);
33
+ const rootRef = useDropdownDismiss(open, close);
34
+ React.useEffect(() => {
35
+ if (!open || types.length > 0) return;
36
+ const controller = new AbortController();
37
+ let cancelled = false;
38
+ fetchResourceTypes(controller.signal).then((found) => {
39
+ if (!cancelled) setTypes(found);
40
+ });
41
+ return () => {
42
+ cancelled = true;
43
+ controller.abort();
44
+ };
45
+ }, [open, types.length]);
46
+ React.useEffect(() => {
47
+ if (!open) return;
48
+ const controller = new AbortController();
49
+ let cancelled = false;
50
+ const timer = window.setTimeout(async () => {
51
+ setLoading(true);
52
+ try {
53
+ const result = await searchResourceOptions({ query: query.trim(), resourceTypeId: typeFilter }, controller.signal);
54
+ if (cancelled) return;
55
+ setOptions(result.items);
56
+ setTotal(result.total);
57
+ } catch {
58
+ if (!cancelled) {
59
+ setOptions([]);
60
+ setTotal(0);
61
+ }
62
+ } finally {
63
+ if (!cancelled) setLoading(false);
64
+ }
65
+ }, 250);
66
+ return () => {
67
+ cancelled = true;
68
+ controller.abort();
69
+ window.clearTimeout(timer);
70
+ };
71
+ }, [open, query, typeFilter]);
72
+ const selectedIds = new Set(value.map((resource) => resource.id));
73
+ const visibleOptions = options.filter((option) => !selectedIds.has(option.id));
74
+ const hiddenCount = Math.max(0, total - options.length);
75
+ const activeType = typeFilter ? types.find((type) => type.id === typeFilter) ?? null : null;
76
+ const filteredTypes = typeQuery.trim().length ? types.filter((type) => type.name.toLowerCase().includes(typeQuery.trim().toLowerCase())) : types;
77
+ const selectType = (id) => {
78
+ setTypeFilter(id);
79
+ setTypeMenuOpen(false);
80
+ setTypeQuery("");
81
+ };
82
+ return /* @__PURE__ */ jsxs(
83
+ "div",
84
+ {
85
+ ref: rootRef,
86
+ className: "relative w-full",
87
+ onBlur: (event) => {
88
+ if (!event.currentTarget.contains(event.relatedTarget)) close();
89
+ },
90
+ children: [
91
+ /* @__PURE__ */ jsxs(
92
+ "div",
93
+ {
94
+ className: cn(
95
+ "flex min-h-14 w-full flex-wrap content-center items-center gap-2 rounded-md bg-background px-2.5 py-2",
96
+ CONTROL_BORDER
97
+ ),
98
+ children: [
99
+ value.map((resource) => /* @__PURE__ */ jsxs("span", { className: "inline-flex shrink-0 items-center gap-1.5 rounded-full bg-muted py-1 pl-2 pr-2", children: [
100
+ /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "size-3.5 text-muted-foreground" }),
101
+ /* @__PURE__ */ jsx("span", { className: "max-w-40 truncate text-xs font-medium text-foreground", children: resource.label }),
102
+ /* @__PURE__ */ jsx(
103
+ IconButton,
104
+ {
105
+ variant: "ghost",
106
+ size: "xs",
107
+ onClick: (event) => {
108
+ event.stopPropagation();
109
+ onChange(value.filter((entry) => entry.id !== resource.id));
110
+ },
111
+ "aria-label": t("customers.calendar.editor.removeResource", "Remove {name}", { name: resource.label }),
112
+ className: "size-5 shrink-0",
113
+ children: /* @__PURE__ */ jsx(Plus, { "aria-hidden": true, className: "size-3.5 rotate-45 opacity-50" })
114
+ }
115
+ )
116
+ ] }, resource.id)),
117
+ /* @__PURE__ */ jsx(
118
+ Input,
119
+ {
120
+ type: "text",
121
+ value: query,
122
+ onChange: (event) => setQuery(event.target.value),
123
+ onFocus: () => setOpen(true),
124
+ placeholder: value.length > 0 ? "" : placeholder,
125
+ "aria-label": ariaLabel,
126
+ role: "combobox",
127
+ "aria-expanded": open,
128
+ className: "min-w-36 flex-1 border-0 bg-transparent px-0 shadow-none hover:bg-transparent focus-within:border-transparent focus-within:shadow-none",
129
+ inputClassName: "text-sm text-foreground placeholder:text-muted-foreground"
130
+ }
131
+ )
132
+ ]
133
+ }
134
+ ),
135
+ open ? /* @__PURE__ */ jsxs("div", { className: cn(DROPDOWN_PANEL_CLASS, "flex max-h-none flex-col overflow-visible p-0"), children: [
136
+ types.length > 0 ? /* @__PURE__ */ jsx("div", { className: "border-b border-border p-1.5", children: /* @__PURE__ */ jsxs(
137
+ Button,
138
+ {
139
+ type: "button",
140
+ variant: "outline",
141
+ "aria-haspopup": "listbox",
142
+ "aria-expanded": typeMenuOpen,
143
+ onClick: () => setTypeMenuOpen((previous) => !previous),
144
+ className: cn("h-8 w-full justify-between bg-background px-2 text-sm font-normal shadow-none", CONTROL_BORDER),
145
+ children: [
146
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: activeType ? `${activeType.name} (${activeType.count})` : t("customers.calendar.editor.resourceTypes.all", "All types") }),
147
+ /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": true, className: "size-4 shrink-0 opacity-60" })
148
+ ]
149
+ }
150
+ ) }) : null,
151
+ typeMenuOpen ? /* @__PURE__ */ jsxs("div", { role: "listbox", "aria-label": t("customers.calendar.editor.resourceTypes.filterLabel", "Resource type"), className: "flex flex-col", children: [
152
+ types.length > TYPE_SEARCH_THRESHOLD ? /* @__PURE__ */ jsx("div", { className: "border-b border-border p-1.5", children: /* @__PURE__ */ jsx(
153
+ Input,
154
+ {
155
+ type: "text",
156
+ value: typeQuery,
157
+ onChange: (event) => setTypeQuery(event.target.value),
158
+ placeholder: t("customers.calendar.editor.resourceTypes.searchPlaceholder", "Search types\u2026"),
159
+ "aria-label": t("customers.calendar.editor.resourceTypes.searchPlaceholder", "Search types\u2026"),
160
+ autoFocus: true,
161
+ className: "h-8"
162
+ }
163
+ ) }) : null,
164
+ /* @__PURE__ */ jsxs("div", { className: "max-h-56 overflow-y-auto p-1", children: [
165
+ /* @__PURE__ */ jsx(TypeRow, { label: t("customers.calendar.editor.resourceTypes.all", "All types"), active: typeFilter === null, onSelect: () => selectType(null) }),
166
+ filteredTypes.map((type) => /* @__PURE__ */ jsx(TypeRow, { label: type.name, count: type.count, active: typeFilter === type.id, onSelect: () => selectType(type.id) }, type.id)),
167
+ filteredTypes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: t("customers.calendar.editor.noResults", "No results") }) : null
168
+ ] })
169
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
170
+ /* @__PURE__ */ jsxs("div", { role: "listbox", "aria-label": ariaLabel, className: "max-h-56 overflow-y-auto p-1", children: [
171
+ loading ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: t("customers.calendar.editor.searching", "Searching\u2026") }) : null,
172
+ !loading && visibleOptions.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-3 text-center text-xs text-muted-foreground", children: t("customers.calendar.editor.noResults", "No results") }) : null,
173
+ !loading ? visibleOptions.map((option) => /* @__PURE__ */ jsxs(
174
+ Button,
175
+ {
176
+ type: "button",
177
+ variant: "ghost",
178
+ role: "option",
179
+ "aria-selected": false,
180
+ title: option.label,
181
+ onClick: () => {
182
+ onChange([...value, { id: option.id, label: option.label }]);
183
+ setQuery("");
184
+ },
185
+ className: "h-auto w-full justify-start gap-2 whitespace-normal px-2 py-1.5 text-left text-sm font-normal text-foreground",
186
+ children: [
187
+ /* @__PURE__ */ jsx(Box, { "aria-hidden": true, className: "size-4 text-muted-foreground" }),
188
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: option.label })
189
+ ]
190
+ },
191
+ option.id
192
+ )) : null
193
+ ] }),
194
+ !loading && hiddenCount > 0 ? /* @__PURE__ */ jsx("p", { className: "border-t border-border px-2 py-1.5 text-center text-xs text-muted-foreground", children: t("customers.calendar.editor.resourceTypes.moreHint", "Showing {shown} of {total} \u2014 keep typing to narrow", {
195
+ shown: options.length,
196
+ total
197
+ }) }) : null
198
+ ] })
199
+ ] }) : null
200
+ ]
201
+ }
202
+ );
203
+ }
204
+ function TypeRow({ label, count, active, onSelect }) {
205
+ return /* @__PURE__ */ jsxs(
206
+ Button,
207
+ {
208
+ type: "button",
209
+ variant: "ghost",
210
+ role: "option",
211
+ "aria-selected": active,
212
+ onClick: onSelect,
213
+ className: cn("h-auto w-full justify-start gap-2 px-2 py-1.5 text-left text-sm font-normal", active ? "bg-muted text-foreground" : "text-foreground"),
214
+ children: [
215
+ /* @__PURE__ */ jsx(Check, { "aria-hidden": true, className: cn("size-4 shrink-0", active ? "opacity-100" : "opacity-0") }),
216
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", children: label }),
217
+ typeof count === "number" ? /* @__PURE__ */ jsx("span", { className: "shrink-0 tabular-nums text-xs text-muted-foreground", children: count }) : null
218
+ ]
219
+ }
220
+ );
221
+ }
222
+ export {
223
+ ResourcesField
224
+ };
225
+ //# sourceMappingURL=ResourcesField.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/customers/components/calendar/editor/ResourcesField.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Box, Check, ChevronDown, Plus } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport type { EditorResource } from '../../../lib/calendar/editorPayload'\nimport { fetchResourceTypes, searchResourceOptions, type ResourceOption, type ResourceType } from './lookups'\nimport { CONTROL_BORDER, DROPDOWN_PANEL_CLASS, useDropdownDismiss } from './inputs'\n\nconst TYPE_SEARCH_THRESHOLD = 8\n\n// Multi-select of bookable resources (rooms, cars, equipment) from the\n// resources module. Rendered only when that module is loaded \u2014 the calendar\n// consumes its public list API and stores FK-id + label snapshots in\n// `linkedEntities`, never resource entities (#3552). A potentially large\n// catalog (many resources AND many types) stays navigable with a collapsible\n// resource-type filter (searchable, with counts), server-side search and a\n// \"showing N of M\" hint.\nexport function ResourcesField({\n placeholder,\n ariaLabel,\n value,\n onChange,\n}: {\n placeholder: string\n ariaLabel: string\n value: EditorResource[]\n onChange(next: EditorResource[]): void\n}) {\n const t = useT()\n const [query, setQuery] = React.useState('')\n const [open, setOpen] = React.useState(false)\n const [options, setOptions] = React.useState<ResourceOption[]>([])\n const [total, setTotal] = React.useState(0)\n const [loading, setLoading] = React.useState(false)\n const [types, setTypes] = React.useState<ResourceType[]>([])\n const [typeFilter, setTypeFilter] = React.useState<string | null>(null)\n const [typeMenuOpen, setTypeMenuOpen] = React.useState(false)\n const [typeQuery, setTypeQuery] = React.useState('')\n const close = React.useCallback(() => {\n setOpen(false)\n setTypeMenuOpen(false)\n }, [])\n const rootRef = useDropdownDismiss(open, close)\n\n // Load the type filter once the dropdown first opens.\n React.useEffect(() => {\n if (!open || types.length > 0) return\n const controller = new AbortController()\n let cancelled = false\n fetchResourceTypes(controller.signal).then((found) => {\n if (!cancelled) setTypes(found)\n })\n return () => {\n cancelled = true\n controller.abort()\n }\n }, [open, types.length])\n\n React.useEffect(() => {\n if (!open) return\n const controller = new AbortController()\n let cancelled = false\n const timer = window.setTimeout(async () => {\n setLoading(true)\n try {\n const result = await searchResourceOptions({ query: query.trim(), resourceTypeId: typeFilter }, controller.signal)\n if (cancelled) return\n setOptions(result.items)\n setTotal(result.total)\n } catch {\n if (!cancelled) {\n setOptions([])\n setTotal(0)\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }, 250)\n return () => {\n cancelled = true\n controller.abort()\n window.clearTimeout(timer)\n }\n }, [open, query, typeFilter])\n\n const selectedIds = new Set(value.map((resource) => resource.id))\n const visibleOptions = options.filter((option) => !selectedIds.has(option.id))\n const hiddenCount = Math.max(0, total - options.length)\n const activeType = typeFilter ? types.find((type) => type.id === typeFilter) ?? null : null\n const filteredTypes = typeQuery.trim().length\n ? types.filter((type) => type.name.toLowerCase().includes(typeQuery.trim().toLowerCase()))\n : types\n\n const selectType = (id: string | null) => {\n setTypeFilter(id)\n setTypeMenuOpen(false)\n setTypeQuery('')\n }\n\n return (\n <div\n ref={rootRef}\n className=\"relative w-full\"\n onBlur={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) close()\n }}\n >\n <div\n className={cn(\n 'flex min-h-14 w-full flex-wrap content-center items-center gap-2 rounded-md bg-background px-2.5 py-2',\n CONTROL_BORDER,\n )}\n >\n {value.map((resource) => (\n <span key={resource.id} className=\"inline-flex shrink-0 items-center gap-1.5 rounded-full bg-muted py-1 pl-2 pr-2\">\n <Box aria-hidden className=\"size-3.5 text-muted-foreground\" />\n <span className=\"max-w-40 truncate text-xs font-medium text-foreground\">{resource.label}</span>\n <IconButton\n variant=\"ghost\"\n size=\"xs\"\n onClick={(event) => {\n event.stopPropagation()\n onChange(value.filter((entry) => entry.id !== resource.id))\n }}\n aria-label={t('customers.calendar.editor.removeResource', 'Remove {name}', { name: resource.label })}\n className=\"size-5 shrink-0\"\n >\n <Plus aria-hidden className=\"size-3.5 rotate-45 opacity-50\" />\n </IconButton>\n </span>\n ))}\n <Input\n type=\"text\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n onFocus={() => setOpen(true)}\n placeholder={value.length > 0 ? '' : placeholder}\n aria-label={ariaLabel}\n role=\"combobox\"\n aria-expanded={open}\n className=\"min-w-36 flex-1 border-0 bg-transparent px-0 shadow-none hover:bg-transparent focus-within:border-transparent focus-within:shadow-none\"\n inputClassName=\"text-sm text-foreground placeholder:text-muted-foreground\"\n />\n </div>\n {open ? (\n <div className={cn(DROPDOWN_PANEL_CLASS, 'flex max-h-none flex-col overflow-visible p-0')}>\n {types.length > 0 ? (\n <div className=\"border-b border-border p-1.5\">\n <Button\n type=\"button\"\n variant=\"outline\"\n aria-haspopup=\"listbox\"\n aria-expanded={typeMenuOpen}\n onClick={() => setTypeMenuOpen((previous) => !previous)}\n className={cn('h-8 w-full justify-between bg-background px-2 text-sm font-normal shadow-none', CONTROL_BORDER)}\n >\n <span className=\"truncate\">\n {activeType\n ? `${activeType.name} (${activeType.count})`\n : t('customers.calendar.editor.resourceTypes.all', 'All types')}\n </span>\n <ChevronDown aria-hidden className=\"size-4 shrink-0 opacity-60\" />\n </Button>\n </div>\n ) : null}\n\n {typeMenuOpen ? (\n <div role=\"listbox\" aria-label={t('customers.calendar.editor.resourceTypes.filterLabel', 'Resource type')} className=\"flex flex-col\">\n {types.length > TYPE_SEARCH_THRESHOLD ? (\n <div className=\"border-b border-border p-1.5\">\n <Input\n type=\"text\"\n value={typeQuery}\n onChange={(event) => setTypeQuery(event.target.value)}\n placeholder={t('customers.calendar.editor.resourceTypes.searchPlaceholder', 'Search types\u2026')}\n aria-label={t('customers.calendar.editor.resourceTypes.searchPlaceholder', 'Search types\u2026')}\n autoFocus\n className=\"h-8\"\n />\n </div>\n ) : null}\n <div className=\"max-h-56 overflow-y-auto p-1\">\n <TypeRow label={t('customers.calendar.editor.resourceTypes.all', 'All types')} active={typeFilter === null} onSelect={() => selectType(null)} />\n {filteredTypes.map((type) => (\n <TypeRow key={type.id} label={type.name} count={type.count} active={typeFilter === type.id} onSelect={() => selectType(type.id)} />\n ))}\n {filteredTypes.length === 0 ? (\n <p className=\"px-2 py-3 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.noResults', 'No results')}\n </p>\n ) : null}\n </div>\n </div>\n ) : (\n <>\n <div role=\"listbox\" aria-label={ariaLabel} className=\"max-h-56 overflow-y-auto p-1\">\n {loading ? (\n <p className=\"px-2 py-3 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.searching', 'Searching\u2026')}\n </p>\n ) : null}\n {!loading && visibleOptions.length === 0 ? (\n <p className=\"px-2 py-3 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.noResults', 'No results')}\n </p>\n ) : null}\n {!loading\n ? visibleOptions.map((option) => (\n <Button\n key={option.id}\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={false}\n title={option.label}\n onClick={() => {\n onChange([...value, { id: option.id, label: option.label }])\n setQuery('')\n }}\n className=\"h-auto w-full justify-start gap-2 whitespace-normal px-2 py-1.5 text-left text-sm font-normal text-foreground\"\n >\n <Box aria-hidden className=\"size-4 text-muted-foreground\" />\n <span className=\"min-w-0 flex-1 truncate\">{option.label}</span>\n </Button>\n ))\n : null}\n </div>\n {!loading && hiddenCount > 0 ? (\n <p className=\"border-t border-border px-2 py-1.5 text-center text-xs text-muted-foreground\">\n {t('customers.calendar.editor.resourceTypes.moreHint', 'Showing {shown} of {total} \u2014 keep typing to narrow', {\n shown: options.length,\n total,\n })}\n </p>\n ) : null}\n </>\n )}\n </div>\n ) : null}\n </div>\n )\n}\n\nfunction TypeRow({ label, count, active, onSelect }: { label: string; count?: number; active: boolean; onSelect(): void }) {\n return (\n <Button\n type=\"button\"\n variant=\"ghost\"\n role=\"option\"\n aria-selected={active}\n onClick={onSelect}\n className={cn('h-auto w-full justify-start gap-2 px-2 py-1.5 text-left text-sm font-normal', active ? 'bg-muted text-foreground' : 'text-foreground')}\n >\n <Check aria-hidden className={cn('size-4 shrink-0', active ? 'opacity-100' : 'opacity-0')} />\n <span className=\"min-w-0 flex-1 truncate\">{label}</span>\n {typeof count === 'number' ? <span className=\"shrink-0 tabular-nums text-xs text-muted-foreground\">{count}</span> : null}\n </Button>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { jsx } from "react/jsx-runtime";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { cn } from "@open-mercato/shared/lib/utils";
4
4
  import { Button } from "@open-mercato/ui/primitives/button";
5
5
  function SegmentGroup({
@@ -14,23 +14,28 @@ function SegmentGroup({
14
14
  {
15
15
  role: "group",
16
16
  "aria-label": ariaLabel,
17
- className: "inline-flex max-w-full items-start overflow-x-auto rounded-md border border-border bg-background",
17
+ className: "flex w-full items-stretch overflow-hidden rounded-md border border-border bg-background",
18
18
  children: options.map((option, index) => {
19
19
  const isActive = option.value === value;
20
- return /* @__PURE__ */ jsx(
20
+ return /* @__PURE__ */ jsxs(
21
21
  Button,
22
22
  {
23
23
  type: "button",
24
24
  variant: "ghost",
25
25
  "aria-pressed": isActive,
26
26
  onClick: () => onChange(option.value),
27
+ title: option.label,
28
+ "data-state": isActive ? "active" : "inactive",
27
29
  className: cn(
28
- "h-auto rounded-none border-0 text-sm font-medium leading-5 shadow-none",
29
- size === "md" ? "px-4 py-2" : "px-3.5 py-1.5",
30
+ "h-auto min-w-0 flex-1 justify-center gap-1 rounded-none border-0 px-1.5 text-sm font-medium leading-5 shadow-none",
31
+ size === "md" ? "py-2" : "py-1.5",
30
32
  index > 0 && "border-l border-border",
31
- isActive ? "bg-muted text-foreground hover:bg-muted" : "bg-background text-muted-foreground"
33
+ isActive ? "bg-accent-indigo font-semibold text-accent-indigo-foreground hover:bg-accent-indigo hover:text-accent-indigo-foreground dark:hover:bg-accent-indigo" : "bg-background text-muted-foreground hover:bg-muted hover:text-foreground"
32
34
  ),
33
- children: option.label
35
+ children: [
36
+ option.icon ? /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: cn("shrink-0", isActive ? "opacity-100" : "opacity-70"), children: option.icon }) : null,
37
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: option.label })
38
+ ]
34
39
  },
35
40
  option.value
36
41
  );
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customers/components/calendar/editor/SegmentGroup.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\n\nexport type SegmentOption<T extends string> = { value: T; label: string }\n\nexport function SegmentGroup<T extends string>({\n options,\n value,\n onChange,\n size = 'sm',\n ariaLabel,\n}: {\n options: Array<SegmentOption<T>>\n value: T\n onChange(next: T): void\n size?: 'sm' | 'md'\n ariaLabel: string\n}) {\n return (\n <div\n role=\"group\"\n aria-label={ariaLabel}\n className=\"inline-flex max-w-full items-start overflow-x-auto rounded-md border border-border bg-background\"\n >\n {options.map((option, index) => {\n const isActive = option.value === value\n return (\n <Button\n key={option.value}\n type=\"button\"\n variant=\"ghost\"\n aria-pressed={isActive}\n onClick={() => onChange(option.value)}\n className={cn(\n 'h-auto rounded-none border-0 text-sm font-medium leading-5 shadow-none',\n size === 'md' ? 'px-4 py-2' : 'px-3.5 py-1.5',\n index > 0 && 'border-l border-border',\n isActive ? 'bg-muted text-foreground hover:bg-muted' : 'bg-background text-muted-foreground',\n )}\n >\n {option.label}\n </Button>\n )\n })}\n </div>\n )\n}\n"],
5
- "mappings": ";AA8BU;AA3BV,SAAS,UAAU;AACnB,SAAS,cAAc;AAIhB,SAAS,aAA+B;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAMG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,WAAU;AAAA,MAET,kBAAQ,IAAI,CAAC,QAAQ,UAAU;AAC9B,cAAM,WAAW,OAAO,UAAU;AAClC,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,gBAAc;AAAA,YACd,SAAS,MAAM,SAAS,OAAO,KAAK;AAAA,YACpC,WAAW;AAAA,cACT;AAAA,cACA,SAAS,OAAO,cAAc;AAAA,cAC9B,QAAQ,KAAK;AAAA,cACb,WAAW,4CAA4C;AAAA,YACzD;AAAA,YAEC,iBAAO;AAAA;AAAA,UAZH,OAAO;AAAA,QAad;AAAA,MAEJ,CAAC;AAAA;AAAA,EACH;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\n\nexport type SegmentOption<T extends string> = { value: T; label: string; icon?: React.ReactNode }\n\nexport function SegmentGroup<T extends string>({\n options,\n value,\n onChange,\n size = 'sm',\n ariaLabel,\n}: {\n options: Array<SegmentOption<T>>\n value: T\n onChange(next: T): void\n size?: 'sm' | 'md'\n ariaLabel: string\n}) {\n return (\n <div\n role=\"group\"\n aria-label={ariaLabel}\n className=\"flex w-full items-stretch overflow-hidden rounded-md border border-border bg-background\"\n >\n {options.map((option, index) => {\n const isActive = option.value === value\n return (\n <Button\n key={option.value}\n type=\"button\"\n variant=\"ghost\"\n aria-pressed={isActive}\n onClick={() => onChange(option.value)}\n title={option.label}\n data-state={isActive ? 'active' : 'inactive'}\n className={cn(\n 'h-auto min-w-0 flex-1 justify-center gap-1 rounded-none border-0 px-1.5 text-sm font-medium leading-5 shadow-none',\n size === 'md' ? 'py-2' : 'py-1.5',\n index > 0 && 'border-l border-border',\n isActive\n // Keep the indigo fill on hover in BOTH themes \u2014 the ghost Button\n // ships `dark:hover:bg-accent/50`, which would otherwise wash the\n // active segment out on hover in dark mode.\n ? 'bg-accent-indigo font-semibold text-accent-indigo-foreground hover:bg-accent-indigo hover:text-accent-indigo-foreground dark:hover:bg-accent-indigo'\n : 'bg-background text-muted-foreground hover:bg-muted hover:text-foreground',\n )}\n >\n {option.icon ? <span aria-hidden className={cn('shrink-0', isActive ? 'opacity-100' : 'opacity-70')}>{option.icon}</span> : null}\n <span className=\"truncate\">{option.label}</span>\n </Button>\n )\n })}\n </div>\n )\n}\n"],
5
+ "mappings": ";AA8BU,SAoBiB,KApBjB;AA3BV,SAAS,UAAU;AACnB,SAAS,cAAc;AAIhB,SAAS,aAA+B;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AACF,GAMG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,WAAU;AAAA,MAET,kBAAQ,IAAI,CAAC,QAAQ,UAAU;AAC9B,cAAM,WAAW,OAAO,UAAU;AAClC,eACE;AAAA,UAAC;AAAA;AAAA,YAEC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,gBAAc;AAAA,YACd,SAAS,MAAM,SAAS,OAAO,KAAK;AAAA,YACpC,OAAO,OAAO;AAAA,YACd,cAAY,WAAW,WAAW;AAAA,YAClC,WAAW;AAAA,cACT;AAAA,cACA,SAAS,OAAO,SAAS;AAAA,cACzB,QAAQ,KAAK;AAAA,cACb,WAII,wJACA;AAAA,YACN;AAAA,YAEC;AAAA,qBAAO,OAAO,oBAAC,UAAK,eAAW,MAAC,WAAW,GAAG,YAAY,WAAW,gBAAgB,YAAY,GAAI,iBAAO,MAAK,IAAU;AAAA,cAC5H,oBAAC,UAAK,WAAU,YAAY,iBAAO,OAAM;AAAA;AAAA;AAAA,UApBpC,OAAO;AAAA,QAqBd;AAAA,MAEJ,CAAC;AAAA;AAAA,EACH;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -1,17 +1,41 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { ChevronDown, Plus } from "lucide-react";
3
+ import * as React from "react";
4
+ import { Plus } from "lucide-react";
4
5
  import { cn } from "@open-mercato/shared/lib/utils";
5
6
  import { Avatar } from "@open-mercato/ui/primitives/avatar";
7
+ import { DatePicker } from "@open-mercato/ui/primitives/date-picker";
6
8
  import { IconButton } from "@open-mercato/ui/primitives/icon-button";
7
- import { Input } from "@open-mercato/ui/primitives/input";
9
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@open-mercato/ui/primitives/select";
8
10
  import { Switch } from "@open-mercato/ui/primitives/switch";
9
11
  const CONTROL_BORDER = "border border-input";
10
12
  const CONTROL_TEXT = "text-sm text-foreground placeholder:text-muted-foreground";
11
13
  const LABEL_CLASS = "text-xs font-medium text-muted-foreground";
12
14
  const DROPDOWN_PANEL_CLASS = "absolute z-50 mt-1 max-h-56 w-full overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md";
13
- function Field({ label, children, error }) {
14
- return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-1.5", children: [
15
+ function useDropdownDismiss(open, onClose) {
16
+ const rootRef = React.useRef(null);
17
+ React.useEffect(() => {
18
+ if (!open) return;
19
+ const handlePointerDown = (event) => {
20
+ if (rootRef.current && event.target instanceof Node && !rootRef.current.contains(event.target)) onClose();
21
+ };
22
+ const handleKeyDown = (event) => {
23
+ if (event.key === "Escape") {
24
+ event.stopPropagation();
25
+ onClose();
26
+ }
27
+ };
28
+ document.addEventListener("pointerdown", handlePointerDown, true);
29
+ document.addEventListener("keydown", handleKeyDown, true);
30
+ return () => {
31
+ document.removeEventListener("pointerdown", handlePointerDown, true);
32
+ document.removeEventListener("keydown", handleKeyDown, true);
33
+ };
34
+ }, [open, onClose]);
35
+ return rootRef;
36
+ }
37
+ function Field({ label, children, error, className }) {
38
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex w-full flex-col gap-1.5", className), children: [
15
39
  /* @__PURE__ */ jsx("span", { className: LABEL_CLASS, children: label }),
16
40
  children,
17
41
  error ? /* @__PURE__ */ jsx("p", { className: "text-xs text-status-error-text", children: error }) : null
@@ -30,18 +54,6 @@ function UppercaseBadge({ style, className, children }) {
30
54
  }
31
55
  );
32
56
  }
33
- function categoryPillStyle(color) {
34
- if (typeof color === "string" && /^#[0-9a-fA-F]{6}$/.test(color)) {
35
- return { backgroundColor: `${color}59`, color: `color-mix(in srgb, ${color} 55%, black)` };
36
- }
37
- if (typeof color === "string" && color.length > 0) {
38
- return {
39
- backgroundColor: `color-mix(in srgb, ${color} 35%, white)`,
40
- color: `color-mix(in srgb, ${color} 55%, black)`
41
- };
42
- }
43
- return {};
44
- }
45
57
  function AllDayToggle({ checked, onCheckedChange, label }) {
46
58
  return /* @__PURE__ */ jsx(
47
59
  Switch,
@@ -53,61 +65,59 @@ function AllDayToggle({ checked, onCheckedChange, label }) {
53
65
  }
54
66
  );
55
67
  }
56
- function formatDisplayDate(value, locale) {
68
+ function parseDateValue(value) {
69
+ if (!value) return null;
57
70
  const parsed = /* @__PURE__ */ new Date(`${value}T00:00:00`);
58
- if (Number.isNaN(parsed.getTime())) return value;
59
- return new Intl.DateTimeFormat(locale, { weekday: "short", month: "short", day: "numeric", year: "numeric" }).format(parsed);
71
+ return Number.isNaN(parsed.getTime()) ? null : parsed;
72
+ }
73
+ function formatDateValue(date) {
74
+ return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
75
+ }
76
+ const EDITOR_SCROLL_EVENT = "om-calendar-editor-scroll";
77
+ function useCloseOnEditorScroll(setOpen) {
78
+ React.useEffect(() => {
79
+ const handler = () => setOpen(false);
80
+ document.addEventListener(EDITOR_SCROLL_EVENT, handler);
81
+ return () => document.removeEventListener(EDITOR_SCROLL_EVENT, handler);
82
+ }, [setOpen]);
60
83
  }
61
84
  function DateControl({
62
85
  value,
63
86
  onChange,
64
87
  ariaLabel,
65
- locale,
66
88
  className
67
89
  }) {
68
- return /* @__PURE__ */ jsxs(
69
- "div",
70
- {
71
- className: cn(
72
- "relative flex h-9 w-full items-center justify-between rounded-md bg-background px-3",
73
- CONTROL_BORDER,
74
- className
75
- ),
76
- children: [
77
- /* @__PURE__ */ jsx("span", { className: "truncate text-sm text-foreground", children: formatDisplayDate(value, locale) }),
78
- /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "h-px w-2 shrink-0" }),
79
- /* @__PURE__ */ jsx(ChevronDown, { "aria-hidden": true, className: "size-4 shrink-0 opacity-60" }),
80
- /* @__PURE__ */ jsx(
81
- Input,
82
- {
83
- type: "date",
84
- value,
85
- onChange: (event) => {
86
- if (event.target.value) onChange(event.target.value);
87
- },
88
- "aria-label": ariaLabel,
89
- className: "absolute inset-0 h-full w-full cursor-pointer border-0 bg-transparent p-0 opacity-0 shadow-none hover:bg-transparent",
90
- inputClassName: "h-full cursor-pointer"
91
- }
92
- )
93
- ]
94
- }
95
- );
96
- }
97
- function TimeControl({ value, onChange, ariaLabel }) {
90
+ const [open, setOpen] = React.useState(false);
91
+ useCloseOnEditorScroll(setOpen);
98
92
  return /* @__PURE__ */ jsx(
99
- Input,
93
+ DatePicker,
100
94
  {
101
- type: "time",
102
- value,
103
- onChange: (event) => {
104
- if (event.target.value) onChange(event.target.value);
95
+ value: parseDateValue(value),
96
+ onChange: (date) => {
97
+ if (date) onChange(formatDateValue(date));
105
98
  },
99
+ footer: "none",
100
+ open,
101
+ onOpenChange: setOpen,
106
102
  "aria-label": ariaLabel,
107
- className: "h-9 w-32 shrink-0"
103
+ className: cn("w-full", className)
108
104
  }
109
105
  );
110
106
  }
107
+ const TIME_OPTIONS = Array.from({ length: 48 }, (_, index) => {
108
+ const hour = Math.floor(index / 2);
109
+ const minute = index % 2 === 0 ? "00" : "30";
110
+ return `${String(hour).padStart(2, "0")}:${minute}`;
111
+ });
112
+ function TimeControl({ value, onChange, ariaLabel }) {
113
+ const [open, setOpen] = React.useState(false);
114
+ useCloseOnEditorScroll(setOpen);
115
+ const options = value && !TIME_OPTIONS.includes(value) ? [value, ...TIME_OPTIONS] : TIME_OPTIONS;
116
+ return /* @__PURE__ */ jsxs(Select, { value, onValueChange: onChange, open, onOpenChange: setOpen, children: [
117
+ /* @__PURE__ */ jsx(SelectTrigger, { "aria-label": ariaLabel, className: "h-9 w-32 shrink-0", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
118
+ /* @__PURE__ */ jsx(SelectContent, { className: "max-h-64", children: options.map((time) => /* @__PURE__ */ jsx(SelectItem, { value: time, children: time }, time)) })
119
+ ] });
120
+ }
111
121
  function PersonChip({
112
122
  name,
113
123
  badge,
@@ -150,11 +160,12 @@ export {
150
160
  CONTROL_TEXT,
151
161
  DROPDOWN_PANEL_CLASS,
152
162
  DateControl,
163
+ EDITOR_SCROLL_EVENT,
153
164
  Field,
154
165
  LABEL_CLASS,
155
166
  PersonChip,
156
167
  TimeControl,
157
168
  UppercaseBadge,
158
- categoryPillStyle
169
+ useDropdownDismiss
159
170
  };
160
171
  //# sourceMappingURL=inputs.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/customers/components/calendar/editor/inputs.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { ChevronDown, Plus } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\n\nexport const CONTROL_BORDER = 'border border-input'\nexport const CONTROL_TEXT = 'text-sm text-foreground placeholder:text-muted-foreground'\nexport const LABEL_CLASS = 'text-xs font-medium text-muted-foreground'\nexport const DROPDOWN_PANEL_CLASS =\n 'absolute z-50 mt-1 max-h-56 w-full overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md'\n\nexport function Field({ label, children, error }: { label: string; children: React.ReactNode; error?: string | null }) {\n return (\n <div className=\"flex w-full flex-col gap-1.5\">\n <span className={LABEL_CLASS}>{label}</span>\n {children}\n {error ? <p className=\"text-xs text-status-error-text\">{error}</p> : null}\n </div>\n )\n}\n\nexport function UppercaseBadge({ style, className, children }: { style?: React.CSSProperties; className?: string; children: React.ReactNode }) {\n return (\n <span\n className={cn(\n 'inline-flex shrink-0 items-center justify-center rounded-full bg-muted px-2 py-0.5 text-overline font-medium uppercase text-muted-foreground',\n className,\n )}\n style={style}\n >\n {children}\n </span>\n )\n}\n\n// Mockup tint colors are illustrative; runtime pills derive from the\n// dictionary entry color using the appearanceColor hex-alpha tint technique\n// (see EventBlock.softTintStyle) with a darkened text shade of the same hue.\nexport function categoryPillStyle(color: string | null | undefined): React.CSSProperties {\n if (typeof color === 'string' && /^#[0-9a-fA-F]{6}$/.test(color)) {\n return { backgroundColor: `${color}59`, color: `color-mix(in srgb, ${color} 55%, black)` }\n }\n if (typeof color === 'string' && color.length > 0) {\n return {\n backgroundColor: `color-mix(in srgb, ${color} 35%, white)`,\n color: `color-mix(in srgb, ${color} 55%, black)`,\n }\n }\n return {}\n}\n\nexport function AllDayToggle({ checked, onCheckedChange, label }: { checked: boolean; onCheckedChange(next: boolean): void; label: string }) {\n return (\n <Switch\n checked={checked}\n aria-label={label}\n onCheckedChange={onCheckedChange}\n className=\"h-6 w-10\"\n />\n )\n}\n\nfunction formatDisplayDate(value: string, locale: string): string {\n const parsed = new Date(`${value}T00:00:00`)\n if (Number.isNaN(parsed.getTime())) return value\n return new Intl.DateTimeFormat(locale, { weekday: 'short', month: 'short', day: 'numeric', year: 'numeric' }).format(parsed)\n}\n\nexport function DateControl({\n value,\n onChange,\n ariaLabel,\n locale,\n className,\n}: {\n value: string\n onChange(next: string): void\n ariaLabel: string\n locale: string\n className?: string\n}) {\n return (\n <div\n className={cn(\n 'relative flex h-9 w-full items-center justify-between rounded-md bg-background px-3',\n CONTROL_BORDER,\n className,\n )}\n >\n <span className=\"truncate text-sm text-foreground\">{formatDisplayDate(value, locale)}</span>\n <span aria-hidden className=\"h-px w-2 shrink-0\" />\n <ChevronDown aria-hidden className=\"size-4 shrink-0 opacity-60\" />\n <Input\n type=\"date\"\n value={value}\n onChange={(event) => { if (event.target.value) onChange(event.target.value) }}\n aria-label={ariaLabel}\n className=\"absolute inset-0 h-full w-full cursor-pointer border-0 bg-transparent p-0 opacity-0 shadow-none hover:bg-transparent\"\n inputClassName=\"h-full cursor-pointer\"\n />\n </div>\n )\n}\n\nexport function TimeControl({ value, onChange, ariaLabel }: { value: string; onChange(next: string): void; ariaLabel: string }) {\n return (\n <Input\n type=\"time\"\n value={value}\n onChange={(event) => { if (event.target.value) onChange(event.target.value) }}\n aria-label={ariaLabel}\n className=\"h-9 w-32 shrink-0\"\n />\n )\n}\n\nexport function PersonChip({\n name,\n badge,\n compact,\n onRemove,\n removeLabel,\n}: {\n name: string\n badge?: React.ReactNode\n compact?: boolean\n onRemove?: () => void\n removeLabel?: string\n}) {\n return (\n <span\n className={cn(\n 'inline-flex shrink-0 items-center gap-1.5 rounded-full bg-muted pl-1 pr-2',\n compact ? 'py-0.5' : 'py-1',\n )}\n >\n <Avatar size=\"xs\" label={name} />\n <span className=\"max-w-40 truncate text-xs font-medium text-foreground\">{name}</span>\n {badge}\n {onRemove ? (\n <IconButton\n variant=\"ghost\"\n size=\"xs\"\n onClick={(event) => { event.stopPropagation(); onRemove() }}\n aria-label={removeLabel}\n className=\"size-5 shrink-0\"\n >\n <Plus aria-hidden className=\"size-3.5 rotate-45 opacity-50\" />\n </IconButton>\n ) : null}\n </span>\n )\n}\n"],
5
- "mappings": ";AAkBI,SACE,KADF;AAfJ,SAAS,aAAa,YAAY;AAClC,SAAS,UAAU;AACnB,SAAS,cAAc;AACvB,SAAS,kBAAkB;AAC3B,SAAS,aAAa;AACtB,SAAS,cAAc;AAEhB,MAAM,iBAAiB;AACvB,MAAM,eAAe;AACrB,MAAM,cAAc;AACpB,MAAM,uBACX;AAEK,SAAS,MAAM,EAAE,OAAO,UAAU,MAAM,GAAwE;AACrH,SACE,qBAAC,SAAI,WAAU,gCACb;AAAA,wBAAC,UAAK,WAAW,aAAc,iBAAM;AAAA,IACpC;AAAA,IACA,QAAQ,oBAAC,OAAE,WAAU,kCAAkC,iBAAM,IAAO;AAAA,KACvE;AAEJ;AAEO,SAAS,eAAe,EAAE,OAAO,WAAW,SAAS,GAAmF;AAC7I,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,MACF;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;AAKO,SAAS,kBAAkB,OAAuD;AACvF,MAAI,OAAO,UAAU,YAAY,oBAAoB,KAAK,KAAK,GAAG;AAChE,WAAO,EAAE,iBAAiB,GAAG,KAAK,MAAM,OAAO,sBAAsB,KAAK,eAAe;AAAA,EAC3F;AACA,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,GAAG;AACjD,WAAO;AAAA,MACL,iBAAiB,sBAAsB,KAAK;AAAA,MAC5C,OAAO,sBAAsB,KAAK;AAAA,IACpC;AAAA,EACF;AACA,SAAO,CAAC;AACV;AAEO,SAAS,aAAa,EAAE,SAAS,iBAAiB,MAAM,GAA8E;AAC3I,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,cAAY;AAAA,MACZ;AAAA,MACA,WAAU;AAAA;AAAA,EACZ;AAEJ;AAEA,SAAS,kBAAkB,OAAe,QAAwB;AAChE,QAAM,SAAS,oBAAI,KAAK,GAAG,KAAK,WAAW;AAC3C,MAAI,OAAO,MAAM,OAAO,QAAQ,CAAC,EAAG,QAAO;AAC3C,SAAO,IAAI,KAAK,eAAe,QAAQ,EAAE,SAAS,SAAS,OAAO,SAAS,KAAK,WAAW,MAAM,UAAU,CAAC,EAAE,OAAO,MAAM;AAC7H;AAEO,SAAS,YAAY;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MAEA;AAAA,4BAAC,UAAK,WAAU,oCAAoC,4BAAkB,OAAO,MAAM,GAAE;AAAA,QACrF,oBAAC,UAAK,eAAW,MAAC,WAAU,qBAAoB;AAAA,QAChD,oBAAC,eAAY,eAAW,MAAC,WAAU,8BAA6B;AAAA,QAChE;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL;AAAA,YACA,UAAU,CAAC,UAAU;AAAE,kBAAI,MAAM,OAAO,MAAO,UAAS,MAAM,OAAO,KAAK;AAAA,YAAE;AAAA,YAC5E,cAAY;AAAA,YACZ,WAAU;AAAA,YACV,gBAAe;AAAA;AAAA,QACjB;AAAA;AAAA;AAAA,EACF;AAEJ;AAEO,SAAS,YAAY,EAAE,OAAO,UAAU,UAAU,GAAuE;AAC9H,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL;AAAA,MACA,UAAU,CAAC,UAAU;AAAE,YAAI,MAAM,OAAO,MAAO,UAAS,MAAM,OAAO,KAAK;AAAA,MAAE;AAAA,MAC5E,cAAY;AAAA,MACZ,WAAU;AAAA;AAAA,EACZ;AAEJ;AAEO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAMG;AACD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,UAAU,WAAW;AAAA,MACvB;AAAA,MAEA;AAAA,4BAAC,UAAO,MAAK,MAAK,OAAO,MAAM;AAAA,QAC/B,oBAAC,UAAK,WAAU,yDAAyD,gBAAK;AAAA,QAC7E;AAAA,QACA,WACC;AAAA,UAAC;AAAA;AAAA,YACC,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS,CAAC,UAAU;AAAE,oBAAM,gBAAgB;AAAG,uBAAS;AAAA,YAAE;AAAA,YAC1D,cAAY;AAAA,YACZ,WAAU;AAAA,YAEV,8BAAC,QAAK,eAAW,MAAC,WAAU,iCAAgC;AAAA;AAAA,QAC9D,IACE;AAAA;AAAA;AAAA,EACN;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { Plus } from 'lucide-react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Avatar } from '@open-mercato/ui/primitives/avatar'\nimport { DatePicker } from '@open-mercato/ui/primitives/date-picker'\nimport { IconButton } from '@open-mercato/ui/primitives/icon-button'\nimport { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@open-mercato/ui/primitives/select'\nimport { Switch } from '@open-mercato/ui/primitives/switch'\n\nexport const CONTROL_BORDER = 'border border-input'\nexport const CONTROL_TEXT = 'text-sm text-foreground placeholder:text-muted-foreground'\nexport const LABEL_CLASS = 'text-xs font-medium text-muted-foreground'\nexport const DROPDOWN_PANEL_CLASS =\n 'absolute z-50 mt-1 max-h-56 w-full overflow-y-auto rounded-md border border-border bg-popover p-1 text-popover-foreground shadow-md'\n\n/**\n * Closes an editor dropdown on pointer-down outside the component or Escape.\n * Blur alone is unreliable inside the Radix dialog focus trap (#3552 feedback:\n * the Resources list stayed open), so dismissal listens on the document while\n * the dropdown is open. Returns the ref to attach to the component root.\n */\nexport function useDropdownDismiss(open: boolean, onClose: () => void): React.RefObject<HTMLDivElement | null> {\n const rootRef = React.useRef<HTMLDivElement | null>(null)\n React.useEffect(() => {\n if (!open) return\n const handlePointerDown = (event: PointerEvent) => {\n if (rootRef.current && event.target instanceof Node && !rootRef.current.contains(event.target)) onClose()\n }\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n // Swallow Escape so the dialog itself stays open.\n event.stopPropagation()\n onClose()\n }\n }\n document.addEventListener('pointerdown', handlePointerDown, true)\n document.addEventListener('keydown', handleKeyDown, true)\n return () => {\n document.removeEventListener('pointerdown', handlePointerDown, true)\n document.removeEventListener('keydown', handleKeyDown, true)\n }\n }, [open, onClose])\n return rootRef\n}\n\nexport function Field({ label, children, error, className }: { label: string; children: React.ReactNode; error?: string | null; className?: string }) {\n return (\n <div className={cn('flex w-full flex-col gap-1.5', className)}>\n <span className={LABEL_CLASS}>{label}</span>\n {children}\n {error ? <p className=\"text-xs text-status-error-text\">{error}</p> : null}\n </div>\n )\n}\n\nexport function UppercaseBadge({ style, className, children }: { style?: React.CSSProperties; className?: string; children: React.ReactNode }) {\n return (\n <span\n className={cn(\n 'inline-flex shrink-0 items-center justify-center rounded-full bg-muted px-2 py-0.5 text-overline font-medium uppercase text-muted-foreground',\n className,\n )}\n style={style}\n >\n {children}\n </span>\n )\n}\n\nexport function AllDayToggle({ checked, onCheckedChange, label }: { checked: boolean; onCheckedChange(next: boolean): void; label: string }) {\n return (\n <Switch\n checked={checked}\n aria-label={label}\n onCheckedChange={onCheckedChange}\n className=\"h-6 w-10\"\n />\n )\n}\n\nfunction parseDateValue(value: string): Date | null {\n if (!value) return null\n const parsed = new Date(`${value}T00:00:00`)\n return Number.isNaN(parsed.getTime()) ? null : parsed\n}\n\nfunction formatDateValue(date: Date): string {\n return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`\n}\n\n// Close the field's popover when the surrounding editor dialog scrolls, so a\n// portalled DS popover (DatePicker/Select) doesn't float over the form or drift\n// from its field (#3747 feedback). Radix ignores synthetic dismiss events, so\n// this drives the popover's controlled `open` state instead. The editor\n// dispatches `EDITOR_SCROLL_EVENT` on scroll.\nexport const EDITOR_SCROLL_EVENT = 'om-calendar-editor-scroll'\nfunction useCloseOnEditorScroll(setOpen: (open: boolean) => void) {\n React.useEffect(() => {\n const handler = () => setOpen(false)\n document.addEventListener(EDITOR_SCROLL_EVENT, handler)\n return () => document.removeEventListener(EDITOR_SCROLL_EVENT, handler)\n }, [setOpen])\n}\n\n// DS DatePicker (calendar popover) + DS Select (scrolling 30-min list) replace\n// the native `<input type=date|time>` overlays, which only opened via the\n// browser chevron and did not match the design system (#3747 feedback).\nexport function DateControl({\n value,\n onChange,\n ariaLabel,\n className,\n}: {\n value: string\n onChange(next: string): void\n ariaLabel: string\n locale: string\n className?: string\n}) {\n const [open, setOpen] = React.useState(false)\n useCloseOnEditorScroll(setOpen)\n return (\n <DatePicker\n value={parseDateValue(value)}\n onChange={(date) => { if (date) onChange(formatDateValue(date)) }}\n footer=\"none\"\n open={open}\n onOpenChange={setOpen}\n aria-label={ariaLabel}\n className={cn('w-full', className)}\n />\n )\n}\n\n// 24h times at 30-minute steps.\nconst TIME_OPTIONS: string[] = Array.from({ length: 48 }, (_, index) => {\n const hour = Math.floor(index / 2)\n const minute = index % 2 === 0 ? '00' : '30'\n return `${String(hour).padStart(2, '0')}:${minute}`\n})\n\nexport function TimeControl({ value, onChange, ariaLabel }: { value: string; onChange(next: string): void; ariaLabel: string }) {\n const [open, setOpen] = React.useState(false)\n useCloseOnEditorScroll(setOpen)\n // Keep an off-grid value (e.g. an imported 22:15) selectable.\n const options = value && !TIME_OPTIONS.includes(value) ? [value, ...TIME_OPTIONS] : TIME_OPTIONS\n return (\n <Select value={value} onValueChange={onChange} open={open} onOpenChange={setOpen}>\n <SelectTrigger aria-label={ariaLabel} className=\"h-9 w-32 shrink-0\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent className=\"max-h-64\">\n {options.map((time) => (\n <SelectItem key={time} value={time}>{time}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n}\n\nexport function PersonChip({\n name,\n badge,\n compact,\n onRemove,\n removeLabel,\n}: {\n name: string\n badge?: React.ReactNode\n compact?: boolean\n onRemove?: () => void\n removeLabel?: string\n}) {\n return (\n <span\n className={cn(\n 'inline-flex shrink-0 items-center gap-1.5 rounded-full bg-muted pl-1 pr-2',\n compact ? 'py-0.5' : 'py-1',\n )}\n >\n <Avatar size=\"xs\" label={name} />\n <span className=\"max-w-40 truncate text-xs font-medium text-foreground\">{name}</span>\n {badge}\n {onRemove ? (\n <IconButton\n variant=\"ghost\"\n size=\"xs\"\n onClick={(event) => { event.stopPropagation(); onRemove() }}\n aria-label={removeLabel}\n className=\"size-5 shrink-0\"\n >\n <Plus aria-hidden className=\"size-3.5 rotate-45 opacity-50\" />\n </IconButton>\n ) : null}\n </span>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }