@mieweb/ui 0.11.0-dev.29 → 0.11.0-dev.30

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 (116) hide show
  1. package/MIGRATION.md +67 -0
  2. package/README.md +10 -0
  3. package/dist/EncounterMdyEditor-3SQY4MK2.js +286 -0
  4. package/dist/EncounterMdyEditor-3SQY4MK2.js.map +1 -0
  5. package/dist/EncounterMdyEditor-73PTTIRY.cjs +288 -0
  6. package/dist/EncounterMdyEditor-73PTTIRY.cjs.map +1 -0
  7. package/dist/chunk-25MPUVTM.js +339 -0
  8. package/dist/chunk-25MPUVTM.js.map +1 -0
  9. package/dist/{chunk-2VISTX7O.js → chunk-3NOVISM6.js} +3 -3
  10. package/dist/{chunk-2VISTX7O.js.map → chunk-3NOVISM6.js.map} +1 -1
  11. package/dist/{chunk-6GKIISNZ.cjs → chunk-3Y3ZCDNF.cjs} +176 -63
  12. package/dist/chunk-3Y3ZCDNF.cjs.map +1 -0
  13. package/dist/{chunk-AK4LHJPC.js → chunk-4JXO5JTE.js} +84 -3
  14. package/dist/chunk-4JXO5JTE.js.map +1 -0
  15. package/dist/chunk-4NB4WOGU.cjs +666 -0
  16. package/dist/chunk-4NB4WOGU.cjs.map +1 -0
  17. package/dist/{chunk-434L5RYF.js → chunk-4P27Y52M.js} +3 -3
  18. package/dist/{chunk-434L5RYF.js.map → chunk-4P27Y52M.js.map} +1 -1
  19. package/dist/{chunk-PRA2V5BP.cjs → chunk-5KGYTNOA.cjs} +7 -7
  20. package/dist/{chunk-PRA2V5BP.cjs.map → chunk-5KGYTNOA.cjs.map} +1 -1
  21. package/dist/chunk-5LNLB2XT.js +634 -0
  22. package/dist/chunk-5LNLB2XT.js.map +1 -0
  23. package/dist/chunk-5YOT4DVH.js +31 -0
  24. package/dist/chunk-5YOT4DVH.js.map +1 -0
  25. package/dist/chunk-7HFZL5V5.js +2425 -0
  26. package/dist/chunk-7HFZL5V5.js.map +1 -0
  27. package/dist/{chunk-XZNHS7YQ.cjs → chunk-7XC6E4BT.cjs} +3 -3
  28. package/dist/{chunk-XZNHS7YQ.cjs.map → chunk-7XC6E4BT.cjs.map} +1 -1
  29. package/dist/{chunk-JM4YFLO4.js → chunk-AIJYZP7M.js} +3 -3
  30. package/dist/{chunk-JM4YFLO4.js.map → chunk-AIJYZP7M.js.map} +1 -1
  31. package/dist/chunk-BBLEGF5L.js +66 -0
  32. package/dist/chunk-BBLEGF5L.js.map +1 -0
  33. package/dist/{chunk-MBDT2MGR.js → chunk-BIQIE3VW.js} +4 -4
  34. package/dist/{chunk-MBDT2MGR.js.map → chunk-BIQIE3VW.js.map} +1 -1
  35. package/dist/chunk-FSLI5IKP.cjs +54 -0
  36. package/dist/chunk-FSLI5IKP.cjs.map +1 -0
  37. package/dist/chunk-GADJASST.js +3 -0
  38. package/dist/chunk-GADJASST.js.map +1 -0
  39. package/dist/chunk-IXUIOR4K.cjs +2457 -0
  40. package/dist/chunk-IXUIOR4K.cjs.map +1 -0
  41. package/dist/chunk-K2BY7YPJ.cjs +4 -0
  42. package/dist/chunk-K2BY7YPJ.cjs.map +1 -0
  43. package/dist/{chunk-H3P7POFC.cjs → chunk-MA3Y6AYA.cjs} +3 -3
  44. package/dist/{chunk-H3P7POFC.cjs.map → chunk-MA3Y6AYA.cjs.map} +1 -1
  45. package/dist/chunk-QO4A6U73.cjs +342 -0
  46. package/dist/chunk-QO4A6U73.cjs.map +1 -0
  47. package/dist/{chunk-43QBEF6N.js → chunk-QR7B6UPR.js} +176 -63
  48. package/dist/chunk-QR7B6UPR.js.map +1 -0
  49. package/dist/chunk-SMPLM6K3.js +4107 -0
  50. package/dist/chunk-SMPLM6K3.js.map +1 -0
  51. package/dist/chunk-SV6YGK4U.cjs +88 -0
  52. package/dist/chunk-SV6YGK4U.cjs.map +1 -0
  53. package/dist/{chunk-MQ5WJ3NO.js → chunk-UDUYZDTA.js} +14 -72
  54. package/dist/chunk-UDUYZDTA.js.map +1 -0
  55. package/dist/{chunk-ZLBNYCYT.cjs → chunk-XPVQI3MM.cjs} +84 -3
  56. package/dist/chunk-XPVQI3MM.cjs.map +1 -0
  57. package/dist/{chunk-AQUHEKXL.cjs → chunk-XQHDQ64G.cjs} +4 -4
  58. package/dist/{chunk-AQUHEKXL.cjs.map → chunk-XQHDQ64G.cjs.map} +1 -1
  59. package/dist/chunk-Z2OOJGHL.cjs +4155 -0
  60. package/dist/chunk-Z2OOJGHL.cjs.map +1 -0
  61. package/dist/{chunk-QKW2QCGI.cjs → chunk-ZK2R6UGF.cjs} +13 -72
  62. package/dist/chunk-ZK2R6UGF.cjs.map +1 -0
  63. package/dist/components/ActionPlan/index.cjs +4 -4
  64. package/dist/components/ActionPlan/index.js +2 -2
  65. package/dist/components/AlertDialog/index.d.cts +1 -1
  66. package/dist/components/AlertDialog/index.d.ts +1 -1
  67. package/dist/components/EmailEditor/index.cjs +13 -12
  68. package/dist/components/EmailEditor/index.js +4 -3
  69. package/dist/components/MediaEditor/index.cjs +7 -7
  70. package/dist/components/MediaEditor/index.js +2 -2
  71. package/dist/components/RichTextEditor/index.cjs +5 -4
  72. package/dist/components/RichTextEditor/index.d.cts +34 -0
  73. package/dist/components/RichTextEditor/index.d.ts +34 -0
  74. package/dist/components/RichTextEditor/index.js +2 -1
  75. package/dist/components/SuperChat/index.cjs +10 -9
  76. package/dist/components/SuperChat/index.cjs.map +1 -1
  77. package/dist/components/SuperChat/index.js +2 -1
  78. package/dist/components/SuperChat/index.js.map +1 -1
  79. package/dist/components/Textarea/index.cjs +6 -5
  80. package/dist/components/Textarea/index.js +3 -2
  81. package/dist/esheet.cjs +4237 -25
  82. package/dist/esheet.cjs.map +1 -1
  83. package/dist/esheet.d.cts +753 -6
  84. package/dist/esheet.d.ts +753 -6
  85. package/dist/esheet.js +4099 -21
  86. package/dist/esheet.js.map +1 -1
  87. package/dist/index.cjs +204 -3072
  88. package/dist/index.cjs.map +1 -1
  89. package/dist/index.d.cts +71 -9
  90. package/dist/index.d.ts +71 -9
  91. package/dist/index.js +38 -2968
  92. package/dist/index.js.map +1 -1
  93. package/dist/kerebron.cjs +7 -268
  94. package/dist/kerebron.cjs.map +1 -1
  95. package/dist/kerebron.d.cts +7 -1
  96. package/dist/kerebron.d.ts +7 -1
  97. package/dist/kerebron.js +6 -270
  98. package/dist/kerebron.js.map +1 -1
  99. package/dist/model-WFFZRQMY.js +18 -0
  100. package/dist/model-WFFZRQMY.js.map +1 -0
  101. package/dist/model-Y7WRIGGA.cjs +79 -0
  102. package/dist/model-Y7WRIGGA.cjs.map +1 -0
  103. package/dist/styles.css +1 -1
  104. package/dist/tailwind-preset.cjs +4 -4
  105. package/dist/tailwind-preset.js +1 -1
  106. package/package.json +30 -9
  107. package/dist/chunk-43QBEF6N.js.map +0 -1
  108. package/dist/chunk-6GKIISNZ.cjs.map +0 -1
  109. package/dist/chunk-AK4LHJPC.js.map +0 -1
  110. package/dist/chunk-MQ5WJ3NO.js.map +0 -1
  111. package/dist/chunk-QKW2QCGI.cjs.map +0 -1
  112. package/dist/chunk-SFSBYC46.js +0 -1596
  113. package/dist/chunk-SFSBYC46.js.map +0 -1
  114. package/dist/chunk-ZIJMLRI3.cjs +0 -1633
  115. package/dist/chunk-ZIJMLRI3.cjs.map +0 -1
  116. package/dist/chunk-ZLBNYCYT.cjs.map +0 -1
@@ -0,0 +1,4107 @@
1
+ import { CodingChips, RowActionToolbar, RowIconButton, currentAssertion } from './chunk-5LNLB2XT.js';
2
+ import { Tooltip } from './chunk-YFWDS6MX.js';
3
+ import { RadioGroup, Radio } from './chunk-EPSB4WTL.js';
4
+ import { Label } from './chunk-I3WGZPI6.js';
5
+ import { DateInput } from './chunk-I6Y2KLTZ.js';
6
+ import { Textarea } from './chunk-3NOVISM6.js';
7
+ import { useMessages } from './chunk-5YOT4DVH.js';
8
+ import { Select } from './chunk-VCI2Y7ST.js';
9
+ import { Dropdown, DropdownItem, DropdownSeparator } from './chunk-OYUIEJPN.js';
10
+ import { Card, CardHeader, CardContent } from './chunk-QYSUYB3S.js';
11
+ import { Checkbox } from './chunk-753Z3NXL.js';
12
+ import { Modal, ModalHeader, ModalTitle, ModalClose, ModalBody, ModalFooter } from './chunk-TWQSMUE6.js';
13
+ import { Input } from './chunk-3IMM2PM4.js';
14
+ import { useDragReorder, reorderIds, dragIndicatorClasses } from './chunk-YUMOM3KE.js';
15
+ import { useLiveAnnouncement } from './chunk-HNXLLDPN.js';
16
+ import { useFocusTrap } from './chunk-4SMSH4OY.js';
17
+ import { useEscapeKey } from './chunk-T4ME7QCT.js';
18
+ import { useClickOutside } from './chunk-62ZOA6PX.js';
19
+ import { useAnchoredPosition } from './chunk-WSNMQQ4P.js';
20
+ import { dateToDisplayFormat, isValidDate, displayFormatToDateString } from './chunk-RC2YMOMS.js';
21
+ import { isStorybookDocsMode } from './chunk-VSQF22GL.js';
22
+ import { AlertCircle, Plus, RefreshCw, Pencil, GripVertical, Trash2, StickyNote, Ban, ChevronUp, ChevronDown, Link, MoreVertical, Send, Stethoscope, Scan, TestTube2, Pill, CalendarCheck, ExternalLink, ThumbsUp, HandMetal, ThumbsDown, HelpCircle, AlertTriangle } from './chunk-BKSPDTX4.js';
23
+ import { isRtl } from './chunk-YVKBXNPK.js';
24
+ import { Badge } from './chunk-522WJIVI.js';
25
+ import { Button } from './chunk-WFBUBT54.js';
26
+ import { cn } from './chunk-TQONQSJO.js';
27
+ import * as React4 from 'react';
28
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
29
+ import { createPortal } from 'react-dom';
30
+ import { Calendar, ChevronLeft, ChevronRight, ChevronDown as ChevronDown$1 } from 'lucide-react';
31
+
32
+ var TYPE_ORDER = ["drug", "food", "environmental", "other"];
33
+ var ALLERGY_TYPE_LABELS = {
34
+ drug: "Drug",
35
+ food: "Food",
36
+ environmental: "Environmental",
37
+ other: "Other"
38
+ };
39
+ var SEVERITY_BADGE = {
40
+ severe: "danger",
41
+ moderate: "warning",
42
+ mild: "secondary"
43
+ };
44
+ var DEFAULT_ACTIONS = ["correct", "note", "remove"];
45
+ var ACTION_META = {
46
+ correct: { label: "Correct", icon: Pencil },
47
+ note: { label: "Notes", icon: StickyNote },
48
+ remove: { label: "Remove", icon: Ban }
49
+ };
50
+ function AllergyRow({
51
+ allergy,
52
+ actions,
53
+ readOnly,
54
+ drag,
55
+ onMove,
56
+ onAction
57
+ }) {
58
+ const m = useMessages();
59
+ const handleRowKeyDown = (e) => {
60
+ if (e.target !== e.currentTarget) return;
61
+ if (e.altKey && (e.key === "ArrowUp" || e.key === "ArrowDown")) {
62
+ e.preventDefault();
63
+ onMove?.(allergy, e.key === "ArrowUp" ? -1 : 1);
64
+ } else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
65
+ e.preventDefault();
66
+ const list = e.currentTarget.closest("ul");
67
+ if (!list) return;
68
+ const rows = Array.from(
69
+ list.querySelectorAll("li[data-allergy-id]")
70
+ );
71
+ const i = rows.indexOf(e.currentTarget);
72
+ rows[e.key === "ArrowUp" ? Math.max(0, i - 1) : Math.min(rows.length - 1, i + 1)]?.focus();
73
+ }
74
+ };
75
+ return (
76
+ // Rows are focus stops when reordering is enabled so drag & drop has a
77
+ // keyboard equivalent (Alt+↑/↓) — 508.
78
+ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */
79
+ /* @__PURE__ */ jsxs(
80
+ "li",
81
+ {
82
+ "data-slot": "clinical-list-row",
83
+ "data-allergy-id": allergy.id,
84
+ tabIndex: drag.enabled ? 0 : void 0,
85
+ onKeyDown: drag.enabled ? handleRowKeyDown : void 0,
86
+ "aria-label": drag.enabled ? m("{name}. Alt plus arrow keys to reorder.", {
87
+ name: allergy.allergen
88
+ }) : void 0,
89
+ ...drag.rowProps(allergy.id),
90
+ className: cn(
91
+ "group border-border/60 relative flex min-h-11 flex-wrap items-center gap-x-2 gap-y-1 border-b px-1 py-2",
92
+ "focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
93
+ drag.enabled && "cursor-grab active:cursor-grabbing",
94
+ dragIndicatorClasses(drag, allergy.id)
95
+ ),
96
+ children: [
97
+ drag.enabled ? /* @__PURE__ */ jsx(
98
+ GripVertical,
99
+ {
100
+ size: 14,
101
+ "aria-hidden": true,
102
+ className: "text-muted-foreground/50 shrink-0"
103
+ }
104
+ ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 select-none", children: "\u2022" }),
105
+ /* @__PURE__ */ jsx(
106
+ "span",
107
+ {
108
+ className: cn(
109
+ "text-foreground font-medium",
110
+ allergy.inactive && "text-muted-foreground line-through"
111
+ ),
112
+ children: allergy.allergen
113
+ }
114
+ ),
115
+ allergy.code && /* @__PURE__ */ jsxs(
116
+ "span",
117
+ {
118
+ className: "text-muted-foreground/70 text-xs",
119
+ title: allergy.code.display,
120
+ children: [
121
+ allergy.code.system,
122
+ " ",
123
+ allergy.code.code
124
+ ]
125
+ }
126
+ ),
127
+ allergy.severity && /* @__PURE__ */ jsx(Badge, { variant: SEVERITY_BADGE[allergy.severity], size: "sm", children: m(allergy.severity) }),
128
+ allergy.kind === "intolerance" && /* @__PURE__ */ jsx(Badge, { variant: "secondary", size: "sm", children: m("intolerance") }),
129
+ allergy.reaction && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-sm", children: [
130
+ "\u2014 ",
131
+ allergy.reaction
132
+ ] }),
133
+ allergy.onsetDate && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: m("since {date}", { date: allergy.onsetDate }) }),
134
+ allergy.inactive && /* @__PURE__ */ jsx(Badge, { variant: "secondary", size: "sm", children: m("inactive") }),
135
+ allergy.note && /* @__PURE__ */ jsxs("span", { className: "flex w-full basis-full items-center gap-1.5 ps-4", children: [
136
+ /* @__PURE__ */ jsx(StickyNote, { size: 12, className: "text-muted-foreground" }),
137
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: allergy.note })
138
+ ] }),
139
+ !readOnly && actions.length > 0 && /* @__PURE__ */ jsx(
140
+ RowActionToolbar,
141
+ {
142
+ label: m("Actions for {name}", { name: allergy.allergen }),
143
+ children: actions.map((action) => /* @__PURE__ */ jsx(
144
+ RowIconButton,
145
+ {
146
+ label: m(ACTION_META[action].label),
147
+ icon: ACTION_META[action].icon,
148
+ onClick: () => onAction?.(allergy, action)
149
+ },
150
+ action
151
+ ))
152
+ }
153
+ )
154
+ ]
155
+ }
156
+ )
157
+ );
158
+ }
159
+ var AllergyList = React4.forwardRef(
160
+ ({
161
+ allergies,
162
+ noKnownAllergies,
163
+ onNoKnownAllergiesChange,
164
+ title = "Allergies",
165
+ actions = DEFAULT_ACTIONS,
166
+ onAction,
167
+ onReorder,
168
+ addSearch,
169
+ onAdd,
170
+ readOnly = false,
171
+ className,
172
+ "data-testid": dataTestId,
173
+ ...props
174
+ }, ref) => {
175
+ const m = useMessages();
176
+ const [announcement, setAnnouncement] = useLiveAnnouncement();
177
+ const groups = TYPE_ORDER.map((type) => ({
178
+ type,
179
+ label: ALLERGY_TYPE_LABELS[type],
180
+ items: allergies.filter((a) => (a.type ?? "other") === type)
181
+ })).filter((g) => g.items.length > 0);
182
+ const empty = allergies.length === 0;
183
+ const typeOf = React4.useCallback(
184
+ (id) => allergies.find((a) => a.id === id)?.type ?? "other",
185
+ [allergies]
186
+ );
187
+ const drag = useDragReorder({
188
+ ids: allergies.map((a) => a.id),
189
+ onReorder: readOnly || !onReorder ? void 0 : (ids) => {
190
+ setAnnouncement(m("Allergy list reordered"));
191
+ onReorder(ids);
192
+ },
193
+ canDropOn: (dragged, target) => typeOf(dragged) === typeOf(target)
194
+ });
195
+ const moveAllergy = React4.useCallback(
196
+ (allergy, dir) => {
197
+ if (readOnly || !onReorder) return;
198
+ const ids = allergies.map((a) => a.id);
199
+ const from = ids.indexOf(allergy.id);
200
+ let to = from + dir;
201
+ while (to >= 0 && to < allergies.length && (allergies[to].type ?? "other") !== (allergy.type ?? "other")) {
202
+ to += dir;
203
+ }
204
+ if (to < 0 || to >= allergies.length) return;
205
+ [ids[from], ids[to]] = [ids[to], ids[from]];
206
+ setAnnouncement(
207
+ m(dir === -1 ? "{name} moved up" : "{name} moved down", {
208
+ name: allergy.allergen
209
+ })
210
+ );
211
+ onReorder(ids);
212
+ },
213
+ [allergies, m, onReorder, readOnly, setAnnouncement]
214
+ );
215
+ return /* @__PURE__ */ jsxs(
216
+ Card,
217
+ {
218
+ ref,
219
+ padding: "none",
220
+ className: cn("w-full", className),
221
+ "data-testid": dataTestId,
222
+ ...props,
223
+ children: [
224
+ title !== null && /* @__PURE__ */ jsx(CardHeader, { className: "border-border bg-muted/50 border-b px-4 py-3", children: /* @__PURE__ */ jsxs("h3", { className: "text-foreground flex items-center gap-2 text-sm font-semibold tracking-wide uppercase", children: [
225
+ /* @__PURE__ */ jsx(
226
+ AlertCircle,
227
+ {
228
+ size: 16,
229
+ className: "text-red-600 dark:text-red-400"
230
+ }
231
+ ),
232
+ m(title)
233
+ ] }) }),
234
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-5 px-4 py-4", children: [
235
+ empty ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
236
+ noKnownAllergies ? /* @__PURE__ */ jsxs("p", { className: "text-foreground flex items-center gap-1.5 text-sm", children: [
237
+ /* @__PURE__ */ jsx(Badge, { variant: "success", size: "sm", children: "\u2713" }),
238
+ m("No known allergies (NKA)")
239
+ ] }) : /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: m("Allergy status not recorded.") }),
240
+ !readOnly && onNoKnownAllergiesChange && /* @__PURE__ */ jsx(
241
+ Button,
242
+ {
243
+ variant: noKnownAllergies ? "secondary" : "outline",
244
+ size: "sm",
245
+ onClick: () => onNoKnownAllergiesChange(!noKnownAllergies),
246
+ children: noKnownAllergies ? m("Clear no-known-allergies") : m("Confirm: no known allergies")
247
+ }
248
+ )
249
+ ] }) : groups.map(({ type, label, items }) => /* @__PURE__ */ jsxs("section", { "aria-label": m(label), children: [
250
+ /* @__PURE__ */ jsx(
251
+ "h4",
252
+ {
253
+ "data-slot": "clinical-list-heading",
254
+ className: "border-border text-foreground border-b pb-1 text-sm font-semibold tracking-wide uppercase",
255
+ children: m(label)
256
+ }
257
+ ),
258
+ /* @__PURE__ */ jsx("ul", { className: "mt-1", children: items.map((allergy) => /* @__PURE__ */ jsx(
259
+ AllergyRow,
260
+ {
261
+ allergy,
262
+ actions,
263
+ readOnly,
264
+ drag,
265
+ onMove: moveAllergy,
266
+ onAction
267
+ },
268
+ allergy.id
269
+ )) })
270
+ ] }, type)),
271
+ !readOnly && (addSearch || onAdd) && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
272
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs font-semibold tracking-wide uppercase", children: m("Add allergy") }),
273
+ addSearch,
274
+ onAdd && /* @__PURE__ */ jsx(
275
+ Button,
276
+ {
277
+ variant: "outline",
278
+ size: "sm",
279
+ onClick: onAdd,
280
+ leftIcon: /* @__PURE__ */ jsx(Plus, { size: 12 }),
281
+ className: "h-auto rounded-full px-2.5 py-1 text-xs",
282
+ children: m("Add allergy\u2026")
283
+ }
284
+ )
285
+ ] })
286
+ ] }),
287
+ /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "sr-only", children: announcement })
288
+ ]
289
+ }
290
+ );
291
+ }
292
+ );
293
+ AllergyList.displayName = "AllergyList";
294
+ var CodeLookupContext = React4.createContext(null);
295
+ function CodeLookupProvider({
296
+ component,
297
+ indexUrl = "/codify",
298
+ locale = "en",
299
+ memory,
300
+ children
301
+ }) {
302
+ const value = React4.useMemo(
303
+ () => ({ component, indexUrl, locale, memory }),
304
+ [component, indexUrl, locale, memory]
305
+ );
306
+ return /* @__PURE__ */ jsx(CodeLookupContext.Provider, { value, children });
307
+ }
308
+ function useCodeLookupConfig() {
309
+ return React4.useContext(CodeLookupContext);
310
+ }
311
+ function newId() {
312
+ return `alg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
313
+ }
314
+ var TYPE_OPTIONS = [
315
+ { value: "drug", label: "Drug" },
316
+ { value: "food", label: "Food" },
317
+ { value: "environmental", label: "Environmental" },
318
+ { value: "other", label: "Other" }
319
+ ];
320
+ function AllergyEditor({
321
+ allergy,
322
+ codeLookup,
323
+ requireCode = false,
324
+ onClose,
325
+ onSave
326
+ }) {
327
+ const m = useMessages();
328
+ const [draft, setDraft] = React4.useState(
329
+ () => allergy ?? { id: newId(), allergen: "", type: "drug" }
330
+ );
331
+ const patch = (p) => setDraft((prev) => ({ ...prev, ...p }));
332
+ const bodyRef = React4.useRef(null);
333
+ React4.useEffect(() => {
334
+ bodyRef.current?.querySelector("input")?.focus();
335
+ }, []);
336
+ const hasCode = Boolean(draft.code?.system.trim() && draft.code.code.trim());
337
+ const unchangedLegacyAllergen = allergy && !allergy.code && draft.allergen === allergy.allergen && (draft.type ?? "drug") === (allergy.type ?? "drug");
338
+ const canSave = draft.allergen.trim().length > 0 && (!requireCode || hasCode || Boolean(unchangedLegacyAllergen));
339
+ const isDrug = (draft.type ?? "drug") === "drug";
340
+ return /* @__PURE__ */ jsxs(Modal, { open: true, onOpenChange: (o) => !o && onClose(), size: "md", children: [
341
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
342
+ /* @__PURE__ */ jsx(ModalTitle, { children: allergy ? m("Correct Allergy") : m("Add Allergy") }),
343
+ /* @__PURE__ */ jsx(ModalClose, { "aria-label": m("Close") })
344
+ ] }),
345
+ /* @__PURE__ */ jsx(ModalBody, { className: "space-y-4", children: /* @__PURE__ */ jsxs("div", { ref: bodyRef, className: "contents", children: [
346
+ /* @__PURE__ */ jsxs(
347
+ RadioGroup,
348
+ {
349
+ name: "alg-kind",
350
+ label: m("Mechanism"),
351
+ description: m(
352
+ "A true immune-mediated allergy, or a non-immune intolerance (e.g. GI upset)"
353
+ ),
354
+ value: draft.kind ?? "allergy",
355
+ onValueChange: (v) => patch({ kind: v }),
356
+ orientation: "horizontal",
357
+ size: "sm",
358
+ children: [
359
+ /* @__PURE__ */ jsx(Radio, { value: "allergy", label: m("Allergy") }),
360
+ /* @__PURE__ */ jsx(Radio, { value: "intolerance", label: m("Intolerance") })
361
+ ]
362
+ }
363
+ ),
364
+ /* @__PURE__ */ jsx(
365
+ Select,
366
+ {
367
+ label: m("Type"),
368
+ options: TYPE_OPTIONS.map((option) => ({
369
+ ...option,
370
+ label: m(option.label)
371
+ })),
372
+ value: draft.type ?? "drug",
373
+ onValueChange: (v) => {
374
+ const type = v;
375
+ patch({
376
+ type,
377
+ ...type !== (draft.type ?? "drug") && { code: void 0 }
378
+ });
379
+ }
380
+ }
381
+ ),
382
+ codeLookup && (isDrug || requireCode) ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
383
+ /* @__PURE__ */ jsx(Label, { htmlFor: "alg-search", children: m("Allergen") }),
384
+ /* @__PURE__ */ jsx(
385
+ codeLookup.component,
386
+ {
387
+ indexUrl: codeLookup.indexUrl,
388
+ locale: codeLookup.locale,
389
+ domains: isDrug ? ["med"] : ["condition"],
390
+ searchCodetypes: isDrug ? void 0 : ["SNOMED US"],
391
+ bare: true,
392
+ clearOnSelect: false,
393
+ placeholder: isDrug ? m("Search drug allergens \u2014 e.g. penicillin") : m("Search SNOMED allergen substances"),
394
+ initialQuery: allergy?.allergen || void 0,
395
+ initialSearch: allergy ? !allergy.code : void 0,
396
+ onSelect: (result) => {
397
+ if (requireCode && (!result.codetype.trim() || !result.fullcode.trim()))
398
+ return;
399
+ patch({
400
+ allergen: result.label,
401
+ code: {
402
+ system: result.codetype,
403
+ code: result.fullcode,
404
+ display: result.label
405
+ }
406
+ });
407
+ },
408
+ onFreeText: requireCode ? void 0 : (text) => patch({ allergen: text, code: void 0 })
409
+ }
410
+ ),
411
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: draft.code ? m("Coded: {system} {code}", {
412
+ system: draft.code.system,
413
+ code: draft.code.code
414
+ }) : draft.allergen ? m('Uncoded free text: "{name}"', {
415
+ name: draft.allergen
416
+ }) : requireCode ? m("Pick a catalog result to code the allergen") : m(
417
+ "Pick a result to code the allergen, or press Enter for free text"
418
+ ) })
419
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
420
+ /* @__PURE__ */ jsx(Label, { htmlFor: "alg-name", children: m("Allergen") }),
421
+ /* @__PURE__ */ jsx(
422
+ Input,
423
+ {
424
+ id: "alg-name",
425
+ value: draft.allergen,
426
+ readOnly: requireCode,
427
+ onChange: (e) => patch({ allergen: e.target.value, code: void 0 })
428
+ }
429
+ )
430
+ ] }),
431
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
432
+ /* @__PURE__ */ jsx(Label, { htmlFor: "alg-reaction", children: m("Reaction") }),
433
+ /* @__PURE__ */ jsx(
434
+ Input,
435
+ {
436
+ id: "alg-reaction",
437
+ value: draft.reaction ?? "",
438
+ onChange: (e) => patch({ reaction: e.target.value })
439
+ }
440
+ )
441
+ ] }),
442
+ /* @__PURE__ */ jsxs(
443
+ RadioGroup,
444
+ {
445
+ name: "alg-severity",
446
+ label: m("Severity"),
447
+ value: draft.severity ?? "",
448
+ onValueChange: (v) => patch({ severity: v || void 0 }),
449
+ orientation: "horizontal",
450
+ size: "sm",
451
+ children: [
452
+ /* @__PURE__ */ jsx(Radio, { value: "mild", label: m("Mild") }),
453
+ /* @__PURE__ */ jsx(Radio, { value: "moderate", label: m("Moderate") }),
454
+ /* @__PURE__ */ jsx(Radio, { value: "severe", label: m("Severe") })
455
+ ]
456
+ }
457
+ ),
458
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
459
+ /* @__PURE__ */ jsx(Label, { htmlFor: "alg-onset", children: m("Onset date") }),
460
+ /* @__PURE__ */ jsx(
461
+ DateInput,
462
+ {
463
+ id: "alg-onset",
464
+ value: draft.onsetDate ?? "",
465
+ onChange: (v) => patch({ onsetDate: v })
466
+ }
467
+ )
468
+ ] }),
469
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
470
+ /* @__PURE__ */ jsx(Label, { htmlFor: "alg-note", children: m("Note") }),
471
+ /* @__PURE__ */ jsx(
472
+ Textarea,
473
+ {
474
+ id: "alg-note",
475
+ value: draft.note ?? "",
476
+ onChange: (e) => patch({ note: e.target.value }),
477
+ rows: 2
478
+ }
479
+ )
480
+ ] })
481
+ ] }) }),
482
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
483
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: m("Cancel") }),
484
+ /* @__PURE__ */ jsx(
485
+ Button,
486
+ {
487
+ onClick: () => {
488
+ onSave({ ...draft, allergen: draft.allergen.trim() });
489
+ onClose();
490
+ },
491
+ disabled: !canSave,
492
+ children: m("Save")
493
+ }
494
+ )
495
+ ] })
496
+ ] });
497
+ }
498
+ function AllergyManager({
499
+ allergies: controlled,
500
+ defaultAllergies,
501
+ onChange,
502
+ noKnownAllergies,
503
+ onNoKnownAllergiesChange,
504
+ title = "Allergies",
505
+ codeLookup,
506
+ requireCode = false,
507
+ inlineAddSearch = false,
508
+ readOnly = false,
509
+ className,
510
+ "data-testid": dataTestId
511
+ }) {
512
+ const m = useMessages();
513
+ const isControlled = controlled !== void 0;
514
+ const [internal, setInternal] = React4.useState(
515
+ defaultAllergies ?? []
516
+ );
517
+ const allergies = isControlled ? controlled : internal;
518
+ const ambientCodeLookup = useCodeLookupConfig();
519
+ const effectiveCodeLookup = codeLookup === false ? void 0 : codeLookup ?? ambientCodeLookup ?? void 0;
520
+ const [dialog, setDialog] = React4.useState(null);
521
+ const [noteDraft, setNoteDraft] = React4.useState("");
522
+ const commit = (next) => {
523
+ if (!isControlled) setInternal(next);
524
+ if (next.length > 0 && noKnownAllergies) {
525
+ onNoKnownAllergiesChange?.(false);
526
+ }
527
+ onChange?.(next);
528
+ };
529
+ const handleAction = (allergy, action) => {
530
+ switch (action) {
531
+ case "remove":
532
+ commit(allergies.filter((a) => a.id !== allergy.id));
533
+ break;
534
+ case "correct":
535
+ setDialog({ kind: "correct", allergy });
536
+ break;
537
+ case "note":
538
+ setNoteDraft(allergy.note ?? "");
539
+ setDialog({ kind: "note", allergy });
540
+ break;
541
+ }
542
+ };
543
+ const addSearch = inlineAddSearch && effectiveCodeLookup && !readOnly ? /* @__PURE__ */ jsx(
544
+ effectiveCodeLookup.component,
545
+ {
546
+ indexUrl: effectiveCodeLookup.indexUrl,
547
+ locale: effectiveCodeLookup.locale,
548
+ domains: ["med"],
549
+ bare: true,
550
+ clearOnSelect: true,
551
+ placeholder: m(
552
+ "Search drug allergens to add \u2014 use Add allergy\u2026 for food/environmental"
553
+ ),
554
+ onSelect: (result) => {
555
+ if (requireCode && (!result.codetype.trim() || !result.fullcode.trim()))
556
+ return;
557
+ commit([
558
+ ...allergies,
559
+ {
560
+ id: newId(),
561
+ allergen: result.label,
562
+ type: "drug",
563
+ code: {
564
+ system: result.codetype,
565
+ code: result.fullcode,
566
+ display: result.label
567
+ }
568
+ }
569
+ ]);
570
+ },
571
+ onFreeText: requireCode ? void 0 : (text) => (
572
+ // this search bar is explicitly for drug allergens — free-text
573
+ // entries carry type 'drug' like the coded path
574
+ commit([
575
+ ...allergies,
576
+ { id: newId(), allergen: text, type: "drug" }
577
+ ])
578
+ )
579
+ }
580
+ ) : void 0;
581
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
582
+ /* @__PURE__ */ jsx(
583
+ AllergyList,
584
+ {
585
+ allergies,
586
+ noKnownAllergies,
587
+ onNoKnownAllergiesChange,
588
+ title,
589
+ onAction: handleAction,
590
+ onReorder: (ids) => commit(
591
+ [...allergies].sort((a, b) => ids.indexOf(a.id) - ids.indexOf(b.id))
592
+ ),
593
+ addSearch,
594
+ onAdd: requireCode && !effectiveCodeLookup ? void 0 : () => setDialog({ kind: "add" }),
595
+ readOnly,
596
+ className,
597
+ "data-testid": dataTestId
598
+ }
599
+ ),
600
+ dialog && (dialog.kind === "correct" || dialog.kind === "add") && /* @__PURE__ */ jsx(
601
+ AllergyEditor,
602
+ {
603
+ allergy: dialog.kind === "correct" ? dialog.allergy : void 0,
604
+ codeLookup: effectiveCodeLookup,
605
+ requireCode,
606
+ onClose: () => setDialog(null),
607
+ onSave: (saved) => {
608
+ if (dialog.kind === "correct") {
609
+ commit(allergies.map((a) => a.id === saved.id ? saved : a));
610
+ } else {
611
+ commit([...allergies, saved]);
612
+ }
613
+ }
614
+ },
615
+ dialog.kind === "correct" ? dialog.allergy.id : "add"
616
+ ),
617
+ dialog?.kind === "note" && /* @__PURE__ */ jsxs(Modal, { open: true, onOpenChange: (o) => !o && setDialog(null), size: "md", children: [
618
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
619
+ /* @__PURE__ */ jsx(ModalTitle, { children: m("Allergy Notes") }),
620
+ /* @__PURE__ */ jsx(ModalClose, { "aria-label": m("Close") })
621
+ ] }),
622
+ /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-4", children: [
623
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: dialog.allergy.allergen }),
624
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
625
+ /* @__PURE__ */ jsx(Label, { htmlFor: "alg-note-dlg", children: m("Note") }),
626
+ /* @__PURE__ */ jsx(
627
+ Textarea,
628
+ {
629
+ id: "alg-note-dlg",
630
+ value: noteDraft,
631
+ onChange: (e) => setNoteDraft(e.target.value),
632
+ rows: 4
633
+ }
634
+ )
635
+ ] })
636
+ ] }),
637
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
638
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => setDialog(null), children: m("Cancel") }),
639
+ /* @__PURE__ */ jsx(
640
+ Button,
641
+ {
642
+ onClick: () => {
643
+ commit(
644
+ allergies.map(
645
+ (a) => a.id === dialog.allergy.id ? { ...a, note: noteDraft.trim() || void 0 } : a
646
+ )
647
+ );
648
+ setDialog(null);
649
+ },
650
+ children: m("Save")
651
+ }
652
+ )
653
+ ] })
654
+ ] })
655
+ ] });
656
+ }
657
+ function orderTypeForCodetype(codetype) {
658
+ const ct = codetype.toUpperCase();
659
+ if (["RXNORM", "FDB", "FDB MEDNAME", "NDC", "CVX"].includes(ct)) {
660
+ return "medication";
661
+ }
662
+ if (ct.startsWith("LOINC") || ct.endsWith("ORDER")) return "lab";
663
+ return "procedure";
664
+ }
665
+ function contextOrderSearch(ctx) {
666
+ const ContextOrderSearch = ({
667
+ domains,
668
+ preferDomains,
669
+ preferCodetypes,
670
+ billableOnly,
671
+ placeholder,
672
+ onPick,
673
+ onFreeText
674
+ }) => {
675
+ const Lookup = ctx.component;
676
+ return /* @__PURE__ */ jsx(
677
+ Lookup,
678
+ {
679
+ indexUrl: ctx.indexUrl,
680
+ locale: ctx.locale,
681
+ domains,
682
+ preferDomains,
683
+ preferCodetypes,
684
+ billableOnly,
685
+ bare: true,
686
+ clearOnSelect: true,
687
+ placeholder,
688
+ onSelect: (result) => onPick({
689
+ fullid: result.fullid,
690
+ label: result.label,
691
+ codetype: result.codetype,
692
+ fullcode: result.fullcode
693
+ }),
694
+ onFreeText
695
+ }
696
+ );
697
+ };
698
+ return ContextOrderSearch;
699
+ }
700
+ function isConditionCodetype(codetype) {
701
+ const ct = codetype.toUpperCase();
702
+ return ct === "ICD10" || ct === "ICD9" || ct === "SNOMED US" || ct === "OSHA" || ct === "FMCSA" || ct === "NFPA" || ct === "FAA";
703
+ }
704
+ var ORDER_TYPE_SEARCH_DOMAINS = {
705
+ medication: ["med", "vaccine"],
706
+ lab: ["lab"],
707
+ imaging: ["procedure"],
708
+ procedure: ["procedure"],
709
+ referral: ["procedure"]
710
+ };
711
+ var ORDER_TYPE_PLACEHOLDERS = {
712
+ auto: "Search medications, labs, imaging, procedures\u2026",
713
+ medication: 'Search medications\u2026 (try "lasix")',
714
+ lab: 'Search labs\u2026 (try "a1c")',
715
+ imaging: 'Search imaging\u2026 (try "chest x")',
716
+ procedure: "Search procedures\u2026",
717
+ referral: "Search referrals & consults\u2026"
718
+ };
719
+ var ORDER_TYPE_META = {
720
+ medication: { label: "Medication", icon: Pill },
721
+ lab: { label: "Lab", icon: TestTube2 },
722
+ imaging: { label: "Imaging", icon: Scan },
723
+ procedure: { label: "Procedure", icon: Stethoscope },
724
+ referral: { label: "Referral", icon: Send }
725
+ };
726
+ var ACTION_META2 = {
727
+ refine: { label: "Refine (more specific)", icon: Pencil },
728
+ revise: { label: "Revise (prior was wrong)", icon: RefreshCw },
729
+ "add-order": { label: "Add order", icon: Plus }
730
+ };
731
+ function OrderContent({ order }) {
732
+ const m = useMessages();
733
+ const meta = ORDER_TYPE_META[order.type];
734
+ const Icon = meta.icon;
735
+ return /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground flex items-center gap-2 text-sm", children: [
736
+ /* @__PURE__ */ jsx(Tooltip, { content: m(meta.label), children: /* @__PURE__ */ jsx(Icon, { size: 14 }) }),
737
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: m(meta.label) }),
738
+ /* @__PURE__ */ jsx("span", { className: "text-foreground", children: order.display }),
739
+ order.priority && order.priority !== "routine" && /* @__PURE__ */ jsx(
740
+ Badge,
741
+ {
742
+ variant: order.priority === "stat" ? "danger" : "warning",
743
+ size: "sm",
744
+ children: m(order.priority)
745
+ }
746
+ ),
747
+ order.detail && /* @__PURE__ */ jsx("span", { className: "text-xs", children: order.detail }),
748
+ order.timing && /* @__PURE__ */ jsx("span", { className: "text-xs", children: order.timing })
749
+ ] });
750
+ }
751
+ function OrderRow({
752
+ order,
753
+ drag,
754
+ controls
755
+ }) {
756
+ const m = useMessages();
757
+ const interactive = Boolean(
758
+ controls?.moveWithin || controls?.moveToProblem || controls?.edit || controls?.editStart || controls?.remove
759
+ );
760
+ const canEdit = Boolean(controls?.edit || controls?.editStart);
761
+ const [editing, setEditing] = React4.useState(null);
762
+ const [menuOpen, setMenuOpen] = React4.useState(false);
763
+ const pick = (fn) => () => {
764
+ setMenuOpen(false);
765
+ fn();
766
+ };
767
+ const beginEdit = () => {
768
+ if (controls?.editStart?.(order)) return;
769
+ if (controls?.edit) {
770
+ setEditing({ display: order.display, detail: order.detail ?? "" });
771
+ }
772
+ };
773
+ const saveEdit = () => {
774
+ if (!editing) return;
775
+ const display = editing.display.trim();
776
+ if (display) {
777
+ controls?.edit?.(order, {
778
+ display,
779
+ detail: editing.detail.trim() || void 0
780
+ });
781
+ }
782
+ setEditing(null);
783
+ };
784
+ const handleKeyDown = (e) => {
785
+ if (e.target !== e.currentTarget || !controls) return;
786
+ if (e.altKey && (e.key === "ArrowUp" || e.key === "ArrowDown")) {
787
+ e.preventDefault();
788
+ controls.moveWithin?.(order, e.key === "ArrowUp" ? -1 : 1);
789
+ } else if (e.altKey && (e.key === "ArrowLeft" || e.key === "ArrowRight")) {
790
+ e.preventDefault();
791
+ const back = isRtl(e.currentTarget) ? "ArrowRight" : "ArrowLeft";
792
+ const adj = controls.adjacentProblem(order, e.key === back ? -1 : 1);
793
+ if (adj) controls.moveToProblem?.(order, adj.concernId);
794
+ } else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
795
+ e.preventDefault();
796
+ const list = e.currentTarget.closest("ul");
797
+ if (!list) return;
798
+ const rows = Array.from(
799
+ list.querySelectorAll("li[data-order-id]")
800
+ );
801
+ const i = rows.indexOf(e.currentTarget);
802
+ rows[e.key === "ArrowUp" ? Math.max(0, i - 1) : Math.min(rows.length - 1, i + 1)]?.focus();
803
+ }
804
+ };
805
+ return (
806
+ // Order rows are focus stops so the drag interactions have a full keyboard
807
+ // equivalent: Alt+↑/↓ reorders, Alt+←/→ moves between problems (508).
808
+ /* @__PURE__ */ jsxs(
809
+ "li",
810
+ {
811
+ "data-order-id": order.orderId,
812
+ tabIndex: interactive && !editing ? 0 : void 0,
813
+ onKeyDown: interactive && !editing ? handleKeyDown : void 0,
814
+ "aria-label": interactive && !editing ? m(
815
+ "{name}. Alt plus up or down to reorder; Alt plus left or right to move to another problem.",
816
+ { name: order.display }
817
+ ) : void 0,
818
+ ...editing ? {} : drag.rowProps(order),
819
+ className: cn(
820
+ "group/order relative flex items-center gap-1.5 py-1",
821
+ "focus-visible:ring-ring rounded focus-visible:ring-2 focus-visible:outline-none",
822
+ drag.enabled && !editing && "cursor-grab active:cursor-grabbing",
823
+ drag.draggingId === order.orderId && "opacity-40",
824
+ drag.over?.type === "order" && drag.over.id === order.orderId && (drag.over.after ? "shadow-[inset_0_-2px_0_0_var(--color-primary-500,#3b82f6)]" : "shadow-[inset_0_2px_0_0_var(--color-primary-500,#3b82f6)]")
825
+ ),
826
+ children: [
827
+ drag.enabled && !editing && /* @__PURE__ */ jsx(
828
+ GripVertical,
829
+ {
830
+ size: 12,
831
+ "aria-hidden": true,
832
+ className: "text-muted-foreground/40 shrink-0"
833
+ }
834
+ ),
835
+ editing ? /* @__PURE__ */ jsxs(
836
+ "div",
837
+ {
838
+ role: "form",
839
+ "aria-label": m("Edit order {name}", { name: order.display }),
840
+ className: "flex flex-1 flex-wrap items-center gap-1.5",
841
+ children: [
842
+ /* @__PURE__ */ jsx("div", { className: "min-w-40 flex-1", children: /* @__PURE__ */ jsx(
843
+ Input,
844
+ {
845
+ autoFocus: true,
846
+ size: "sm",
847
+ value: editing.display,
848
+ onChange: (e) => setEditing(
849
+ (prev) => prev ? { ...prev, display: e.target.value } : prev
850
+ ),
851
+ onKeyDown: (e) => {
852
+ if (e.key === "Enter") saveEdit();
853
+ else if (e.key === "Escape") setEditing(null);
854
+ },
855
+ "aria-label": m("Order description"),
856
+ className: "h-7"
857
+ }
858
+ ) }),
859
+ /* @__PURE__ */ jsx("div", { className: "w-40", children: /* @__PURE__ */ jsx(
860
+ Input,
861
+ {
862
+ size: "sm",
863
+ value: editing.detail,
864
+ onChange: (e) => setEditing(
865
+ (prev) => prev ? { ...prev, detail: e.target.value } : prev
866
+ ),
867
+ onKeyDown: (e) => {
868
+ if (e.key === "Enter") saveEdit();
869
+ else if (e.key === "Escape") setEditing(null);
870
+ },
871
+ placeholder: m("detail (sig, timing\u2026)"),
872
+ "aria-label": m("Order detail"),
873
+ className: "h-7 text-xs"
874
+ }
875
+ ) }),
876
+ /* @__PURE__ */ jsx(
877
+ Button,
878
+ {
879
+ variant: "outline",
880
+ size: "sm",
881
+ onClick: saveEdit,
882
+ disabled: !editing.display.trim(),
883
+ className: "h-7 text-xs",
884
+ children: m("Save")
885
+ }
886
+ ),
887
+ /* @__PURE__ */ jsx(
888
+ Button,
889
+ {
890
+ variant: "ghost",
891
+ size: "sm",
892
+ onClick: () => setEditing(null),
893
+ className: "h-7 text-xs",
894
+ children: m("Cancel")
895
+ }
896
+ )
897
+ ]
898
+ }
899
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
900
+ /* @__PURE__ */ jsx(OrderContent, { order }),
901
+ interactive && /* @__PURE__ */ jsxs(
902
+ RowActionToolbar,
903
+ {
904
+ label: m("Actions for {name}", { name: order.display }),
905
+ group: "order",
906
+ children: [
907
+ canEdit && /* @__PURE__ */ jsx(
908
+ RowIconButton,
909
+ {
910
+ label: m("Edit"),
911
+ icon: Pencil,
912
+ size: "sm",
913
+ onClick: beginEdit
914
+ }
915
+ ),
916
+ (controls?.moveWithin || controls?.moveToProblem && controls.targets.some(
917
+ (t) => t.concernId !== order.concernId
918
+ )) && /* @__PURE__ */ jsxs(
919
+ Dropdown,
920
+ {
921
+ open: menuOpen,
922
+ onOpenChange: setMenuOpen,
923
+ trigger: /* @__PURE__ */ jsx(
924
+ Button,
925
+ {
926
+ variant: "ghost",
927
+ size: "icon",
928
+ "aria-label": m("Move {name}", { name: order.display }),
929
+ className: "h-7 w-7 shrink-0",
930
+ children: /* @__PURE__ */ jsx(MoreVertical, { size: 14 })
931
+ }
932
+ ),
933
+ placement: "bottom-end",
934
+ children: [
935
+ controls?.moveWithin && /* @__PURE__ */ jsxs(Fragment, { children: [
936
+ /* @__PURE__ */ jsx(
937
+ DropdownItem,
938
+ {
939
+ icon: /* @__PURE__ */ jsx(ChevronUp, { size: 14 }),
940
+ onClick: pick(() => controls.moveWithin?.(order, -1)),
941
+ children: m("Move up")
942
+ }
943
+ ),
944
+ /* @__PURE__ */ jsx(
945
+ DropdownItem,
946
+ {
947
+ icon: /* @__PURE__ */ jsx(ChevronDown, { size: 14 }),
948
+ onClick: pick(() => controls.moveWithin?.(order, 1)),
949
+ children: m("Move down")
950
+ }
951
+ )
952
+ ] }),
953
+ controls?.moveToProblem && controls.targets.some(
954
+ (t) => t.concernId !== order.concernId
955
+ ) && /* @__PURE__ */ jsxs(Fragment, { children: [
956
+ controls.moveWithin && /* @__PURE__ */ jsx(DropdownSeparator, {}),
957
+ controls.targets.filter((t) => t.concernId !== order.concernId).map((t) => /* @__PURE__ */ jsx(
958
+ DropdownItem,
959
+ {
960
+ icon: /* @__PURE__ */ jsx(Link, { size: 14 }),
961
+ onClick: pick(
962
+ () => controls.moveToProblem?.(order, t.concernId)
963
+ ),
964
+ children: m("Move to: {name}", { name: t.label })
965
+ },
966
+ t.concernId
967
+ ))
968
+ ] })
969
+ ]
970
+ }
971
+ ),
972
+ controls?.remove && /* @__PURE__ */ jsx(
973
+ RowIconButton,
974
+ {
975
+ label: m("Remove"),
976
+ icon: Trash2,
977
+ size: "sm",
978
+ onClick: () => controls.remove?.(order)
979
+ }
980
+ )
981
+ ]
982
+ }
983
+ )
984
+ ] })
985
+ ]
986
+ }
987
+ )
988
+ );
989
+ }
990
+ function AddOrderForm({
991
+ id,
992
+ problemText,
993
+ onSubmit,
994
+ onCancel,
995
+ renderSearch
996
+ }) {
997
+ const m = useMessages();
998
+ const [type, setType] = React4.useState(
999
+ renderSearch ? "auto" : "medication"
1000
+ );
1001
+ const [display, setDisplay] = React4.useState("");
1002
+ const inputRef = React4.useRef(null);
1003
+ React4.useEffect(() => {
1004
+ inputRef.current?.focus();
1005
+ }, []);
1006
+ const submit = () => {
1007
+ const text = display.trim();
1008
+ if (!text) return;
1009
+ onSubmit({ type: type === "auto" ? "procedure" : type, display: text });
1010
+ setDisplay("");
1011
+ inputRef.current?.focus();
1012
+ };
1013
+ const handlePick = (pick) => {
1014
+ onSubmit({
1015
+ type: type === "auto" ? orderTypeForCodetype(pick.codetype) : type,
1016
+ display: pick.label,
1017
+ code: {
1018
+ fullid: pick.fullid,
1019
+ codetype: pick.codetype,
1020
+ fullcode: pick.fullcode
1021
+ }
1022
+ });
1023
+ };
1024
+ return /* @__PURE__ */ jsxs(
1025
+ "div",
1026
+ {
1027
+ id,
1028
+ role: "form",
1029
+ "aria-label": m("Add order for {name}", { name: problemText }),
1030
+ className: "border-border bg-muted/40 ms-2.5 mt-1.5 flex flex-wrap items-center gap-1.5 rounded-md border border-dashed p-2",
1031
+ children: [
1032
+ /* @__PURE__ */ jsxs(
1033
+ "select",
1034
+ {
1035
+ "aria-label": m("Order type filter"),
1036
+ "data-slot": "select-trigger",
1037
+ value: type,
1038
+ onChange: (e) => setType(e.target.value),
1039
+ onKeyDown: (e) => {
1040
+ if (e.key === "Escape") onCancel();
1041
+ },
1042
+ className: cn(
1043
+ "border-border bg-background text-foreground rounded-md border px-1.5 text-sm",
1044
+ renderSearch ? "h-10" : "h-8",
1045
+ "focus:ring-ring focus:ring-2 focus:outline-none"
1046
+ ),
1047
+ children: [
1048
+ renderSearch && /* @__PURE__ */ jsx("option", { value: "auto", children: m("Auto") }),
1049
+ Object.keys(ORDER_TYPE_META).map((t) => /* @__PURE__ */ jsx("option", { value: t, children: m(ORDER_TYPE_META[t].label) }, t))
1050
+ ]
1051
+ }
1052
+ ),
1053
+ renderSearch ? /* @__PURE__ */ jsx("div", { className: "min-w-64 flex-1", children: renderSearch({
1054
+ domains: type === "auto" ? void 0 : ORDER_TYPE_SEARCH_DOMAINS[type],
1055
+ placeholder: m(ORDER_TYPE_PLACEHOLDERS[type]),
1056
+ onPick: handlePick,
1057
+ onFreeText: (text) => {
1058
+ const t = text.trim();
1059
+ if (!t) return;
1060
+ onSubmit({
1061
+ type: type === "auto" ? "procedure" : type,
1062
+ display: t
1063
+ });
1064
+ }
1065
+ }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1066
+ /* @__PURE__ */ jsx("div", { className: "min-w-48 flex-1", children: /* @__PURE__ */ jsx(
1067
+ Input,
1068
+ {
1069
+ ref: inputRef,
1070
+ size: "sm",
1071
+ value: display,
1072
+ onChange: (e) => setDisplay(e.target.value),
1073
+ onKeyDown: (e) => {
1074
+ if (e.key === "Enter") submit();
1075
+ else if (e.key === "Escape") onCancel();
1076
+ },
1077
+ placeholder: m("e.g. lisinopril 10 mg tablet \u2014 1 po daily"),
1078
+ "aria-label": m("Order description")
1079
+ }
1080
+ ) }),
1081
+ /* @__PURE__ */ jsx(
1082
+ Button,
1083
+ {
1084
+ variant: "outline",
1085
+ size: "sm",
1086
+ onClick: submit,
1087
+ disabled: !display.trim(),
1088
+ leftIcon: /* @__PURE__ */ jsx(Plus, { size: 12 }),
1089
+ className: "h-8",
1090
+ children: m("Add")
1091
+ }
1092
+ )
1093
+ ] }),
1094
+ /* @__PURE__ */ jsx(
1095
+ Button,
1096
+ {
1097
+ variant: "ghost",
1098
+ size: "sm",
1099
+ onClick: onCancel,
1100
+ className: renderSearch ? "h-10" : "h-8",
1101
+ children: m("Done")
1102
+ }
1103
+ )
1104
+ ]
1105
+ }
1106
+ );
1107
+ }
1108
+ var Assessment = React4.forwardRef(
1109
+ ({
1110
+ concerns,
1111
+ items,
1112
+ orders = [],
1113
+ title = "Assessment & plan",
1114
+ showPlan = true,
1115
+ onShowPlanChange,
1116
+ onAction,
1117
+ rowActions = [
1118
+ ...Object.keys(ACTION_META2),
1119
+ "remove",
1120
+ "move"
1121
+ ],
1122
+ orderActions = ["edit", "remove", "move"],
1123
+ addModes,
1124
+ addFormLabel = "Add concern or order",
1125
+ addFormStart,
1126
+ onRemoveAssessment,
1127
+ onAddOrder,
1128
+ onAddAssessment,
1129
+ onLinkOrder,
1130
+ onEditOrderStart,
1131
+ onEditOrder,
1132
+ onRemoveOrder,
1133
+ onReorderItems,
1134
+ onReorderOrders,
1135
+ renderOrderSearch,
1136
+ readOnly = false,
1137
+ defaultAddMode = "auto",
1138
+ billableOnly = false,
1139
+ className,
1140
+ "data-testid": dataTestId,
1141
+ ...props
1142
+ }, ref) => {
1143
+ const m = useMessages();
1144
+ const instanceId = React4.useId();
1145
+ const [addingFor, setAddingFor] = React4.useState(null);
1146
+ const requestedAddModes = addModes ?? [
1147
+ "auto",
1148
+ "problem",
1149
+ "order"
1150
+ ];
1151
+ const availableAddModes = requestedAddModes.filter((mode) => {
1152
+ if (mode === "problem") return Boolean(onAddAssessment);
1153
+ if (mode === "order") return Boolean(onAddOrder);
1154
+ return Boolean(onAddAssessment && onAddOrder);
1155
+ });
1156
+ const initialAddMode = availableAddModes.includes(defaultAddMode) ? defaultAddMode : availableAddModes[0] ?? defaultAddMode;
1157
+ const [selectedAddMode, setSelectedAddMode] = React4.useState(initialAddMode);
1158
+ const addMode = availableAddModes.includes(selectedAddMode) ? selectedAddMode : availableAddModes[0] ?? defaultAddMode;
1159
+ const [pendingFreeText, setPendingFreeText] = React4.useState(
1160
+ null
1161
+ );
1162
+ React4.useEffect(() => {
1163
+ if (addMode !== "auto") setPendingFreeText(null);
1164
+ }, [addMode]);
1165
+ const [announcement, setAnnouncement] = useLiveAnnouncement();
1166
+ const ambientCodeLookup = useCodeLookupConfig();
1167
+ const effectiveRenderOrderSearch = renderOrderSearch === false ? void 0 : renderOrderSearch ?? (ambientCodeLookup ? contextOrderSearch(ambientCodeLookup) : void 0);
1168
+ const concernById = React4.useMemo(
1169
+ () => new Map(concerns.map((c) => [c.concernId, c])),
1170
+ [concerns]
1171
+ );
1172
+ const assertionOf = (item) => concernById.get(item.concernId)?.assertions.find((a) => a.id === item.assertionId);
1173
+ const unlinkedOrders = orders.filter(
1174
+ (o) => !o.concernId || !items.some((i) => i.concernId === o.concernId)
1175
+ );
1176
+ const toolbarActions = rowActions.filter(
1177
+ (action) => action in ACTION_META2 && (action === "add-order" ? Boolean(onAddOrder || onAction) : Boolean(onAction))
1178
+ );
1179
+ const showConcernToolbar = toolbarActions.length > 0 || rowActions.includes("remove") && Boolean(onRemoveAssessment);
1180
+ const drag = useDragReorder({
1181
+ ids: items.map((i) => i.concernId),
1182
+ onReorder: readOnly || !rowActions.includes("move") || !onReorderItems ? void 0 : (ids) => {
1183
+ setAnnouncement(m("Assessment concerns reordered"));
1184
+ onReorderItems(ids);
1185
+ }
1186
+ });
1187
+ const problemLabel = React4.useCallback(
1188
+ (concernId) => concernById.get(concernId)?.assertions.find(
1189
+ (a) => items.some(
1190
+ (i) => i.concernId === concernId && i.assertionId === a.id
1191
+ )
1192
+ )?.text ?? concernId,
1193
+ [concernById, items]
1194
+ );
1195
+ const orderControls = readOnly ? void 0 : {
1196
+ moveWithin: orderActions.includes("move") && onReorderOrders ? (order, dir) => {
1197
+ const planIds = orders.filter((o) => o.concernId === order.concernId).map((o) => o.orderId);
1198
+ const i = planIds.indexOf(order.orderId);
1199
+ const target = planIds[i + dir];
1200
+ if (!target) return;
1201
+ onReorderOrders(
1202
+ reorderIds(
1203
+ orders.map((o) => o.orderId),
1204
+ order.orderId,
1205
+ target,
1206
+ dir === 1
1207
+ )
1208
+ );
1209
+ setAnnouncement(
1210
+ m(dir === -1 ? "{name} moved up" : "{name} moved down", {
1211
+ name: order.display
1212
+ })
1213
+ );
1214
+ } : void 0,
1215
+ moveToProblem: orderActions.includes("move") && onLinkOrder ? (order, concernId) => {
1216
+ onLinkOrder(order, concernId);
1217
+ setAnnouncement(
1218
+ m("{name} moved to {target}", {
1219
+ name: order.display,
1220
+ target: problemLabel(concernId)
1221
+ })
1222
+ );
1223
+ } : void 0,
1224
+ edit: orderActions.includes("edit") && onEditOrder ? (order, changes) => {
1225
+ onEditOrder(order, changes);
1226
+ setAnnouncement(
1227
+ m("{name} updated", { name: changes.display })
1228
+ );
1229
+ } : void 0,
1230
+ editStart: orderActions.includes("edit") ? onEditOrderStart : void 0,
1231
+ remove: orderActions.includes("remove") && onRemoveOrder ? (order) => {
1232
+ onRemoveOrder(order);
1233
+ setAnnouncement(m("{name} removed", { name: order.display }));
1234
+ } : void 0,
1235
+ adjacentProblem: (order, dir) => {
1236
+ const i = items.findIndex((it) => it.concernId === order.concernId);
1237
+ const adj = items[i + dir];
1238
+ return adj ? { concernId: adj.concernId, label: problemLabel(adj.concernId) } : null;
1239
+ },
1240
+ targets: items.map((it) => ({
1241
+ concernId: it.concernId,
1242
+ label: problemLabel(it.concernId)
1243
+ }))
1244
+ };
1245
+ const orderDragEnabled = !readOnly && orderActions.includes("move") && Boolean(onReorderOrders || onLinkOrder);
1246
+ const [draggingOrderId, setDraggingOrderId] = React4.useState(
1247
+ null
1248
+ );
1249
+ const draggingOrderRef = React4.useRef(null);
1250
+ const [orderOver, setOrderOver] = React4.useState(null);
1251
+ const resetOrderDrag = () => {
1252
+ draggingOrderRef.current = null;
1253
+ setDraggingOrderId(null);
1254
+ setOrderOver(null);
1255
+ };
1256
+ const dropOnOrder = (dragged, target, after) => {
1257
+ if (dragged.concernId !== target.concernId && target.concernId) {
1258
+ onLinkOrder?.(dragged, target.concernId);
1259
+ setAnnouncement(
1260
+ m("{name} moved to {target}", {
1261
+ name: dragged.display,
1262
+ target: problemLabel(target.concernId)
1263
+ })
1264
+ );
1265
+ } else {
1266
+ setAnnouncement(m("{name} reordered", { name: dragged.display }));
1267
+ }
1268
+ onReorderOrders?.(
1269
+ reorderIds(
1270
+ orders.map((o) => o.orderId),
1271
+ dragged.orderId,
1272
+ target.orderId,
1273
+ after
1274
+ )
1275
+ );
1276
+ };
1277
+ const orderDrag = {
1278
+ enabled: orderDragEnabled,
1279
+ draggingId: draggingOrderId,
1280
+ over: orderOver,
1281
+ rowProps: (order) => orderDragEnabled ? {
1282
+ draggable: true,
1283
+ onDragStart: (e) => {
1284
+ e.stopPropagation();
1285
+ e.dataTransfer.effectAllowed = "move";
1286
+ e.dataTransfer.setData("text/plain", order.orderId);
1287
+ draggingOrderRef.current = order.orderId;
1288
+ setDraggingOrderId(order.orderId);
1289
+ },
1290
+ onDragEnd: (e) => {
1291
+ e.stopPropagation();
1292
+ resetOrderDrag();
1293
+ },
1294
+ onDragOver: (e) => {
1295
+ const dragged = draggingOrderRef.current;
1296
+ if (!dragged || dragged === order.orderId) return;
1297
+ e.preventDefault();
1298
+ e.stopPropagation();
1299
+ e.dataTransfer.dropEffect = "move";
1300
+ const r = e.currentTarget.getBoundingClientRect();
1301
+ const after = e.clientY > r.top + r.height / 2;
1302
+ setOrderOver({ type: "order", id: order.orderId, after });
1303
+ },
1304
+ onDrop: (e) => {
1305
+ const draggedId = draggingOrderRef.current ?? e.dataTransfer.getData("text/plain");
1306
+ const dragged = orders.find((o) => o.orderId === draggedId);
1307
+ if (!dragged || dragged.orderId === order.orderId) return;
1308
+ e.preventDefault();
1309
+ e.stopPropagation();
1310
+ const r = e.currentTarget.getBoundingClientRect();
1311
+ const after = e.clientY > r.top + r.height / 2;
1312
+ dropOnOrder(dragged, order, after);
1313
+ resetOrderDrag();
1314
+ }
1315
+ } : {}
1316
+ };
1317
+ const blockProps = (concernId) => {
1318
+ const itemProps = drag.rowProps(concernId);
1319
+ if (!orderDragEnabled) return itemProps;
1320
+ return {
1321
+ ...itemProps,
1322
+ onDragOver: (e) => {
1323
+ const draggingOrder = draggingOrderRef.current;
1324
+ if (draggingOrder) {
1325
+ const dragged = orders.find((o) => o.orderId === draggingOrder);
1326
+ if (dragged?.concernId === concernId) return;
1327
+ e.preventDefault();
1328
+ e.dataTransfer.dropEffect = "move";
1329
+ setOrderOver({ type: "concern", concernId });
1330
+ return;
1331
+ }
1332
+ itemProps.onDragOver?.(e);
1333
+ },
1334
+ onDragLeave: (e) => {
1335
+ if (draggingOrderRef.current) {
1336
+ setOrderOver(
1337
+ (prev) => prev?.type === "concern" && prev.concernId === concernId ? null : prev
1338
+ );
1339
+ return;
1340
+ }
1341
+ itemProps.onDragLeave?.(e);
1342
+ },
1343
+ onDrop: (e) => {
1344
+ const payload = draggingOrderRef.current ?? e.dataTransfer.getData("text/plain");
1345
+ const dragged = orders.find((o) => o.orderId === payload);
1346
+ if (dragged) {
1347
+ e.preventDefault();
1348
+ if (dragged.concernId !== concernId) {
1349
+ onLinkOrder?.(dragged, concernId);
1350
+ setAnnouncement(
1351
+ m("{name} moved to {target}", {
1352
+ name: dragged.display,
1353
+ target: problemLabel(concernId)
1354
+ })
1355
+ );
1356
+ }
1357
+ resetOrderDrag();
1358
+ return;
1359
+ }
1360
+ itemProps.onDrop?.(e);
1361
+ }
1362
+ };
1363
+ };
1364
+ return /* @__PURE__ */ jsxs(
1365
+ Card,
1366
+ {
1367
+ ref,
1368
+ padding: "none",
1369
+ className: cn("w-full", className),
1370
+ "data-testid": dataTestId,
1371
+ ...props,
1372
+ children: [
1373
+ title !== null && /* @__PURE__ */ jsxs(CardHeader, { className: "border-border bg-muted/50 flex flex-row items-center justify-between border-b px-4 py-3", children: [
1374
+ /* @__PURE__ */ jsx("h3", { className: "text-foreground text-sm font-semibold tracking-wide uppercase", children: m(title) }),
1375
+ onShowPlanChange && /* @__PURE__ */ jsx(
1376
+ Checkbox,
1377
+ {
1378
+ size: "sm",
1379
+ checked: showPlan,
1380
+ onChange: (e) => onShowPlanChange(e.target.checked),
1381
+ label: m("Show plan")
1382
+ }
1383
+ )
1384
+ ] }),
1385
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-4 px-4 py-4", children: [
1386
+ items.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: m("No problems assessed this visit.") }),
1387
+ /* @__PURE__ */ jsx("ol", { className: "space-y-3", children: items.map((item, index) => {
1388
+ const assertion = assertionOf(item);
1389
+ if (!assertion) return null;
1390
+ const linkedOrders = orders.filter(
1391
+ (o) => o.concernId === item.concernId
1392
+ );
1393
+ const bp = blockProps(item.concernId);
1394
+ const formOpen = addingFor === item.concernId;
1395
+ const addOrderFormId = `${instanceId}-add-order-${item.concernId}`;
1396
+ return /* @__PURE__ */ jsxs(
1397
+ "li",
1398
+ {
1399
+ "data-slot": "assessment-problem",
1400
+ "data-concern-id": item.concernId,
1401
+ ...bp,
1402
+ draggable: drag.enabled && !formOpen,
1403
+ className: cn(
1404
+ "group border-border/60 relative rounded-md border px-3 py-2",
1405
+ drag.enabled && !formOpen && "cursor-grab active:cursor-grabbing",
1406
+ dragIndicatorClasses(drag, item.concernId),
1407
+ orderOver?.type === "concern" && orderOver.concernId === item.concernId && "ring-primary-400 bg-primary-50/50 dark:bg-primary-950/50 ring-2"
1408
+ ),
1409
+ children: [
1410
+ /* @__PURE__ */ jsxs(
1411
+ "div",
1412
+ {
1413
+ className: cn(
1414
+ "flex flex-wrap items-center gap-x-2 gap-y-1",
1415
+ drag.enabled && formOpen && "cursor-grab active:cursor-grabbing"
1416
+ ),
1417
+ draggable: drag.enabled && formOpen,
1418
+ onDragStart: formOpen ? bp.onDragStart : void 0,
1419
+ onDragEnd: formOpen ? bp.onDragEnd : void 0,
1420
+ children: [
1421
+ drag.enabled && /* @__PURE__ */ jsx(
1422
+ GripVertical,
1423
+ {
1424
+ size: 14,
1425
+ "aria-hidden": true,
1426
+ className: "text-muted-foreground/50 shrink-0"
1427
+ }
1428
+ ),
1429
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-sm font-semibold tabular-nums", children: [
1430
+ index + 1,
1431
+ "."
1432
+ ] }),
1433
+ /* @__PURE__ */ jsx("span", { className: "text-foreground font-medium", children: assertion.text }),
1434
+ assertion.verificationStatus !== "confirmed" && /* @__PURE__ */ jsx(Badge, { variant: "warning", size: "sm", children: m(assertion.verificationStatus) }),
1435
+ /* @__PURE__ */ jsx(CodingChips, { coding: assertion.coding }),
1436
+ !readOnly && showConcernToolbar && /* @__PURE__ */ jsxs(
1437
+ RowActionToolbar,
1438
+ {
1439
+ label: m("Actions for {name}", {
1440
+ name: assertion.text
1441
+ }),
1442
+ align: "top",
1443
+ className: cn(
1444
+ "pointer-fine:group-has-[[data-order-id]:hover]:pointer-events-none",
1445
+ "pointer-fine:group-has-[[data-order-id]:hover]:opacity-0",
1446
+ "pointer-fine:group-has-[[data-order-id]:focus-within]:pointer-events-none",
1447
+ "pointer-fine:group-has-[[data-order-id]:focus-within]:opacity-0"
1448
+ ),
1449
+ children: [
1450
+ toolbarActions.map((action) => {
1451
+ const meta = ACTION_META2[action];
1452
+ return /* @__PURE__ */ jsx(
1453
+ RowIconButton,
1454
+ {
1455
+ label: m(meta.label),
1456
+ icon: meta.icon,
1457
+ size: "sm",
1458
+ expanded: action === "add-order" && onAddOrder ? addingFor === item.concernId : void 0,
1459
+ controls: action === "add-order" && onAddOrder ? addOrderFormId : void 0,
1460
+ onClick: () => {
1461
+ if (action === "add-order" && onAddOrder) {
1462
+ setAddingFor(
1463
+ (prev) => prev === item.concernId ? null : item.concernId
1464
+ );
1465
+ } else {
1466
+ onAction?.(item, action);
1467
+ }
1468
+ }
1469
+ },
1470
+ action
1471
+ );
1472
+ }),
1473
+ rowActions.includes("remove") && onRemoveAssessment && /* @__PURE__ */ jsx(
1474
+ RowIconButton,
1475
+ {
1476
+ label: m("Remove {name} from assessment", {
1477
+ name: assertion.text
1478
+ }),
1479
+ icon: Trash2,
1480
+ size: "sm",
1481
+ onClick: () => {
1482
+ onRemoveAssessment(item);
1483
+ setAnnouncement(
1484
+ m("{name} removed from assessment", {
1485
+ name: assertion.text
1486
+ })
1487
+ );
1488
+ }
1489
+ }
1490
+ )
1491
+ ]
1492
+ }
1493
+ )
1494
+ ]
1495
+ }
1496
+ ),
1497
+ item.note && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-1 ps-6 text-sm", children: item.note }),
1498
+ showPlan && linkedOrders.length > 0 && /* @__PURE__ */ jsx(
1499
+ "ul",
1500
+ {
1501
+ "aria-label": m("Plan for {name}", {
1502
+ name: assertion.text
1503
+ }),
1504
+ className: "border-border ms-2.5 mt-1.5 border-s ps-4",
1505
+ children: linkedOrders.map((order) => /* @__PURE__ */ jsx(
1506
+ OrderRow,
1507
+ {
1508
+ order,
1509
+ drag: orderDrag,
1510
+ controls: orderControls
1511
+ },
1512
+ order.orderId
1513
+ ))
1514
+ }
1515
+ ),
1516
+ !readOnly && onAddOrder && addingFor === item.concernId && /* @__PURE__ */ jsx(
1517
+ AddOrderForm,
1518
+ {
1519
+ id: addOrderFormId,
1520
+ problemText: assertion.text,
1521
+ onSubmit: (order) => {
1522
+ onAddOrder(item, order);
1523
+ setAnnouncement(
1524
+ m("{name} added to {target}", {
1525
+ name: order.display,
1526
+ target: assertion.text
1527
+ })
1528
+ );
1529
+ setAddingFor(null);
1530
+ },
1531
+ onCancel: () => setAddingFor(null),
1532
+ renderSearch: effectiveRenderOrderSearch
1533
+ }
1534
+ )
1535
+ ]
1536
+ },
1537
+ item.concernId
1538
+ );
1539
+ }) }),
1540
+ !readOnly && availableAddModes.length > 0 && effectiveRenderOrderSearch && (onAddAssessment || onAddOrder) && /* @__PURE__ */ jsxs(
1541
+ "div",
1542
+ {
1543
+ role: "form",
1544
+ "aria-label": m(addFormLabel),
1545
+ className: "border-border bg-muted/40 flex flex-col items-stretch gap-2 rounded-md border border-dashed p-2 sm:flex-row sm:flex-wrap sm:items-end",
1546
+ children: [
1547
+ addFormStart,
1548
+ /* @__PURE__ */ jsxs(
1549
+ "select",
1550
+ {
1551
+ "aria-label": m("What to add"),
1552
+ "data-slot": "select-trigger",
1553
+ value: addMode,
1554
+ onChange: (e) => {
1555
+ setSelectedAddMode(e.target.value);
1556
+ setPendingFreeText(null);
1557
+ },
1558
+ className: cn(
1559
+ "border-border bg-background text-foreground h-10 rounded-md border px-1.5 text-sm",
1560
+ "focus:ring-ring focus:ring-2 focus:outline-none"
1561
+ ),
1562
+ children: [
1563
+ availableAddModes.includes("auto") && /* @__PURE__ */ jsx("option", { value: "auto", children: m("Add (auto)") }),
1564
+ availableAddModes.includes("problem") && onAddAssessment && /* @__PURE__ */ jsx("option", { value: "problem", children: m("Add concern") }),
1565
+ availableAddModes.includes("order") && onAddOrder && /* @__PURE__ */ jsx("option", { value: "order", children: m("Add order") })
1566
+ ]
1567
+ }
1568
+ ),
1569
+ /* @__PURE__ */ jsx("div", { className: "min-w-64 flex-1", children: effectiveRenderOrderSearch({
1570
+ domains: addMode === "problem" ? ["condition", "occupational"] : addMode === "order" ? ["med", "lab", "procedure", "vaccine"] : void 0,
1571
+ // auto: everything — the pick decides
1572
+ // auto: concerns rank ahead of orders — surveillance
1573
+ // programs first (rare, high-value hits like "dot" or
1574
+ // "hearing"), then diagnoses, then meds/labs/procedures.
1575
+ // concern mode: programs still lead the diagnoses.
1576
+ preferDomains: addMode === "auto" ? ["occupational", "condition"] : addMode === "problem" ? ["occupational"] : void 0,
1577
+ // assessments code in ICD-10: boost it over SNOMED
1578
+ preferCodetypes: addMode !== "order" ? ["ICD10"] : void 0,
1579
+ billableOnly: addMode !== "order" && billableOnly,
1580
+ placeholder: m(
1581
+ addMode === "problem" ? 'Search diagnoses & programs\u2026 (try "chf" or "hearing conservation")' : addMode === "order" ? "Search medications, labs, imaging, procedures\u2026" : "Add concern or order\u2026 (a diagnosis or surveillance program becomes a concern; meds, labs & imaging become orders)"
1582
+ ),
1583
+ onPick: (pick) => {
1584
+ const asProblem = addMode === "problem" || addMode === "auto" && isConditionCodetype(pick.codetype) && Boolean(onAddAssessment);
1585
+ if (asProblem) {
1586
+ onAddAssessment?.({
1587
+ label: pick.label,
1588
+ code: {
1589
+ fullid: pick.fullid,
1590
+ codetype: pick.codetype,
1591
+ fullcode: pick.fullcode
1592
+ }
1593
+ });
1594
+ } else {
1595
+ onAddOrder?.(null, {
1596
+ type: orderTypeForCodetype(pick.codetype),
1597
+ display: pick.label,
1598
+ code: {
1599
+ fullid: pick.fullid,
1600
+ codetype: pick.codetype,
1601
+ fullcode: pick.fullcode
1602
+ }
1603
+ });
1604
+ }
1605
+ },
1606
+ onFreeText: (text) => {
1607
+ if (addMode === "problem") {
1608
+ onAddAssessment?.({ label: text });
1609
+ } else if (addMode === "order") {
1610
+ onAddOrder?.(null, {
1611
+ type: "procedure",
1612
+ display: text
1613
+ });
1614
+ } else {
1615
+ setPendingFreeText(text);
1616
+ }
1617
+ }
1618
+ }) }),
1619
+ addMode === "auto" && pendingFreeText !== null && /* @__PURE__ */ jsxs(
1620
+ "div",
1621
+ {
1622
+ role: "group",
1623
+ "aria-label": m('Add "{name}" as', { name: pendingFreeText }),
1624
+ className: "flex w-full flex-wrap items-center gap-1.5 ps-1",
1625
+ children: [
1626
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-sm", children: [
1627
+ m("Add"),
1628
+ " ",
1629
+ /* @__PURE__ */ jsxs("span", { className: "text-foreground font-medium", children: [
1630
+ "\u201C",
1631
+ pendingFreeText,
1632
+ "\u201D"
1633
+ ] }),
1634
+ " ",
1635
+ m("as:")
1636
+ ] }),
1637
+ onAddAssessment && /* @__PURE__ */ jsx(
1638
+ Button,
1639
+ {
1640
+ variant: "outline",
1641
+ size: "sm",
1642
+ className: "h-7 text-xs",
1643
+ onClick: () => {
1644
+ onAddAssessment({ label: pendingFreeText });
1645
+ setPendingFreeText(null);
1646
+ },
1647
+ children: m("Concern")
1648
+ }
1649
+ ),
1650
+ onAddOrder && /* @__PURE__ */ jsx(
1651
+ Button,
1652
+ {
1653
+ variant: "outline",
1654
+ size: "sm",
1655
+ className: "h-7 text-xs",
1656
+ onClick: () => {
1657
+ onAddOrder(null, {
1658
+ type: "procedure",
1659
+ display: pendingFreeText
1660
+ });
1661
+ setPendingFreeText(null);
1662
+ },
1663
+ children: m("Order")
1664
+ }
1665
+ ),
1666
+ /* @__PURE__ */ jsx(
1667
+ Button,
1668
+ {
1669
+ variant: "ghost",
1670
+ size: "sm",
1671
+ className: "h-7 text-xs",
1672
+ onClick: () => setPendingFreeText(null),
1673
+ children: m("Cancel")
1674
+ }
1675
+ )
1676
+ ]
1677
+ }
1678
+ )
1679
+ ]
1680
+ }
1681
+ ),
1682
+ showPlan && unlinkedOrders.length > 0 && /* @__PURE__ */ jsxs(
1683
+ "section",
1684
+ {
1685
+ "aria-label": m("Orders not linked to a concern"),
1686
+ className: "rounded-md border border-amber-300 bg-amber-50 px-3 py-2 dark:border-amber-700 dark:bg-amber-950",
1687
+ children: [
1688
+ /* @__PURE__ */ jsxs("h4", { className: "flex items-center gap-1.5 text-sm font-semibold text-amber-900 dark:text-amber-200", children: [
1689
+ /* @__PURE__ */ jsx(AlertCircle, { size: 14 }),
1690
+ m("Orders not linked to a concern")
1691
+ ] }),
1692
+ /* @__PURE__ */ jsx("ul", { className: "mt-1", children: unlinkedOrders.map((order) => (
1693
+ // Same row component as linked orders: focusable, Alt+arrow
1694
+ // keyboard moves, and the Move menu (508 parity with drag)
1695
+ /* @__PURE__ */ jsx(
1696
+ OrderRow,
1697
+ {
1698
+ order,
1699
+ drag: orderDrag,
1700
+ controls: orderControls
1701
+ },
1702
+ order.orderId
1703
+ )
1704
+ )) })
1705
+ ]
1706
+ }
1707
+ )
1708
+ ] }),
1709
+ /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "sr-only", children: announcement })
1710
+ ]
1711
+ }
1712
+ );
1713
+ }
1714
+ );
1715
+ Assessment.displayName = "Assessment";
1716
+ var MODE_META = {
1717
+ add: { title: "Add problem", changeType: void 0 },
1718
+ observe: { title: "Progress observation", changeType: void 0 },
1719
+ refine: { title: "Refine problem", changeType: "refinement" },
1720
+ revise: { title: "Revise problem", changeType: "revision" },
1721
+ relate: { title: "Relate problem", changeType: void 0 }
1722
+ };
1723
+ var CODING_SYSTEMS = ["SNOMED", "ICD-10-CM", "ICD-11"];
1724
+ var VERIFICATION_OPTIONS = [
1725
+ { value: "unconfirmed", label: "Unconfirmed" },
1726
+ { value: "provisional", label: "Provisional" },
1727
+ { value: "differential", label: "Differential" },
1728
+ { value: "confirmed", label: "Confirmed" }
1729
+ ];
1730
+ var RELATIONSHIP_OPTIONS = [
1731
+ { value: "caused-by", label: "Caused by" },
1732
+ { value: "evolved-from", label: "Evolved from" },
1733
+ { value: "differential-sibling", label: "Differential sibling" },
1734
+ { value: "complication-of", label: "Complication of" }
1735
+ ];
1736
+ var SEVERITIES = ["mild", "moderate", "severe"];
1737
+ var CONFIDENCES = ["low", "medium", "high"];
1738
+ function normalizeConditionCodingSystem(codetype) {
1739
+ const ct = codetype.toUpperCase().replace(/[^A-Z0-9]/g, "");
1740
+ if (ct === "ICD10" || ct === "ICD10CM") return "ICD-10-CM";
1741
+ if (ct === "ICD11") return "ICD-11";
1742
+ if (ct === "SNOMED" || ct === "SNOMEDCT" || ct === "SNOMEDUS") {
1743
+ return "SNOMED";
1744
+ }
1745
+ return codetype;
1746
+ }
1747
+ function withSinglePrimary(coding) {
1748
+ if (coding.length === 0) return coding;
1749
+ const primaryIndex = coding.findIndex((entry) => entry.primary);
1750
+ const selectedIndex = primaryIndex >= 0 ? primaryIndex : 0;
1751
+ return coding.map((entry, index) => ({
1752
+ ...entry,
1753
+ primary: index === selectedIndex
1754
+ }));
1755
+ }
1756
+ function contextConditionCodeSearch(ctx) {
1757
+ const ContextConditionCodeSearch = ({
1758
+ placeholder,
1759
+ onPick,
1760
+ onFreeText
1761
+ }) => {
1762
+ const Lookup = ctx.component;
1763
+ return /* @__PURE__ */ jsx(
1764
+ Lookup,
1765
+ {
1766
+ indexUrl: ctx.indexUrl,
1767
+ locale: ctx.locale,
1768
+ domains: ["condition"],
1769
+ preferCodetypes: ["ICD10"],
1770
+ bare: true,
1771
+ clearOnSelect: false,
1772
+ placeholder,
1773
+ onSelect: (result) => onPick({
1774
+ fullid: result.fullid,
1775
+ label: result.label,
1776
+ codetype: result.codetype,
1777
+ fullcode: result.fullcode
1778
+ }),
1779
+ onFreeText
1780
+ }
1781
+ );
1782
+ };
1783
+ return ContextConditionCodeSearch;
1784
+ }
1785
+ function FieldFlag({
1786
+ label,
1787
+ value,
1788
+ onChange,
1789
+ hasValue
1790
+ }) {
1791
+ const m = useMessages();
1792
+ const unknown = value?.known === false;
1793
+ return /* @__PURE__ */ jsxs(
1794
+ "span",
1795
+ {
1796
+ role: "group",
1797
+ "aria-label": m("Certainty for {label}", { label }),
1798
+ className: "flex items-center gap-1",
1799
+ children: [
1800
+ /* @__PURE__ */ jsx(
1801
+ "button",
1802
+ {
1803
+ type: "button",
1804
+ "aria-pressed": unknown,
1805
+ onClick: () => onChange(
1806
+ unknown ? void 0 : { known: false, reason: "asked-unknown" }
1807
+ ),
1808
+ className: cn(
1809
+ "rounded-full border px-2 py-0.5 text-[11px] font-medium transition-colors",
1810
+ unknown ? "border-warning/50 bg-warning/10 text-warning-800 dark:text-warning-200" : "border-border text-muted-foreground hover:text-foreground"
1811
+ ),
1812
+ children: m("Unknown")
1813
+ }
1814
+ ),
1815
+ hasValue && !unknown && CONFIDENCES.map((c) => {
1816
+ const active = value?.known !== false && value?.confidence === c;
1817
+ return /* @__PURE__ */ jsx(
1818
+ "button",
1819
+ {
1820
+ type: "button",
1821
+ "aria-pressed": active,
1822
+ "aria-label": m("{label} confidence {confidence}", {
1823
+ label,
1824
+ confidence: m(c)
1825
+ }),
1826
+ onClick: () => onChange(active ? void 0 : { known: true, confidence: c }),
1827
+ className: cn(
1828
+ "rounded-full border px-1.5 py-0.5 text-[11px] transition-colors",
1829
+ active ? "border-primary-400 bg-primary-100 text-primary-900 dark:border-primary-600 dark:bg-primary-950 dark:text-primary-200" : "border-border text-muted-foreground hover:text-foreground"
1830
+ ),
1831
+ children: m(c)
1832
+ },
1833
+ c
1834
+ );
1835
+ })
1836
+ ]
1837
+ }
1838
+ );
1839
+ }
1840
+ function ObservationHistory({ concern }) {
1841
+ const m = useMessages();
1842
+ const observations = concern?.observations;
1843
+ if (!observations?.length) return null;
1844
+ return /* @__PURE__ */ jsxs("section", { "aria-label": m("Past observations"), className: "space-y-1", children: [
1845
+ /* @__PURE__ */ jsx("h4", { className: "text-muted-foreground text-xs font-semibold tracking-wide uppercase", children: m("Observations") }),
1846
+ /* @__PURE__ */ jsx("ol", { className: "border-border max-h-40 space-y-1.5 overflow-y-auto rounded-md border p-2", children: [...observations].reverse().map((o) => /* @__PURE__ */ jsxs("li", { className: "flex flex-wrap gap-x-2 text-sm", children: [
1847
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs tabular-nums", children: o.date }),
1848
+ /* @__PURE__ */ jsx("span", { className: "text-foreground flex-1", children: o.text }),
1849
+ o.author && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-xs", children: [
1850
+ "\u2014 ",
1851
+ o.author
1852
+ ] })
1853
+ ] }, o.id)) })
1854
+ ] });
1855
+ }
1856
+ function ConditionEditor({
1857
+ mode,
1858
+ open,
1859
+ onOpenChange,
1860
+ concern,
1861
+ relatableConcerns = [],
1862
+ onSave,
1863
+ onRelate,
1864
+ onAddObservation,
1865
+ renderCodeSearch,
1866
+ className,
1867
+ "data-testid": dataTestId
1868
+ }) {
1869
+ const m = useMessages();
1870
+ const prior = concern ? currentAssertion(concern) : void 0;
1871
+ const ambientCodeLookup = useCodeLookupConfig();
1872
+ const effectiveRenderCodeSearch = renderCodeSearch === false ? void 0 : renderCodeSearch ?? (ambientCodeLookup ? contextConditionCodeSearch(ambientCodeLookup) : void 0);
1873
+ const [text, setText] = React4.useState("");
1874
+ const [coding, setCoding] = React4.useState([]);
1875
+ const [verification, setVerification] = React4.useState("confirmed");
1876
+ const [severity, setSeverity] = React4.useState();
1877
+ const [onsetDate, setOnsetDate] = React4.useState("");
1878
+ const [onsetFuzzy, setOnsetFuzzy] = React4.useState("");
1879
+ const [note, setNote] = React4.useState("");
1880
+ const [progression, setProgression] = React4.useState(false);
1881
+ const [fields, setFields] = React4.useState({});
1882
+ const [relType, setRelType] = React4.useState("caused-by");
1883
+ const [relTarget, setRelTarget] = React4.useState("");
1884
+ const [observation, setObservation] = React4.useState("");
1885
+ React4.useEffect(() => {
1886
+ if (!open) return;
1887
+ setText(prior?.text ?? "");
1888
+ setCoding(withSinglePrimary(prior?.coding ?? []));
1889
+ setVerification(
1890
+ mode === "add" ? "unconfirmed" : prior?.verificationStatus ?? "confirmed"
1891
+ );
1892
+ setSeverity(prior?.severity);
1893
+ const priorOnsetDate = prior?.onset?.date;
1894
+ setOnsetDate(priorOnsetDate ? dateToDisplayFormat(priorOnsetDate) : "");
1895
+ setOnsetFuzzy(priorOnsetDate ? "" : prior?.onset?.fuzzy ?? "");
1896
+ setNote("");
1897
+ setProgression(false);
1898
+ setFields(prior?.uncertainty?.fields ?? {});
1899
+ setRelType("caused-by");
1900
+ setRelTarget("");
1901
+ setObservation("");
1902
+ }, [open, mode, prior]);
1903
+ const setField = (field) => (f) => setFields((prev) => {
1904
+ const next = { ...prev };
1905
+ if (f) next[field] = f;
1906
+ else delete next[field];
1907
+ return next;
1908
+ });
1909
+ const updateCoding = (i, patch) => setCoding((prev) => prev.map((c, j) => j === i ? { ...c, ...patch } : c));
1910
+ const codingUnknown = fields.coding?.known === false;
1911
+ const hasInvalidOnsetDate = fields.onset?.known !== false && Boolean(onsetDate) && !isValidDate(onsetDate);
1912
+ const handleCodePick = (pick) => {
1913
+ const system = normalizeConditionCodingSystem(pick.codetype);
1914
+ setCoding(
1915
+ (prev) => prev.some((c) => c.system === system && c.code === pick.fullcode) ? prev : withSinglePrimary([
1916
+ ...prev,
1917
+ { system, code: pick.fullcode, display: pick.label }
1918
+ ])
1919
+ );
1920
+ setText((prev) => prev || pick.label);
1921
+ setFields((prev) => {
1922
+ if (prev.coding?.known !== false) return prev;
1923
+ const next = { ...prev };
1924
+ delete next.coding;
1925
+ return next;
1926
+ });
1927
+ };
1928
+ const handleSave = () => {
1929
+ if (mode === "relate") {
1930
+ if (!relTarget) return;
1931
+ onRelate?.({ type: relType, concernId: relTarget });
1932
+ onOpenChange(false);
1933
+ return;
1934
+ }
1935
+ if (mode === "observe") {
1936
+ if (!observation.trim()) return;
1937
+ onAddObservation?.(observation.trim());
1938
+ onOpenChange(false);
1939
+ return;
1940
+ }
1941
+ if (!text.trim() || hasInvalidOnsetDate) return;
1942
+ if (observation.trim()) onAddObservation?.(observation.trim());
1943
+ const uncertainty = Object.keys(fields).length > 0 ? { fields } : void 0;
1944
+ onSave?.({
1945
+ text: text.trim(),
1946
+ // write the trimmed values back — whitespace in codes breaks equality
1947
+ // checks downstream. A field marked explicitly unknown must not carry a
1948
+ // value (e.g. one seeded from the prior assertion): value + "unknown"
1949
+ // would be contradictory, so unknown wins and the value is dropped.
1950
+ coding: fields.coding?.known === false ? [] : withSinglePrimary(
1951
+ coding.filter((c) => c.code.trim()).map((c) => ({
1952
+ ...c,
1953
+ code: c.code.trim(),
1954
+ display: c.display?.trim() || void 0,
1955
+ mappedFrom: c.mappedFrom?.trim() || void 0
1956
+ }))
1957
+ ),
1958
+ verificationStatus: verification,
1959
+ changeType: mode === "refine" ? progression ? "progression" : "refinement" : MODE_META[mode].changeType,
1960
+ supersedes: prior?.id,
1961
+ severity: fields.severity?.known === false ? void 0 : severity,
1962
+ onset: fields.onset?.known === false ? void 0 : onsetDate || onsetFuzzy ? {
1963
+ date: onsetDate ? displayFormatToDateString(onsetDate) : void 0,
1964
+ fuzzy: onsetFuzzy || void 0
1965
+ } : void 0,
1966
+ uncertainty,
1967
+ note: note.trim() || void 0
1968
+ });
1969
+ onOpenChange(false);
1970
+ };
1971
+ const saveDisabled = mode === "relate" ? !relTarget : mode === "observe" ? !observation.trim() : !text.trim() || hasInvalidOnsetDate;
1972
+ return /* @__PURE__ */ jsxs(
1973
+ Modal,
1974
+ {
1975
+ open,
1976
+ onOpenChange,
1977
+ size: "2xl",
1978
+ className,
1979
+ children: [
1980
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsxs(ModalTitle, { children: [
1981
+ m(MODE_META[mode].title),
1982
+ prior ? ` \u2014 ${prior.text}` : ""
1983
+ ] }) }),
1984
+ /* @__PURE__ */ jsx(ModalBody, { "data-testid": dataTestId, className: "space-y-5", children: /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
1985
+ mode === "revise" && /* @__PURE__ */ jsxs(
1986
+ "div",
1987
+ {
1988
+ role: "alert",
1989
+ className: "border-warning/30 bg-warning/10 text-warning-800 dark:text-warning-200 flex items-start gap-2 rounded-md border px-3 py-2 text-sm",
1990
+ children: [
1991
+ /* @__PURE__ */ jsx(AlertTriangle, { size: 16 }),
1992
+ /* @__PURE__ */ jsx("span", { children: m(
1993
+ "Revising marks the prior assertion refuted (it was never true) \u2014 history is preserved on the timeline. Use Refine instead if the prior was correct but less specific."
1994
+ ) })
1995
+ ]
1996
+ }
1997
+ ),
1998
+ mode === "relate" ? /* @__PURE__ */ jsxs(Fragment, { children: [
1999
+ /* @__PURE__ */ jsx(
2000
+ Select,
2001
+ {
2002
+ label: m("Relationship"),
2003
+ options: RELATIONSHIP_OPTIONS.map((option) => ({
2004
+ ...option,
2005
+ label: m(option.label)
2006
+ })),
2007
+ value: relType,
2008
+ onValueChange: (v) => setRelType(v)
2009
+ }
2010
+ ),
2011
+ /* @__PURE__ */ jsx(
2012
+ Select,
2013
+ {
2014
+ label: m("Related problem"),
2015
+ placeholder: m("Select a problem\u2026"),
2016
+ options: relatableConcerns.filter((c) => c.concernId !== concern?.concernId).map((c) => ({
2017
+ value: c.concernId,
2018
+ label: currentAssertion(c)?.text ?? c.concernId
2019
+ })),
2020
+ value: relTarget,
2021
+ onValueChange: setRelTarget
2022
+ }
2023
+ )
2024
+ ] }) : mode === "observe" ? /* @__PURE__ */ jsxs(Fragment, { children: [
2025
+ /* @__PURE__ */ jsx(ObservationHistory, { concern }),
2026
+ /* @__PURE__ */ jsx(
2027
+ Textarea,
2028
+ {
2029
+ label: m("New observation"),
2030
+ value: observation,
2031
+ onChange: (e) => setObservation(e.target.value),
2032
+ placeholder: m(
2033
+ "e.g. BP improving on current regimen; patient reports better sleep"
2034
+ ),
2035
+ rows: 3,
2036
+ autoFocus: true
2037
+ }
2038
+ )
2039
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2040
+ (mode === "refine" || mode === "revise") && /* @__PURE__ */ jsx(ObservationHistory, { concern }),
2041
+ mode === "add" && effectiveRenderCodeSearch && !text.trim() ? /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2042
+ /* @__PURE__ */ jsxs("span", { className: "text-foreground block text-sm font-medium", children: [
2043
+ m("Problem name"),
2044
+ /* @__PURE__ */ jsx("span", { className: "text-destructive ms-1", "aria-hidden": "true", children: "*" })
2045
+ ] }),
2046
+ effectiveRenderCodeSearch({
2047
+ placeholder: m(
2048
+ "Search conditions \u2014 or type a name and press Enter\u2026"
2049
+ ),
2050
+ onPick: handleCodePick,
2051
+ onFreeText: (t) => setText(t.trim())
2052
+ }),
2053
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: m(
2054
+ "Picking a code fills the name and coding together; free text is fine \u2014 codes can be added any time."
2055
+ ) })
2056
+ ] }) : /* @__PURE__ */ jsx(
2057
+ Input,
2058
+ {
2059
+ label: m("Problem name"),
2060
+ required: true,
2061
+ value: text,
2062
+ onChange: (e) => setText(e.target.value),
2063
+ placeholder: m(
2064
+ "e.g. Type 1 diabetes mellitus with neuropathy"
2065
+ )
2066
+ }
2067
+ ),
2068
+ mode === "refine" && /* @__PURE__ */ jsx(
2069
+ Checkbox,
2070
+ {
2071
+ size: "sm",
2072
+ checked: progression,
2073
+ onChange: (e) => setProgression(e.target.checked),
2074
+ label: m(
2075
+ "Disease progressed (e.g. new complication) rather than a more specific diagnosis"
2076
+ )
2077
+ }
2078
+ ),
2079
+ /* @__PURE__ */ jsxs("fieldset", { className: "space-y-3", children: [
2080
+ /* @__PURE__ */ jsx("legend", { className: "sr-only", children: m("Coding") }),
2081
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
2082
+ /* @__PURE__ */ jsxs("span", { className: "flex flex-wrap items-center gap-2 text-sm font-medium", children: [
2083
+ m("Coding"),
2084
+ /* @__PURE__ */ jsx(
2085
+ FieldFlag,
2086
+ {
2087
+ label: m("Coding"),
2088
+ value: fields.coding,
2089
+ onChange: setField("coding"),
2090
+ hasValue: coding.length > 0
2091
+ }
2092
+ )
2093
+ ] }),
2094
+ /* @__PURE__ */ jsx(
2095
+ Button,
2096
+ {
2097
+ variant: "ghost",
2098
+ size: "sm",
2099
+ disabled: codingUnknown,
2100
+ onClick: () => setCoding((prev) => [
2101
+ ...prev,
2102
+ {
2103
+ system: "SNOMED",
2104
+ code: "",
2105
+ primary: prev.length === 0
2106
+ }
2107
+ ]),
2108
+ leftIcon: /* @__PURE__ */ jsx(Plus, { size: 12 }),
2109
+ className: "h-8 shrink-0 text-xs",
2110
+ children: m("Add code")
2111
+ }
2112
+ )
2113
+ ] }),
2114
+ effectiveRenderCodeSearch && !(mode === "add" && !text.trim()) && /* @__PURE__ */ jsx("div", { children: effectiveRenderCodeSearch({
2115
+ placeholder: m("Search codes \u2014 ICD-10-CM / SNOMED\u2026"),
2116
+ onPick: handleCodePick,
2117
+ onFreeText: (t) => setText((prev) => prev || t)
2118
+ }) }),
2119
+ coding.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: m(
2120
+ "Optional \u2014 a name-only problem is valid. Codes can be added any time."
2121
+ ) }),
2122
+ coding.length > 0 && /* @__PURE__ */ jsxs(
2123
+ "div",
2124
+ {
2125
+ "aria-hidden": "true",
2126
+ "data-slot": "condition-coding-header",
2127
+ className: "text-muted-foreground hidden grid-cols-[minmax(8rem,1.1fr)_minmax(7rem,0.9fr)_minmax(10rem,1.5fr)_5.5rem_2.5rem] items-center gap-2 px-1 text-xs font-medium sm:!grid",
2128
+ children: [
2129
+ /* @__PURE__ */ jsx("span", { children: m("System") }),
2130
+ /* @__PURE__ */ jsx("span", { children: m("Code") }),
2131
+ /* @__PURE__ */ jsx("span", { children: m("Display") }),
2132
+ /* @__PURE__ */ jsx("span", { className: "text-center", children: m("Primary code") }),
2133
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: m("Actions") })
2134
+ ]
2135
+ }
2136
+ ),
2137
+ coding.length > 0 && /* @__PURE__ */ jsx(
2138
+ RadioGroup,
2139
+ {
2140
+ name: "primary-coding",
2141
+ label: m("Primary display code"),
2142
+ value: String(
2143
+ Math.max(
2144
+ 0,
2145
+ coding.findIndex((entry) => entry.primary)
2146
+ )
2147
+ ),
2148
+ onValueChange: (value) => {
2149
+ const selectedIndex = Number(value);
2150
+ setCoding(
2151
+ (prev) => prev.map((entry, index) => ({
2152
+ ...entry,
2153
+ primary: index === selectedIndex
2154
+ }))
2155
+ );
2156
+ },
2157
+ size: "sm",
2158
+ className: "gap-2 [&_[data-slot=radio-group-items]]:gap-2 sm:[&_[data-slot=radio-group-legend]]:sr-only",
2159
+ children: coding.map((c, i) => /* @__PURE__ */ jsxs(
2160
+ "div",
2161
+ {
2162
+ "data-slot": "condition-coding-row",
2163
+ className: "border-border/70 grid grid-cols-1 gap-2 rounded-md border p-2 sm:grid-cols-[minmax(8rem,1.1fr)_minmax(7rem,0.9fr)_minmax(10rem,1.5fr)_5.5rem_2.5rem] sm:items-center sm:border-0 sm:p-0",
2164
+ children: [
2165
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2166
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs sm:hidden", children: m("System") }),
2167
+ /* @__PURE__ */ jsx(
2168
+ Select,
2169
+ {
2170
+ "aria-label": m("Coding system {index}", {
2171
+ index: i + 1
2172
+ }),
2173
+ options: CODING_SYSTEMS.map((s) => ({
2174
+ value: s,
2175
+ label: s
2176
+ })),
2177
+ value: c.system,
2178
+ onValueChange: (v) => updateCoding(i, { system: v }),
2179
+ disabled: codingUnknown
2180
+ }
2181
+ )
2182
+ ] }),
2183
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2184
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs sm:hidden", children: m("Code") }),
2185
+ /* @__PURE__ */ jsx(
2186
+ Input,
2187
+ {
2188
+ "aria-label": m("Code {index}", { index: i + 1 }),
2189
+ value: c.code,
2190
+ onChange: (e) => updateCoding(i, { code: e.target.value }),
2191
+ placeholder: m("Code"),
2192
+ disabled: codingUnknown,
2193
+ className: "font-mono"
2194
+ }
2195
+ )
2196
+ ] }),
2197
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2198
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs sm:hidden", children: m("Display") }),
2199
+ /* @__PURE__ */ jsx(
2200
+ Input,
2201
+ {
2202
+ "aria-label": m("Display {index}", { index: i + 1 }),
2203
+ value: c.display ?? "",
2204
+ onChange: (e) => updateCoding(i, { display: e.target.value }),
2205
+ placeholder: m("Display"),
2206
+ disabled: codingUnknown
2207
+ }
2208
+ )
2209
+ ] }),
2210
+ /* @__PURE__ */ jsxs("div", { className: "flex min-h-10 items-center gap-1.5 text-xs sm:justify-center", children: [
2211
+ /* @__PURE__ */ jsx(
2212
+ Radio,
2213
+ {
2214
+ value: String(i),
2215
+ "aria-label": m("Primary code: {system} {code}", {
2216
+ system: c.system,
2217
+ code: c.code || m("row {index}", { index: i + 1 })
2218
+ }),
2219
+ disabled: codingUnknown
2220
+ }
2221
+ ),
2222
+ /* @__PURE__ */ jsx("span", { className: "sm:sr-only", children: m("Primary code") })
2223
+ ] }),
2224
+ /* @__PURE__ */ jsx(
2225
+ Button,
2226
+ {
2227
+ variant: "ghost",
2228
+ size: "icon",
2229
+ "aria-label": m("Remove code {index}", {
2230
+ index: i + 1
2231
+ }),
2232
+ disabled: codingUnknown,
2233
+ onClick: () => setCoding(
2234
+ (prev) => withSinglePrimary(
2235
+ prev.filter((_, index) => index !== i)
2236
+ )
2237
+ ),
2238
+ className: "h-10 w-10 justify-self-start sm:justify-self-center",
2239
+ children: /* @__PURE__ */ jsx(Trash2, { size: 14 })
2240
+ }
2241
+ )
2242
+ ]
2243
+ },
2244
+ i
2245
+ ))
2246
+ }
2247
+ )
2248
+ ] }),
2249
+ /* @__PURE__ */ jsx(
2250
+ Select,
2251
+ {
2252
+ label: m("Verification status"),
2253
+ options: VERIFICATION_OPTIONS.map((option) => ({
2254
+ ...option,
2255
+ label: m(option.label)
2256
+ })),
2257
+ value: verification,
2258
+ onValueChange: (v) => setVerification(v)
2259
+ }
2260
+ ),
2261
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-x-4 gap-y-2", children: [
2262
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2263
+ /* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: m("Severity") }),
2264
+ /* @__PURE__ */ jsx(
2265
+ "div",
2266
+ {
2267
+ role: "group",
2268
+ "aria-label": m("Severity"),
2269
+ className: "border-border inline-flex items-center overflow-hidden rounded-md border",
2270
+ children: SEVERITIES.map((s) => {
2271
+ const active = severity === s;
2272
+ return /* @__PURE__ */ jsx(
2273
+ "button",
2274
+ {
2275
+ type: "button",
2276
+ "aria-pressed": active,
2277
+ disabled: fields.severity?.known === false,
2278
+ onClick: () => setSeverity(active ? void 0 : s),
2279
+ className: cn(
2280
+ "border-border border-s px-2.5 py-1 text-xs font-medium capitalize transition-colors first:border-s-0",
2281
+ "disabled:opacity-40",
2282
+ active ? "bg-primary-600 text-white" : "bg-background text-muted-foreground hover:text-foreground"
2283
+ ),
2284
+ children: m(s)
2285
+ },
2286
+ s
2287
+ );
2288
+ })
2289
+ }
2290
+ )
2291
+ ] }),
2292
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2293
+ /* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: m("Certainty") }),
2294
+ /* @__PURE__ */ jsx(
2295
+ FieldFlag,
2296
+ {
2297
+ label: m("Severity"),
2298
+ value: fields.severity,
2299
+ onChange: setField("severity"),
2300
+ hasValue: Boolean(severity)
2301
+ }
2302
+ )
2303
+ ] })
2304
+ ] }),
2305
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2306
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 text-sm font-medium", children: [
2307
+ m("Onset"),
2308
+ /* @__PURE__ */ jsx(
2309
+ FieldFlag,
2310
+ {
2311
+ label: m("Onset"),
2312
+ value: fields.onset,
2313
+ onChange: setField("onset"),
2314
+ hasValue: Boolean(onsetDate || onsetFuzzy)
2315
+ }
2316
+ )
2317
+ ] }),
2318
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 items-end gap-2 sm:grid-cols-[minmax(10rem,0.8fr)_auto_minmax(14rem,1.2fr)]", children: [
2319
+ /* @__PURE__ */ jsx(
2320
+ DateInput,
2321
+ {
2322
+ "aria-label": m("Onset date (exact)"),
2323
+ value: onsetDate,
2324
+ onChange: (value) => {
2325
+ setOnsetDate(value);
2326
+ if (value) setOnsetFuzzy("");
2327
+ },
2328
+ disabled: fields.onset?.known === false,
2329
+ validateOnBlur: true,
2330
+ error: hasInvalidOnsetDate ? m("Please enter a valid date (MM/DD/YYYY)") : void 0,
2331
+ width: "full"
2332
+ }
2333
+ ),
2334
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground pb-3 text-center text-xs", children: m("or") }),
2335
+ /* @__PURE__ */ jsx(
2336
+ Input,
2337
+ {
2338
+ "aria-label": m("Onset (fuzzy)"),
2339
+ value: onsetFuzzy,
2340
+ onChange: (e) => {
2341
+ setOnsetFuzzy(e.target.value);
2342
+ if (e.target.value) setOnsetDate("");
2343
+ },
2344
+ disabled: fields.onset?.known === false,
2345
+ placeholder: m(
2346
+ 'Fuzzy \u2014 "since her twenties", "~3 months ago"'
2347
+ )
2348
+ }
2349
+ )
2350
+ ] })
2351
+ ] }),
2352
+ /* @__PURE__ */ jsx(
2353
+ Textarea,
2354
+ {
2355
+ label: m("Note"),
2356
+ value: note,
2357
+ onChange: (e) => setNote(e.target.value),
2358
+ placeholder: m(
2359
+ "e.g. patient thinks it started after the fall, not sure"
2360
+ ),
2361
+ rows: 2
2362
+ }
2363
+ ),
2364
+ Object.keys(fields).length > 0 && /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground flex items-center gap-1.5 text-xs", children: [
2365
+ /* @__PURE__ */ jsx(HelpCircle, { size: 12 }),
2366
+ /* @__PURE__ */ jsxs("span", { children: [
2367
+ m("Uncertainty recorded:"),
2368
+ " ",
2369
+ Object.entries(fields).map(
2370
+ ([k, f]) => f?.known === false ? m("{field} unknown", { field: m(k) }) : m("{confidence} confidence in {field}", {
2371
+ confidence: m(f?.confidence ?? "unspecified"),
2372
+ field: m(k)
2373
+ })
2374
+ ).join(", ")
2375
+ ] })
2376
+ ] })
2377
+ ] })
2378
+ ] }) }),
2379
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
2380
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => onOpenChange(false), children: m("Cancel") }),
2381
+ /* @__PURE__ */ jsx(Button, { onClick: handleSave, disabled: saveDisabled, children: m(
2382
+ mode === "relate" ? "Relate" : mode === "observe" ? "Add observation" : "Save"
2383
+ ) }),
2384
+ mode !== "relate" && mode !== "observe" && !text.trim() && /* @__PURE__ */ jsx(Badge, { variant: "outline", size: "sm", className: "text-muted-foreground", children: m("name required") })
2385
+ ] })
2386
+ ]
2387
+ }
2388
+ );
2389
+ }
2390
+ ConditionEditor.displayName = "ConditionEditor";
2391
+ function getDefaultPresets(labels = {}) {
2392
+ const {
2393
+ today = "Today",
2394
+ thisWeek = "This Week",
2395
+ thisMonth = "This Month",
2396
+ lastMonth = "Last Month",
2397
+ last24Hours = "Last 24 Hours",
2398
+ last7Days = "Last 7 Days",
2399
+ last30Days = "Last 30 Days"
2400
+ } = labels;
2401
+ return [
2402
+ { key: "today", label: today },
2403
+ { key: "this-week", label: thisWeek },
2404
+ { key: "this-month", label: thisMonth },
2405
+ { key: "last-month", label: lastMonth },
2406
+ { key: "last-24-hours", label: last24Hours },
2407
+ { key: "last-7-days", label: last7Days },
2408
+ { key: "last-30-days", label: last30Days }
2409
+ ];
2410
+ }
2411
+ function getExtendedPresets(labels = {}) {
2412
+ const {
2413
+ today = "Today",
2414
+ yesterday = "Yesterday",
2415
+ thisWeek = "This Week",
2416
+ lastWeek = "Last Week",
2417
+ thisMonth = "This Month",
2418
+ lastMonth = "Last Month",
2419
+ thisQuarter = "This Quarter",
2420
+ lastQuarter = "Last Quarter",
2421
+ thisYear = "This Year",
2422
+ lastYear = "Last Year",
2423
+ last24Hours = "Last 24 Hours",
2424
+ last7Days = "Last 7 Days",
2425
+ last30Days = "Last 30 Days",
2426
+ last90Days = "Last 90 Days",
2427
+ last365Days = "Last 365 Days"
2428
+ } = labels;
2429
+ return [
2430
+ { key: "today", label: today },
2431
+ { key: "yesterday", label: yesterday },
2432
+ { key: "last-24-hours", label: last24Hours },
2433
+ { key: "this-week", label: thisWeek },
2434
+ { key: "last-week", label: lastWeek },
2435
+ { key: "last-7-days", label: last7Days },
2436
+ { key: "this-month", label: thisMonth },
2437
+ { key: "last-month", label: lastMonth },
2438
+ { key: "last-30-days", label: last30Days },
2439
+ { key: "this-quarter", label: thisQuarter },
2440
+ { key: "last-quarter", label: lastQuarter },
2441
+ { key: "last-90-days", label: last90Days },
2442
+ { key: "this-year", label: thisYear },
2443
+ { key: "last-year", label: lastYear },
2444
+ { key: "last-365-days", label: last365Days }
2445
+ ];
2446
+ }
2447
+ function endOfDay(d) {
2448
+ return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);
2449
+ }
2450
+ function calculateDateRange(presetKey) {
2451
+ const now = /* @__PURE__ */ new Date();
2452
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
2453
+ switch (presetKey) {
2454
+ case "today":
2455
+ return { start: today, end: endOfDay(today) };
2456
+ case "yesterday": {
2457
+ const yesterday = new Date(today);
2458
+ yesterday.setDate(today.getDate() - 1);
2459
+ return { start: yesterday, end: endOfDay(yesterday) };
2460
+ }
2461
+ case "this-week": {
2462
+ const dayOfWeek = today.getDay();
2463
+ const sunday = new Date(today);
2464
+ sunday.setDate(today.getDate() - dayOfWeek);
2465
+ const saturday = new Date(sunday);
2466
+ saturday.setDate(sunday.getDate() + 6);
2467
+ return { start: sunday, end: endOfDay(saturday) };
2468
+ }
2469
+ case "last-week": {
2470
+ const dayOfWeek = today.getDay();
2471
+ const lastSunday = new Date(today);
2472
+ lastSunday.setDate(today.getDate() - dayOfWeek - 7);
2473
+ const lastSaturday = new Date(lastSunday);
2474
+ lastSaturday.setDate(lastSunday.getDate() + 6);
2475
+ return { start: lastSunday, end: endOfDay(lastSaturday) };
2476
+ }
2477
+ case "this-month": {
2478
+ const firstDay = new Date(now.getFullYear(), now.getMonth(), 1);
2479
+ const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0);
2480
+ return { start: firstDay, end: endOfDay(lastDay) };
2481
+ }
2482
+ case "last-month": {
2483
+ const firstDay = new Date(now.getFullYear(), now.getMonth() - 1, 1);
2484
+ const lastDay = new Date(now.getFullYear(), now.getMonth(), 0);
2485
+ return { start: firstDay, end: endOfDay(lastDay) };
2486
+ }
2487
+ case "this-quarter": {
2488
+ const quarter = Math.floor(now.getMonth() / 3);
2489
+ const firstDay = new Date(now.getFullYear(), quarter * 3, 1);
2490
+ const lastDay = new Date(now.getFullYear(), quarter * 3 + 3, 0);
2491
+ return { start: firstDay, end: endOfDay(lastDay) };
2492
+ }
2493
+ case "last-quarter": {
2494
+ const quarter = Math.floor(now.getMonth() / 3);
2495
+ const firstDay = new Date(now.getFullYear(), (quarter - 1) * 3, 1);
2496
+ const lastDay = new Date(now.getFullYear(), quarter * 3, 0);
2497
+ return { start: firstDay, end: endOfDay(lastDay) };
2498
+ }
2499
+ case "this-year": {
2500
+ const firstDay = new Date(now.getFullYear(), 0, 1);
2501
+ const lastDay = new Date(now.getFullYear(), 11, 31);
2502
+ return { start: firstDay, end: endOfDay(lastDay) };
2503
+ }
2504
+ case "last-year": {
2505
+ const firstDay = new Date(now.getFullYear() - 1, 0, 1);
2506
+ const lastDay = new Date(now.getFullYear() - 1, 11, 31);
2507
+ return { start: firstDay, end: endOfDay(lastDay) };
2508
+ }
2509
+ case "last-24-hours":
2510
+ return {
2511
+ start: new Date(now.getTime() - 24 * 60 * 60 * 1e3),
2512
+ end: now
2513
+ };
2514
+ case "last-7-days":
2515
+ return {
2516
+ start: new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3),
2517
+ end: now
2518
+ };
2519
+ case "last-30-days":
2520
+ return {
2521
+ start: new Date(now.getTime() - 30 * 24 * 60 * 60 * 1e3),
2522
+ end: now
2523
+ };
2524
+ case "last-90-days":
2525
+ return {
2526
+ start: new Date(now.getTime() - 90 * 24 * 60 * 60 * 1e3),
2527
+ end: now
2528
+ };
2529
+ case "last-365-days":
2530
+ return {
2531
+ start: new Date(now.getTime() - 365 * 24 * 60 * 60 * 1e3),
2532
+ end: now
2533
+ };
2534
+ default:
2535
+ return { start: null, end: null };
2536
+ }
2537
+ }
2538
+ function formatDateRange(range) {
2539
+ if (!range.start && !range.end) return "";
2540
+ const formatDate = (d) => {
2541
+ if (!d) return "";
2542
+ return d.toLocaleDateString("en-US", {
2543
+ month: "short",
2544
+ day: "numeric",
2545
+ year: "numeric"
2546
+ });
2547
+ };
2548
+ if (range.start && range.end) {
2549
+ return `${formatDate(range.start)} - ${formatDate(range.end)}`;
2550
+ }
2551
+ return formatDate(range.start || range.end);
2552
+ }
2553
+ function DateRangePicker({
2554
+ value,
2555
+ onChange,
2556
+ presets,
2557
+ activePreset,
2558
+ placeholder = "Pick a date range",
2559
+ className,
2560
+ align = "auto",
2561
+ showPresets = true,
2562
+ variant = "desktop",
2563
+ labels = {}
2564
+ }) {
2565
+ const finalPresets = presets || getDefaultPresets(labels);
2566
+ const [isCalendarOpen, setIsCalendarOpen] = React4.useState(false);
2567
+ const [leftMonth, setLeftMonth] = React4.useState(
2568
+ () => value?.start?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth()
2569
+ );
2570
+ const [leftYear, setLeftYear] = React4.useState(
2571
+ () => value?.start?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear()
2572
+ );
2573
+ const [rangeStart, setRangeStart] = React4.useState(
2574
+ value?.start ?? null
2575
+ );
2576
+ const [rangeEnd, setRangeEnd] = React4.useState(
2577
+ value?.end ?? null
2578
+ );
2579
+ const [selectingEnd, setSelectingEnd] = React4.useState(false);
2580
+ const [hoverDate, setHoverDate] = React4.useState(null);
2581
+ const calendarRef = React4.useRef(null);
2582
+ const triggerRef = React4.useRef(null);
2583
+ const [resolvedAlign, setResolvedAlign] = React4.useState(
2584
+ align === "end" ? "end" : "start"
2585
+ );
2586
+ const isMobileVariant = variant === "mobile";
2587
+ const isResponsive = variant === "responsive";
2588
+ const focusTrapRef = useFocusTrap(
2589
+ isMobileVariant && isCalendarOpen
2590
+ );
2591
+ const rightMonth = leftMonth === 11 ? 0 : leftMonth + 1;
2592
+ const rightYear = leftMonth === 11 ? leftYear + 1 : leftYear;
2593
+ React4.useEffect(() => {
2594
+ if (!value) {
2595
+ setRangeStart(null);
2596
+ setRangeEnd(null);
2597
+ return;
2598
+ }
2599
+ setRangeStart(value.start ?? null);
2600
+ setRangeEnd(value.end ?? null);
2601
+ }, [value]);
2602
+ const wrapperRef = React4.useRef(null);
2603
+ const outsideRefs = React4.useMemo(
2604
+ () => [wrapperRef, calendarRef],
2605
+ [calendarRef]
2606
+ );
2607
+ useClickOutside(
2608
+ outsideRefs,
2609
+ () => {
2610
+ setIsCalendarOpen(false);
2611
+ setSelectingEnd(false);
2612
+ },
2613
+ isCalendarOpen
2614
+ );
2615
+ const { anchorRef, floatingRef, style } = useAnchoredPosition({
2616
+ open: !isMobileVariant && isCalendarOpen,
2617
+ placement: resolvedAlign === "end" ? "bottom-end" : "bottom-start"
2618
+ });
2619
+ useEscapeKey(() => {
2620
+ setIsCalendarOpen(false);
2621
+ setSelectingEnd(false);
2622
+ triggerRef.current?.focus();
2623
+ }, isCalendarOpen);
2624
+ React4.useEffect(() => {
2625
+ if (isStorybookDocsMode()) return;
2626
+ if (isMobileVariant && isCalendarOpen) {
2627
+ const prev = document.body.style.overflow;
2628
+ document.body.style.overflow = "hidden";
2629
+ return () => {
2630
+ document.body.style.overflow = prev;
2631
+ };
2632
+ }
2633
+ }, [isMobileVariant, isCalendarOpen]);
2634
+ React4.useLayoutEffect(() => {
2635
+ if (isMobileVariant || !isCalendarOpen) return;
2636
+ if (align === "start" || align === "end") {
2637
+ setResolvedAlign(align);
2638
+ return;
2639
+ }
2640
+ if (typeof window === "undefined") return;
2641
+ const trigger = triggerRef.current;
2642
+ if (!trigger) return;
2643
+ const rect = trigger.getBoundingClientRect();
2644
+ const estimatedPopupWidth = showPresets ? 840 : 640;
2645
+ const margin = 8;
2646
+ const overflowsRight = rect.left + estimatedPopupWidth > window.innerWidth - margin;
2647
+ const fitsLeftAligned = rect.right - estimatedPopupWidth >= margin;
2648
+ setResolvedAlign(overflowsRight && fitsLeftAligned ? "end" : "start");
2649
+ }, [align, isCalendarOpen, isMobileVariant, showPresets]);
2650
+ const handlePresetSelect = (presetKey) => {
2651
+ const range = calculateDateRange(presetKey);
2652
+ setRangeStart(range.start);
2653
+ setRangeEnd(range.end);
2654
+ setSelectingEnd(false);
2655
+ setIsCalendarOpen(false);
2656
+ if (range.start) {
2657
+ setLeftMonth(range.start.getMonth());
2658
+ setLeftYear(range.start.getFullYear());
2659
+ }
2660
+ onChange(range, presetKey);
2661
+ };
2662
+ const handleDayClick = (day, month, year) => {
2663
+ const clicked = new Date(year, month, day);
2664
+ if (!selectingEnd) {
2665
+ setRangeStart(clicked);
2666
+ setRangeEnd(null);
2667
+ setSelectingEnd(true);
2668
+ } else {
2669
+ let start = rangeStart;
2670
+ let end = clicked;
2671
+ if (end < start) {
2672
+ [start, end] = [end, start];
2673
+ }
2674
+ setRangeStart(start);
2675
+ setRangeEnd(end);
2676
+ setSelectingEnd(false);
2677
+ if (variant !== "mobile") {
2678
+ setIsCalendarOpen(false);
2679
+ }
2680
+ onChange({ start, end });
2681
+ }
2682
+ };
2683
+ const toggleCalendar = () => {
2684
+ if (!isCalendarOpen) {
2685
+ if (value?.start) {
2686
+ setRangeStart(value.start);
2687
+ setRangeEnd(value.end ?? null);
2688
+ setLeftMonth(value.start.getMonth());
2689
+ setLeftYear(value.start.getFullYear());
2690
+ } else {
2691
+ const now = /* @__PURE__ */ new Date();
2692
+ setLeftMonth(now.getMonth());
2693
+ setLeftYear(now.getFullYear());
2694
+ }
2695
+ setSelectingEnd(false);
2696
+ }
2697
+ setIsCalendarOpen(!isCalendarOpen);
2698
+ };
2699
+ const goToPrevMonth = () => {
2700
+ if (leftMonth === 0) {
2701
+ setLeftMonth(11);
2702
+ setLeftYear(leftYear - 1);
2703
+ } else {
2704
+ setLeftMonth(leftMonth - 1);
2705
+ }
2706
+ };
2707
+ const goToNextMonth = () => {
2708
+ if (leftMonth === 11) {
2709
+ setLeftMonth(0);
2710
+ setLeftYear(leftYear + 1);
2711
+ } else {
2712
+ setLeftMonth(leftMonth + 1);
2713
+ }
2714
+ };
2715
+ const getDaysInMonth = (month, year) => new Date(year, month + 1, 0).getDate();
2716
+ const getFirstDayOfMonth = (month, year) => new Date(year, month, 1).getDay();
2717
+ const isSameDay = (a, b) => a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
2718
+ const isInRange = (date) => {
2719
+ const start = rangeStart;
2720
+ const end = selectingEnd ? hoverDate : rangeEnd;
2721
+ if (!start || !end) return false;
2722
+ const [lo, hi] = start <= end ? [start, end] : [end, start];
2723
+ return date > lo && date < hi;
2724
+ };
2725
+ const isStart = (date) => {
2726
+ if (!rangeStart) return false;
2727
+ return isSameDay(date, rangeStart);
2728
+ };
2729
+ const isEnd = (date) => {
2730
+ const end = selectingEnd ? hoverDate : rangeEnd;
2731
+ if (!end) return false;
2732
+ return isSameDay(date, end);
2733
+ };
2734
+ const isToday = (date) => {
2735
+ const today = /* @__PURE__ */ new Date();
2736
+ return isSameDay(date, today);
2737
+ };
2738
+ const displayValue = value ? formatDateRange(value) : "";
2739
+ const monthNames = React4.useMemo(
2740
+ () => Array.from(
2741
+ { length: 12 },
2742
+ (_, i) => new Intl.DateTimeFormat(void 0, { month: "long" }).format(
2743
+ new Date(2e3, i, 1)
2744
+ )
2745
+ ),
2746
+ []
2747
+ );
2748
+ const weekdayNames = React4.useMemo(
2749
+ () => Array.from(
2750
+ { length: 7 },
2751
+ (_, i) => new Intl.DateTimeFormat(void 0, { weekday: "short" }).format(
2752
+ new Date(1970, 0, 4 + i)
2753
+ )
2754
+ ),
2755
+ []
2756
+ );
2757
+ const renderMonthGrid = (month, year) => {
2758
+ const daysInMonth = getDaysInMonth(month, year);
2759
+ const firstDay = getFirstDayOfMonth(month, year);
2760
+ const prevMonth = month === 0 ? 11 : month - 1;
2761
+ const prevYear = month === 0 ? year - 1 : year;
2762
+ const daysInPrevMonth = getDaysInMonth(prevMonth, prevYear);
2763
+ const cells = [];
2764
+ for (let i = firstDay - 1; i >= 0; i--) {
2765
+ cells.push({
2766
+ day: daysInPrevMonth - i,
2767
+ month: prevMonth,
2768
+ year: prevYear,
2769
+ isCurrentMonth: false
2770
+ });
2771
+ }
2772
+ for (let i = 1; i <= daysInMonth; i++) {
2773
+ cells.push({ day: i, month, year, isCurrentMonth: true });
2774
+ }
2775
+ const nextMonth = month === 11 ? 0 : month + 1;
2776
+ const nextYear = month === 11 ? year + 1 : year;
2777
+ const remaining = 42 - cells.length;
2778
+ for (let i = 1; i <= remaining; i++) {
2779
+ cells.push({
2780
+ day: i,
2781
+ month: nextMonth,
2782
+ year: nextYear,
2783
+ isCurrentMonth: false
2784
+ });
2785
+ }
2786
+ return /* @__PURE__ */ jsxs("div", { className: "w-[280px]", "data-slot": "date-range-grid", children: [
2787
+ /* @__PURE__ */ jsx("div", { className: "mb-1 grid grid-cols-7", "data-slot": "date-range-weekdays", children: weekdayNames.map((dayName) => /* @__PURE__ */ jsx(
2788
+ "div",
2789
+ {
2790
+ className: "text-muted-foreground py-1 text-center text-xs font-medium",
2791
+ "data-slot": "date-range-weekday",
2792
+ children: dayName
2793
+ },
2794
+ dayName
2795
+ )) }),
2796
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7", "data-slot": "date-range-day-grid", children: cells.map((cell, index) => {
2797
+ const date = new Date(cell.year, cell.month, cell.day);
2798
+ const inRange = isInRange(date);
2799
+ const start = isStart(date);
2800
+ const end = isEnd(date);
2801
+ const today = isToday(date);
2802
+ return /* @__PURE__ */ jsx(
2803
+ "button",
2804
+ {
2805
+ type: "button",
2806
+ "data-slot": "date-range-day",
2807
+ onClick: () => handleDayClick(cell.day, cell.month, cell.year),
2808
+ onMouseEnter: () => {
2809
+ if (selectingEnd) {
2810
+ setHoverDate(date);
2811
+ }
2812
+ },
2813
+ className: cn(
2814
+ "relative h-9 w-10 text-center text-sm",
2815
+ "focus:ring-ring focus:z-10 focus:ring-2 focus:outline-none",
2816
+ "transition-colors",
2817
+ // Not current month — dimmed
2818
+ !cell.isCurrentMonth && "text-muted-foreground/50",
2819
+ // Current month — normal
2820
+ cell.isCurrentMonth && "text-foreground",
2821
+ // Hover
2822
+ "hover:bg-muted",
2823
+ // In range background
2824
+ inRange && "bg-muted",
2825
+ // Start date
2826
+ start && "bg-foreground text-background hover:bg-foreground rounded-s-md font-semibold",
2827
+ // End date
2828
+ end && !start && "border-foreground text-foreground rounded-e-md border font-semibold",
2829
+ // Today indicator (only if not start/end)
2830
+ today && !start && !end && "font-semibold"
2831
+ ),
2832
+ children: cell.day
2833
+ },
2834
+ index
2835
+ );
2836
+ }) })
2837
+ ] });
2838
+ };
2839
+ return /* @__PURE__ */ jsxs(
2840
+ "div",
2841
+ {
2842
+ ref: (node) => {
2843
+ wrapperRef.current = node;
2844
+ anchorRef.current = node;
2845
+ },
2846
+ className: cn("relative inline-block", className),
2847
+ "data-slot": "date-range-picker",
2848
+ children: [
2849
+ /* @__PURE__ */ jsxs(
2850
+ "button",
2851
+ {
2852
+ ref: triggerRef,
2853
+ type: "button",
2854
+ onClick: toggleCalendar,
2855
+ "aria-expanded": isCalendarOpen,
2856
+ "aria-haspopup": "dialog",
2857
+ "data-slot": "date-range-trigger",
2858
+ className: cn(
2859
+ "border-input bg-background hover:bg-muted",
2860
+ "inline-flex w-[300px] items-center gap-2 rounded-md border px-4 py-2 text-start text-sm font-normal",
2861
+ "focus:ring-ring focus:ring-2 focus:outline-none",
2862
+ "transition-colors",
2863
+ !displayValue && "text-muted-foreground"
2864
+ ),
2865
+ children: [
2866
+ /* @__PURE__ */ jsx(Calendar, { className: "h-4 w-4", "data-slot": "date-range-trigger-icon" }),
2867
+ displayValue || placeholder
2868
+ ]
2869
+ }
2870
+ ),
2871
+ isMobileVariant && isCalendarOpen && // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
2872
+ /* @__PURE__ */ jsx(
2873
+ "div",
2874
+ {
2875
+ "data-slot": "date-range-mobile-overlay",
2876
+ className: "fixed inset-0 z-50 flex items-end justify-center bg-black/40",
2877
+ onClick: (event) => {
2878
+ if (event.target !== event.currentTarget) return;
2879
+ setIsCalendarOpen(false);
2880
+ setSelectingEnd(false);
2881
+ setHoverDate(null);
2882
+ if (triggerRef.current instanceof HTMLElement) {
2883
+ triggerRef.current.focus();
2884
+ }
2885
+ },
2886
+ children: /* @__PURE__ */ jsxs(
2887
+ "div",
2888
+ {
2889
+ ref: (node) => {
2890
+ calendarRef.current = node;
2891
+ focusTrapRef.current = node;
2892
+ },
2893
+ className: "bg-background animate-in slide-in-from-bottom max-h-[90vh] w-full max-w-md overflow-y-auto rounded-t-2xl px-6 pt-4 pb-6 shadow-xl",
2894
+ role: "dialog",
2895
+ "aria-modal": "true",
2896
+ "aria-labelledby": "mobile-date-range-title",
2897
+ "data-slot": "date-range-mobile-sheet",
2898
+ children: [
2899
+ /* @__PURE__ */ jsx(
2900
+ "div",
2901
+ {
2902
+ className: "bg-muted mx-auto mb-4 h-1 w-10 rounded-full",
2903
+ "data-slot": "date-range-mobile-handle"
2904
+ }
2905
+ ),
2906
+ /* @__PURE__ */ jsx(
2907
+ "h3",
2908
+ {
2909
+ id: "mobile-date-range-title",
2910
+ className: "mb-4 text-lg font-semibold",
2911
+ "data-slot": "date-range-mobile-title",
2912
+ children: placeholder
2913
+ }
2914
+ ),
2915
+ /* @__PURE__ */ jsxs(
2916
+ "div",
2917
+ {
2918
+ className: "mb-3 flex items-center justify-center gap-4",
2919
+ "data-slot": "date-range-month-header",
2920
+ children: [
2921
+ /* @__PURE__ */ jsx(
2922
+ "button",
2923
+ {
2924
+ type: "button",
2925
+ onClick: goToPrevMonth,
2926
+ className: "hover:bg-muted rounded-md p-1 transition-colors",
2927
+ "aria-label": "Previous month",
2928
+ "data-slot": "date-range-nav",
2929
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4 rtl:-scale-x-100" })
2930
+ }
2931
+ ),
2932
+ /* @__PURE__ */ jsxs(
2933
+ "div",
2934
+ {
2935
+ className: "text-sm font-medium",
2936
+ "data-slot": "date-range-month-label",
2937
+ children: [
2938
+ monthNames[leftMonth],
2939
+ " ",
2940
+ leftYear
2941
+ ]
2942
+ }
2943
+ ),
2944
+ /* @__PURE__ */ jsx(
2945
+ "button",
2946
+ {
2947
+ type: "button",
2948
+ onClick: goToNextMonth,
2949
+ className: "hover:bg-muted rounded-md p-1 transition-colors",
2950
+ "aria-label": "Next month",
2951
+ "data-slot": "date-range-nav",
2952
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 rtl:-scale-x-100" })
2953
+ }
2954
+ )
2955
+ ]
2956
+ }
2957
+ ),
2958
+ /* @__PURE__ */ jsx(
2959
+ "div",
2960
+ {
2961
+ className: "flex justify-center",
2962
+ onMouseLeave: () => setHoverDate(null),
2963
+ children: renderMonthGrid(leftMonth, leftYear)
2964
+ }
2965
+ ),
2966
+ /* @__PURE__ */ jsx(
2967
+ "button",
2968
+ {
2969
+ type: "button",
2970
+ "data-slot": "date-range-mobile-done",
2971
+ onClick: () => {
2972
+ setIsCalendarOpen(false);
2973
+ setSelectingEnd(false);
2974
+ if (triggerRef.current instanceof HTMLElement) {
2975
+ triggerRef.current.focus();
2976
+ }
2977
+ },
2978
+ className: "border-input hover:bg-muted mt-6 w-full rounded-lg border py-3 text-sm font-medium transition-colors",
2979
+ children: labels?.done ?? "Done"
2980
+ }
2981
+ )
2982
+ ]
2983
+ }
2984
+ )
2985
+ }
2986
+ ),
2987
+ !isMobileVariant && isCalendarOpen && createPortal(
2988
+ /* @__PURE__ */ jsx(
2989
+ "div",
2990
+ {
2991
+ ref: (node) => {
2992
+ calendarRef.current = node;
2993
+ floatingRef.current = node;
2994
+ },
2995
+ style,
2996
+ className: cn(
2997
+ "overflow-auto",
2998
+ "bg-background border-border rounded-lg border shadow-lg"
2999
+ ),
3000
+ role: "dialog",
3001
+ "aria-label": "Choose date range",
3002
+ "data-slot": "date-range-popup",
3003
+ children: /* @__PURE__ */ jsxs("div", { className: "flex", children: [
3004
+ showPresets && /* @__PURE__ */ jsx(
3005
+ "div",
3006
+ {
3007
+ className: cn(
3008
+ "border-border flex w-[200px] shrink-0 flex-col gap-0.5 border-e p-3",
3009
+ isResponsive && "hidden md:flex"
3010
+ ),
3011
+ "data-slot": "date-range-presets",
3012
+ children: finalPresets.map((preset) => /* @__PURE__ */ jsx(
3013
+ "button",
3014
+ {
3015
+ type: "button",
3016
+ onClick: () => handlePresetSelect(preset.key),
3017
+ "data-slot": "date-range-preset",
3018
+ className: cn(
3019
+ "rounded-md px-3 py-1.5 text-start text-sm transition-colors",
3020
+ "hover:bg-muted",
3021
+ activePreset === preset.key && "bg-primary-800 text-white"
3022
+ ),
3023
+ children: preset.label
3024
+ },
3025
+ preset.key
3026
+ ))
3027
+ }
3028
+ ),
3029
+ /* @__PURE__ */ jsxs("div", { className: "p-4", "data-slot": "date-range-calendar", children: [
3030
+ /* @__PURE__ */ jsx(
3031
+ "p",
3032
+ {
3033
+ className: "text-muted-foreground mb-4 text-sm",
3034
+ "data-slot": "date-range-subtitle",
3035
+ children: "Select a start and end date from the calendar."
3036
+ }
3037
+ ),
3038
+ /* @__PURE__ */ jsxs(
3039
+ "div",
3040
+ {
3041
+ className: "flex gap-8",
3042
+ onMouseLeave: () => setHoverDate(null),
3043
+ "data-slot": "date-range-calendars",
3044
+ children: [
3045
+ /* @__PURE__ */ jsxs("div", { children: [
3046
+ /* @__PURE__ */ jsxs(
3047
+ "div",
3048
+ {
3049
+ className: "mb-3 flex items-center",
3050
+ "data-slot": "date-range-month-header",
3051
+ children: [
3052
+ /* @__PURE__ */ jsx(
3053
+ "button",
3054
+ {
3055
+ type: "button",
3056
+ onClick: goToPrevMonth,
3057
+ className: "hover:bg-muted rounded-md p-1 transition-colors",
3058
+ "aria-label": "Previous month",
3059
+ "data-slot": "date-range-nav",
3060
+ children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4 rtl:-scale-x-100" })
3061
+ }
3062
+ ),
3063
+ /* @__PURE__ */ jsxs(
3064
+ "div",
3065
+ {
3066
+ className: "flex-1 text-center text-sm font-medium",
3067
+ "data-slot": "date-range-month-label",
3068
+ children: [
3069
+ monthNames[leftMonth],
3070
+ " ",
3071
+ leftYear
3072
+ ]
3073
+ }
3074
+ ),
3075
+ isResponsive && /* @__PURE__ */ jsx(
3076
+ "button",
3077
+ {
3078
+ type: "button",
3079
+ onClick: goToNextMonth,
3080
+ className: "hover:bg-muted rounded-md p-1 transition-colors md:hidden",
3081
+ "aria-label": "Next month",
3082
+ "data-slot": "date-range-nav",
3083
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 rtl:-scale-x-100" })
3084
+ }
3085
+ )
3086
+ ]
3087
+ }
3088
+ ),
3089
+ renderMonthGrid(leftMonth, leftYear)
3090
+ ] }),
3091
+ /* @__PURE__ */ jsxs("div", { className: cn(isResponsive && "hidden md:block"), children: [
3092
+ /* @__PURE__ */ jsxs(
3093
+ "div",
3094
+ {
3095
+ className: "mb-3 flex items-center",
3096
+ "data-slot": "date-range-month-header",
3097
+ children: [
3098
+ /* @__PURE__ */ jsxs(
3099
+ "div",
3100
+ {
3101
+ className: "flex-1 text-center text-sm font-medium",
3102
+ "data-slot": "date-range-month-label",
3103
+ children: [
3104
+ monthNames[rightMonth],
3105
+ " ",
3106
+ rightYear
3107
+ ]
3108
+ }
3109
+ ),
3110
+ /* @__PURE__ */ jsx(
3111
+ "button",
3112
+ {
3113
+ type: "button",
3114
+ onClick: goToNextMonth,
3115
+ className: "hover:bg-muted rounded-md p-1 transition-colors",
3116
+ "aria-label": "Next month",
3117
+ "data-slot": "date-range-nav",
3118
+ children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4 rtl:-scale-x-100" })
3119
+ }
3120
+ )
3121
+ ]
3122
+ }
3123
+ ),
3124
+ renderMonthGrid(rightMonth, rightYear)
3125
+ ] })
3126
+ ]
3127
+ }
3128
+ )
3129
+ ] })
3130
+ ] })
3131
+ }
3132
+ ),
3133
+ document.body
3134
+ )
3135
+ ]
3136
+ }
3137
+ );
3138
+ }
3139
+ function DateRangeFilter({
3140
+ value: _value,
3141
+ onChange,
3142
+ presets,
3143
+ activePreset,
3144
+ variant = "outline",
3145
+ className,
3146
+ labels = {}
3147
+ }) {
3148
+ const finalPresets = presets || getDefaultPresets(labels);
3149
+ const activeLabel = finalPresets.find((p) => p.key === activePreset)?.label;
3150
+ const handlePresetSelect = (presetKey) => {
3151
+ const range = calculateDateRange(presetKey);
3152
+ onChange(range, presetKey);
3153
+ };
3154
+ return /* @__PURE__ */ jsx(
3155
+ Dropdown,
3156
+ {
3157
+ trigger: /* @__PURE__ */ jsxs(Button, { variant, size: "md", className, children: [
3158
+ /* @__PURE__ */ jsx(Calendar, { className: "me-2 h-4 w-4" }),
3159
+ activeLabel || labels.filter || "Filter by Date",
3160
+ /* @__PURE__ */ jsx(ChevronDown$1, { className: "ms-2 h-3 w-3" })
3161
+ ] }),
3162
+ className: "w-56",
3163
+ "data-slot": "date-range-filter",
3164
+ children: finalPresets.map((preset) => /* @__PURE__ */ jsx(
3165
+ DropdownItem,
3166
+ {
3167
+ onClick: () => handlePresetSelect(preset.key),
3168
+ className: cn(
3169
+ activePreset === preset.key && "bg-primary/10 text-primary"
3170
+ ),
3171
+ children: preset.label
3172
+ },
3173
+ preset.key
3174
+ ))
3175
+ }
3176
+ );
3177
+ }
3178
+ var GROUP_ORDER = [
3179
+ "unreconciled",
3180
+ "taking",
3181
+ "taking-noncompliant",
3182
+ "not-taking",
3183
+ "unknown"
3184
+ ];
3185
+ var MEDICATION_STATUS_LABELS = {
3186
+ unreconciled: "Unreconciled",
3187
+ taking: "Taking as Directed",
3188
+ "taking-noncompliant": "Not Taking as Directed",
3189
+ "not-taking": "Not Taking",
3190
+ unknown: "Unknown"
3191
+ };
3192
+ var DEFAULT_ACTIONS2 = [
3193
+ "open",
3194
+ "correct",
3195
+ "refill",
3196
+ "add-task",
3197
+ "note",
3198
+ "remove"
3199
+ ];
3200
+ var ACTION_META3 = {
3201
+ open: { label: "Open", icon: ExternalLink },
3202
+ correct: { label: "Correct", icon: Pencil },
3203
+ refill: { label: "Refill", icon: RefreshCw },
3204
+ "add-task": { label: "Add Task", icon: CalendarCheck },
3205
+ note: { label: "Notes", icon: StickyNote },
3206
+ remove: { label: "Remove", icon: Ban },
3207
+ "move-up": { label: "Move Up", icon: ChevronUp },
3208
+ "move-down": { label: "Move Down", icon: ChevronDown }
3209
+ };
3210
+ var STATUS_BUTTON_META = [
3211
+ { status: "taking", label: "Taking", icon: ThumbsUp },
3212
+ {
3213
+ status: "taking-noncompliant",
3214
+ label: "Taking \u2013 Noncompliant",
3215
+ icon: HandMetal
3216
+ },
3217
+ { status: "not-taking", label: "Not Taking", icon: ThumbsDown },
3218
+ { status: "unknown", label: "Unknown", icon: HelpCircle }
3219
+ ];
3220
+ function MedicationRow({
3221
+ medication,
3222
+ actions,
3223
+ readOnly,
3224
+ drag,
3225
+ onMove,
3226
+ onStatusChange,
3227
+ onAction
3228
+ }) {
3229
+ const m = useMessages();
3230
+ const handleRowKeyDown = (e) => {
3231
+ if (e.target !== e.currentTarget) return;
3232
+ if (e.altKey && (e.key === "ArrowUp" || e.key === "ArrowDown")) {
3233
+ e.preventDefault();
3234
+ onMove?.(medication, e.key === "ArrowUp" ? -1 : 1);
3235
+ } else if (e.key === "ArrowUp" || e.key === "ArrowDown") {
3236
+ e.preventDefault();
3237
+ const list = e.currentTarget.closest("ul");
3238
+ if (!list) return;
3239
+ const rows = Array.from(
3240
+ list.querySelectorAll("li[data-medication-id]")
3241
+ );
3242
+ const i = rows.indexOf(e.currentTarget);
3243
+ rows[e.key === "ArrowUp" ? Math.max(0, i - 1) : Math.min(rows.length - 1, i + 1)]?.focus();
3244
+ }
3245
+ };
3246
+ return (
3247
+ // Rows are focus stops when reordering is enabled so drag & drop has a
3248
+ // keyboard equivalent (Alt+↑/↓) — 508.
3249
+ /* eslint-disable jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/no-noninteractive-tabindex */
3250
+ /* @__PURE__ */ jsxs(
3251
+ "li",
3252
+ {
3253
+ "data-slot": "clinical-list-row",
3254
+ "data-medication-id": medication.id,
3255
+ tabIndex: drag.enabled ? 0 : void 0,
3256
+ onKeyDown: drag.enabled ? handleRowKeyDown : void 0,
3257
+ "aria-label": drag.enabled ? m("{name}. Alt plus arrow keys to reorder.", {
3258
+ name: medication.name
3259
+ }) : void 0,
3260
+ ...drag.rowProps(medication.id),
3261
+ className: cn(
3262
+ "group border-border/60 relative flex min-h-11 flex-wrap items-center gap-x-2 gap-y-1 border-b px-1 py-2",
3263
+ "focus-visible:ring-ring focus-visible:ring-2 focus-visible:outline-none",
3264
+ drag.enabled && "cursor-grab active:cursor-grabbing",
3265
+ dragIndicatorClasses(drag, medication.id)
3266
+ ),
3267
+ children: [
3268
+ drag.enabled ? /* @__PURE__ */ jsx(
3269
+ GripVertical,
3270
+ {
3271
+ size: 14,
3272
+ "aria-hidden": true,
3273
+ className: "text-muted-foreground/50 shrink-0"
3274
+ }
3275
+ ) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground/60 select-none", children: "\u2022" }),
3276
+ /* @__PURE__ */ jsx("span", { className: "text-foreground font-medium", children: medication.name }),
3277
+ medication.code && /* @__PURE__ */ jsxs(
3278
+ "span",
3279
+ {
3280
+ className: "text-muted-foreground/70 text-xs",
3281
+ title: medication.code.display,
3282
+ children: [
3283
+ medication.code.system,
3284
+ " ",
3285
+ medication.code.code
3286
+ ]
3287
+ }
3288
+ ),
3289
+ medication.sig && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: medication.sig }),
3290
+ medication.expired && /* @__PURE__ */ jsx("span", { className: "text-sm font-bold text-red-600 dark:text-red-400", children: m("EXPIRED") }),
3291
+ medication.discontinuedDate && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-sm", children: [
3292
+ m("Discontinued on:"),
3293
+ " ",
3294
+ medication.discontinuedDate
3295
+ ] }),
3296
+ (medication.note || medication.task) && /* @__PURE__ */ jsxs("span", { className: "flex w-full basis-full flex-col gap-0.5 ps-4", children: [
3297
+ medication.note && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground flex items-center gap-1.5 text-xs", children: [
3298
+ /* @__PURE__ */ jsx(StickyNote, { size: 12 }),
3299
+ medication.note
3300
+ ] }),
3301
+ medication.task && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground flex items-center gap-1.5 text-xs", children: [
3302
+ /* @__PURE__ */ jsx(CalendarCheck, { size: 12 }),
3303
+ m("Task:"),
3304
+ " ",
3305
+ medication.task
3306
+ ] })
3307
+ ] }),
3308
+ !readOnly && /* @__PURE__ */ jsxs(
3309
+ RowActionToolbar,
3310
+ {
3311
+ label: m("Actions for {name}", { name: medication.name }),
3312
+ children: [
3313
+ actions.includes("open") && /* @__PURE__ */ jsx(
3314
+ RowIconButton,
3315
+ {
3316
+ label: m(ACTION_META3.open.label),
3317
+ icon: ACTION_META3.open.icon,
3318
+ onClick: () => onAction?.(medication, "open")
3319
+ }
3320
+ ),
3321
+ STATUS_BUTTON_META.map(({ status, label, icon }) => /* @__PURE__ */ jsx(
3322
+ RowIconButton,
3323
+ {
3324
+ label: m(label),
3325
+ icon,
3326
+ active: medication.status === status,
3327
+ onClick: () => onStatusChange?.(medication, status)
3328
+ },
3329
+ status
3330
+ )),
3331
+ actions.filter((a) => a !== "open").map((action) => /* @__PURE__ */ jsx(
3332
+ RowIconButton,
3333
+ {
3334
+ label: m(ACTION_META3[action].label),
3335
+ icon: ACTION_META3[action].icon,
3336
+ onClick: () => onAction?.(medication, action)
3337
+ },
3338
+ action
3339
+ ))
3340
+ ]
3341
+ }
3342
+ )
3343
+ ]
3344
+ }
3345
+ )
3346
+ );
3347
+ }
3348
+ var MedicationList = React4.forwardRef(
3349
+ ({
3350
+ medications,
3351
+ title = "Presenting medications",
3352
+ onStatusChange,
3353
+ onAction,
3354
+ onReorder,
3355
+ actions = DEFAULT_ACTIONS2,
3356
+ quickAddOptions,
3357
+ onQuickAdd,
3358
+ onAddOther,
3359
+ readOnly = false,
3360
+ reconciledMessage = "All medications reconciled.",
3361
+ emptyMessage = "No medications recorded.",
3362
+ addSearch,
3363
+ className,
3364
+ "data-testid": dataTestId,
3365
+ ...props
3366
+ }, ref) => {
3367
+ const m = useMessages();
3368
+ const [announcement, setAnnouncement] = useLiveAnnouncement();
3369
+ const handleStatusChange = React4.useCallback(
3370
+ (medication, status) => {
3371
+ setAnnouncement(
3372
+ m("{name} marked {status}", {
3373
+ name: medication.name,
3374
+ status: m(MEDICATION_STATUS_LABELS[status])
3375
+ })
3376
+ );
3377
+ onStatusChange?.(medication, status);
3378
+ },
3379
+ [m, onStatusChange, setAnnouncement]
3380
+ );
3381
+ const groups = GROUP_ORDER.map((status) => ({
3382
+ status,
3383
+ label: MEDICATION_STATUS_LABELS[status],
3384
+ items: medications.filter((m2) => m2.status === status)
3385
+ })).filter((g) => g.items.length > 0 || g.status === "unreconciled");
3386
+ const statusById = React4.useMemo(
3387
+ () => new Map(medications.map((m2) => [m2.id, m2.status])),
3388
+ [medications]
3389
+ );
3390
+ const statusOf = React4.useCallback(
3391
+ (id) => statusById.get(id),
3392
+ [statusById]
3393
+ );
3394
+ const drag = useDragReorder({
3395
+ ids: medications.map((m2) => m2.id),
3396
+ onReorder: readOnly || !onReorder ? void 0 : (ids) => {
3397
+ setAnnouncement(m("Medication list reordered"));
3398
+ onReorder(ids);
3399
+ },
3400
+ canDropOn: (dragged, target) => statusOf(dragged) === statusOf(target)
3401
+ });
3402
+ const moveMedication = React4.useCallback(
3403
+ (medication, dir) => {
3404
+ if (readOnly || !onReorder) return;
3405
+ const ids = medications.map((m2) => m2.id);
3406
+ const from = ids.indexOf(medication.id);
3407
+ let to = from + dir;
3408
+ while (to >= 0 && to < medications.length && medications[to].status !== medication.status) {
3409
+ to += dir;
3410
+ }
3411
+ if (to < 0 || to >= medications.length) return;
3412
+ [ids[from], ids[to]] = [ids[to], ids[from]];
3413
+ setAnnouncement(
3414
+ m(dir === -1 ? "{name} moved up" : "{name} moved down", {
3415
+ name: medication.name
3416
+ })
3417
+ );
3418
+ onReorder(ids);
3419
+ },
3420
+ [m, medications, onReorder, readOnly, setAnnouncement]
3421
+ );
3422
+ return /* @__PURE__ */ jsxs(
3423
+ Card,
3424
+ {
3425
+ ref,
3426
+ padding: "none",
3427
+ className: cn("w-full", className),
3428
+ "data-testid": dataTestId,
3429
+ ...props,
3430
+ children: [
3431
+ title !== null && /* @__PURE__ */ jsx(CardHeader, { className: "border-border bg-muted/50 border-b px-4 py-3", children: /* @__PURE__ */ jsx("h3", { className: "text-foreground text-sm font-semibold tracking-wide uppercase", children: m(title) }) }),
3432
+ /* @__PURE__ */ jsxs(CardContent, { className: "space-y-6 px-4 py-4", children: [
3433
+ groups.map(({ status, label, items }) => /* @__PURE__ */ jsxs("section", { "aria-label": m(label), children: [
3434
+ /* @__PURE__ */ jsx(
3435
+ "h4",
3436
+ {
3437
+ "data-slot": "clinical-list-heading",
3438
+ className: "border-border text-foreground border-b pb-1 text-sm font-semibold tracking-wide uppercase",
3439
+ children: m(label)
3440
+ }
3441
+ ),
3442
+ items.length > 0 ? /* @__PURE__ */ jsx("ul", { className: "mt-1", children: items.map((medication) => /* @__PURE__ */ jsx(
3443
+ MedicationRow,
3444
+ {
3445
+ medication,
3446
+ actions,
3447
+ readOnly,
3448
+ drag,
3449
+ onMove: moveMedication,
3450
+ onStatusChange: handleStatusChange,
3451
+ onAction
3452
+ },
3453
+ medication.id
3454
+ )) }) : medications.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground mt-2 text-sm", children: m(emptyMessage) }) : /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground mt-2 flex items-center gap-1.5 text-sm", children: [
3455
+ /* @__PURE__ */ jsx(Badge, { variant: "success", size: "sm", children: "\u2713" }),
3456
+ m(reconciledMessage)
3457
+ ] })
3458
+ ] }, status)),
3459
+ !readOnly && (addSearch || quickAddOptions?.length || onAddOther) && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
3460
+ /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs font-semibold tracking-wide uppercase", children: m("Add medication") }),
3461
+ addSearch,
3462
+ (quickAddOptions?.length || onAddOther) && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
3463
+ quickAddOptions?.map((name) => /* @__PURE__ */ jsx(
3464
+ Button,
3465
+ {
3466
+ variant: "ghost",
3467
+ size: "sm",
3468
+ onClick: () => onQuickAdd?.(name),
3469
+ leftIcon: /* @__PURE__ */ jsx(Plus, { size: 12 }),
3470
+ className: "border-border h-auto rounded-full border px-2.5 py-1 text-xs",
3471
+ children: name
3472
+ },
3473
+ name
3474
+ )),
3475
+ onAddOther && /* @__PURE__ */ jsx(
3476
+ Button,
3477
+ {
3478
+ variant: "outline",
3479
+ size: "sm",
3480
+ onClick: onAddOther,
3481
+ leftIcon: /* @__PURE__ */ jsx(Plus, { size: 12 }),
3482
+ className: "h-auto rounded-full px-2.5 py-1 text-xs",
3483
+ children: m("Other\u2026")
3484
+ }
3485
+ )
3486
+ ] })
3487
+ ] })
3488
+ ] }),
3489
+ /* @__PURE__ */ jsx("div", { "aria-live": "polite", className: "sr-only", children: announcement })
3490
+ ]
3491
+ }
3492
+ );
3493
+ }
3494
+ );
3495
+ MedicationList.displayName = "MedicationList";
3496
+ var DOSE_FORMS = [
3497
+ "tablet",
3498
+ "capsule",
3499
+ "solution",
3500
+ "suspension",
3501
+ "syrup",
3502
+ "cream",
3503
+ "ointment",
3504
+ "gel",
3505
+ "patch",
3506
+ "suppository",
3507
+ "spray",
3508
+ "drops",
3509
+ "inhaler",
3510
+ "injection",
3511
+ "lozenge",
3512
+ "powder",
3513
+ "film"
3514
+ ];
3515
+ var FORM_TO_UNIT = {
3516
+ tablet: "tablet",
3517
+ capsule: "capsule",
3518
+ solution: "milliliter",
3519
+ suspension: "milliliter",
3520
+ syrup: "milliliter",
3521
+ cream: "gram",
3522
+ ointment: "gram",
3523
+ gel: "gram",
3524
+ patch: "patch",
3525
+ suppository: "suppository",
3526
+ spray: "spray",
3527
+ drops: "milliliter",
3528
+ inhaler: "each",
3529
+ injection: "milliliter",
3530
+ lozenge: "each",
3531
+ powder: "gram",
3532
+ film: "each"
3533
+ };
3534
+ function doseFormToQuantityUnit(doseForm) {
3535
+ const normalized = doseForm.toLowerCase().trim();
3536
+ return (FORM_TO_UNIT[normalized] ?? normalized) || void 0;
3537
+ }
3538
+ function parseMedicationLabel(label) {
3539
+ const lower = label.toLowerCase();
3540
+ const strengthMatch = lower.match(
3541
+ /(\d{1,7}(?:\.\d{1,4})?(?:\s{0,4}\/\s{0,4}\d{1,7}(?:\.\d{1,4})?)?)\s{0,4}(mg\/ml|mcg\/ml|mg|mcg|g|ml|units?|%|meq)\b/
3542
+ );
3543
+ const doseForm = DOSE_FORMS.find((f) => lower.includes(f));
3544
+ return {
3545
+ strength: strengthMatch ? `${strengthMatch[1].replace(/\s+/g, "")} ${strengthMatch[2]}` : void 0,
3546
+ doseForm
3547
+ };
3548
+ }
3549
+ var SIG_ROUTES = [
3550
+ [/\bby mouth\b|\boral(ly)?\b|\bpo\b/, "oral"],
3551
+ [/\bsublingual(ly)?\b|\bunder the tongue\b|\bsl\b/, "sublingual"],
3552
+ [/\bsubcutaneous(ly)?\b|\bsubq\b|\bsc\b|\bsq\b/, "subcutaneous"],
3553
+ [/\bintramuscular(ly)?\b|\bim\b/, "intramuscular"],
3554
+ [/\bintravenous(ly)?\b|\biv\b/, "intravenous"],
3555
+ [/\binhal(e|ation|ed)\b|\bpuffs?\b|\bnebuliz/, "inhalation"],
3556
+ [/\beyes?\b|\bophthalmic\b/, "ophthalmic"],
3557
+ [/\bears?\b|\botic\b/, "otic"],
3558
+ [/\bnostrils?\b|\bnasal(ly)?\b|\bintranasal/, "nasal"],
3559
+ [/\brectal(ly)?\b|\bpr\b/, "rectal"],
3560
+ [/\btransdermal\b|\bto (the )?skin\b|\bapply\b|\btopical(ly)?\b/, "topical"]
3561
+ ];
3562
+ var SIG_FREQUENCIES = [
3563
+ [/\bevery other day\b|\bqod\b/, "Every other day"],
3564
+ [/\btwice (a |per )?day\b|\btwice daily\b|\bbid\b/, "Twice daily"],
3565
+ [
3566
+ /\b(three times|3 times)( a| per)? day\b|\b(three times|3 times) daily\b|\btid\b/,
3567
+ "Three times daily"
3568
+ ],
3569
+ [
3570
+ /\b(four times|4 times)( a| per)? day\b|\b(four times|4 times) daily\b|\bqid\b/,
3571
+ "Four times daily"
3572
+ ],
3573
+ [/\bevery morning\b|\bqam\b/, "Every morning"],
3574
+ [/\b(at )?bedtime\b|\bqhs\b|\bat night\b/, "Every bedtime"],
3575
+ [/\bweekly\b|\bonce a week\b|\bevery week\b/, "Weekly"],
3576
+ [
3577
+ /\bonce (a |per )?day\b|\bonce daily\b|\bdaily\b|\bevery day\b|\bqd\b/,
3578
+ "Once daily"
3579
+ ]
3580
+ ];
3581
+ function parseSig(sig) {
3582
+ const lower = sig.toLowerCase();
3583
+ const route = SIG_ROUTES.find(([re]) => re.test(lower))?.[1];
3584
+ const frequency = SIG_FREQUENCIES.find(([re]) => re.test(lower))?.[1];
3585
+ const prn = /\bas needed\b|\bprn\b/.test(lower);
3586
+ return { route, frequency, prn };
3587
+ }
3588
+ function labelToMedicationFields(label) {
3589
+ const { strength, doseForm } = parseMedicationLabel(label);
3590
+ return {
3591
+ ...strength && { strength },
3592
+ ...doseForm && {
3593
+ doseForm,
3594
+ quantityUnit: doseFormToQuantityUnit(doseForm)
3595
+ }
3596
+ };
3597
+ }
3598
+ function lookupToMedicationFields(result) {
3599
+ return {
3600
+ name: result.label,
3601
+ code: {
3602
+ system: result.codetype,
3603
+ code: result.fullcode,
3604
+ display: result.label
3605
+ },
3606
+ ...labelToMedicationFields(result.label)
3607
+ };
3608
+ }
3609
+ function newId2() {
3610
+ return `med-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
3611
+ }
3612
+ function MedicationEditor({
3613
+ open,
3614
+ medication,
3615
+ codeLookup,
3616
+ requireCode = false,
3617
+ onClose,
3618
+ onSave
3619
+ }) {
3620
+ const m = useMessages();
3621
+ const ambientCodeLookup = useCodeLookupConfig();
3622
+ const effectiveCodeLookup = codeLookup === false ? void 0 : codeLookup ?? ambientCodeLookup ?? void 0;
3623
+ const [draft, setDraft] = React4.useState(
3624
+ () => medication ?? {
3625
+ id: newId2(),
3626
+ name: "",
3627
+ status: "unreconciled"
3628
+ }
3629
+ );
3630
+ const bodyRef = React4.useRef(null);
3631
+ React4.useEffect(() => {
3632
+ const input = bodyRef.current?.querySelector("input");
3633
+ input?.focus();
3634
+ }, []);
3635
+ const patch = (p) => setDraft((prev) => ({ ...prev, ...p }));
3636
+ const handleCodeSelect = (result) => {
3637
+ if (requireCode && (!result.codetype.trim() || !result.fullcode.trim()))
3638
+ return;
3639
+ patch(lookupToMedicationFields(result));
3640
+ };
3641
+ const handleSigChange = (sig) => {
3642
+ const { route, frequency, prn } = parseSig(sig);
3643
+ patch({ sig, route, frequency, prn: prn || void 0 });
3644
+ };
3645
+ const hasCode = Boolean(draft.code?.system.trim() && draft.code.code.trim());
3646
+ const unchangedLegacyName = medication && !medication.code && draft.name === medication.name;
3647
+ const canSave = draft.name.trim().length > 0 && (!requireCode || hasCode || Boolean(unchangedLegacyName));
3648
+ if (!open) return null;
3649
+ return /* @__PURE__ */ jsxs(Modal, { open: true, onOpenChange: (o) => !o && onClose(), size: "lg", children: [
3650
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
3651
+ /* @__PURE__ */ jsx(ModalTitle, { children: medication ? m("Correct Medication") : m("Add Medication") }),
3652
+ /* @__PURE__ */ jsx(ModalClose, { "aria-label": m("Close") })
3653
+ ] }),
3654
+ /* @__PURE__ */ jsx(ModalBody, { className: "space-y-5", children: /* @__PURE__ */ jsxs("div", { ref: bodyRef, className: "contents", children: [
3655
+ /* @__PURE__ */ jsxs("section", { className: "space-y-3", "aria-label": m("Medication"), children: [
3656
+ effectiveCodeLookup ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3657
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-search", children: m("Medication") }),
3658
+ /* @__PURE__ */ jsx(
3659
+ effectiveCodeLookup.component,
3660
+ {
3661
+ indexUrl: effectiveCodeLookup.indexUrl,
3662
+ locale: effectiveCodeLookup.locale,
3663
+ domains: ["med"],
3664
+ bare: true,
3665
+ clearOnSelect: false,
3666
+ placeholder: m("Search RxNorm / FDB \u2014 e.g. lisinopril"),
3667
+ initialQuery: medication?.name || void 0,
3668
+ initialSearch: medication ? !medication.code : void 0,
3669
+ onSelect: handleCodeSelect,
3670
+ onFreeText: requireCode ? void 0 : (text) => patch({ name: text, code: void 0 })
3671
+ }
3672
+ ),
3673
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: draft.code ? m("Coded: {system} {code}", {
3674
+ system: draft.code.system,
3675
+ code: draft.code.code
3676
+ }) : draft.name ? m(
3677
+ 'Uncoded free text: "{name}" \u2014 pick a result to code it',
3678
+ { name: draft.name }
3679
+ ) : requireCode ? m("Pick a catalog result to code the medication") : m(
3680
+ "Pick a result to code the medication, or press Enter for free text"
3681
+ ) })
3682
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3683
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-name", children: m("Medication") }),
3684
+ /* @__PURE__ */ jsx(
3685
+ Input,
3686
+ {
3687
+ id: "med-name",
3688
+ value: draft.name,
3689
+ readOnly: requireCode,
3690
+ onChange: (e) => patch({ name: e.target.value, code: void 0 })
3691
+ }
3692
+ )
3693
+ ] }),
3694
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
3695
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3696
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-strength", children: m("Strength") }),
3697
+ /* @__PURE__ */ jsx(
3698
+ Input,
3699
+ {
3700
+ id: "med-strength",
3701
+ value: draft.strength ?? "",
3702
+ onChange: (e) => patch({ strength: e.target.value })
3703
+ }
3704
+ )
3705
+ ] }),
3706
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3707
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-form", children: m("Dose form") }),
3708
+ /* @__PURE__ */ jsx(
3709
+ Input,
3710
+ {
3711
+ id: "med-form",
3712
+ value: draft.doseForm ?? "",
3713
+ onChange: (e) => {
3714
+ const doseForm = e.target.value;
3715
+ patch({
3716
+ doseForm,
3717
+ quantityUnit: doseFormToQuantityUnit(doseForm)
3718
+ });
3719
+ }
3720
+ }
3721
+ )
3722
+ ] })
3723
+ ] })
3724
+ ] }),
3725
+ /* @__PURE__ */ jsxs("section", { className: "space-y-3", "aria-label": m("Dispensing"), children: [
3726
+ /* @__PURE__ */ jsx("h4", { className: "text-muted-foreground text-xs font-semibold tracking-wide uppercase", children: m("Dispensing") }),
3727
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-3", children: [
3728
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3729
+ /* @__PURE__ */ jsxs(Label, { htmlFor: "med-qty", children: [
3730
+ m("Quantity"),
3731
+ draft.quantityUnit ? ` (${draft.quantityUnit}s)` : ""
3732
+ ] }),
3733
+ /* @__PURE__ */ jsx(
3734
+ Input,
3735
+ {
3736
+ id: "med-qty",
3737
+ inputMode: "decimal",
3738
+ value: draft.quantity ?? "",
3739
+ onChange: (e) => patch({ quantity: e.target.value })
3740
+ }
3741
+ )
3742
+ ] }),
3743
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3744
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-days", children: m("Days supply") }),
3745
+ /* @__PURE__ */ jsx(
3746
+ Input,
3747
+ {
3748
+ id: "med-days",
3749
+ inputMode: "numeric",
3750
+ value: draft.daysSupply ?? "",
3751
+ onChange: (e) => patch({ daysSupply: e.target.value })
3752
+ }
3753
+ )
3754
+ ] }),
3755
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3756
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-refills", children: m("Refills") }),
3757
+ /* @__PURE__ */ jsx(
3758
+ Input,
3759
+ {
3760
+ id: "med-refills",
3761
+ inputMode: "numeric",
3762
+ value: draft.refills ?? "",
3763
+ onChange: (e) => patch({ refills: e.target.value })
3764
+ }
3765
+ )
3766
+ ] })
3767
+ ] }),
3768
+ /* @__PURE__ */ jsxs(
3769
+ RadioGroup,
3770
+ {
3771
+ name: "med-substitution",
3772
+ label: m("Substitution"),
3773
+ value: draft.substitution ?? "0",
3774
+ onValueChange: (v) => patch({ substitution: v }),
3775
+ orientation: "horizontal",
3776
+ size: "sm",
3777
+ children: [
3778
+ /* @__PURE__ */ jsx(Radio, { value: "0", label: m("Substitution permitted") }),
3779
+ /* @__PURE__ */ jsx(Radio, { value: "1", label: m("Dispense as written (DAW)") })
3780
+ ]
3781
+ }
3782
+ )
3783
+ ] }),
3784
+ /* @__PURE__ */ jsxs("section", { className: "space-y-3", "aria-label": m("Directions"), children: [
3785
+ /* @__PURE__ */ jsx("h4", { className: "text-muted-foreground text-xs font-semibold tracking-wide uppercase", children: m("Directions") }),
3786
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3787
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-sig", children: m("Sig (patient directions)") }),
3788
+ /* @__PURE__ */ jsx(
3789
+ Textarea,
3790
+ {
3791
+ id: "med-sig",
3792
+ value: draft.sig ?? "",
3793
+ onChange: (e) => handleSigChange(e.target.value),
3794
+ rows: 2
3795
+ }
3796
+ ),
3797
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", "aria-live": "polite", children: draft.route || draft.frequency || draft.prn ? /* @__PURE__ */ jsxs(Fragment, { children: [
3798
+ m("Derived:"),
3799
+ " ",
3800
+ [
3801
+ draft.route && m("route {route}", { route: draft.route }),
3802
+ draft.frequency && m(draft.frequency.toLowerCase()),
3803
+ draft.prn && m("PRN (as needed)")
3804
+ ].filter(Boolean).join(" \xB7 ")
3805
+ ] }) : m("Route, frequency, and PRN are derived from the directions") })
3806
+ ] })
3807
+ ] }),
3808
+ /* @__PURE__ */ jsxs("section", { className: "space-y-3", "aria-label": m("Dates and context"), children: [
3809
+ /* @__PURE__ */ jsx("h4", { className: "text-muted-foreground text-xs font-semibold tracking-wide uppercase", children: m("Dates & context") }),
3810
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-3", children: [
3811
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3812
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-start", children: m("Start date") }),
3813
+ /* @__PURE__ */ jsx(
3814
+ DateInput,
3815
+ {
3816
+ id: "med-start",
3817
+ value: draft.startDate ?? "",
3818
+ onChange: (v) => patch({ startDate: v })
3819
+ }
3820
+ )
3821
+ ] }),
3822
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3823
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-end", children: m("End date") }),
3824
+ /* @__PURE__ */ jsx(
3825
+ DateInput,
3826
+ {
3827
+ id: "med-end",
3828
+ value: draft.endDate ?? "",
3829
+ onChange: (v) => patch({ endDate: v })
3830
+ }
3831
+ )
3832
+ ] })
3833
+ ] }),
3834
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3835
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-indication", children: m("Indication") }),
3836
+ /* @__PURE__ */ jsx(
3837
+ Input,
3838
+ {
3839
+ id: "med-indication",
3840
+ value: draft.indication ?? "",
3841
+ onChange: (e) => patch({ indication: e.target.value })
3842
+ }
3843
+ )
3844
+ ] }),
3845
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3846
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-pharmacy-notes", children: m("Pharmacy notes") }),
3847
+ /* @__PURE__ */ jsx(
3848
+ Textarea,
3849
+ {
3850
+ id: "med-pharmacy-notes",
3851
+ value: draft.pharmacyNotes ?? "",
3852
+ onChange: (e) => patch({ pharmacyNotes: e.target.value }),
3853
+ rows: 2
3854
+ }
3855
+ )
3856
+ ] })
3857
+ ] })
3858
+ ] }) }),
3859
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
3860
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: m("Cancel") }),
3861
+ /* @__PURE__ */ jsx(
3862
+ Button,
3863
+ {
3864
+ onClick: () => {
3865
+ onSave({ ...draft, name: draft.name.trim() });
3866
+ onClose();
3867
+ },
3868
+ disabled: !canSave,
3869
+ children: m("Save")
3870
+ }
3871
+ )
3872
+ ] })
3873
+ ] });
3874
+ }
3875
+ var MANAGED_ACTIONS = [
3876
+ "correct",
3877
+ "add-task",
3878
+ "note",
3879
+ "remove"
3880
+ ];
3881
+ function newId3() {
3882
+ return `med-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
3883
+ }
3884
+ function MedicationDialog({
3885
+ dialog,
3886
+ onClose,
3887
+ onSave
3888
+ }) {
3889
+ const m = useMessages();
3890
+ const [form, setForm] = React4.useState(
3891
+ () => dialog.kind === "note" ? { note: dialog.medication.note ?? "" } : { task: dialog.medication.task ?? "" }
3892
+ );
3893
+ const set = (key) => (e) => setForm((prev) => ({ ...prev, [key]: e.target.value }));
3894
+ const handleSave = () => {
3895
+ onSave(form);
3896
+ onClose();
3897
+ };
3898
+ return /* @__PURE__ */ jsxs(Modal, { open: true, onOpenChange: (open) => !open && onClose(), size: "md", children: [
3899
+ /* @__PURE__ */ jsxs(ModalHeader, { children: [
3900
+ /* @__PURE__ */ jsx(ModalTitle, { children: dialog.kind === "note" ? m("Medication Notes") : m("Add Task") }),
3901
+ /* @__PURE__ */ jsx(ModalClose, { "aria-label": m("Close") })
3902
+ ] }),
3903
+ /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-4", children: [
3904
+ /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: dialog.medication.name }),
3905
+ dialog.kind === "note" ? /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3906
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-note", children: m("Note") }),
3907
+ /* @__PURE__ */ jsx(
3908
+ Textarea,
3909
+ {
3910
+ id: "med-note",
3911
+ value: form.note,
3912
+ onChange: set("note"),
3913
+ rows: 4
3914
+ }
3915
+ )
3916
+ ] }) : /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
3917
+ /* @__PURE__ */ jsx(Label, { htmlFor: "med-task", children: m("Task") }),
3918
+ /* @__PURE__ */ jsx(
3919
+ Textarea,
3920
+ {
3921
+ id: "med-task",
3922
+ value: form.task,
3923
+ onChange: set("task"),
3924
+ rows: 3,
3925
+ placeholder: m("Follow-up task for this medication")
3926
+ }
3927
+ )
3928
+ ] })
3929
+ ] }),
3930
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
3931
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: onClose, children: m("Cancel") }),
3932
+ /* @__PURE__ */ jsx(Button, { onClick: handleSave, children: m("Save") })
3933
+ ] })
3934
+ ] });
3935
+ }
3936
+ function MedicationReconciliation({
3937
+ medications: controlledMedications,
3938
+ defaultMedications,
3939
+ onChange,
3940
+ title = "Presenting medications",
3941
+ quickAddOptions,
3942
+ actions = MANAGED_ACTIONS,
3943
+ onAction,
3944
+ codeLookup,
3945
+ requireCode = false,
3946
+ inlineAddSearch = false,
3947
+ readOnly = false,
3948
+ reconciledMessage,
3949
+ emptyMessage,
3950
+ className,
3951
+ "data-testid": dataTestId
3952
+ }) {
3953
+ const m = useMessages();
3954
+ const isControlled = controlledMedications !== void 0;
3955
+ const [internal, setInternal] = React4.useState(
3956
+ defaultMedications ?? []
3957
+ );
3958
+ const medications = isControlled ? controlledMedications : internal;
3959
+ const ambientCodeLookup = useCodeLookupConfig();
3960
+ const effectiveCodeLookup = codeLookup === false ? void 0 : codeLookup ?? ambientCodeLookup ?? void 0;
3961
+ const [dialog, setDialog] = React4.useState(null);
3962
+ const commit = (next) => {
3963
+ if (!isControlled) setInternal(next);
3964
+ onChange?.(next);
3965
+ };
3966
+ const patchMedication = (id, patch) => {
3967
+ commit(medications.map((m2) => m2.id === id ? { ...m2, ...patch } : m2));
3968
+ };
3969
+ const moveMedication = (med, direction) => {
3970
+ const meds = [...medications];
3971
+ const group = meds.filter((m2) => m2.status === med.status);
3972
+ const groupIndex = group.findIndex((m2) => m2.id === med.id);
3973
+ const swapWith = group[groupIndex + direction];
3974
+ if (!swapWith) return;
3975
+ const i = meds.findIndex((m2) => m2.id === med.id);
3976
+ const j = meds.findIndex((m2) => m2.id === swapWith.id);
3977
+ [meds[i], meds[j]] = [meds[j], meds[i]];
3978
+ commit(meds);
3979
+ };
3980
+ const handleAction = (med, action) => {
3981
+ switch (action) {
3982
+ case "remove":
3983
+ commit(medications.filter((m2) => m2.id !== med.id));
3984
+ break;
3985
+ case "move-up":
3986
+ moveMedication(med, -1);
3987
+ break;
3988
+ case "move-down":
3989
+ moveMedication(med, 1);
3990
+ break;
3991
+ case "correct":
3992
+ case "note":
3993
+ case "add-task":
3994
+ setDialog({ kind: action, medication: med });
3995
+ break;
3996
+ default:
3997
+ onAction?.(med, action);
3998
+ break;
3999
+ }
4000
+ };
4001
+ const addMedication = (name, sig) => {
4002
+ commit([
4003
+ ...medications,
4004
+ { id: newId3(), name, sig: void 0, status: "unreconciled" }
4005
+ ]);
4006
+ };
4007
+ const handleDialogSave = (form) => {
4008
+ if (!dialog) return;
4009
+ switch (dialog.kind) {
4010
+ case "note":
4011
+ patchMedication(dialog.medication.id, {
4012
+ note: form.note.trim() || void 0
4013
+ });
4014
+ break;
4015
+ case "add-task":
4016
+ patchMedication(dialog.medication.id, {
4017
+ task: form.task.trim() || void 0
4018
+ });
4019
+ break;
4020
+ }
4021
+ };
4022
+ const editorOpen = dialog?.kind === "correct" || dialog?.kind === "add";
4023
+ const editorTarget = dialog?.kind === "correct" ? dialog.medication : void 0;
4024
+ const handleEditorSave = (med) => {
4025
+ if (dialog?.kind === "correct") {
4026
+ commit(medications.map((m2) => m2.id === med.id ? med : m2));
4027
+ } else {
4028
+ commit([...medications, { ...med, status: "unreconciled" }]);
4029
+ }
4030
+ };
4031
+ const addSearch = inlineAddSearch && effectiveCodeLookup && !readOnly ? /* @__PURE__ */ jsx(
4032
+ effectiveCodeLookup.component,
4033
+ {
4034
+ indexUrl: effectiveCodeLookup.indexUrl,
4035
+ locale: effectiveCodeLookup.locale,
4036
+ domains: ["med"],
4037
+ bare: true,
4038
+ clearOnSelect: true,
4039
+ placeholder: m("Search medications to add"),
4040
+ onSelect: (result) => {
4041
+ if (requireCode && (!result.codetype.trim() || !result.fullcode.trim()))
4042
+ return;
4043
+ commit([
4044
+ ...medications,
4045
+ {
4046
+ id: newId3(),
4047
+ name: result.label,
4048
+ status: "unreconciled",
4049
+ ...lookupToMedicationFields(result)
4050
+ }
4051
+ ]);
4052
+ },
4053
+ onFreeText: requireCode ? void 0 : (text) => addMedication(text)
4054
+ }
4055
+ ) : void 0;
4056
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
4057
+ /* @__PURE__ */ jsx(
4058
+ MedicationList,
4059
+ {
4060
+ medications,
4061
+ title,
4062
+ readOnly,
4063
+ actions,
4064
+ onStatusChange: (med, status) => patchMedication(med.id, { status }),
4065
+ onAction: handleAction,
4066
+ onReorder: (ids) => commit(
4067
+ [...medications].sort(
4068
+ (a, b) => ids.indexOf(a.id) - ids.indexOf(b.id)
4069
+ )
4070
+ ),
4071
+ quickAddOptions: requireCode ? void 0 : quickAddOptions,
4072
+ onQuickAdd: (name) => addMedication(name),
4073
+ onAddOther: requireCode && !effectiveCodeLookup ? void 0 : () => setDialog({ kind: "add" }),
4074
+ addSearch,
4075
+ reconciledMessage,
4076
+ emptyMessage,
4077
+ className,
4078
+ "data-testid": dataTestId
4079
+ }
4080
+ ),
4081
+ dialog && (dialog.kind === "note" || dialog.kind === "add-task") && /* @__PURE__ */ jsx(
4082
+ MedicationDialog,
4083
+ {
4084
+ dialog,
4085
+ onClose: () => setDialog(null),
4086
+ onSave: handleDialogSave
4087
+ },
4088
+ dialog.kind + dialog.medication.id
4089
+ ),
4090
+ editorOpen && /* @__PURE__ */ jsx(
4091
+ MedicationEditor,
4092
+ {
4093
+ open: true,
4094
+ medication: editorTarget,
4095
+ codeLookup: codeLookup === false ? false : effectiveCodeLookup,
4096
+ requireCode,
4097
+ onClose: () => setDialog(null),
4098
+ onSave: handleEditorSave
4099
+ },
4100
+ editorTarget?.id ?? "add"
4101
+ )
4102
+ ] });
4103
+ }
4104
+
4105
+ export { ALLERGY_TYPE_LABELS, AllergyList, AllergyManager, Assessment, CodeLookupContext, CodeLookupProvider, ConditionEditor, DateRangeFilter, DateRangePicker, MEDICATION_STATUS_LABELS, MedicationEditor, MedicationList, MedicationReconciliation, ORDER_TYPE_META, ORDER_TYPE_SEARCH_DOMAINS, calculateDateRange, doseFormToQuantityUnit, getDefaultPresets, getExtendedPresets, isConditionCodetype, labelToMedicationFields, lookupToMedicationFields, normalizeConditionCodingSystem, orderTypeForCodetype, parseMedicationLabel, parseSig, useCodeLookupConfig };
4106
+ //# sourceMappingURL=chunk-SMPLM6K3.js.map
4107
+ //# sourceMappingURL=chunk-SMPLM6K3.js.map