formanitor 0.0.11 → 0.0.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -15,6 +15,10 @@ var reactDayPicker = require('react-day-picker');
15
15
  var PopoverPrimitive = require('@radix-ui/react-popover');
16
16
  var reactTable = require('@tanstack/react-table');
17
17
  var RadioGroupPrimitive = require('@radix-ui/react-radio-group');
18
+ var DialogPrimitive = require('@radix-ui/react-dialog');
19
+ var nlp = require('compromise');
20
+
21
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
18
22
 
19
23
  function _interopNamespace(e) {
20
24
  if (e && e.__esModule) return e;
@@ -40,6 +44,8 @@ var SelectPrimitive__namespace = /*#__PURE__*/_interopNamespace(SelectPrimitive)
40
44
  var CheckboxPrimitive__namespace = /*#__PURE__*/_interopNamespace(CheckboxPrimitive);
41
45
  var PopoverPrimitive__namespace = /*#__PURE__*/_interopNamespace(PopoverPrimitive);
42
46
  var RadioGroupPrimitive__namespace = /*#__PURE__*/_interopNamespace(RadioGroupPrimitive);
47
+ var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive);
48
+ var nlp__default = /*#__PURE__*/_interopDefault(nlp);
43
49
 
44
50
  // src/core/validate.ts
45
51
  var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -52,6 +58,11 @@ function validateField(value, field) {
52
58
  if (Array.isArray(value) && value.length === 0) {
53
59
  return "This field is required";
54
60
  }
61
+ if (field.type === "smart_textarea" && typeof value === "object" && !Array.isArray(value)) {
62
+ if (!value.text || value.text.trim() === "") {
63
+ return "This field is required";
64
+ }
65
+ }
55
66
  }
56
67
  if (value === null || value === void 0 || value === "") {
57
68
  return null;
@@ -239,6 +250,7 @@ var FormStore = class {
239
250
  } else {
240
251
  if (field.type === "repeatable") values[field.id] = [];
241
252
  else if (field.type === "checkbox") values[field.id] = [];
253
+ else if (field.type === "smart_textarea") values[field.id] = { text: "", extractedKeywords: [] };
242
254
  else values[field.id] = null;
243
255
  }
244
256
  });
@@ -279,6 +291,31 @@ var FormStore = class {
279
291
  this.scheduleSave();
280
292
  }
281
293
  }
294
+ /**
295
+ * Set multiple values at once (e.g. from AI analysis). Only updates keys that
296
+ * exist in the schema. Runs evaluate/notify/save once.
297
+ * @param partial Map of fieldId -> value
298
+ * @param options.touch If false, do not mark fields as touched (e.g. for AI-suggested values)
299
+ */
300
+ setValues(partial, options) {
301
+ const touch = options?.touch !== false;
302
+ let changed = false;
303
+ const newValues = { ...this.state.values };
304
+ const newTouched = { ...this.state.touched };
305
+ for (const [fieldId, value] of Object.entries(partial)) {
306
+ if (!this.fieldMap[fieldId]) continue;
307
+ if (newValues[fieldId] === value) continue;
308
+ newValues[fieldId] = value;
309
+ if (touch) newTouched[fieldId] = true;
310
+ changed = true;
311
+ }
312
+ if (!changed) return;
313
+ this.state.values = newValues;
314
+ this.state.touched = newTouched;
315
+ this.evaluate();
316
+ this.notify();
317
+ this.scheduleSave();
318
+ }
282
319
  setTouched(fieldId) {
283
320
  if (this.state.touched[fieldId]) return;
284
321
  this.state.touched = { ...this.state.touched, [fieldId]: true };
@@ -361,6 +398,15 @@ var FormStore = class {
361
398
  getSerializedValuesForSubmission() {
362
399
  return this.serializeValuesForSubmission(this.state.values);
363
400
  }
401
+ /**
402
+ * Raw form values as stored internally, without any serialization or
403
+ * transformation. Useful when consumers need access to richer structures
404
+ * (e.g. smart_textarea keyword metadata) alongside the flattened values
405
+ * returned by getSerializedValuesForSubmission.
406
+ */
407
+ getRawValues() {
408
+ return { ...this.state.values };
409
+ }
364
410
  serializeValuesForSubmission(values) {
365
411
  const result = { ...values };
366
412
  this.schema.fields.forEach((field) => {
@@ -368,6 +414,11 @@ var FormStore = class {
368
414
  delete result[field.id];
369
415
  return;
370
416
  }
417
+ if (field.type === "smart_textarea") {
418
+ const raw = values[field.id];
419
+ result[field.id] = raw?.text ?? null;
420
+ return;
421
+ }
371
422
  if (field.type === "image_upload" || field.type === "signature") {
372
423
  const raw = values[field.id];
373
424
  if (raw !== void 0 && raw !== null && raw !== "") {
@@ -428,6 +479,36 @@ var FormStore = class {
428
479
  this.state.values[comp.target] = 0;
429
480
  }
430
481
  }
482
+ } else if (comp.type === "formula") {
483
+ const evalExpr = (expr) => {
484
+ if (expr.type === "field") {
485
+ return Number(this.state.values[expr.field]) || 0;
486
+ }
487
+ if (expr.type === "number") {
488
+ return expr.value;
489
+ }
490
+ const left = evalExpr(expr.left);
491
+ const right = evalExpr(expr.right);
492
+ switch (expr.op) {
493
+ case "+":
494
+ return left + right;
495
+ case "-":
496
+ return left - right;
497
+ case "*":
498
+ return left * right;
499
+ case "/":
500
+ return right === 0 ? 0 : left / right;
501
+ case "**":
502
+ return Math.pow(left, right);
503
+ default:
504
+ return 0;
505
+ }
506
+ };
507
+ const result = evalExpr(comp.expression);
508
+ const value = Number.isFinite(result) ? Math.round(result * 10) / 10 : "";
509
+ if (this.state.values[comp.target] !== value) {
510
+ this.state.values[comp.target] = value;
511
+ }
431
512
  }
432
513
  });
433
514
  }
@@ -583,7 +664,12 @@ var FormStore = class {
583
664
  }
584
665
  };
585
666
  var FormContext = React11.createContext(null);
586
- var FormProvider = ({ schema, config, children }) => {
667
+ var FieldHandlersContext = React11.createContext({});
668
+ function useFieldHandlers(fieldId) {
669
+ const map = React11.useContext(FieldHandlersContext);
670
+ return map[fieldId] ?? { onSearch: async () => [], recentlyUsed: [] };
671
+ }
672
+ var FormProvider = ({ schema, config, fieldHandlers, children }) => {
587
673
  const storeRef = React11.useRef(null);
588
674
  if (!storeRef.current) {
589
675
  storeRef.current = new FormStore(schema, config);
@@ -596,7 +682,7 @@ var FormProvider = ({ schema, config, children }) => {
596
682
  React11.useEffect(() => {
597
683
  storeRef.current?.load();
598
684
  }, []);
599
- return /* @__PURE__ */ jsxRuntime.jsx(FormContext.Provider, { value: storeRef.current, children });
685
+ return /* @__PURE__ */ jsxRuntime.jsx(FormContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsxRuntime.jsx(FieldHandlersContext.Provider, { value: fieldHandlers ?? {}, children }) });
600
686
  };
601
687
  function useFormStore() {
602
688
  const context = React11.useContext(FormContext);
@@ -623,6 +709,7 @@ function useForm() {
623
709
  availableTransitions: store.getAvailableTransitions(),
624
710
  hasPersistence: store.hasPersistence(),
625
711
  getSerializedValues: store.getSerializedValuesForSubmission.bind(store),
712
+ getRawValues: store.getRawValues.bind(store),
626
713
  markSubmitAttempted: store.markSubmitAttempted.bind(store),
627
714
  flushPendingUploads: store.flushPendingUploads.bind(store)
628
715
  };
@@ -689,8 +776,9 @@ var Textarea = React11__namespace.forwardRef(
689
776
  return /* @__PURE__ */ jsxRuntime.jsx(
690
777
  "textarea",
691
778
  {
779
+ spellCheck: false,
692
780
  className: cn(
693
- "flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
781
+ "flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm placeholder:text-muted-foreground focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
694
782
  height == null && "min-h-[80px]",
695
783
  className
696
784
  ),
@@ -702,6 +790,28 @@ var Textarea = React11__namespace.forwardRef(
702
790
  }
703
791
  );
704
792
  Textarea.displayName = "Textarea";
793
+
794
+ // src/ui/themes/textareaThemes.ts
795
+ var textareaThemes = {
796
+ "highlight-label": {
797
+ wrapperClassName: "rounded-md overflow-hidden flex flex-col border border-[#D0D0D0]",
798
+ labelClassName: "rounded-t-md bg-[#FEE8EC] px-3 py-1 text-sm shrink-0 z-[1] text-slate-600",
799
+ inputClassName: "rounded-t-none rounded-b-md border-0 border-t border-[#D0D0D0] text-xs resize-none -mt-1 min-w-0 bg-white text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50"
800
+ }
801
+ };
802
+
803
+ // src/ui/themes/index.ts
804
+ var themeRegistries = {
805
+ textarea: textareaThemes,
806
+ text: textareaThemes
807
+ // same themes can apply to single-line text if desired
808
+ };
809
+ function getThemeConfig(fieldType, themeName) {
810
+ if (!themeName) return void 0;
811
+ const registry = themeRegistries[fieldType];
812
+ if (!registry) return void 0;
813
+ return registry[themeName];
814
+ }
705
815
  var TextWidget = ({ fieldId }) => {
706
816
  const { fieldDef, value, setValue, setTouched, error, disabled, touched } = useField(fieldId);
707
817
  const { state } = useForm();
@@ -710,12 +820,20 @@ var TextWidget = ({ fieldId }) => {
710
820
  const Component = fieldDef.type === "textarea" || fieldDef.type === "richtext" ? Textarea : Input;
711
821
  const isTextarea = fieldDef.type === "textarea" || fieldDef.type === "richtext";
712
822
  const height = isTextarea && "height" in fieldDef ? fieldDef.height : void 0;
713
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
714
- /* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: fieldId, children: [
715
- fieldDef.label,
716
- " ",
717
- fieldDef.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
718
- ] }),
823
+ const theme = getThemeConfig(
824
+ isTextarea ? "textarea" : "text",
825
+ fieldDef.theme
826
+ );
827
+ const wrapperClass = theme?.wrapperClassName ?? "space-y-2";
828
+ const labelClass = theme?.labelClassName;
829
+ const inputClass = cn(theme?.inputClassName, showError && "border-red-500");
830
+ const labelContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
831
+ fieldDef.label,
832
+ " ",
833
+ fieldDef.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
834
+ ] });
835
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClass, children: [
836
+ theme ? /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: fieldId, className: labelClass, children: labelContent }) : /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: fieldId, children: labelContent }),
719
837
  /* @__PURE__ */ jsxRuntime.jsx(
720
838
  Component,
721
839
  {
@@ -725,7 +843,7 @@ var TextWidget = ({ fieldId }) => {
725
843
  onBlur: setTouched,
726
844
  disabled,
727
845
  type: fieldDef.type === "number" ? "number" : "text",
728
- className: showError ? "border-red-500" : "",
846
+ className: inputClass,
729
847
  ...isTextarea && { height }
730
848
  }
731
849
  ),
@@ -2301,135 +2419,2100 @@ var CheckboxWidget = ({ fieldId }) => {
2301
2419
  showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
2302
2420
  ] });
2303
2421
  };
2304
- var FieldRenderer = ({ fieldId }) => {
2305
- const { fieldDef, visible } = useField(fieldId);
2306
- if (!visible || !fieldDef) {
2307
- return null;
2422
+ var AISuggestionsContext = React11.createContext({});
2423
+ function useAISuggestions() {
2424
+ return React11.useContext(AISuggestionsContext);
2425
+ }
2426
+ var AISuggestionsProvider = AISuggestionsContext.Provider;
2427
+ var Dialog = DialogPrimitive__namespace.Root;
2428
+ var DialogPortal = DialogPrimitive__namespace.Portal;
2429
+ var DialogOverlay = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2430
+ DialogPrimitive__namespace.Overlay,
2431
+ {
2432
+ ref,
2433
+ className: cn(
2434
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
2435
+ className
2436
+ ),
2437
+ ...props
2308
2438
  }
2309
- switch (fieldDef.type) {
2310
- case "text":
2311
- case "number":
2312
- return /* @__PURE__ */ jsxRuntime.jsx(TextWidget, { fieldId });
2313
- case "textarea":
2314
- case "richtext":
2315
- return /* @__PURE__ */ jsxRuntime.jsx(TextWidget, { fieldId });
2316
- case "date":
2317
- return /* @__PURE__ */ jsxRuntime.jsx(DateWidget, { fieldId });
2318
- case "datetime":
2319
- return /* @__PURE__ */ jsxRuntime.jsx(DateTimeWidget, { fieldId });
2320
- case "select":
2321
- return /* @__PURE__ */ jsxRuntime.jsx(SelectWidget, { fieldId });
2322
- case "radio":
2323
- return /* @__PURE__ */ jsxRuntime.jsx(RadioWidget, { fieldId });
2324
- case "checkbox":
2325
- return /* @__PURE__ */ jsxRuntime.jsx(CheckboxWidget, { fieldId });
2326
- case "multiselect":
2327
- return /* @__PURE__ */ jsxRuntime.jsx(MultiSelectWidget, { fieldId });
2328
- case "repeatable":
2329
- return /* @__PURE__ */ jsxRuntime.jsx(RepeatableWidget, { fieldId });
2330
- case "image_upload":
2331
- return /* @__PURE__ */ jsxRuntime.jsx(ImageUploadWidget, { fieldId });
2332
- case "signature":
2333
- return /* @__PURE__ */ jsxRuntime.jsx(SignatureUploadWidget, { fieldId });
2334
- case "editable_table":
2335
- return /* @__PURE__ */ jsxRuntime.jsx(EditableTableWidget, { fieldId });
2336
- case "static_text": {
2337
- const def = fieldDef;
2338
- const size = def.size ?? "regular";
2339
- const labelClass = size === "large" ? "text-base" : "text-sm";
2340
- const descClass = size === "large" ? "text-sm" : "text-xs";
2341
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${labelClass} text-muted-foreground`, children: [
2342
- def.label,
2343
- def.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: `mt-1 ${descClass} opacity-90`, children: def.description })
2344
- ] });
2439
+ ));
2440
+ DialogOverlay.displayName = DialogPrimitive__namespace.Overlay.displayName;
2441
+ var DialogContent = React11__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
2442
+ /* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
2443
+ /* @__PURE__ */ jsxRuntime.jsxs(
2444
+ DialogPrimitive__namespace.Content,
2445
+ {
2446
+ ref,
2447
+ className: cn(
2448
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
2449
+ className
2450
+ ),
2451
+ ...props,
2452
+ children: [
2453
+ children,
2454
+ /* @__PURE__ */ jsxRuntime.jsxs(DialogPrimitive__namespace.Close, { className: "absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground", children: [
2455
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }),
2456
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Close" })
2457
+ ] })
2458
+ ]
2345
2459
  }
2346
- default:
2347
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-red-500", children: [
2348
- "Unknown field type: ",
2349
- fieldDef.type
2350
- ] });
2460
+ )
2461
+ ] }));
2462
+ DialogContent.displayName = DialogPrimitive__namespace.Content.displayName;
2463
+ var DialogHeader = ({
2464
+ className,
2465
+ ...props
2466
+ }) => /* @__PURE__ */ jsxRuntime.jsx(
2467
+ "div",
2468
+ {
2469
+ className: cn(
2470
+ "flex flex-col space-y-1.5 text-center sm:text-left",
2471
+ className
2472
+ ),
2473
+ ...props
2474
+ }
2475
+ );
2476
+ DialogHeader.displayName = "DialogHeader";
2477
+ var DialogTitle = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2478
+ DialogPrimitive__namespace.Title,
2479
+ {
2480
+ ref,
2481
+ className: cn(
2482
+ "text-lg font-semibold leading-none tracking-tight",
2483
+ className
2484
+ ),
2485
+ ...props
2486
+ }
2487
+ ));
2488
+ DialogTitle.displayName = DialogPrimitive__namespace.Title.displayName;
2489
+ var DialogDescription = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
2490
+ DialogPrimitive__namespace.Description,
2491
+ {
2492
+ ref,
2493
+ className: cn("text-sm text-muted-foreground", className),
2494
+ ...props
2351
2495
  }
2496
+ ));
2497
+ DialogDescription.displayName = DialogPrimitive__namespace.Description.displayName;
2498
+ var AddEntityDialog = ({
2499
+ open,
2500
+ onClose,
2501
+ title,
2502
+ recentlyUsedSlot,
2503
+ children,
2504
+ actionSlot
2505
+ }) => {
2506
+ return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange: (o) => !o && onClose(), children: /* @__PURE__ */ jsxRuntime.jsxs(DialogContent, { className: "max-w-lg w-full p-0 gap-0 flex flex-col max-h-[90vh] overflow-hidden", children: [
2507
+ /* @__PURE__ */ jsxRuntime.jsx(DialogHeader, { className: "px-6 pt-6 pb-4 shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { className: "text-lg font-semibold", children: title }) }),
2508
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 flex flex-col gap-4 overflow-y-auto flex-1 min-h-0 pb-4", children: [
2509
+ recentlyUsedSlot && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2510
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mb-2", children: "Recently Used" }),
2511
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: recentlyUsedSlot })
2512
+ ] }),
2513
+ children
2514
+ ] }),
2515
+ actionSlot != null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4 flex justify-end shrink-0 border-t border-border bg-background", children: actionSlot })
2516
+ ] }) });
2352
2517
  };
2353
- var Section = ({ title, children }) => {
2354
- const [expanded, setExpanded] = React11.useState(true);
2355
- const handleToggle = () => setExpanded((prev) => !prev);
2356
- const contentClassName = cn(
2357
- "overflow-hidden transition-[height] duration-200 ease-in-out",
2358
- !expanded && "hidden"
2359
- );
2360
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 border rounded-lg bg-white shadow-sm overflow-hidden", children: [
2361
- /* @__PURE__ */ jsxRuntime.jsxs(
2518
+ function mapToMedication(result) {
2519
+ return {
2520
+ id: result.id,
2521
+ brand_name: result.name,
2522
+ generic_name: result.generic_name ?? "",
2523
+ frequency: "custom",
2524
+ is_custom: false,
2525
+ duration_value: "3",
2526
+ duration_unit: "days",
2527
+ before_after: "AFTER FOOD",
2528
+ morning: "0",
2529
+ afternoon: "0",
2530
+ night: "0",
2531
+ notes: ""
2532
+ };
2533
+ }
2534
+ function mapAIMedicationToMedication(ai) {
2535
+ const id = typeof ai.id === "string" ? Number.parseInt(ai.id, 10) || 0 : Number(ai.id);
2536
+ return {
2537
+ id: Number.isNaN(id) ? 0 : id,
2538
+ brand_name: ai.brand_name ?? "",
2539
+ generic_name: ai.generic_name ?? "",
2540
+ frequency: "custom",
2541
+ is_custom: false,
2542
+ duration_value: "3",
2543
+ duration_unit: "days",
2544
+ before_after: "AFTER FOOD",
2545
+ morning: "1",
2546
+ afternoon: "0",
2547
+ night: "1",
2548
+ notes: ai.reason ?? ""
2549
+ };
2550
+ }
2551
+ function createCustomMedication(name, customId) {
2552
+ return {
2553
+ id: customId,
2554
+ brand_name: name.trim(),
2555
+ generic_name: "",
2556
+ frequency: "custom",
2557
+ is_custom: true,
2558
+ duration_value: "3",
2559
+ duration_unit: "days",
2560
+ before_after: "AFTER FOOD",
2561
+ morning: "0",
2562
+ afternoon: "0",
2563
+ night: "0",
2564
+ notes: ""
2565
+ };
2566
+ }
2567
+ function Spinner({
2568
+ value,
2569
+ onChange
2570
+ }) {
2571
+ const num = parseInt(value, 10) || 0;
2572
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center", children: [
2573
+ /* @__PURE__ */ jsxRuntime.jsx(
2362
2574
  "button",
2363
2575
  {
2364
2576
  type: "button",
2365
- onClick: handleToggle,
2366
- className: "w-full flex items-center justify-between gap-2 p-4 text-left border-b hover:bg-gray-50/80 transition-colors",
2367
- children: [
2368
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-medium", children: title }),
2369
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 text-muted-foreground", "aria-hidden": true, children: expanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-5 w-5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-5 w-5" }) })
2370
- ]
2577
+ className: "text-muted-foreground hover:text-foreground cursor-pointer",
2578
+ onClick: () => onChange(String(num + 1)),
2579
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-3 w-3" })
2371
2580
  }
2372
2581
  ),
2373
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: contentClassName, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 pt-4 space-y-4 border-t-0", children }) })
2374
- ] });
2375
- };
2376
- var ColumnLayout = ({
2377
- columns,
2378
- label,
2379
- children
2380
- }) => {
2381
- const gridCols = Math.max(1, Math.min(columns, 12));
2382
- const gridClass = `grid gap-4 w-full`;
2383
- const style = {
2384
- gridTemplateColumns: `repeat(${gridCols}, minmax(0, 1fr))`
2385
- };
2386
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6", children: [
2387
- label != null && label !== "" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 text-sm font-medium text-gray-700", children: label }),
2388
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: gridClass, style, children })
2582
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm w-4 text-center leading-none", children: num }),
2583
+ /* @__PURE__ */ jsxRuntime.jsx(
2584
+ "button",
2585
+ {
2586
+ type: "button",
2587
+ className: "text-muted-foreground hover:text-foreground cursor-pointer",
2588
+ onClick: () => onChange(String(Math.max(0, num - 1))),
2589
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-3 w-3" })
2590
+ }
2591
+ )
2389
2592
  ] });
2390
- };
2391
- var DynamicForm = () => {
2392
- const store = useFormStore();
2393
- const schema = store.getSchema();
2394
- const { state } = useForm();
2395
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
2396
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4", children: [
2397
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold", children: schema.title }),
2398
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-gray-500", children: [
2399
- "Version: ",
2400
- schema.version,
2401
- " | State: ",
2402
- state.workflowState
2403
- ] })
2593
+ }
2594
+ function MedicationCard({
2595
+ med,
2596
+ onUpdate,
2597
+ onRemove
2598
+ }) {
2599
+ const [notesOpen, setNotesOpen] = React11.useState(false);
2600
+ function set(key, val) {
2601
+ onUpdate({ ...med, [key]: val });
2602
+ }
2603
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-gray-50 rounded-lg p-3 space-y-2", children: [
2604
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
2605
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-sm flex-1 min-w-0 truncate", children: med.brand_name }),
2606
+ /* @__PURE__ */ jsxRuntime.jsxs(
2607
+ "select",
2608
+ {
2609
+ className: "text-xs border border-border rounded px-2 py-1 bg-white shrink-0",
2610
+ value: med.frequency,
2611
+ onChange: (e) => set("frequency", e.target.value),
2612
+ children: [
2613
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "custom", children: "select frequency" }),
2614
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "OD", children: "OD" }),
2615
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "BD", children: "BD" }),
2616
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "TDS", children: "TDS" }),
2617
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "QID", children: "QID" }),
2618
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "SOS", children: "SOS" })
2619
+ ]
2620
+ }
2621
+ ),
2622
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-4 items-center shrink-0", children: ["morning", "afternoon", "night"].map((slot) => /* @__PURE__ */ jsxRuntime.jsx(
2623
+ "span",
2624
+ {
2625
+ className: "text-[10px] font-semibold uppercase text-muted-foreground tracking-wide",
2626
+ children: slot === "afternoon" ? "AFTERNOON" : slot === "morning" ? "MORNING" : "NIGHT"
2627
+ },
2628
+ slot
2629
+ )) }),
2630
+ /* @__PURE__ */ jsxRuntime.jsx(
2631
+ "button",
2632
+ {
2633
+ type: "button",
2634
+ onClick: onRemove,
2635
+ className: "text-red-400 hover:text-red-600 shrink-0 p-1 cursor-pointer",
2636
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" })
2637
+ }
2638
+ )
2404
2639
  ] }),
2405
- schema.layout.map((node) => {
2406
- if (node.type === "section") {
2407
- return /* @__PURE__ */ jsxRuntime.jsx(Section, { title: node.title, children: node.children.map(
2408
- (child, index) => typeof child === "string" ? /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId: child }, child) : child.type === "column_layout" ? /* @__PURE__ */ jsxRuntime.jsx(
2409
- ColumnLayout,
2640
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [
2641
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
2642
+ /* @__PURE__ */ jsxRuntime.jsx(
2643
+ "input",
2644
+ {
2645
+ type: "text",
2646
+ inputMode: "numeric",
2647
+ pattern: "[0-9]*",
2648
+ className: "w-11 text-sm border border-border rounded px-2 py-1 bg-white",
2649
+ value: med.duration_value,
2650
+ onChange: (e) => {
2651
+ const v = e.target.value.replace(/\D/g, "");
2652
+ set("duration_value", v === "" ? "" : v);
2653
+ }
2654
+ }
2655
+ ),
2656
+ /* @__PURE__ */ jsxRuntime.jsxs(
2657
+ "select",
2658
+ {
2659
+ className: "text-xs border border-border rounded px-2 py-1 bg-white",
2660
+ value: med.duration_unit,
2661
+ onChange: (e) => set("duration_unit", e.target.value),
2662
+ children: [
2663
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "days", children: "days" }),
2664
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "weeks", children: "weeks" }),
2665
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "months", children: "months" })
2666
+ ]
2667
+ }
2668
+ )
2669
+ ] }),
2670
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-2 items-center flex-1 min-w-0 justify-end", children: ["morning", "afternoon", "night"].map((slot) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col items-center gap-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(Spinner, { value: med[slot], onChange: (v) => set(slot, v) }) }, slot)) }),
2671
+ /* @__PURE__ */ jsxRuntime.jsxs(
2672
+ "select",
2673
+ {
2674
+ className: "text-xs border border-border rounded px-2 py-1 bg-white shrink-0",
2675
+ value: med.before_after === "AFTER FOOD" ? "AF" : "BF",
2676
+ onChange: (e) => set("before_after", e.target.value === "AF" ? "AFTER FOOD" : "BEFORE FOOD"),
2677
+ children: [
2678
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "AF", children: "AF" }),
2679
+ /* @__PURE__ */ jsxRuntime.jsx("option", { value: "BF", children: "BF" })
2680
+ ]
2681
+ }
2682
+ )
2683
+ ] }),
2684
+ /* @__PURE__ */ jsxRuntime.jsxs(
2685
+ "div",
2686
+ {
2687
+ role: "button",
2688
+ tabIndex: 0,
2689
+ onClick: () => setNotesOpen((o) => !o),
2690
+ onKeyDown: (e) => {
2691
+ if (e.key === "Enter" || e.key === " ") {
2692
+ e.preventDefault();
2693
+ setNotesOpen((o) => !o);
2694
+ }
2695
+ },
2696
+ className: cn(
2697
+ "flex items-center gap-2 w-full text-left text-xs text-muted-foreground hover:text-foreground",
2698
+ "cursor-pointer py-2 px-2 rounded border border-transparent hover:bg-gray-100/80 transition-colors"
2699
+ ),
2700
+ children: [
2701
+ /* @__PURE__ */ jsxRuntime.jsx(
2702
+ lucideReact.ChevronRight,
2410
2703
  {
2411
- columns: child.columns,
2412
- label: child.label,
2413
- children: child.children.map((fieldId) => /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }, fieldId))
2414
- },
2415
- child.id
2416
- ) : /* @__PURE__ */ jsxRuntime.jsx(React11__namespace.default.Fragment, {}, child.id ?? index)
2417
- ) }, node.id);
2704
+ className: cn("h-3 w-3 shrink-0 transition-transform", notesOpen && "rotate-90")
2705
+ }
2706
+ ),
2707
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Medicine Notes" })
2708
+ ]
2418
2709
  }
2419
- if (node.type === "column_layout") {
2420
- return /* @__PURE__ */ jsxRuntime.jsx(
2421
- ColumnLayout,
2422
- {
2423
- columns: node.columns,
2424
- label: node.label,
2425
- children: node.children.map((fieldId) => /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }, fieldId))
2426
- },
2427
- node.id
2428
- );
2710
+ ),
2711
+ notesOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10 mt-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(
2712
+ "textarea",
2713
+ {
2714
+ className: "block w-full text-xs border border-border rounded px-2 py-1 resize-none bg-white min-h-0",
2715
+ rows: 2,
2716
+ placeholder: "Add notes...",
2717
+ value: med.notes,
2718
+ onChange: (e) => set("notes", e.target.value)
2429
2719
  }
2430
- return null;
2431
- })
2720
+ ) })
2432
2721
  ] });
2722
+ }
2723
+ var ADD_FEEDBACK_MS = 320;
2724
+ function SearchResultCard({
2725
+ result,
2726
+ isAdding,
2727
+ onSelect
2728
+ }) {
2729
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2730
+ "button",
2731
+ {
2732
+ type: "button",
2733
+ onClick: onSelect,
2734
+ className: cn(
2735
+ "w-full text-left border rounded-lg px-3 py-2 transition-all duration-300 ease-out cursor-pointer flex items-center justify-between gap-2",
2736
+ isAdding ? "border-green-400 bg-green-50 scale-[0.98]" : "border-border bg-white hover:bg-gray-50"
2737
+ ),
2738
+ children: [
2739
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 text-left", children: [
2740
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold leading-tight truncate", children: result.name }),
2741
+ result.generic_name && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground mt-0.5 truncate", children: [
2742
+ "Generic: ",
2743
+ result.generic_name
2744
+ ] })
2745
+ ] }),
2746
+ isAdding && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4 shrink-0 text-green-600" })
2747
+ ]
2748
+ }
2749
+ );
2750
+ }
2751
+ var AddMedicationField = ({
2752
+ label = "Medications",
2753
+ value,
2754
+ onChange,
2755
+ onSearch,
2756
+ recentlyUsed = [],
2757
+ suggestedMedications = []
2758
+ }) => {
2759
+ const [open, setOpen] = React11.useState(false);
2760
+ const [query, setQuery] = React11.useState("");
2761
+ const [results, setResults] = React11.useState([]);
2762
+ const [loading, setLoading] = React11.useState(false);
2763
+ const [addingResultId, setAddingResultId] = React11.useState(null);
2764
+ const debounceRef = React11.useRef(null);
2765
+ const customIdRef = React11.useRef(-1);
2766
+ const valueRef = React11.useRef([]);
2767
+ const searchInputRef = React11.useRef(null);
2768
+ const safeValue = Array.isArray(value) ? value : [];
2769
+ valueRef.current = safeValue;
2770
+ const addedIds = new Set(safeValue.map((m) => String(m.id)));
2771
+ React11.useEffect(() => {
2772
+ if (!open) {
2773
+ setQuery("");
2774
+ setResults([]);
2775
+ setAddingResultId(null);
2776
+ }
2777
+ }, [open]);
2778
+ function handleQueryChange(q) {
2779
+ setQuery(q);
2780
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2781
+ if (!q.trim()) {
2782
+ setResults([]);
2783
+ return;
2784
+ }
2785
+ debounceRef.current = setTimeout(async () => {
2786
+ setLoading(true);
2787
+ try {
2788
+ const res = await onSearch(q);
2789
+ setResults(res);
2790
+ } finally {
2791
+ setLoading(false);
2792
+ }
2793
+ }, 500);
2794
+ }
2795
+ function handleRecentlyUsedToggle(result) {
2796
+ if (addedIds.has(String(result.id))) {
2797
+ onChange(safeValue.filter((m) => String(m.id) !== String(result.id)));
2798
+ } else {
2799
+ onChange([...safeValue, mapToMedication(result)]);
2800
+ }
2801
+ }
2802
+ function handleResultClick(result) {
2803
+ if (addedIds.has(String(result.id))) return;
2804
+ setAddingResultId(result.id);
2805
+ setTimeout(() => {
2806
+ onChange([...valueRef.current, mapToMedication(result)]);
2807
+ setAddingResultId(null);
2808
+ setQuery("");
2809
+ setResults([]);
2810
+ setTimeout(() => searchInputRef.current?.focus(), 0);
2811
+ }, ADD_FEEDBACK_MS);
2812
+ }
2813
+ function handleSave() {
2814
+ const trimmed = query.trim();
2815
+ if (!trimmed) return;
2816
+ const customId = customIdRef.current--;
2817
+ onChange([...safeValue, createCustomMedication(trimmed, customId)]);
2818
+ setQuery("");
2819
+ setResults([]);
2820
+ }
2821
+ function handleAddSuggestion(ai) {
2822
+ if (addedIds.has(String(ai.id))) return;
2823
+ onChange([...safeValue, mapAIMedicationToMedication(ai)]);
2824
+ }
2825
+ function updateMed(index, updated) {
2826
+ const next = [...safeValue];
2827
+ next[index] = updated;
2828
+ onChange(next);
2829
+ }
2830
+ function removeMed(index) {
2831
+ onChange(safeValue.filter((_, i) => i !== index));
2832
+ }
2833
+ const recentlyUsedChips = recentlyUsed.map((r) => {
2834
+ const isAdded = addedIds.has(String(r.id));
2835
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2836
+ "button",
2837
+ {
2838
+ type: "button",
2839
+ onClick: () => handleRecentlyUsedToggle(r),
2840
+ className: cn(
2841
+ "rounded-full border text-xs px-3 py-1 transition-colors cursor-pointer",
2842
+ isAdded ? "border-green-300 bg-green-50 text-green-700" : "border-border bg-white hover:bg-gray-50"
2843
+ ),
2844
+ children: [
2845
+ isAdded && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1", children: "\u2713" }),
2846
+ r.name
2847
+ ]
2848
+ },
2849
+ r.id
2850
+ );
2851
+ });
2852
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md overflow-hidden flex flex-col border bg-white border-[#D0D0D0] space-y-3 p-3 h-fit", children: [
2853
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
2854
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: label }),
2855
+ /* @__PURE__ */ jsxRuntime.jsx(
2856
+ "button",
2857
+ {
2858
+ type: "button",
2859
+ onClick: () => setOpen(true),
2860
+ className: "text-sm font-semibold text-purple-600 hover:text-purple-800 cursor-pointer",
2861
+ children: "ADD"
2862
+ }
2863
+ )
2864
+ ] }),
2865
+ suggestedMedications.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-1.5 items-center", children: [
2866
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground mr-1", children: "Suggestions:" }),
2867
+ suggestedMedications.filter((s) => !addedIds.has(String(s.id))).map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
2868
+ "button",
2869
+ {
2870
+ type: "button",
2871
+ onClick: () => handleAddSuggestion(s),
2872
+ className: "inline-flex items-center gap-1 rounded-full border border-dashed border-purple-300 bg-purple-50/80 text-purple-800 text-xs px-2.5 py-1 hover:bg-purple-100 transition-colors cursor-pointer",
2873
+ children: [
2874
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3 w-3" }),
2875
+ s.brand_name || s.generic_name || String(s.id)
2876
+ ]
2877
+ },
2878
+ String(s.id)
2879
+ ))
2880
+ ] }),
2881
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: safeValue.map((med, i) => /* @__PURE__ */ jsxRuntime.jsx(
2882
+ MedicationCard,
2883
+ {
2884
+ med,
2885
+ onUpdate: (u) => updateMed(i, u),
2886
+ onRemove: () => removeMed(i)
2887
+ },
2888
+ med.id
2889
+ )) }),
2890
+ /* @__PURE__ */ jsxRuntime.jsx(
2891
+ AddEntityDialog,
2892
+ {
2893
+ open,
2894
+ onClose: () => setOpen(false),
2895
+ title: "Add Medication",
2896
+ recentlyUsedSlot: recentlyUsedChips.length > 0 ? recentlyUsedChips : void 0,
2897
+ actionSlot: /* @__PURE__ */ jsxRuntime.jsx(
2898
+ "button",
2899
+ {
2900
+ type: "button",
2901
+ disabled: !query.trim(),
2902
+ onClick: handleSave,
2903
+ className: cn(
2904
+ "rounded-md px-5 py-2 text-sm font-semibold bg-primary text-primary-foreground hover:bg-primary/90 transition-opacity cursor-pointer",
2905
+ !query.trim() && "opacity-40 cursor-not-allowed"
2906
+ ),
2907
+ children: "Save Medicine"
2908
+ }
2909
+ ),
2910
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
2911
+ /* @__PURE__ */ jsxRuntime.jsx(
2912
+ Input,
2913
+ {
2914
+ ref: searchInputRef,
2915
+ value: query,
2916
+ onChange: (e) => handleQueryChange(e.target.value),
2917
+ placeholder: "Search medicines...",
2918
+ className: "border border-border rounded-md focus-visible:ring-0"
2919
+ }
2920
+ ),
2921
+ loading && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground text-center py-2", children: "Searching\u2026" }),
2922
+ !loading && results.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 gap-2 max-h-64 overflow-y-auto", children: results.map((r) => /* @__PURE__ */ jsxRuntime.jsx(
2923
+ SearchResultCard,
2924
+ {
2925
+ result: r,
2926
+ isAdding: addingResultId === r.id,
2927
+ onSelect: () => handleResultClick(r)
2928
+ },
2929
+ r.id
2930
+ )) })
2931
+ ] })
2932
+ }
2933
+ )
2934
+ ] });
2935
+ };
2936
+ var AddMedicationWidget = ({ fieldId }) => {
2937
+ const { value, setValue } = useField(fieldId);
2938
+ const handlers = useFieldHandlers(fieldId);
2939
+ const { medications: suggestedMedications } = useAISuggestions();
2940
+ const safeValue = Array.isArray(value) ? value : [];
2941
+ return /* @__PURE__ */ jsxRuntime.jsx(
2942
+ AddMedicationField,
2943
+ {
2944
+ value: safeValue,
2945
+ onChange: setValue,
2946
+ onSearch: handlers.onSearch,
2947
+ recentlyUsed: handlers.recentlyUsed,
2948
+ suggestedMedications
2949
+ }
2950
+ );
2951
+ };
2952
+ var ADD_FEEDBACK_MS2 = 320;
2953
+ function mapToInvestigation(result) {
2954
+ return {
2955
+ ...result,
2956
+ label: result.name,
2957
+ is_custom: false,
2958
+ fromAI: false
2959
+ };
2960
+ }
2961
+ var AddInvestigationField = ({
2962
+ label = "Investigations",
2963
+ value,
2964
+ onChange,
2965
+ onSearch,
2966
+ recentlyUsed = []
2967
+ }) => {
2968
+ const [open, setOpen] = React11.useState(false);
2969
+ const [query, setQuery] = React11.useState("");
2970
+ const [results, setResults] = React11.useState([]);
2971
+ const [loading, setLoading] = React11.useState(false);
2972
+ const [addingId, setAddingId] = React11.useState(null);
2973
+ const debounceRef = React11.useRef(null);
2974
+ const valueRef = React11.useRef([]);
2975
+ const searchInputRef = React11.useRef(null);
2976
+ valueRef.current = value;
2977
+ const addedIds = new Set(value.map((inv) => inv.id));
2978
+ React11.useEffect(() => {
2979
+ if (!open) {
2980
+ setQuery("");
2981
+ setResults([]);
2982
+ setAddingId(null);
2983
+ }
2984
+ }, [open]);
2985
+ function handleQueryChange(q) {
2986
+ setQuery(q);
2987
+ if (debounceRef.current) clearTimeout(debounceRef.current);
2988
+ if (!q.trim()) {
2989
+ setResults([]);
2990
+ return;
2991
+ }
2992
+ debounceRef.current = setTimeout(async () => {
2993
+ setLoading(true);
2994
+ try {
2995
+ const res = await onSearch(q);
2996
+ setResults(res);
2997
+ } finally {
2998
+ setLoading(false);
2999
+ }
3000
+ }, 500);
3001
+ }
3002
+ function addInvestigation(result) {
3003
+ if (!addedIds.has(result.id)) {
3004
+ onChange([...valueRef.current, mapToInvestigation(result)]);
3005
+ }
3006
+ setQuery("");
3007
+ setResults([]);
3008
+ }
3009
+ function handleResultClick(result) {
3010
+ if (addedIds.has(result.id)) return;
3011
+ setAddingId(result.id);
3012
+ setTimeout(() => {
3013
+ addInvestigation(result);
3014
+ setAddingId(null);
3015
+ setTimeout(() => searchInputRef.current?.focus(), 0);
3016
+ }, ADD_FEEDBACK_MS2);
3017
+ }
3018
+ function removeInvestigation(id) {
3019
+ onChange(value.filter((inv) => inv.id !== id));
3020
+ }
3021
+ function handleRecentlyUsedToggle(result) {
3022
+ if (addedIds.has(result.id)) {
3023
+ removeInvestigation(result.id);
3024
+ } else {
3025
+ addInvestigation(result);
3026
+ }
3027
+ }
3028
+ const recentlyUsedChips = recentlyUsed.map((r) => {
3029
+ const isAdded = addedIds.has(r.id);
3030
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3031
+ "button",
3032
+ {
3033
+ type: "button",
3034
+ onClick: () => handleRecentlyUsedToggle(r),
3035
+ className: cn(
3036
+ "rounded-full border text-xs px-3 py-1 transition-colors",
3037
+ isAdded ? "border-green-300 bg-green-50 text-green-700" : "border-border bg-white hover:bg-gray-50"
3038
+ ),
3039
+ children: [
3040
+ isAdded && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1", children: "\u2713" }),
3041
+ r.name
3042
+ ]
3043
+ },
3044
+ r.id
3045
+ );
3046
+ });
3047
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md overflow-hidden flex flex-col bg-white border border-[#D0D0D0] space-y-3 p-3 h-fit", children: [
3048
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
3049
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: label }),
3050
+ /* @__PURE__ */ jsxRuntime.jsx(
3051
+ "button",
3052
+ {
3053
+ type: "button",
3054
+ onClick: () => setOpen(true),
3055
+ className: "text-sm font-semibold text-purple-600 hover:text-purple-800 cursor-pointer",
3056
+ children: "ADD"
3057
+ }
3058
+ )
3059
+ ] }),
3060
+ value.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border rounded-lg border border-border overflow-hidden", children: value.map((inv) => /* @__PURE__ */ jsxRuntime.jsxs(
3061
+ "div",
3062
+ {
3063
+ className: "flex items-center justify-between px-3 py-2 bg-white hover:bg-gray-50",
3064
+ children: [
3065
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: inv.name }),
3066
+ /* @__PURE__ */ jsxRuntime.jsx(
3067
+ "button",
3068
+ {
3069
+ type: "button",
3070
+ onClick: () => removeInvestigation(inv.id),
3071
+ className: "text-muted-foreground hover:text-red-500 transition-colors cursor-pointer",
3072
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
3073
+ }
3074
+ )
3075
+ ]
3076
+ },
3077
+ inv.id
3078
+ )) }),
3079
+ /* @__PURE__ */ jsxRuntime.jsx(
3080
+ AddEntityDialog,
3081
+ {
3082
+ open,
3083
+ onClose: () => setOpen(false),
3084
+ title: "Add Investigation",
3085
+ recentlyUsedSlot: recentlyUsedChips.length > 0 ? recentlyUsedChips : void 0,
3086
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
3087
+ /* @__PURE__ */ jsxRuntime.jsx(
3088
+ Input,
3089
+ {
3090
+ ref: searchInputRef,
3091
+ value: query,
3092
+ onChange: (e) => handleQueryChange(e.target.value),
3093
+ placeholder: "Search for an investigation...",
3094
+ className: "border border-border rounded-md focus-visible:ring-0"
3095
+ }
3096
+ ),
3097
+ loading && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground py-1", children: "Searching\u2026" }),
3098
+ !loading && results.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border border-border rounded-md overflow-hidden", children: results.map((r) => {
3099
+ const isAdding = addingId === r.id;
3100
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3101
+ "button",
3102
+ {
3103
+ type: "button",
3104
+ onClick: () => handleResultClick(r),
3105
+ className: cn(
3106
+ "w-full text-left px-3 py-2 text-sm transition-all duration-300 ease-out border-b border-border last:border-b-0 cursor-pointer flex items-center justify-between gap-2",
3107
+ isAdding ? "bg-green-50 border-green-200" : "hover:bg-gray-50"
3108
+ ),
3109
+ children: [
3110
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: r.name }),
3111
+ isAdding && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4 shrink-0 text-green-600" })
3112
+ ]
3113
+ },
3114
+ r.id
3115
+ );
3116
+ }) })
3117
+ ] })
3118
+ }
3119
+ )
3120
+ ] });
3121
+ };
3122
+ var AddInvestigationWidget = ({ fieldId }) => {
3123
+ const { value, setValue } = useField(fieldId);
3124
+ const handlers = useFieldHandlers(fieldId);
3125
+ return /* @__PURE__ */ jsxRuntime.jsx(
3126
+ AddInvestigationField,
3127
+ {
3128
+ value: value ?? [],
3129
+ onChange: setValue,
3130
+ onSearch: handlers.onSearch,
3131
+ recentlyUsed: handlers.recentlyUsed
3132
+ }
3133
+ );
3134
+ };
3135
+ var ADD_FEEDBACK_MS3 = 320;
3136
+ function mapToProcedure(result) {
3137
+ return {
3138
+ ...result,
3139
+ label: result.name,
3140
+ is_custom: false,
3141
+ fromAI: false
3142
+ };
3143
+ }
3144
+ var AddProcedureField = ({
3145
+ label = "Procedures",
3146
+ value,
3147
+ onChange,
3148
+ onSearch,
3149
+ recentlyUsed = []
3150
+ }) => {
3151
+ const [open, setOpen] = React11.useState(false);
3152
+ const [query, setQuery] = React11.useState("");
3153
+ const [results, setResults] = React11.useState([]);
3154
+ const [loading, setLoading] = React11.useState(false);
3155
+ const [addingId, setAddingId] = React11.useState(null);
3156
+ const debounceRef = React11.useRef(null);
3157
+ const valueRef = React11.useRef([]);
3158
+ const searchInputRef = React11.useRef(null);
3159
+ valueRef.current = value;
3160
+ const addedIds = new Set(value.map((proc) => proc.id));
3161
+ React11.useEffect(() => {
3162
+ if (!open) {
3163
+ setQuery("");
3164
+ setResults([]);
3165
+ setAddingId(null);
3166
+ }
3167
+ }, [open]);
3168
+ function handleQueryChange(q) {
3169
+ setQuery(q);
3170
+ if (debounceRef.current) clearTimeout(debounceRef.current);
3171
+ if (!q.trim()) {
3172
+ setResults([]);
3173
+ return;
3174
+ }
3175
+ debounceRef.current = setTimeout(async () => {
3176
+ setLoading(true);
3177
+ try {
3178
+ const res = await onSearch(q);
3179
+ setResults(res);
3180
+ } finally {
3181
+ setLoading(false);
3182
+ }
3183
+ }, 500);
3184
+ }
3185
+ function addProcedure(result) {
3186
+ if (!addedIds.has(result.id)) {
3187
+ onChange([...valueRef.current, mapToProcedure(result)]);
3188
+ }
3189
+ setQuery("");
3190
+ setResults([]);
3191
+ }
3192
+ function handleResultClick(result) {
3193
+ if (addedIds.has(result.id)) return;
3194
+ setAddingId(result.id);
3195
+ setTimeout(() => {
3196
+ addProcedure(result);
3197
+ setAddingId(null);
3198
+ setTimeout(() => searchInputRef.current?.focus(), 0);
3199
+ }, ADD_FEEDBACK_MS3);
3200
+ }
3201
+ function removeProcedure(id) {
3202
+ onChange(value.filter((proc) => proc.id !== id));
3203
+ }
3204
+ function handleRecentlyUsedToggle(result) {
3205
+ if (addedIds.has(result.id)) {
3206
+ removeProcedure(result.id);
3207
+ } else {
3208
+ addProcedure(result);
3209
+ }
3210
+ }
3211
+ const recentlyUsedChips = recentlyUsed.map((r) => {
3212
+ const isAdded = addedIds.has(r.id);
3213
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3214
+ "button",
3215
+ {
3216
+ type: "button",
3217
+ onClick: () => handleRecentlyUsedToggle(r),
3218
+ className: cn(
3219
+ "rounded-full border text-xs px-3 py-1 transition-colors",
3220
+ isAdded ? "border-green-300 bg-green-50 text-green-700" : "border-border bg-white hover:bg-gray-50"
3221
+ ),
3222
+ children: [
3223
+ isAdded && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1", children: "\u2713" }),
3224
+ r.name
3225
+ ]
3226
+ },
3227
+ r.id
3228
+ );
3229
+ });
3230
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md overflow-hidden flex flex-col bg-white border border-[#D0D0D0] space-y-3 p-3 h-fit", children: [
3231
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
3232
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: label }),
3233
+ /* @__PURE__ */ jsxRuntime.jsx(
3234
+ "button",
3235
+ {
3236
+ type: "button",
3237
+ onClick: () => setOpen(true),
3238
+ className: "text-sm font-semibold text-purple-600 hover:text-purple-800 cursor-pointer",
3239
+ children: "ADD"
3240
+ }
3241
+ )
3242
+ ] }),
3243
+ value.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border rounded-lg border border-border overflow-hidden", children: value.map((proc) => /* @__PURE__ */ jsxRuntime.jsxs(
3244
+ "div",
3245
+ {
3246
+ className: "flex items-center justify-between px-3 py-2 bg-white hover:bg-gray-50",
3247
+ children: [
3248
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: proc.name }),
3249
+ /* @__PURE__ */ jsxRuntime.jsx(
3250
+ "button",
3251
+ {
3252
+ type: "button",
3253
+ onClick: () => removeProcedure(proc.id),
3254
+ className: "text-muted-foreground hover:text-red-500 transition-colors cursor-pointer",
3255
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
3256
+ }
3257
+ )
3258
+ ]
3259
+ },
3260
+ proc.id
3261
+ )) }),
3262
+ /* @__PURE__ */ jsxRuntime.jsx(
3263
+ AddEntityDialog,
3264
+ {
3265
+ open,
3266
+ onClose: () => setOpen(false),
3267
+ title: "Add Procedure",
3268
+ recentlyUsedSlot: recentlyUsedChips.length > 0 ? recentlyUsedChips : void 0,
3269
+ children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
3270
+ /* @__PURE__ */ jsxRuntime.jsx(
3271
+ Input,
3272
+ {
3273
+ ref: searchInputRef,
3274
+ value: query,
3275
+ onChange: (e) => handleQueryChange(e.target.value),
3276
+ placeholder: "Search for a procedure...",
3277
+ className: "border border-border rounded-md focus-visible:ring-0"
3278
+ }
3279
+ ),
3280
+ loading && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground py-1", children: "Searching\u2026" }),
3281
+ !loading && results.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border border-border rounded-md overflow-hidden", children: results.map((r) => {
3282
+ const isAdding = addingId === r.id;
3283
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3284
+ "button",
3285
+ {
3286
+ type: "button",
3287
+ onClick: () => handleResultClick(r),
3288
+ className: cn(
3289
+ "w-full text-left px-3 py-2 text-sm transition-all duration-300 ease-out border-b border-border last:border-b-0 cursor-pointer flex items-center justify-between gap-2",
3290
+ isAdding ? "bg-green-50 border-green-200" : "hover:bg-gray-50"
3291
+ ),
3292
+ children: [
3293
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: r.name }),
3294
+ isAdding && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4 shrink-0 text-green-600" })
3295
+ ]
3296
+ },
3297
+ r.id
3298
+ );
3299
+ }) })
3300
+ ] })
3301
+ }
3302
+ )
3303
+ ] });
3304
+ };
3305
+ var AddProcedureWidget = ({ fieldId }) => {
3306
+ const { value, setValue } = useField(fieldId);
3307
+ const handlers = useFieldHandlers(fieldId);
3308
+ return /* @__PURE__ */ jsxRuntime.jsx(
3309
+ AddProcedureField,
3310
+ {
3311
+ value: value ?? [],
3312
+ onChange: setValue,
3313
+ onSearch: handlers.onSearch,
3314
+ recentlyUsed: handlers.recentlyUsed
3315
+ }
3316
+ );
3317
+ };
3318
+ function getStatusBasedClass(status) {
3319
+ switch ((status || "").toLowerCase()) {
3320
+ case "high":
3321
+ return "bg-red-100 text-red-500";
3322
+ case "medium":
3323
+ return "bg-yellow-100 text-yellow-600";
3324
+ case "low":
3325
+ return "bg-green-100 text-green-600";
3326
+ default:
3327
+ return "bg-gray-200 text-gray-600";
3328
+ }
3329
+ }
3330
+ function DiagnosisCard({ item, defaultOpen = false }) {
3331
+ const [open, setOpen] = React11.useState(defaultOpen);
3332
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border border-gray-200 rounded-lg bg-[#F2F2F2] mb-2 last:mb-0 overflow-hidden", children: [
3333
+ /* @__PURE__ */ jsxRuntime.jsxs(
3334
+ "button",
3335
+ {
3336
+ type: "button",
3337
+ onClick: () => setOpen((o) => !o),
3338
+ className: cn(
3339
+ "w-full text-left flex items-center gap-2 py-2 px-3 rounded-lg",
3340
+ "cursor-pointer hover:bg-gray-200/60 transition-colors"
3341
+ ),
3342
+ children: [
3343
+ /* @__PURE__ */ jsxRuntime.jsx(
3344
+ lucideReact.ChevronRight,
3345
+ {
3346
+ className: cn("h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform", open && "rotate-90")
3347
+ }
3348
+ ),
3349
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
3350
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500 font-medium", children: item.category.charAt(0).toUpperCase() + item.category.slice(1) }),
3351
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-black font-semibold mt-0.5 leading-tight", children: item.condition })
3352
+ ] })
3353
+ ]
3354
+ }
3355
+ ),
3356
+ open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 px-3 pb-3 pt-0 pl-9", children: [
3357
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-xs font-semibold text-gray-500", children: [
3358
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "LIKELIHOOD" }),
3359
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "SEVERITY" }),
3360
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "TREATABILITY" })
3361
+ ] }),
3362
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between gap-2", children: [
3363
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("px-2.5 py-0.5 rounded-full text-xs font-bold shrink-0", getStatusBasedClass(item.likelihood)), children: item.likelihood }),
3364
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("px-2.5 py-0.5 rounded-full text-xs font-bold shrink-0", getStatusBasedClass(item.severity)), children: item.severity }),
3365
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: cn("px-2.5 py-0.5 rounded-full text-xs font-bold shrink-0", getStatusBasedClass(item.treatability)), children: item.treatability })
3366
+ ] }),
3367
+ item.keyFeatures && item.keyFeatures.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-1", children: [
3368
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs font-semibold text-gray-500 mb-0.5 uppercase tracking-wider", children: "Key Features" }),
3369
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "list-disc pl-4 text-xs text-gray-700 space-y-0.5", children: item.keyFeatures.map((feature, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: feature }, i)) })
3370
+ ] }),
3371
+ item.nextSteps && item.nextSteps.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-1", children: [
3372
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs font-semibold text-gray-500 mb-0.5 uppercase tracking-wider", children: "Next Steps" }),
3373
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "list-disc pl-4 text-xs text-gray-700 space-y-0.5", children: item.nextSteps.map((step, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: step }, i)) })
3374
+ ] })
3375
+ ] })
3376
+ ] });
3377
+ }
3378
+ var DifferentialDiagnosis = ({
3379
+ differentialDiagnosis,
3380
+ className = "",
3381
+ height = "300px"
3382
+ }) => {
3383
+ const hasItems = Array.isArray(differentialDiagnosis) && differentialDiagnosis.length > 0;
3384
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3385
+ "div",
3386
+ {
3387
+ className: cn(
3388
+ "rounded-md overflow-hidden flex flex-col border border-[#D0D0D0]",
3389
+ className
3390
+ ),
3391
+ children: [
3392
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-t-md bg-[#FEE8EC] px-3 py-1.5 text-xs shrink-0 z-[1] text-slate-600 flex items-center font-semibold", children: /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Differential Diagnosis" }) }),
3393
+ /* @__PURE__ */ jsxRuntime.jsxs(
3394
+ "div",
3395
+ {
3396
+ className: cn(
3397
+ "relative bg-white rounded-b-md border-t border-[#D0D0D0] overflow-y-auto p-3",
3398
+ "focus-visible:outline-none"
3399
+ ),
3400
+ style: { minHeight: height, maxHeight: height },
3401
+ children: [
3402
+ hasItems ? differentialDiagnosis.map((item, idx) => /* @__PURE__ */ jsxRuntime.jsx(DiagnosisCard, { item, defaultOpen: idx === 0 }, idx)) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No differential diagnosis yet." }),
3403
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-0 h-[30%] bg-gradient-to-t from-white via-white/80 to-transparent" })
3404
+ ]
3405
+ }
3406
+ )
3407
+ ]
3408
+ }
3409
+ );
3410
+ };
3411
+ var DifferentialDiagnosisWidget = ({ fieldId }) => {
3412
+ const { value } = useField(fieldId);
3413
+ const items = Array.isArray(value) ? value : [];
3414
+ return /* @__PURE__ */ jsxRuntime.jsx(DifferentialDiagnosis, { differentialDiagnosis: items });
3415
+ };
3416
+ var Spinner2 = React11__namespace.forwardRef(
3417
+ ({ className, size = "md", ...props }, ref) => {
3418
+ const sizeClasses = {
3419
+ sm: "h-4 w-4 border-2",
3420
+ md: "h-6 w-6 border-2",
3421
+ lg: "h-8 w-8 border-3"
3422
+ };
3423
+ return /* @__PURE__ */ jsxRuntime.jsx(
3424
+ "div",
3425
+ {
3426
+ ref,
3427
+ className: cn(
3428
+ "animate-spin rounded-full border-primary border-t-transparent",
3429
+ sizeClasses[size],
3430
+ className
3431
+ ),
3432
+ ...props,
3433
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Loading..." })
3434
+ }
3435
+ );
3436
+ }
3437
+ );
3438
+ Spinner2.displayName = "Spinner";
3439
+ function buildItems(data) {
3440
+ const items = [];
3441
+ const hasBP = data.blood_pressure_systolic != null || data.blood_pressure_diastolic != null;
3442
+ if (hasBP) {
3443
+ const sys = data.blood_pressure_systolic ?? "\u2013";
3444
+ const dia = data.blood_pressure_diastolic ?? "\u2013";
3445
+ items.push({ label: "BP", value: `${sys}/${dia}` });
3446
+ }
3447
+ if (data.heart_rate != null)
3448
+ items.push({ label: "Heart Rate", value: `${data.heart_rate} bpm` });
3449
+ if (data.respiratory_rate != null)
3450
+ items.push({ label: "Resp. Rate", value: `${data.respiratory_rate}/min` });
3451
+ if (data.oxygen_saturation != null)
3452
+ items.push({ label: "SpO\u2082", value: `${data.oxygen_saturation}%` });
3453
+ if (data.temperature != null)
3454
+ items.push({ label: "Temp", value: `${data.temperature} \xB0F` });
3455
+ if (data.height != null)
3456
+ items.push({ label: "Height", value: `${data.height} cm` });
3457
+ if (data.weight != null)
3458
+ items.push({ label: "Weight", value: `${data.weight} kg` });
3459
+ if (data.bmi != null)
3460
+ items.push({ label: "BMI", value: `${data.bmi}` });
3461
+ if (data.blood_sugar != null)
3462
+ items.push({ label: "Blood Sugar", value: `${data.blood_sugar} mg/dL` });
3463
+ if (data.circumference_of_head_in_cms != null)
3464
+ items.push({ label: "Head Circ.", value: `${data.circumference_of_head_in_cms} cm` });
3465
+ if (data.circumference_of_waist_in_cms != null)
3466
+ items.push({ label: "Waist Circ.", value: `${data.circumference_of_waist_in_cms} cm` });
3467
+ return items;
3468
+ }
3469
+ function formatRecordedAt(iso) {
3470
+ if (!iso) return "";
3471
+ try {
3472
+ return new Date(iso).toLocaleString(void 0, {
3473
+ day: "2-digit",
3474
+ month: "short",
3475
+ year: "numeric",
3476
+ hour: "2-digit",
3477
+ minute: "2-digit"
3478
+ });
3479
+ } catch {
3480
+ return iso;
3481
+ }
3482
+ }
3483
+ function VitalChip({ label, value }) {
3484
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0 rounded-md min-w-fit px-1.5", children: [
3485
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-sm text-gray-500 uppercase tracking-wide leading-none", children: label }),
3486
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-900 leading-tight", children: value })
3487
+ ] });
3488
+ }
3489
+ var Vitals = ({
3490
+ data,
3491
+ loading = false,
3492
+ error = null,
3493
+ onRefresh,
3494
+ className
3495
+ }) => {
3496
+ const items = data ? buildItems(data) : [];
3497
+ const hasItems = items.length > 0;
3498
+ const recordedAt = data?.recorded_at ? formatRecordedAt(data.recorded_at) : null;
3499
+ const notes = data?.notes?.trim() || null;
3500
+ return /* @__PURE__ */ jsxRuntime.jsx(
3501
+ "div",
3502
+ {
3503
+ className: cn(
3504
+ "rounded-md overflow-hidden border border-[#D0D0D0] bg-white px-2 py-2",
3505
+ className
3506
+ ),
3507
+ children: loading && !hasItems ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 py-1", children: [
3508
+ /* @__PURE__ */ jsxRuntime.jsx(Spinner2, { size: "sm" }),
3509
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Loading vitals\u2026" })
3510
+ ] }) : error ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500 py-1", children: error }) : hasItems ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1.5", children: [
3511
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-3 gap-y-1", children: [
3512
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-x-2 gap-y-0.5", children: items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(VitalChip, { ...item }, item.label)) }),
3513
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-end gap-0.5 shrink-0", children: [
3514
+ recordedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-slate-500", children: recordedAt }),
3515
+ onRefresh && /* @__PURE__ */ jsxRuntime.jsxs(
3516
+ "button",
3517
+ {
3518
+ type: "button",
3519
+ onClick: onRefresh,
3520
+ disabled: loading,
3521
+ className: cn(
3522
+ "flex items-center gap-0.5 text-[10px] font-medium text-slate-600",
3523
+ "hover:text-slate-900 transition-colors disabled:opacity-50 cursor-pointer"
3524
+ ),
3525
+ "aria-label": "Refresh vitals",
3526
+ children: [
3527
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: cn("h-2.5 w-2.5", loading && "animate-spin") }),
3528
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Refresh" })
3529
+ ]
3530
+ }
3531
+ )
3532
+ ] })
3533
+ ] }),
3534
+ notes && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-600 pt-1.5 mt-0.5", children: notes })
3535
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 py-1", children: [
3536
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No vitals recorded." }),
3537
+ onRefresh && /* @__PURE__ */ jsxRuntime.jsxs(
3538
+ "button",
3539
+ {
3540
+ type: "button",
3541
+ onClick: onRefresh,
3542
+ disabled: loading,
3543
+ className: cn(
3544
+ "flex items-center gap-0.5 text-[10px] font-medium text-slate-600",
3545
+ "hover:text-slate-900 transition-colors disabled:opacity-50 shrink-0"
3546
+ ),
3547
+ "aria-label": "Refresh vitals",
3548
+ children: [
3549
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: cn("h-2.5 w-2.5", loading && "animate-spin") }),
3550
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Refresh" })
3551
+ ]
3552
+ }
3553
+ )
3554
+ ] })
3555
+ }
3556
+ );
3557
+ };
3558
+ var VitalsWidget = ({ fieldId }) => {
3559
+ const { value, setValue, fieldDef } = useField(fieldId);
3560
+ const handlers = useFieldHandlers(fieldId);
3561
+ const [loading, setLoading] = React11.useState(false);
3562
+ const [error, setError] = React11.useState(null);
3563
+ console.log("[VitalsWidget] fieldId:", fieldId, "| onFetch present:", !!handlers.onFetch, "| value:", value);
3564
+ const fetchVitals = React11.useCallback(async () => {
3565
+ if (!handlers.onFetch) return;
3566
+ setLoading(true);
3567
+ setError(null);
3568
+ try {
3569
+ const data = await handlers.onFetch();
3570
+ console.log("[VitalsWidget] fetched data:", data);
3571
+ setValue(data);
3572
+ } catch {
3573
+ setError("Failed to load vitals. Please try again.");
3574
+ } finally {
3575
+ setLoading(false);
3576
+ }
3577
+ }, [handlers, setValue]);
3578
+ React11.useEffect(() => {
3579
+ fetchVitals();
3580
+ }, []);
3581
+ return /* @__PURE__ */ jsxRuntime.jsx(
3582
+ Vitals,
3583
+ {
3584
+ label: fieldDef?.label ?? "Vitals",
3585
+ data: value ?? null,
3586
+ loading,
3587
+ error,
3588
+ onRefresh: handlers.onFetch ? fetchVitals : void 0
3589
+ }
3590
+ );
3591
+ };
3592
+ function normalizeOptions(response) {
3593
+ if (Array.isArray(response)) {
3594
+ return response.map((o) => ({ label: String(o.label), value: String(o.value) }));
3595
+ }
3596
+ if (response?.specializations?.length) {
3597
+ return response.specializations.map((s) => ({ label: s, value: s }));
3598
+ }
3599
+ return [];
3600
+ }
3601
+ function normalizeValue(value) {
3602
+ if (!value || !Array.isArray(value)) return [];
3603
+ return value.map((item) => {
3604
+ if (item && typeof item === "object" && "specialization" in item) {
3605
+ const s = item.specialization;
3606
+ const u = item.is_urgent;
3607
+ return { specialization: String(s), is_urgent: Boolean(u) };
3608
+ }
3609
+ return { specialization: String(item), is_urgent: false };
3610
+ });
3611
+ }
3612
+ var ReferralWidget = ({ fieldId }) => {
3613
+ const { fieldDef, value, setValue, setTouched, error, disabled, touched } = useField(fieldId);
3614
+ const handlers = useFieldHandlers(fieldId);
3615
+ const [options, setOptions] = React11.useState([]);
3616
+ const [loading, setLoading] = React11.useState(false);
3617
+ const { state } = useForm();
3618
+ const showError = !!error && (touched || state.submitAttempted);
3619
+ const selectedItems = normalizeValue(value);
3620
+ const availableOptions = options.filter(
3621
+ (opt) => !selectedItems.some((item) => item.specialization === opt.value)
3622
+ );
3623
+ const loadOptions = React11.useCallback(async () => {
3624
+ if (handlers.onFetchOptions) {
3625
+ setLoading(true);
3626
+ try {
3627
+ const res = await handlers.onFetchOptions();
3628
+ setOptions(normalizeOptions(res));
3629
+ } finally {
3630
+ setLoading(false);
3631
+ }
3632
+ return;
3633
+ }
3634
+ const def = fieldDef;
3635
+ if (def?.options?.length) {
3636
+ setOptions(def.options.map((o) => ({ label: String(o.label), value: String(o.value) })));
3637
+ } else if (def?.dataSource) {
3638
+ setLoading(true);
3639
+ fetchOptions(def.dataSource.source, def.dataSource.params).then((opts) => setOptions(opts.map((o) => ({ label: String(o.label), value: String(o.value) })))).finally(() => setLoading(false));
3640
+ } else {
3641
+ setOptions([]);
3642
+ }
3643
+ }, [fieldDef, handlers]);
3644
+ React11.useEffect(() => {
3645
+ if (!fieldDef) return;
3646
+ loadOptions();
3647
+ }, [fieldDef, loadOptions]);
3648
+ const handleAdd = React11.useCallback(
3649
+ (specialization) => {
3650
+ const next = [...selectedItems, { specialization, is_urgent: false }];
3651
+ setValue(next);
3652
+ setTouched();
3653
+ },
3654
+ [selectedItems, setValue, setTouched]
3655
+ );
3656
+ const handleRemove = React11.useCallback(
3657
+ (specialization) => {
3658
+ const next = selectedItems.filter((item) => item.specialization !== specialization);
3659
+ setValue(next);
3660
+ setTouched();
3661
+ },
3662
+ [selectedItems, setValue, setTouched]
3663
+ );
3664
+ const handleToggleUrgent = React11.useCallback(
3665
+ (specialization) => {
3666
+ const next = selectedItems.map(
3667
+ (item) => item.specialization === specialization ? { ...item, is_urgent: !item.is_urgent } : item
3668
+ );
3669
+ setValue(next);
3670
+ setTouched();
3671
+ },
3672
+ [selectedItems, setValue, setTouched]
3673
+ );
3674
+ if (!fieldDef) return null;
3675
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
3676
+ /* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: fieldId, children: [
3677
+ "Referral to ",
3678
+ fieldDef.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
3679
+ ] }),
3680
+ loading && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Loading..." }),
3681
+ !loading && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3682
+ /* @__PURE__ */ jsxRuntime.jsxs(
3683
+ Select,
3684
+ {
3685
+ disabled,
3686
+ value: "",
3687
+ onValueChange: (val) => {
3688
+ if (val) {
3689
+ handleAdd(val);
3690
+ }
3691
+ },
3692
+ children: [
3693
+ /* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { id: fieldId, className: showError ? "border-red-500" : "", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Select specializations..." }) }),
3694
+ /* @__PURE__ */ jsxRuntime.jsxs(SelectContent, { children: [
3695
+ availableOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)),
3696
+ availableOptions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-2 px-2 text-sm text-muted-foreground", children: "All specializations selected" })
3697
+ ] })
3698
+ ]
3699
+ },
3700
+ `referral-${selectedItems.map((i) => i.specialization).join(",")}`
3701
+ ),
3702
+ selectedItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 mt-1.5", children: selectedItems.map((item) => {
3703
+ const isUrgent = item.is_urgent;
3704
+ const chipClass = isUrgent ? "inline-flex items-center gap-0.5 px-2 py-0.5 bg-red-100 text-red-800 border border-red-300 rounded-full text-xs max-w-full cursor-pointer hover:bg-red-200 transition-colors" : "inline-flex items-center gap-0.5 px-2 py-0.5 bg-blue-100 text-blue-800 rounded-full text-xs max-w-full cursor-pointer hover:bg-blue-200 transition-colors";
3705
+ return /* @__PURE__ */ jsxRuntime.jsxs(
3706
+ "div",
3707
+ {
3708
+ className: chipClass,
3709
+ onClick: (e) => {
3710
+ if (e.target instanceof HTMLElement && e.target.closest("button")) return;
3711
+ handleToggleUrgent(item.specialization);
3712
+ },
3713
+ children: [
3714
+ isUrgent && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 bg-red-600 text-white text-[10px] font-bold px-1 py-0.5 rounded mr-1", children: "U" }),
3715
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate max-w-[120px]", children: item.specialization }),
3716
+ /* @__PURE__ */ jsxRuntime.jsx(
3717
+ "button",
3718
+ {
3719
+ type: "button",
3720
+ onClick: (e) => {
3721
+ e.stopPropagation();
3722
+ handleRemove(item.specialization);
3723
+ },
3724
+ className: "ml-0.5 text-blue-600 hover:text-blue-800 focus:outline-none flex-shrink-0 text-sm leading-none",
3725
+ "aria-label": `Remove ${item.specialization}`,
3726
+ children: "\xD7"
3727
+ }
3728
+ )
3729
+ ]
3730
+ },
3731
+ item.specialization
3732
+ );
3733
+ }) }),
3734
+ selectedItems.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "No specializations selected" }),
3735
+ selectedItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-1.5", children: "Click a chip to mark as urgent" })
3736
+ ] }),
3737
+ showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
3738
+ ] });
3739
+ };
3740
+ var Card = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
3741
+ "div",
3742
+ {
3743
+ ref,
3744
+ className: cn(
3745
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
3746
+ className
3747
+ ),
3748
+ ...props
3749
+ }
3750
+ ));
3751
+ Card.displayName = "Card";
3752
+ var CardHeader = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
3753
+ "div",
3754
+ {
3755
+ ref,
3756
+ className: cn("flex flex-col space-y-1.5 p-6", className),
3757
+ ...props
3758
+ }
3759
+ ));
3760
+ CardHeader.displayName = "CardHeader";
3761
+ var CardTitle = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
3762
+ "h3",
3763
+ {
3764
+ ref,
3765
+ className: cn(
3766
+ "text-2xl font-semibold leading-none tracking-tight",
3767
+ className
3768
+ ),
3769
+ ...props
3770
+ }
3771
+ ));
3772
+ CardTitle.displayName = "CardTitle";
3773
+ var CardDescription = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
3774
+ "p",
3775
+ {
3776
+ ref,
3777
+ className: cn("text-sm text-muted-foreground", className),
3778
+ ...props
3779
+ }
3780
+ ));
3781
+ CardDescription.displayName = "CardDescription";
3782
+ var CardContent = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("p-6 pt-0", className), ...props }));
3783
+ CardContent.displayName = "CardContent";
3784
+ var CardFooter = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
3785
+ "div",
3786
+ {
3787
+ ref,
3788
+ className: cn("flex items-center p-6 pt-0", className),
3789
+ ...props
3790
+ }
3791
+ ));
3792
+ CardFooter.displayName = "CardFooter";
3793
+ var FOLLOWUP_TYPE_OPTIONS = [
3794
+ { value: "no followup", label: "No Follow-up" },
3795
+ { value: "one time", label: "One Time" }
3796
+ ];
3797
+ var UNIT_OPTIONS = [
3798
+ { value: "days", label: "Days" },
3799
+ { value: "weeks", label: "Weeks" },
3800
+ { value: "months", label: "Months" }
3801
+ ];
3802
+ function durationToDays(valueNum, unit) {
3803
+ const today = /* @__PURE__ */ new Date();
3804
+ today.setHours(0, 0, 0, 0);
3805
+ let target;
3806
+ if (unit === "days") target = dateFns.addDays(today, valueNum);
3807
+ else if (unit === "weeks") target = dateFns.addWeeks(today, valueNum);
3808
+ else target = dateFns.addMonths(today, valueNum);
3809
+ return Math.max(0, dateFns.differenceInCalendarDays(target, today));
3810
+ }
3811
+ function dateToDays(date) {
3812
+ const today = /* @__PURE__ */ new Date();
3813
+ today.setHours(0, 0, 0, 0);
3814
+ const d = new Date(date);
3815
+ d.setHours(0, 0, 0, 0);
3816
+ return Math.max(0, dateFns.differenceInCalendarDays(d, today));
3817
+ }
3818
+ function daysToDate(days) {
3819
+ const today = /* @__PURE__ */ new Date();
3820
+ today.setHours(0, 0, 0, 0);
3821
+ return dateFns.addDays(today, days);
3822
+ }
3823
+ function normalizeFollowup(value) {
3824
+ if (value === false || value === null || value === void 0) return false;
3825
+ if (typeof value !== "object") return false;
3826
+ const o = value;
3827
+ if (o.followupType !== "one time") return false;
3828
+ if (typeof o.value === "string" && o.unit === "days") {
3829
+ return { followupType: "one time", value: o.value, unit: "days" };
3830
+ }
3831
+ const today = /* @__PURE__ */ new Date();
3832
+ today.setHours(0, 0, 0, 0);
3833
+ if (o.mode === "date" && typeof o.date === "string") {
3834
+ const d = parseLocalDate(o.date);
3835
+ if (d) {
3836
+ const days2 = dateToDays(d);
3837
+ return { followupType: "one time", value: String(days2), unit: "days" };
3838
+ }
3839
+ }
3840
+ const val = o.value != null ? Number(String(o.value)) : 7;
3841
+ const u = o.unit === "weeks" || o.unit === "months" ? o.unit : "days";
3842
+ const days = durationToDays(Number.isNaN(val) ? 7 : val, u);
3843
+ return { followupType: "one time", value: String(days), unit: "days" };
3844
+ }
3845
+ function getDefaultDate() {
3846
+ const d = /* @__PURE__ */ new Date();
3847
+ d.setDate(d.getDate() + 7);
3848
+ return d;
3849
+ }
3850
+ function parseLocalDate(dateString) {
3851
+ if (!dateString) return null;
3852
+ try {
3853
+ return dateFns.parseISO(dateString);
3854
+ } catch {
3855
+ const parts = dateString.split("-");
3856
+ if (parts.length === 3) {
3857
+ const y = parseInt(parts[0], 10);
3858
+ const m = parseInt(parts[1], 10) - 1;
3859
+ const d = parseInt(parts[2], 10);
3860
+ if (!Number.isNaN(y) && !Number.isNaN(m) && !Number.isNaN(d)) {
3861
+ return new Date(y, m, d);
3862
+ }
3863
+ }
3864
+ return null;
3865
+ }
3866
+ }
3867
+ var FollowupWidget = ({ fieldId }) => {
3868
+ const { fieldDef, value, setValue, setTouched, error, disabled, visible } = useField(fieldId);
3869
+ const followup = normalizeFollowup(value);
3870
+ const [calendarOpen, setCalendarOpen] = React11.useState(false);
3871
+ const currentFollowupType = followup ? followup.followupType : "no followup";
3872
+ const hasFollowup = followup !== false;
3873
+ const [displayMode, setDisplayMode] = React11.useState("duration");
3874
+ const [displayUnit, setDisplayUnit] = React11.useState("days");
3875
+ const daysNum = hasFollowup ? parseInt(followup.value, 10) || 7 : 7;
3876
+ const currentDate = hasFollowup ? daysToDate(daysNum) : null;
3877
+ const followupMode = displayMode;
3878
+ const currentValue = hasFollowup && displayUnit === "days" ? followup.value : hasFollowup ? String(
3879
+ displayUnit === "weeks" ? Math.round(daysNum / 7) || 1 : Math.round(daysNum / 30) || 1
3880
+ ) : "7";
3881
+ const currentUnit = displayUnit;
3882
+ const handleFollowupTypeChange = React11.useCallback(
3883
+ (newType) => {
3884
+ setTouched();
3885
+ if (newType === "no followup") {
3886
+ setValue(false);
3887
+ setCalendarOpen(false);
3888
+ return;
3889
+ }
3890
+ setValue({
3891
+ followupType: "one time",
3892
+ value: hasFollowup ? followup.value : "7",
3893
+ unit: "days"
3894
+ });
3895
+ },
3896
+ [followup, hasFollowup, setValue, setTouched]
3897
+ );
3898
+ const handleModeChange = React11.useCallback((newMode) => {
3899
+ setDisplayMode(newMode);
3900
+ if (newMode === "date") setCalendarOpen(false);
3901
+ }, []);
3902
+ const handleValueChange = React11.useCallback(
3903
+ (newValue) => {
3904
+ setTouched();
3905
+ const num = parseInt(newValue, 10);
3906
+ const min = displayUnit === "days" ? 0 : 1;
3907
+ const valid = (Number.isNaN(num) ? min : num) >= min || newValue === "";
3908
+ if (!valid && newValue !== "") {
3909
+ setValue({ followupType: "one time", value: String(displayUnit === "days" ? 0 : durationToDays(1, displayUnit)), unit: "days" });
3910
+ return;
3911
+ }
3912
+ const days = durationToDays(Number.isNaN(num) ? displayUnit === "days" ? 0 : 1 : num, displayUnit);
3913
+ setValue({ followupType: "one time", value: String(days), unit: "days" });
3914
+ },
3915
+ [displayUnit, setValue, setTouched]
3916
+ );
3917
+ const handleUnitChange = React11.useCallback((newUnit) => {
3918
+ setDisplayUnit(newUnit);
3919
+ }, []);
3920
+ const handleDateSelect = React11.useCallback(
3921
+ (selected) => {
3922
+ setTouched();
3923
+ setCalendarOpen(false);
3924
+ if (!selected) return;
3925
+ const days = dateToDays(selected);
3926
+ setValue({ followupType: "one time", value: String(days), unit: "days" });
3927
+ },
3928
+ [setValue, setTouched]
3929
+ );
3930
+ if (!visible || !fieldDef) return null;
3931
+ const minDate = /* @__PURE__ */ new Date();
3932
+ minDate.setHours(0, 0, 0, 0);
3933
+ const selectedDate = currentDate || getDefaultDate();
3934
+ return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cn(error && "border-destructive"), children: /* @__PURE__ */ jsxRuntime.jsxs(CardContent, { className: "space-y-1 p-2", children: [
3935
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between gap-2", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center flex-shrink-0 gap-2", children: [
3936
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-xs font-semibold text-muted-foreground", children: "Follow-up" }),
3937
+ /* @__PURE__ */ jsxRuntime.jsxs(
3938
+ Select,
3939
+ {
3940
+ value: currentFollowupType,
3941
+ onValueChange: (v) => handleFollowupTypeChange(v),
3942
+ disabled,
3943
+ children: [
3944
+ /* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-[140px] border-none bg-muted/50 text-xs py-1 focus:outline-none focus:ring-0 focus:ring-offset-0", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, {}) }),
3945
+ /* @__PURE__ */ jsxRuntime.jsx(SelectContent, { children: FOLLOWUP_TYPE_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: opt.value, className: "text-xs", children: opt.label }, opt.value)) })
3946
+ ]
3947
+ }
3948
+ )
3949
+ ] }) }),
3950
+ currentFollowupType !== "no followup" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3951
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
3952
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-xs font-medium text-muted-foreground", children: "Schedule by:" }),
3953
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
3954
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
3955
+ /* @__PURE__ */ jsxRuntime.jsx(
3956
+ "input",
3957
+ {
3958
+ type: "radio",
3959
+ name: `followup-mode-${fieldId}`,
3960
+ value: "duration",
3961
+ checked: followupMode === "duration",
3962
+ onChange: () => handleModeChange("duration"),
3963
+ className: "h-4 w-4 text-primary focus:ring-primary",
3964
+ disabled
3965
+ }
3966
+ ),
3967
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Duration" })
3968
+ ] }),
3969
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
3970
+ /* @__PURE__ */ jsxRuntime.jsx(
3971
+ "input",
3972
+ {
3973
+ type: "radio",
3974
+ name: `followup-mode-${fieldId}`,
3975
+ value: "date",
3976
+ checked: followupMode === "date",
3977
+ onChange: () => handleModeChange("date"),
3978
+ className: "h-4 w-4 text-primary focus:ring-primary",
3979
+ disabled
3980
+ }
3981
+ ),
3982
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Date" })
3983
+ ] })
3984
+ ] })
3985
+ ] }),
3986
+ followupMode === "duration" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
3987
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-muted-foreground", children: "In" }),
3988
+ /* @__PURE__ */ jsxRuntime.jsx(
3989
+ "input",
3990
+ {
3991
+ type: "number",
3992
+ min: currentUnit === "days" ? 0 : 1,
3993
+ value: currentValue,
3994
+ onChange: (e) => handleValueChange(e.target.value),
3995
+ disabled,
3996
+ className: "w-16 py-1.5 px-2 border border-input rounded-md text-center text-sm focus:outline-none focus:ring-2 focus:ring-ring",
3997
+ placeholder: "7"
3998
+ }
3999
+ ),
4000
+ /* @__PURE__ */ jsxRuntime.jsxs(
4001
+ Select,
4002
+ {
4003
+ value: currentUnit,
4004
+ onValueChange: (v) => handleUnitChange(v),
4005
+ disabled,
4006
+ children: [
4007
+ /* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-[100px] h-8 text-xs", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { className: "text-xs" }) }),
4008
+ /* @__PURE__ */ jsxRuntime.jsx(SelectContent, { children: UNIT_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: opt.value, className: "text-xs", children: opt.label }, opt.value)) })
4009
+ ]
4010
+ }
4011
+ )
4012
+ ] }),
4013
+ followupMode === "date" && /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: calendarOpen, onOpenChange: setCalendarOpen, children: [
4014
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
4015
+ Button,
4016
+ {
4017
+ type: "button",
4018
+ variant: "outline",
4019
+ className: "w-full justify-start text-left font-normal h-9",
4020
+ disabled,
4021
+ children: [
4022
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "mr-2 h-4 w-4 text-muted-foreground" }),
4023
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: hasFollowup && currentDate ? dateFns.format(currentDate, "MMM d, yyyy") : "Select date" })
4024
+ ]
4025
+ }
4026
+ ) }),
4027
+ /* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "w-auto p-0", align: "start", children: /* @__PURE__ */ jsxRuntime.jsx(
4028
+ Calendar,
4029
+ {
4030
+ mode: "single",
4031
+ selected: selectedDate,
4032
+ onSelect: handleDateSelect,
4033
+ disabled: (date) => date < minDate,
4034
+ initialFocus: true
4035
+ }
4036
+ ) })
4037
+ ] }),
4038
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
4039
+ "One-time appointment",
4040
+ " ",
4041
+ followupMode === "duration" ? `in ${currentValue} ${currentUnit}` : hasFollowup && currentDate ? `on ${dateFns.format(currentDate, "MMM d, yyyy")}` : "date to be selected"
4042
+ ] })
4043
+ ] }),
4044
+ error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: error })
4045
+ ] }) });
4046
+ };
4047
+ function useSmartKeywords(text, onSearch, debounceDelay = 1e3) {
4048
+ console.log("[useSmartKeywords] render", {
4049
+ textLength: text?.length ?? 0,
4050
+ hasOnSearch: typeof onSearch === "function",
4051
+ debounceDelay
4052
+ });
4053
+ const [extractedNouns, setExtractedNouns] = React11.useState([]);
4054
+ const [matchedConditions, setMatchedConditions] = React11.useState([]);
4055
+ const [isProcessing, setIsProcessing] = React11.useState(false);
4056
+ const [error, setError] = React11.useState(null);
4057
+ const timerRef = React11.useRef(null);
4058
+ const abortRef = React11.useRef(0);
4059
+ const processText = React11.useCallback(
4060
+ async (input, runId) => {
4061
+ if (!input || input.trim().length === 0) {
4062
+ console.log("[useSmartKeywords] skip empty input", {
4063
+ runId,
4064
+ inputLength: input?.length ?? 0
4065
+ });
4066
+ setExtractedNouns([]);
4067
+ setMatchedConditions([]);
4068
+ setIsProcessing(false);
4069
+ return;
4070
+ }
4071
+ console.log("[useSmartKeywords] processText start", {
4072
+ runId,
4073
+ inputLength: input.length
4074
+ });
4075
+ setIsProcessing(true);
4076
+ setError(null);
4077
+ try {
4078
+ const nouns = extractNouns(input);
4079
+ const uniqueNouns = Array.from(new Set(nouns));
4080
+ console.log("[useSmartKeywords] extracted nouns", {
4081
+ runId,
4082
+ nouns,
4083
+ uniqueNouns
4084
+ });
4085
+ if (runId !== abortRef.current) return;
4086
+ setExtractedNouns(uniqueNouns);
4087
+ if (uniqueNouns.length === 0) {
4088
+ console.log("[useSmartKeywords] no nouns, skipping search", { runId });
4089
+ setMatchedConditions([]);
4090
+ setIsProcessing(false);
4091
+ return;
4092
+ }
4093
+ const searchResults = await Promise.all(
4094
+ uniqueNouns.map(async (noun) => {
4095
+ const normalised = noun.replace(/\s+/g, " ").replace(/^[\s-]+/, "").trim();
4096
+ try {
4097
+ console.log("[useSmartKeywords] onSearch call", { runId, noun, normalised });
4098
+ const results = await onSearch(normalised);
4099
+ console.log("[useSmartKeywords] onSearch result", {
4100
+ runId,
4101
+ noun,
4102
+ normalised,
4103
+ count: Array.isArray(results) ? results.length : 0
4104
+ });
4105
+ const first = Array.isArray(results) && results.length > 0 ? [results[0]] : [];
4106
+ return { keyword: normalised, matches: first };
4107
+ } catch (err) {
4108
+ console.error("[useSmartKeywords] onSearch error", { runId, noun, err });
4109
+ return { keyword: normalised || noun, matches: [] };
4110
+ }
4111
+ })
4112
+ );
4113
+ if (runId !== abortRef.current) return;
4114
+ const allMatches = searchResults.flatMap(
4115
+ ({ keyword, matches }) => matches.map((record) => ({
4116
+ extractedKeyword: keyword,
4117
+ matchedCondition: {
4118
+ id: record.id,
4119
+ name: record.name,
4120
+ created_at: record.created_at,
4121
+ updated_at: record.updated_at
4122
+ }
4123
+ }))
4124
+ );
4125
+ console.log("[useSmartKeywords] allMatches computed", {
4126
+ runId,
4127
+ matchesCount: allMatches.length
4128
+ });
4129
+ setMatchedConditions(allMatches);
4130
+ } catch (err) {
4131
+ if (runId !== abortRef.current) return;
4132
+ console.error("[useSmartKeywords] processText error", { runId, err });
4133
+ setError(err instanceof Error ? err.message : "Keyword extraction failed");
4134
+ } finally {
4135
+ if (runId === abortRef.current) {
4136
+ console.log("[useSmartKeywords] processText end", { runId });
4137
+ setIsProcessing(false);
4138
+ }
4139
+ }
4140
+ },
4141
+ [onSearch]
4142
+ );
4143
+ React11.useEffect(() => {
4144
+ if (timerRef.current) clearTimeout(timerRef.current);
4145
+ const runId = ++abortRef.current;
4146
+ console.log("[useSmartKeywords] schedule debounce", {
4147
+ runId,
4148
+ debounceDelay,
4149
+ textLength: text?.length ?? 0
4150
+ });
4151
+ timerRef.current = setTimeout(() => {
4152
+ console.log("[useSmartKeywords] debounce fired", { runId });
4153
+ processText(text, runId);
4154
+ }, debounceDelay);
4155
+ return () => {
4156
+ if (timerRef.current) clearTimeout(timerRef.current);
4157
+ };
4158
+ }, [text, debounceDelay, processText]);
4159
+ return { extractedNouns, matchedConditions, isProcessing, error };
4160
+ }
4161
+ function extractNouns(text) {
4162
+ const doc = nlp__default.default(text);
4163
+ const rawNouns = doc.nouns().out("array");
4164
+ const cleaned = rawNouns.map((n) => n.replace(/[^\w\s-]/g, "").trim().toLowerCase()).filter((n) => n.length > 2);
4165
+ console.log("[useSmartKeywords] extractNouns", {
4166
+ inputLength: text?.length ?? 0,
4167
+ rawNouns,
4168
+ cleaned
4169
+ });
4170
+ return cleaned;
4171
+ }
4172
+ var SmartTextareaWidget = ({ fieldId }) => {
4173
+ const { fieldDef, value, setValue, setTouched, error, disabled, touched } = useField(fieldId);
4174
+ const { state } = useForm();
4175
+ const handler = useFieldHandlers(fieldId);
4176
+ const def = fieldDef;
4177
+ const structured = value;
4178
+ const textValue = structured?.text ?? "";
4179
+ const showError = !!error && (touched || state.submitAttempted);
4180
+ const onSearch = React11.useCallback(
4181
+ (query) => handler.onSearch(query),
4182
+ [handler]
4183
+ );
4184
+ const { matchedConditions, isProcessing } = useSmartKeywords(
4185
+ textValue,
4186
+ onSearch,
4187
+ def?.debounceDelay ?? 1e3
4188
+ );
4189
+ const currentKeywords = structured?.extractedKeywords ?? [];
4190
+ const nextKeywords = React11.useMemo(() => {
4191
+ if (matchedConditions.length === 0 && currentKeywords.length === 0) return currentKeywords;
4192
+ if (matchedConditions.length === currentKeywords.length && matchedConditions.every(
4193
+ (m, i) => m.matchedCondition.id === currentKeywords[i]?.matchedCondition.id && m.extractedKeyword === currentKeywords[i]?.extractedKeyword
4194
+ )) {
4195
+ return currentKeywords;
4196
+ }
4197
+ return matchedConditions;
4198
+ }, [matchedConditions, currentKeywords]);
4199
+ const writeValue = React11.useCallback(
4200
+ (text, keywords) => {
4201
+ const next = { text, extractedKeywords: keywords };
4202
+ setValue(next);
4203
+ },
4204
+ [setValue]
4205
+ );
4206
+ React11__namespace.default.useEffect(() => {
4207
+ if (nextKeywords !== currentKeywords) {
4208
+ writeValue(textValue, nextKeywords);
4209
+ }
4210
+ }, [nextKeywords]);
4211
+ const handleTextChange = React11.useCallback(
4212
+ (e) => {
4213
+ writeValue(e.target.value, currentKeywords);
4214
+ },
4215
+ [writeValue, currentKeywords]
4216
+ );
4217
+ if (!def) return null;
4218
+ const height = def.height;
4219
+ const theme = getThemeConfig("textarea", def.theme);
4220
+ const wrapperClass = theme?.wrapperClassName ?? "space-y-2";
4221
+ const labelClass = theme?.labelClassName;
4222
+ const inputClass = cn(theme?.inputClassName, showError && "border-red-500");
4223
+ const labelContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4224
+ def.label,
4225
+ " ",
4226
+ def.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" }),
4227
+ isProcessing && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "inline-block ml-1.5 h-3 w-3 animate-spin text-muted-foreground" })
4228
+ ] });
4229
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClass, children: [
4230
+ theme ? /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: fieldId, className: labelClass, children: labelContent }) : /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: fieldId, children: labelContent }),
4231
+ /* @__PURE__ */ jsxRuntime.jsx(
4232
+ Textarea,
4233
+ {
4234
+ id: fieldId,
4235
+ value: textValue,
4236
+ onChange: handleTextChange,
4237
+ onBlur: setTouched,
4238
+ disabled,
4239
+ placeholder: def.placeholder,
4240
+ className: inputClass,
4241
+ ...height != null ? { height } : {}
4242
+ }
4243
+ ),
4244
+ showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
4245
+ ] });
4246
+ };
4247
+ var FieldRenderer = ({ fieldId }) => {
4248
+ const { fieldDef, visible } = useField(fieldId);
4249
+ if (!visible || !fieldDef) {
4250
+ return null;
4251
+ }
4252
+ switch (fieldDef.type) {
4253
+ case "text":
4254
+ case "number":
4255
+ return /* @__PURE__ */ jsxRuntime.jsx(TextWidget, { fieldId });
4256
+ case "textarea":
4257
+ case "richtext":
4258
+ return /* @__PURE__ */ jsxRuntime.jsx(TextWidget, { fieldId });
4259
+ case "date":
4260
+ return /* @__PURE__ */ jsxRuntime.jsx(DateWidget, { fieldId });
4261
+ case "datetime":
4262
+ return /* @__PURE__ */ jsxRuntime.jsx(DateTimeWidget, { fieldId });
4263
+ case "select":
4264
+ return /* @__PURE__ */ jsxRuntime.jsx(SelectWidget, { fieldId });
4265
+ case "radio":
4266
+ return /* @__PURE__ */ jsxRuntime.jsx(RadioWidget, { fieldId });
4267
+ case "checkbox":
4268
+ return /* @__PURE__ */ jsxRuntime.jsx(CheckboxWidget, { fieldId });
4269
+ case "multiselect":
4270
+ return /* @__PURE__ */ jsxRuntime.jsx(MultiSelectWidget, { fieldId });
4271
+ case "repeatable":
4272
+ return /* @__PURE__ */ jsxRuntime.jsx(RepeatableWidget, { fieldId });
4273
+ case "image_upload":
4274
+ return /* @__PURE__ */ jsxRuntime.jsx(ImageUploadWidget, { fieldId });
4275
+ case "signature":
4276
+ return /* @__PURE__ */ jsxRuntime.jsx(SignatureUploadWidget, { fieldId });
4277
+ case "editable_table":
4278
+ return /* @__PURE__ */ jsxRuntime.jsx(EditableTableWidget, { fieldId });
4279
+ case "medications":
4280
+ return /* @__PURE__ */ jsxRuntime.jsx(AddMedicationWidget, { fieldId });
4281
+ case "investigations":
4282
+ return /* @__PURE__ */ jsxRuntime.jsx(AddInvestigationWidget, { fieldId });
4283
+ case "procedures":
4284
+ return /* @__PURE__ */ jsxRuntime.jsx(AddProcedureWidget, { fieldId });
4285
+ case "differential_diagnosis":
4286
+ return /* @__PURE__ */ jsxRuntime.jsx(DifferentialDiagnosisWidget, { fieldId });
4287
+ case "vitals":
4288
+ return /* @__PURE__ */ jsxRuntime.jsx(VitalsWidget, { fieldId });
4289
+ case "referral":
4290
+ return /* @__PURE__ */ jsxRuntime.jsx(ReferralWidget, { fieldId });
4291
+ case "followup":
4292
+ return /* @__PURE__ */ jsxRuntime.jsx(FollowupWidget, { fieldId });
4293
+ case "smart_textarea":
4294
+ return /* @__PURE__ */ jsxRuntime.jsx(SmartTextareaWidget, { fieldId });
4295
+ case "static_text": {
4296
+ const def = fieldDef;
4297
+ const size = def.size ?? "regular";
4298
+ const labelClass = size === "large" ? "text-base" : "text-sm";
4299
+ const descClass = size === "large" ? "text-sm" : "text-xs";
4300
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${labelClass} text-muted-foreground`, children: [
4301
+ def.label,
4302
+ def.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: `mt-1 ${descClass} opacity-90`, children: def.description })
4303
+ ] });
4304
+ }
4305
+ default:
4306
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-red-500", children: [
4307
+ "Unknown field type: ",
4308
+ fieldDef.type
4309
+ ] });
4310
+ }
4311
+ };
4312
+ var Section = ({ title, children }) => {
4313
+ const [expanded, setExpanded] = React11.useState(true);
4314
+ const handleToggle = () => setExpanded((prev) => !prev);
4315
+ const contentClassName = cn(
4316
+ "overflow-hidden transition-[height] duration-200 ease-in-out",
4317
+ !expanded && "hidden"
4318
+ );
4319
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 border rounded-lg bg-white shadow-sm overflow-hidden", children: [
4320
+ /* @__PURE__ */ jsxRuntime.jsxs(
4321
+ "button",
4322
+ {
4323
+ type: "button",
4324
+ onClick: handleToggle,
4325
+ className: "w-full flex items-center justify-between gap-2 p-4 text-left border-b hover:bg-gray-50/80 transition-colors",
4326
+ children: [
4327
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-medium", children: title }),
4328
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-shrink-0 text-muted-foreground", "aria-hidden": true, children: expanded ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-5 w-5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "h-5 w-5" }) })
4329
+ ]
4330
+ }
4331
+ ),
4332
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: contentClassName, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 pt-4 space-y-4 border-t-0", children }) })
4333
+ ] });
4334
+ };
4335
+ var ColumnLayout = ({
4336
+ columns,
4337
+ label,
4338
+ children
4339
+ }) => {
4340
+ const gridCols = Math.max(1, Math.min(columns, 12));
4341
+ const gridClass = `grid gap-4 w-full`;
4342
+ const style = {
4343
+ gridTemplateColumns: `repeat(${gridCols}, minmax(0, 1fr))`
4344
+ };
4345
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6", children: [
4346
+ label != null && label !== "" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 text-sm font-medium text-gray-700", children: label }),
4347
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: gridClass, style, children })
4348
+ ] });
4349
+ };
4350
+ var DynamicForm = () => {
4351
+ const store = useFormStore();
4352
+ const schema = store.getSchema();
4353
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
4354
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold", children: schema.title }) }),
4355
+ schema.layout.map((node) => {
4356
+ if (node.type === "section") {
4357
+ return /* @__PURE__ */ jsxRuntime.jsx(Section, { title: node.title, children: node.children.map(
4358
+ (child, index) => typeof child === "string" ? /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId: child }, child) : child.type === "column_layout" ? /* @__PURE__ */ jsxRuntime.jsx(
4359
+ ColumnLayout,
4360
+ {
4361
+ columns: child.columns,
4362
+ label: child.label,
4363
+ children: child.children.map((fieldId) => /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }, fieldId))
4364
+ },
4365
+ child.id
4366
+ ) : /* @__PURE__ */ jsxRuntime.jsx(React11__namespace.default.Fragment, {}, child.id ?? index)
4367
+ ) }, node.id);
4368
+ }
4369
+ if (node.type === "column_layout") {
4370
+ return /* @__PURE__ */ jsxRuntime.jsx(
4371
+ ColumnLayout,
4372
+ {
4373
+ columns: node.columns,
4374
+ label: node.label,
4375
+ children: node.children.map((fieldId) => /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }, fieldId))
4376
+ },
4377
+ node.id
4378
+ );
4379
+ }
4380
+ return null;
4381
+ })
4382
+ ] });
4383
+ };
4384
+ var SUGGESTION_KEYS = /* @__PURE__ */ new Set(["medications", "investigations", "procedures"]);
4385
+ function shallowEqualKeys(a, b) {
4386
+ const keysA = Object.keys(a);
4387
+ const keysB = Object.keys(b);
4388
+ if (keysA.length !== keysB.length) return false;
4389
+ for (const k of keysA) {
4390
+ if (!keysB.includes(k) || a[k] !== b[k]) return false;
4391
+ }
4392
+ return true;
4393
+ }
4394
+ function normalizeAIMedications(arr) {
4395
+ let parsed = arr;
4396
+ if (typeof arr === "string") {
4397
+ try {
4398
+ parsed = JSON.parse(arr);
4399
+ } catch {
4400
+ return [];
4401
+ }
4402
+ }
4403
+ if (!Array.isArray(parsed)) return [];
4404
+ return parsed.filter((item) => item != null && typeof item === "object" && "id" in item && "brand_name" in item);
4405
+ }
4406
+ function normalizeDifferentialDiagnosis(val) {
4407
+ let parsed = val;
4408
+ if (typeof val === "string") {
4409
+ try {
4410
+ parsed = JSON.parse(val);
4411
+ } catch {
4412
+ return [];
4413
+ }
4414
+ }
4415
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
4416
+ const result = [];
4417
+ for (const [category, group] of Object.entries(parsed)) {
4418
+ if (!group || typeof group !== "object") continue;
4419
+ const conditions = Array.isArray(group.conditions) ? group.conditions : [];
4420
+ for (const cond of conditions) {
4421
+ if (!cond || typeof cond !== "object") continue;
4422
+ const {
4423
+ condition,
4424
+ likelihood,
4425
+ severity,
4426
+ treatability,
4427
+ keyFeatures,
4428
+ nextSteps
4429
+ } = cond;
4430
+ if (!condition || !likelihood || !severity || !treatability) continue;
4431
+ result.push({
4432
+ category,
4433
+ condition,
4434
+ likelihood,
4435
+ severity,
4436
+ treatability,
4437
+ keyFeatures: Array.isArray(keyFeatures) ? keyFeatures : void 0,
4438
+ nextSteps: Array.isArray(nextSteps) ? nextSteps : void 0
4439
+ });
4440
+ }
4441
+ }
4442
+ return result;
4443
+ }
4444
+ if (!Array.isArray(parsed)) return [];
4445
+ return parsed.filter(
4446
+ (item) => item != null && typeof item === "object" && "category" in item && "condition" in item && "likelihood" in item && "severity" in item && "treatability" in item
4447
+ );
4448
+ }
4449
+ var SmartForm = ({
4450
+ aiAnalysisResult
4451
+ }) => {
4452
+ const store = useFormStore();
4453
+ const schema = store.getSchema();
4454
+ const prevAiResultRef = React11.useRef(void 0);
4455
+ const aiSuggestions = React11.useMemo(() => {
4456
+ if (aiAnalysisResult == null) return { medications: [] };
4457
+ const medications = normalizeAIMedications(aiAnalysisResult.medications);
4458
+ return { medications };
4459
+ }, [aiAnalysisResult]);
4460
+ React11.useEffect(() => {
4461
+ if (aiAnalysisResult == null || Object.keys(aiAnalysisResult).length === 0) return;
4462
+ const prev = prevAiResultRef.current;
4463
+ if (prev != null && prev === aiAnalysisResult && shallowEqualKeys(prev, aiAnalysisResult)) return;
4464
+ prevAiResultRef.current = { ...aiAnalysisResult };
4465
+ const DD_KEYS = /* @__PURE__ */ new Set(["differentialDiagnosis", "differentialdiagnosis"]);
4466
+ const toApply = {};
4467
+ for (const [key, val] of Object.entries(aiAnalysisResult)) {
4468
+ if (!SUGGESTION_KEYS.has(key) && !DD_KEYS.has(key)) toApply[key] = val;
4469
+ }
4470
+ const rawDd = aiAnalysisResult.differentialDiagnosis ?? aiAnalysisResult.differentialdiagnosis;
4471
+ if (rawDd != null) {
4472
+ toApply.differentialdiagnosis = normalizeDifferentialDiagnosis(rawDd);
4473
+ }
4474
+ if (Object.keys(toApply).length > 0) store.setValues(toApply, { touch: false });
4475
+ }, [store, aiAnalysisResult]);
4476
+ const fieldIds = [];
4477
+ function collect(child) {
4478
+ if (typeof child === "string") {
4479
+ fieldIds.push(child);
4480
+ } else if (child.type === "column_layout") {
4481
+ child.children.forEach((id) => fieldIds.push(id));
4482
+ }
4483
+ }
4484
+ schema.layout.forEach((node) => {
4485
+ if (node.type === "section") {
4486
+ node.children.forEach(collect);
4487
+ } else if (node.type === "column_layout") {
4488
+ node.children.forEach((id) => fieldIds.push(id));
4489
+ }
4490
+ });
4491
+ return /* @__PURE__ */ jsxRuntime.jsx(AISuggestionsProvider, { value: aiSuggestions, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-4 gap-2 w-full items-start", children: [
4492
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-1 flex flex-col gap-2", children: fieldIds.map((fieldId) => {
4493
+ const fieldDef = store.getFieldDef(fieldId);
4494
+ if (!fieldDef) return null;
4495
+ const position = fieldDef.position ?? "center";
4496
+ if (position !== "left") return null;
4497
+ return /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }, fieldId);
4498
+ }) }),
4499
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-2 grid grid-cols-2 gap-x-2 space-y-2", children: fieldIds.map((fieldId) => {
4500
+ const fieldDef = store.getFieldDef(fieldId);
4501
+ if (!fieldDef) return null;
4502
+ const position = fieldDef.position ?? "center";
4503
+ if (position !== "center") return null;
4504
+ const colSpanRaw = fieldDef.colSpan ?? 1;
4505
+ const colSpan = colSpanRaw === 2 ? 2 : 1;
4506
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: colSpan === 2 ? "col-span-2" : "col-span-1", children: /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }) }, fieldId);
4507
+ }) }),
4508
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-1 flex flex-col gap-2", children: fieldIds.map((fieldId) => {
4509
+ const fieldDef = store.getFieldDef(fieldId);
4510
+ if (!fieldDef) return null;
4511
+ const position = fieldDef.position ?? "center";
4512
+ if (position !== "right") return null;
4513
+ return /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }, fieldId);
4514
+ }) })
4515
+ ] }) });
2433
4516
  };
2434
4517
  var ReadOnlyText = ({ fieldDef, value }) => {
2435
4518
  if (!fieldDef) return null;
@@ -2957,6 +5040,9 @@ function createUploadHandler(options) {
2957
5040
  };
2958
5041
  }
2959
5042
 
5043
+ exports.AddInvestigationField = AddInvestigationField;
5044
+ exports.AddMedicationField = AddMedicationField;
5045
+ exports.DifferentialDiagnosis = DifferentialDiagnosis;
2960
5046
  exports.DynamicForm = DynamicForm;
2961
5047
  exports.EMAIL_REGEX = EMAIL_REGEX;
2962
5048
  exports.FieldRenderer = FieldRenderer;
@@ -2969,11 +5055,15 @@ exports.ReadOnlyImageUpload = ReadOnlyImageUpload;
2969
5055
  exports.ReadOnlySignature = ReadOnlySignature;
2970
5056
  exports.ReadOnlyTable = ReadOnlyTable;
2971
5057
  exports.SignatureUploadWidget = SignatureUploadWidget;
5058
+ exports.SmartForm = SmartForm;
5059
+ exports.SmartTextareaWidget = SmartTextareaWidget;
2972
5060
  exports.createUploadHandler = createUploadHandler;
2973
5061
  exports.evaluateRules = evaluateRules;
2974
5062
  exports.useField = useField;
5063
+ exports.useFieldHandlers = useFieldHandlers;
2975
5064
  exports.useForm = useForm;
2976
5065
  exports.useFormStore = useFormStore;
5066
+ exports.useSmartKeywords = useSmartKeywords;
2977
5067
  exports.validateField = validateField;
2978
5068
  exports.validateForm = validateForm;
2979
5069
  //# sourceMappingURL=index.cjs.map