formanitor 0.0.10 → 0.0.12
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 +2010 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +251 -4
- package/dist/index.d.ts +251 -4
- package/dist/index.mjs +2008 -128
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -15,6 +15,7 @@ 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');
|
|
18
19
|
|
|
19
20
|
function _interopNamespace(e) {
|
|
20
21
|
if (e && e.__esModule) return e;
|
|
@@ -40,6 +41,7 @@ var SelectPrimitive__namespace = /*#__PURE__*/_interopNamespace(SelectPrimitive)
|
|
|
40
41
|
var CheckboxPrimitive__namespace = /*#__PURE__*/_interopNamespace(CheckboxPrimitive);
|
|
41
42
|
var PopoverPrimitive__namespace = /*#__PURE__*/_interopNamespace(PopoverPrimitive);
|
|
42
43
|
var RadioGroupPrimitive__namespace = /*#__PURE__*/_interopNamespace(RadioGroupPrimitive);
|
|
44
|
+
var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive);
|
|
43
45
|
|
|
44
46
|
// src/core/validate.ts
|
|
45
47
|
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -204,6 +206,7 @@ var FormStore = class {
|
|
|
204
206
|
config;
|
|
205
207
|
// Cache for debounce
|
|
206
208
|
saveTimeout = null;
|
|
209
|
+
draftCallbackTimeout = null;
|
|
207
210
|
constructor(schema, config = {}) {
|
|
208
211
|
this.schema = schema;
|
|
209
212
|
this.config = config;
|
|
@@ -273,6 +276,34 @@ var FormStore = class {
|
|
|
273
276
|
this.checkEvents(fieldId, "onValueChange", value);
|
|
274
277
|
this.evaluate();
|
|
275
278
|
this.notify();
|
|
279
|
+
this.scheduleDraftCallback();
|
|
280
|
+
if (!this.config.onDraftChange) {
|
|
281
|
+
this.scheduleSave();
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Set multiple values at once (e.g. from AI analysis). Only updates keys that
|
|
286
|
+
* exist in the schema. Runs evaluate/notify/save once.
|
|
287
|
+
* @param partial Map of fieldId -> value
|
|
288
|
+
* @param options.touch If false, do not mark fields as touched (e.g. for AI-suggested values)
|
|
289
|
+
*/
|
|
290
|
+
setValues(partial, options) {
|
|
291
|
+
const touch = options?.touch !== false;
|
|
292
|
+
let changed = false;
|
|
293
|
+
const newValues = { ...this.state.values };
|
|
294
|
+
const newTouched = { ...this.state.touched };
|
|
295
|
+
for (const [fieldId, value] of Object.entries(partial)) {
|
|
296
|
+
if (!this.fieldMap[fieldId]) continue;
|
|
297
|
+
if (newValues[fieldId] === value) continue;
|
|
298
|
+
newValues[fieldId] = value;
|
|
299
|
+
if (touch) newTouched[fieldId] = true;
|
|
300
|
+
changed = true;
|
|
301
|
+
}
|
|
302
|
+
if (!changed) return;
|
|
303
|
+
this.state.values = newValues;
|
|
304
|
+
this.state.touched = newTouched;
|
|
305
|
+
this.evaluate();
|
|
306
|
+
this.notify();
|
|
276
307
|
this.scheduleSave();
|
|
277
308
|
}
|
|
278
309
|
setTouched(fieldId) {
|
|
@@ -309,6 +340,26 @@ var FormStore = class {
|
|
|
309
340
|
this.save();
|
|
310
341
|
}, 1e3);
|
|
311
342
|
}
|
|
343
|
+
scheduleDraftCallback() {
|
|
344
|
+
if (!this.config.onDraftChange) return;
|
|
345
|
+
if (this.draftCallbackTimeout) {
|
|
346
|
+
clearTimeout(this.draftCallbackTimeout);
|
|
347
|
+
}
|
|
348
|
+
this.draftCallbackTimeout = setTimeout(() => {
|
|
349
|
+
const payload = {
|
|
350
|
+
// For consumer callbacks, send values in the same
|
|
351
|
+
// serialized shape used for outbound submissions so
|
|
352
|
+
// media/signature fields are wrapped consistently.
|
|
353
|
+
values: this.serializeValuesForSubmission(this.state.values),
|
|
354
|
+
meta: {
|
|
355
|
+
updatedAt: Date.now(),
|
|
356
|
+
workflowState: this.state.workflowState,
|
|
357
|
+
version: this.schema.version
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
this.config.onDraftChange?.(payload);
|
|
361
|
+
}, 2500);
|
|
362
|
+
}
|
|
312
363
|
async save() {
|
|
313
364
|
const payload = {
|
|
314
365
|
values: this.state.values,
|
|
@@ -404,6 +455,36 @@ var FormStore = class {
|
|
|
404
455
|
this.state.values[comp.target] = 0;
|
|
405
456
|
}
|
|
406
457
|
}
|
|
458
|
+
} else if (comp.type === "formula") {
|
|
459
|
+
const evalExpr = (expr) => {
|
|
460
|
+
if (expr.type === "field") {
|
|
461
|
+
return Number(this.state.values[expr.field]) || 0;
|
|
462
|
+
}
|
|
463
|
+
if (expr.type === "number") {
|
|
464
|
+
return expr.value;
|
|
465
|
+
}
|
|
466
|
+
const left = evalExpr(expr.left);
|
|
467
|
+
const right = evalExpr(expr.right);
|
|
468
|
+
switch (expr.op) {
|
|
469
|
+
case "+":
|
|
470
|
+
return left + right;
|
|
471
|
+
case "-":
|
|
472
|
+
return left - right;
|
|
473
|
+
case "*":
|
|
474
|
+
return left * right;
|
|
475
|
+
case "/":
|
|
476
|
+
return right === 0 ? 0 : left / right;
|
|
477
|
+
case "**":
|
|
478
|
+
return Math.pow(left, right);
|
|
479
|
+
default:
|
|
480
|
+
return 0;
|
|
481
|
+
}
|
|
482
|
+
};
|
|
483
|
+
const result = evalExpr(comp.expression);
|
|
484
|
+
const value = Number.isFinite(result) ? Math.round(result * 10) / 10 : "";
|
|
485
|
+
if (this.state.values[comp.target] !== value) {
|
|
486
|
+
this.state.values[comp.target] = value;
|
|
487
|
+
}
|
|
407
488
|
}
|
|
408
489
|
});
|
|
409
490
|
}
|
|
@@ -496,6 +577,7 @@ var FormStore = class {
|
|
|
496
577
|
let needsReeval = false;
|
|
497
578
|
if (next.onUpload !== void 0) this.config.onUpload = next.onUpload;
|
|
498
579
|
if (next.onEvent !== void 0) this.config.onEvent = next.onEvent;
|
|
580
|
+
if (next.onDraftChange !== void 0) this.config.onDraftChange = next.onDraftChange;
|
|
499
581
|
if (next.role !== void 0 && next.role !== this.config.role) {
|
|
500
582
|
this.config.role = next.role;
|
|
501
583
|
needsReeval = true;
|
|
@@ -558,7 +640,12 @@ var FormStore = class {
|
|
|
558
640
|
}
|
|
559
641
|
};
|
|
560
642
|
var FormContext = React11.createContext(null);
|
|
561
|
-
var
|
|
643
|
+
var FieldHandlersContext = React11.createContext({});
|
|
644
|
+
function useFieldHandlers(fieldId) {
|
|
645
|
+
const map = React11.useContext(FieldHandlersContext);
|
|
646
|
+
return map[fieldId] ?? { onSearch: async () => [], recentlyUsed: [] };
|
|
647
|
+
}
|
|
648
|
+
var FormProvider = ({ schema, config, fieldHandlers, children }) => {
|
|
562
649
|
const storeRef = React11.useRef(null);
|
|
563
650
|
if (!storeRef.current) {
|
|
564
651
|
storeRef.current = new FormStore(schema, config);
|
|
@@ -571,7 +658,7 @@ var FormProvider = ({ schema, config, children }) => {
|
|
|
571
658
|
React11.useEffect(() => {
|
|
572
659
|
storeRef.current?.load();
|
|
573
660
|
}, []);
|
|
574
|
-
return /* @__PURE__ */ jsxRuntime.jsx(FormContext.Provider, { value: storeRef.current, children });
|
|
661
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FormContext.Provider, { value: storeRef.current, children: /* @__PURE__ */ jsxRuntime.jsx(FieldHandlersContext.Provider, { value: fieldHandlers ?? {}, children }) });
|
|
575
662
|
};
|
|
576
663
|
function useFormStore() {
|
|
577
664
|
const context = React11.useContext(FormContext);
|
|
@@ -664,8 +751,9 @@ var Textarea = React11__namespace.forwardRef(
|
|
|
664
751
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
665
752
|
"textarea",
|
|
666
753
|
{
|
|
754
|
+
spellCheck: false,
|
|
667
755
|
className: cn(
|
|
668
|
-
"flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm
|
|
756
|
+
"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",
|
|
669
757
|
height == null && "min-h-[80px]",
|
|
670
758
|
className
|
|
671
759
|
),
|
|
@@ -677,6 +765,28 @@ var Textarea = React11__namespace.forwardRef(
|
|
|
677
765
|
}
|
|
678
766
|
);
|
|
679
767
|
Textarea.displayName = "Textarea";
|
|
768
|
+
|
|
769
|
+
// src/ui/themes/textareaThemes.ts
|
|
770
|
+
var textareaThemes = {
|
|
771
|
+
"highlight-label": {
|
|
772
|
+
wrapperClassName: "rounded-md overflow-hidden flex flex-col border border-[#D0D0D0]",
|
|
773
|
+
labelClassName: "rounded-t-md bg-[#FEE8EC] px-3 py-1 text-sm shrink-0 z-[1] text-slate-600",
|
|
774
|
+
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"
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
// src/ui/themes/index.ts
|
|
779
|
+
var themeRegistries = {
|
|
780
|
+
textarea: textareaThemes,
|
|
781
|
+
text: textareaThemes
|
|
782
|
+
// same themes can apply to single-line text if desired
|
|
783
|
+
};
|
|
784
|
+
function getThemeConfig(fieldType, themeName) {
|
|
785
|
+
if (!themeName) return void 0;
|
|
786
|
+
const registry = themeRegistries[fieldType];
|
|
787
|
+
if (!registry) return void 0;
|
|
788
|
+
return registry[themeName];
|
|
789
|
+
}
|
|
680
790
|
var TextWidget = ({ fieldId }) => {
|
|
681
791
|
const { fieldDef, value, setValue, setTouched, error, disabled, touched } = useField(fieldId);
|
|
682
792
|
const { state } = useForm();
|
|
@@ -685,12 +795,20 @@ var TextWidget = ({ fieldId }) => {
|
|
|
685
795
|
const Component = fieldDef.type === "textarea" || fieldDef.type === "richtext" ? Textarea : Input;
|
|
686
796
|
const isTextarea = fieldDef.type === "textarea" || fieldDef.type === "richtext";
|
|
687
797
|
const height = isTextarea && "height" in fieldDef ? fieldDef.height : void 0;
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
798
|
+
const theme = getThemeConfig(
|
|
799
|
+
isTextarea ? "textarea" : "text",
|
|
800
|
+
fieldDef.theme
|
|
801
|
+
);
|
|
802
|
+
const wrapperClass = theme?.wrapperClassName ?? "space-y-2";
|
|
803
|
+
const labelClass = theme?.labelClassName;
|
|
804
|
+
const inputClass = cn(theme?.inputClassName, showError && "border-red-500");
|
|
805
|
+
const labelContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
806
|
+
fieldDef.label,
|
|
807
|
+
" ",
|
|
808
|
+
fieldDef.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
|
|
809
|
+
] });
|
|
810
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClass, children: [
|
|
811
|
+
theme ? /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: fieldId, className: labelClass, children: labelContent }) : /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: fieldId, children: labelContent }),
|
|
694
812
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
695
813
|
Component,
|
|
696
814
|
{
|
|
@@ -700,7 +818,7 @@ var TextWidget = ({ fieldId }) => {
|
|
|
700
818
|
onBlur: setTouched,
|
|
701
819
|
disabled,
|
|
702
820
|
type: fieldDef.type === "number" ? "number" : "text",
|
|
703
|
-
className:
|
|
821
|
+
className: inputClass,
|
|
704
822
|
...isTextarea && { height }
|
|
705
823
|
}
|
|
706
824
|
),
|
|
@@ -2276,135 +2394,1898 @@ var CheckboxWidget = ({ fieldId }) => {
|
|
|
2276
2394
|
showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
|
|
2277
2395
|
] });
|
|
2278
2396
|
};
|
|
2279
|
-
var
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2397
|
+
var AISuggestionsContext = React11.createContext({});
|
|
2398
|
+
function useAISuggestions() {
|
|
2399
|
+
return React11.useContext(AISuggestionsContext);
|
|
2400
|
+
}
|
|
2401
|
+
var AISuggestionsProvider = AISuggestionsContext.Provider;
|
|
2402
|
+
var Dialog = DialogPrimitive__namespace.Root;
|
|
2403
|
+
var DialogPortal = DialogPrimitive__namespace.Portal;
|
|
2404
|
+
var DialogOverlay = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2405
|
+
DialogPrimitive__namespace.Overlay,
|
|
2406
|
+
{
|
|
2407
|
+
ref,
|
|
2408
|
+
className: cn(
|
|
2409
|
+
"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",
|
|
2410
|
+
className
|
|
2411
|
+
),
|
|
2412
|
+
...props
|
|
2283
2413
|
}
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
return /* @__PURE__ */ jsxRuntime.jsx(RepeatableWidget, { fieldId });
|
|
2305
|
-
case "image_upload":
|
|
2306
|
-
return /* @__PURE__ */ jsxRuntime.jsx(ImageUploadWidget, { fieldId });
|
|
2307
|
-
case "signature":
|
|
2308
|
-
return /* @__PURE__ */ jsxRuntime.jsx(SignatureUploadWidget, { fieldId });
|
|
2309
|
-
case "editable_table":
|
|
2310
|
-
return /* @__PURE__ */ jsxRuntime.jsx(EditableTableWidget, { fieldId });
|
|
2311
|
-
case "static_text": {
|
|
2312
|
-
const def = fieldDef;
|
|
2313
|
-
const size = def.size ?? "regular";
|
|
2314
|
-
const labelClass = size === "large" ? "text-base" : "text-sm";
|
|
2315
|
-
const descClass = size === "large" ? "text-sm" : "text-xs";
|
|
2316
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${labelClass} text-muted-foreground`, children: [
|
|
2317
|
-
def.label,
|
|
2318
|
-
def.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: `mt-1 ${descClass} opacity-90`, children: def.description })
|
|
2319
|
-
] });
|
|
2414
|
+
));
|
|
2415
|
+
DialogOverlay.displayName = DialogPrimitive__namespace.Overlay.displayName;
|
|
2416
|
+
var DialogContent = React11__namespace.forwardRef(({ className, children, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsxs(DialogPortal, { children: [
|
|
2417
|
+
/* @__PURE__ */ jsxRuntime.jsx(DialogOverlay, {}),
|
|
2418
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2419
|
+
DialogPrimitive__namespace.Content,
|
|
2420
|
+
{
|
|
2421
|
+
ref,
|
|
2422
|
+
className: cn(
|
|
2423
|
+
"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",
|
|
2424
|
+
className
|
|
2425
|
+
),
|
|
2426
|
+
...props,
|
|
2427
|
+
children: [
|
|
2428
|
+
children,
|
|
2429
|
+
/* @__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: [
|
|
2430
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" }),
|
|
2431
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Close" })
|
|
2432
|
+
] })
|
|
2433
|
+
]
|
|
2320
2434
|
}
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2435
|
+
)
|
|
2436
|
+
] }));
|
|
2437
|
+
DialogContent.displayName = DialogPrimitive__namespace.Content.displayName;
|
|
2438
|
+
var DialogHeader = ({
|
|
2439
|
+
className,
|
|
2440
|
+
...props
|
|
2441
|
+
}) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2442
|
+
"div",
|
|
2443
|
+
{
|
|
2444
|
+
className: cn(
|
|
2445
|
+
"flex flex-col space-y-1.5 text-center sm:text-left",
|
|
2446
|
+
className
|
|
2447
|
+
),
|
|
2448
|
+
...props
|
|
2449
|
+
}
|
|
2450
|
+
);
|
|
2451
|
+
DialogHeader.displayName = "DialogHeader";
|
|
2452
|
+
var DialogTitle = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2453
|
+
DialogPrimitive__namespace.Title,
|
|
2454
|
+
{
|
|
2455
|
+
ref,
|
|
2456
|
+
className: cn(
|
|
2457
|
+
"text-lg font-semibold leading-none tracking-tight",
|
|
2458
|
+
className
|
|
2459
|
+
),
|
|
2460
|
+
...props
|
|
2461
|
+
}
|
|
2462
|
+
));
|
|
2463
|
+
DialogTitle.displayName = DialogPrimitive__namespace.Title.displayName;
|
|
2464
|
+
var DialogDescription = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2465
|
+
DialogPrimitive__namespace.Description,
|
|
2466
|
+
{
|
|
2467
|
+
ref,
|
|
2468
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
2469
|
+
...props
|
|
2326
2470
|
}
|
|
2471
|
+
));
|
|
2472
|
+
DialogDescription.displayName = DialogPrimitive__namespace.Description.displayName;
|
|
2473
|
+
var AddEntityDialog = ({
|
|
2474
|
+
open,
|
|
2475
|
+
onClose,
|
|
2476
|
+
title,
|
|
2477
|
+
recentlyUsedSlot,
|
|
2478
|
+
children,
|
|
2479
|
+
actionSlot
|
|
2480
|
+
}) => {
|
|
2481
|
+
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: [
|
|
2482
|
+
/* @__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 }) }),
|
|
2483
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "px-6 flex flex-col gap-4 overflow-y-auto flex-1 min-h-0 pb-4", children: [
|
|
2484
|
+
recentlyUsedSlot && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2485
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mb-2", children: "Recently Used" }),
|
|
2486
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2", children: recentlyUsedSlot })
|
|
2487
|
+
] }),
|
|
2488
|
+
children
|
|
2489
|
+
] }),
|
|
2490
|
+
actionSlot != null && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-6 py-4 flex justify-end shrink-0 border-t border-border bg-background", children: actionSlot })
|
|
2491
|
+
] }) });
|
|
2327
2492
|
};
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2493
|
+
function mapToMedication(result) {
|
|
2494
|
+
return {
|
|
2495
|
+
id: result.id,
|
|
2496
|
+
brand_name: result.name,
|
|
2497
|
+
generic_name: result.generic_name ?? "",
|
|
2498
|
+
frequency: "custom",
|
|
2499
|
+
is_custom: false,
|
|
2500
|
+
duration_value: "3",
|
|
2501
|
+
duration_unit: "days",
|
|
2502
|
+
before_after: "AFTER FOOD",
|
|
2503
|
+
morning: "0",
|
|
2504
|
+
afternoon: "0",
|
|
2505
|
+
night: "0",
|
|
2506
|
+
notes: ""
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
function mapAIMedicationToMedication(ai) {
|
|
2510
|
+
const id = typeof ai.id === "string" ? Number.parseInt(ai.id, 10) || 0 : Number(ai.id);
|
|
2511
|
+
return {
|
|
2512
|
+
id: Number.isNaN(id) ? 0 : id,
|
|
2513
|
+
brand_name: ai.brand_name ?? "",
|
|
2514
|
+
generic_name: ai.generic_name ?? "",
|
|
2515
|
+
frequency: "custom",
|
|
2516
|
+
is_custom: false,
|
|
2517
|
+
duration_value: "3",
|
|
2518
|
+
duration_unit: "days",
|
|
2519
|
+
before_after: "AFTER FOOD",
|
|
2520
|
+
morning: "1",
|
|
2521
|
+
afternoon: "0",
|
|
2522
|
+
night: "1",
|
|
2523
|
+
notes: ai.reason ?? ""
|
|
2524
|
+
};
|
|
2525
|
+
}
|
|
2526
|
+
function createCustomMedication(name, customId) {
|
|
2527
|
+
return {
|
|
2528
|
+
id: customId,
|
|
2529
|
+
brand_name: name.trim(),
|
|
2530
|
+
generic_name: "",
|
|
2531
|
+
frequency: "custom",
|
|
2532
|
+
is_custom: true,
|
|
2533
|
+
duration_value: "3",
|
|
2534
|
+
duration_unit: "days",
|
|
2535
|
+
before_after: "AFTER FOOD",
|
|
2536
|
+
morning: "0",
|
|
2537
|
+
afternoon: "0",
|
|
2538
|
+
night: "0",
|
|
2539
|
+
notes: ""
|
|
2540
|
+
};
|
|
2541
|
+
}
|
|
2542
|
+
function Spinner({
|
|
2543
|
+
value,
|
|
2544
|
+
onChange
|
|
2545
|
+
}) {
|
|
2546
|
+
const num = parseInt(value, 10) || 0;
|
|
2547
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center", children: [
|
|
2548
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2337
2549
|
"button",
|
|
2338
2550
|
{
|
|
2339
2551
|
type: "button",
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
children:
|
|
2343
|
-
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-medium", children: title }),
|
|
2344
|
-
/* @__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" }) })
|
|
2345
|
-
]
|
|
2552
|
+
className: "text-muted-foreground hover:text-foreground cursor-pointer",
|
|
2553
|
+
onClick: () => onChange(String(num + 1)),
|
|
2554
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "h-3 w-3" })
|
|
2346
2555
|
}
|
|
2347
2556
|
),
|
|
2348
|
-
/* @__PURE__ */ jsxRuntime.jsx("
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
const style = {
|
|
2359
|
-
gridTemplateColumns: `repeat(${gridCols}, minmax(0, 1fr))`
|
|
2360
|
-
};
|
|
2361
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6", children: [
|
|
2362
|
-
label != null && label !== "" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 text-sm font-medium text-gray-700", children: label }),
|
|
2363
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: gridClass, style, children })
|
|
2557
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm w-4 text-center leading-none", children: num }),
|
|
2558
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2559
|
+
"button",
|
|
2560
|
+
{
|
|
2561
|
+
type: "button",
|
|
2562
|
+
className: "text-muted-foreground hover:text-foreground cursor-pointer",
|
|
2563
|
+
onClick: () => onChange(String(Math.max(0, num - 1))),
|
|
2564
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-3 w-3" })
|
|
2565
|
+
}
|
|
2566
|
+
)
|
|
2364
2567
|
] });
|
|
2365
|
-
}
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2568
|
+
}
|
|
2569
|
+
function MedicationCard({
|
|
2570
|
+
med,
|
|
2571
|
+
onUpdate,
|
|
2572
|
+
onRemove
|
|
2573
|
+
}) {
|
|
2574
|
+
const [notesOpen, setNotesOpen] = React11.useState(false);
|
|
2575
|
+
function set(key, val) {
|
|
2576
|
+
onUpdate({ ...med, [key]: val });
|
|
2577
|
+
}
|
|
2578
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-gray-50 rounded-lg p-3 space-y-2", children: [
|
|
2579
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
2580
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-semibold text-sm flex-1 min-w-0 truncate", children: med.brand_name }),
|
|
2581
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2582
|
+
"select",
|
|
2583
|
+
{
|
|
2584
|
+
className: "text-xs border border-border rounded px-2 py-1 bg-white shrink-0",
|
|
2585
|
+
value: med.frequency,
|
|
2586
|
+
onChange: (e) => set("frequency", e.target.value),
|
|
2587
|
+
children: [
|
|
2588
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "custom", children: "select frequency" }),
|
|
2589
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "OD", children: "OD" }),
|
|
2590
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "BD", children: "BD" }),
|
|
2591
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "TDS", children: "TDS" }),
|
|
2592
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "QID", children: "QID" }),
|
|
2593
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "SOS", children: "SOS" })
|
|
2594
|
+
]
|
|
2595
|
+
}
|
|
2596
|
+
),
|
|
2597
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-4 items-center shrink-0", children: ["morning", "afternoon", "night"].map((slot) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2598
|
+
"span",
|
|
2599
|
+
{
|
|
2600
|
+
className: "text-[10px] font-semibold uppercase text-muted-foreground tracking-wide",
|
|
2601
|
+
children: slot === "afternoon" ? "AFTERNOON" : slot === "morning" ? "MORNING" : "NIGHT"
|
|
2602
|
+
},
|
|
2603
|
+
slot
|
|
2604
|
+
)) }),
|
|
2605
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2606
|
+
"button",
|
|
2607
|
+
{
|
|
2608
|
+
type: "button",
|
|
2609
|
+
onClick: onRemove,
|
|
2610
|
+
className: "text-red-400 hover:text-red-600 shrink-0 p-1 cursor-pointer",
|
|
2611
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" })
|
|
2612
|
+
}
|
|
2613
|
+
)
|
|
2379
2614
|
] }),
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2615
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [
|
|
2616
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
|
|
2617
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2618
|
+
"input",
|
|
2619
|
+
{
|
|
2620
|
+
type: "text",
|
|
2621
|
+
inputMode: "numeric",
|
|
2622
|
+
pattern: "[0-9]*",
|
|
2623
|
+
className: "w-11 text-sm border border-border rounded px-2 py-1 bg-white",
|
|
2624
|
+
value: med.duration_value,
|
|
2625
|
+
onChange: (e) => {
|
|
2626
|
+
const v = e.target.value.replace(/\D/g, "");
|
|
2627
|
+
set("duration_value", v === "" ? "" : v);
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
),
|
|
2631
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2632
|
+
"select",
|
|
2633
|
+
{
|
|
2634
|
+
className: "text-xs border border-border rounded px-2 py-1 bg-white",
|
|
2635
|
+
value: med.duration_unit,
|
|
2636
|
+
onChange: (e) => set("duration_unit", e.target.value),
|
|
2637
|
+
children: [
|
|
2638
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "days", children: "days" }),
|
|
2639
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "weeks", children: "weeks" }),
|
|
2640
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "months", children: "months" })
|
|
2641
|
+
]
|
|
2642
|
+
}
|
|
2643
|
+
)
|
|
2644
|
+
] }),
|
|
2645
|
+
/* @__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)) }),
|
|
2646
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2647
|
+
"select",
|
|
2648
|
+
{
|
|
2649
|
+
className: "text-xs border border-border rounded px-2 py-1 bg-white shrink-0",
|
|
2650
|
+
value: med.before_after === "AFTER FOOD" ? "AF" : "BF",
|
|
2651
|
+
onChange: (e) => set("before_after", e.target.value === "AF" ? "AFTER FOOD" : "BEFORE FOOD"),
|
|
2652
|
+
children: [
|
|
2653
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "AF", children: "AF" }),
|
|
2654
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "BF", children: "BF" })
|
|
2655
|
+
]
|
|
2656
|
+
}
|
|
2657
|
+
)
|
|
2658
|
+
] }),
|
|
2659
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2660
|
+
"div",
|
|
2661
|
+
{
|
|
2662
|
+
role: "button",
|
|
2663
|
+
tabIndex: 0,
|
|
2664
|
+
onClick: () => setNotesOpen((o) => !o),
|
|
2665
|
+
onKeyDown: (e) => {
|
|
2666
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
2667
|
+
e.preventDefault();
|
|
2668
|
+
setNotesOpen((o) => !o);
|
|
2669
|
+
}
|
|
2670
|
+
},
|
|
2671
|
+
className: cn(
|
|
2672
|
+
"flex items-center gap-2 w-full text-left text-xs text-muted-foreground hover:text-foreground",
|
|
2673
|
+
"cursor-pointer py-2 px-2 rounded border border-transparent hover:bg-gray-100/80 transition-colors"
|
|
2674
|
+
),
|
|
2675
|
+
children: [
|
|
2676
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2677
|
+
lucideReact.ChevronRight,
|
|
2385
2678
|
{
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(React11__namespace.default.Fragment, {}, child.id ?? index)
|
|
2392
|
-
) }, node.id);
|
|
2679
|
+
className: cn("h-3 w-3 shrink-0 transition-transform", notesOpen && "rotate-90")
|
|
2680
|
+
}
|
|
2681
|
+
),
|
|
2682
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Medicine Notes" })
|
|
2683
|
+
]
|
|
2393
2684
|
}
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
);
|
|
2685
|
+
),
|
|
2686
|
+
notesOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative z-10 mt-0.5", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2687
|
+
"textarea",
|
|
2688
|
+
{
|
|
2689
|
+
className: "block w-full text-xs border border-border rounded px-2 py-1 resize-none bg-white min-h-0",
|
|
2690
|
+
rows: 2,
|
|
2691
|
+
placeholder: "Add notes...",
|
|
2692
|
+
value: med.notes,
|
|
2693
|
+
onChange: (e) => set("notes", e.target.value)
|
|
2404
2694
|
}
|
|
2405
|
-
|
|
2406
|
-
})
|
|
2695
|
+
) })
|
|
2407
2696
|
] });
|
|
2697
|
+
}
|
|
2698
|
+
var ADD_FEEDBACK_MS = 320;
|
|
2699
|
+
function SearchResultCard({
|
|
2700
|
+
result,
|
|
2701
|
+
isAdding,
|
|
2702
|
+
onSelect
|
|
2703
|
+
}) {
|
|
2704
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2705
|
+
"button",
|
|
2706
|
+
{
|
|
2707
|
+
type: "button",
|
|
2708
|
+
onClick: onSelect,
|
|
2709
|
+
className: cn(
|
|
2710
|
+
"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",
|
|
2711
|
+
isAdding ? "border-green-400 bg-green-50 scale-[0.98]" : "border-border bg-white hover:bg-gray-50"
|
|
2712
|
+
),
|
|
2713
|
+
children: [
|
|
2714
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 text-left", children: [
|
|
2715
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-semibold leading-tight truncate", children: result.name }),
|
|
2716
|
+
result.generic_name && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground mt-0.5 truncate", children: [
|
|
2717
|
+
"Generic: ",
|
|
2718
|
+
result.generic_name
|
|
2719
|
+
] })
|
|
2720
|
+
] }),
|
|
2721
|
+
isAdding && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4 shrink-0 text-green-600" })
|
|
2722
|
+
]
|
|
2723
|
+
}
|
|
2724
|
+
);
|
|
2725
|
+
}
|
|
2726
|
+
var AddMedicationField = ({
|
|
2727
|
+
label = "Medications",
|
|
2728
|
+
value,
|
|
2729
|
+
onChange,
|
|
2730
|
+
onSearch,
|
|
2731
|
+
recentlyUsed = [],
|
|
2732
|
+
suggestedMedications = []
|
|
2733
|
+
}) => {
|
|
2734
|
+
const [open, setOpen] = React11.useState(false);
|
|
2735
|
+
const [query, setQuery] = React11.useState("");
|
|
2736
|
+
const [results, setResults] = React11.useState([]);
|
|
2737
|
+
const [loading, setLoading] = React11.useState(false);
|
|
2738
|
+
const [addingResultId, setAddingResultId] = React11.useState(null);
|
|
2739
|
+
const debounceRef = React11.useRef(null);
|
|
2740
|
+
const customIdRef = React11.useRef(-1);
|
|
2741
|
+
const valueRef = React11.useRef([]);
|
|
2742
|
+
const searchInputRef = React11.useRef(null);
|
|
2743
|
+
const safeValue = Array.isArray(value) ? value : [];
|
|
2744
|
+
valueRef.current = safeValue;
|
|
2745
|
+
const addedIds = new Set(safeValue.map((m) => String(m.id)));
|
|
2746
|
+
React11.useEffect(() => {
|
|
2747
|
+
if (!open) {
|
|
2748
|
+
setQuery("");
|
|
2749
|
+
setResults([]);
|
|
2750
|
+
setAddingResultId(null);
|
|
2751
|
+
}
|
|
2752
|
+
}, [open]);
|
|
2753
|
+
function handleQueryChange(q) {
|
|
2754
|
+
setQuery(q);
|
|
2755
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
2756
|
+
if (!q.trim()) {
|
|
2757
|
+
setResults([]);
|
|
2758
|
+
return;
|
|
2759
|
+
}
|
|
2760
|
+
debounceRef.current = setTimeout(async () => {
|
|
2761
|
+
setLoading(true);
|
|
2762
|
+
try {
|
|
2763
|
+
const res = await onSearch(q);
|
|
2764
|
+
setResults(res);
|
|
2765
|
+
} finally {
|
|
2766
|
+
setLoading(false);
|
|
2767
|
+
}
|
|
2768
|
+
}, 500);
|
|
2769
|
+
}
|
|
2770
|
+
function handleRecentlyUsedToggle(result) {
|
|
2771
|
+
if (addedIds.has(String(result.id))) {
|
|
2772
|
+
onChange(safeValue.filter((m) => String(m.id) !== String(result.id)));
|
|
2773
|
+
} else {
|
|
2774
|
+
onChange([...safeValue, mapToMedication(result)]);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
function handleResultClick(result) {
|
|
2778
|
+
if (addedIds.has(String(result.id))) return;
|
|
2779
|
+
setAddingResultId(result.id);
|
|
2780
|
+
setTimeout(() => {
|
|
2781
|
+
onChange([...valueRef.current, mapToMedication(result)]);
|
|
2782
|
+
setAddingResultId(null);
|
|
2783
|
+
setQuery("");
|
|
2784
|
+
setResults([]);
|
|
2785
|
+
setTimeout(() => searchInputRef.current?.focus(), 0);
|
|
2786
|
+
}, ADD_FEEDBACK_MS);
|
|
2787
|
+
}
|
|
2788
|
+
function handleSave() {
|
|
2789
|
+
const trimmed = query.trim();
|
|
2790
|
+
if (!trimmed) return;
|
|
2791
|
+
const customId = customIdRef.current--;
|
|
2792
|
+
onChange([...safeValue, createCustomMedication(trimmed, customId)]);
|
|
2793
|
+
setQuery("");
|
|
2794
|
+
setResults([]);
|
|
2795
|
+
}
|
|
2796
|
+
function handleAddSuggestion(ai) {
|
|
2797
|
+
if (addedIds.has(String(ai.id))) return;
|
|
2798
|
+
onChange([...safeValue, mapAIMedicationToMedication(ai)]);
|
|
2799
|
+
}
|
|
2800
|
+
function updateMed(index, updated) {
|
|
2801
|
+
const next = [...safeValue];
|
|
2802
|
+
next[index] = updated;
|
|
2803
|
+
onChange(next);
|
|
2804
|
+
}
|
|
2805
|
+
function removeMed(index) {
|
|
2806
|
+
onChange(safeValue.filter((_, i) => i !== index));
|
|
2807
|
+
}
|
|
2808
|
+
const recentlyUsedChips = recentlyUsed.map((r) => {
|
|
2809
|
+
const isAdded = addedIds.has(String(r.id));
|
|
2810
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2811
|
+
"button",
|
|
2812
|
+
{
|
|
2813
|
+
type: "button",
|
|
2814
|
+
onClick: () => handleRecentlyUsedToggle(r),
|
|
2815
|
+
className: cn(
|
|
2816
|
+
"rounded-full border text-xs px-3 py-1 transition-colors cursor-pointer",
|
|
2817
|
+
isAdded ? "border-green-300 bg-green-50 text-green-700" : "border-border bg-white hover:bg-gray-50"
|
|
2818
|
+
),
|
|
2819
|
+
children: [
|
|
2820
|
+
isAdded && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1", children: "\u2713" }),
|
|
2821
|
+
r.name
|
|
2822
|
+
]
|
|
2823
|
+
},
|
|
2824
|
+
r.id
|
|
2825
|
+
);
|
|
2826
|
+
});
|
|
2827
|
+
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: [
|
|
2828
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2829
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: label }),
|
|
2830
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2831
|
+
"button",
|
|
2832
|
+
{
|
|
2833
|
+
type: "button",
|
|
2834
|
+
onClick: () => setOpen(true),
|
|
2835
|
+
className: "text-sm font-semibold text-purple-600 hover:text-purple-800 cursor-pointer",
|
|
2836
|
+
children: "ADD"
|
|
2837
|
+
}
|
|
2838
|
+
)
|
|
2839
|
+
] }),
|
|
2840
|
+
suggestedMedications.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap gap-1.5 items-center", children: [
|
|
2841
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground mr-1", children: "Suggestions:" }),
|
|
2842
|
+
suggestedMedications.filter((s) => !addedIds.has(String(s.id))).map((s) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2843
|
+
"button",
|
|
2844
|
+
{
|
|
2845
|
+
type: "button",
|
|
2846
|
+
onClick: () => handleAddSuggestion(s),
|
|
2847
|
+
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",
|
|
2848
|
+
children: [
|
|
2849
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-3 w-3" }),
|
|
2850
|
+
s.brand_name || s.generic_name || String(s.id)
|
|
2851
|
+
]
|
|
2852
|
+
},
|
|
2853
|
+
String(s.id)
|
|
2854
|
+
))
|
|
2855
|
+
] }),
|
|
2856
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: safeValue.map((med, i) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2857
|
+
MedicationCard,
|
|
2858
|
+
{
|
|
2859
|
+
med,
|
|
2860
|
+
onUpdate: (u) => updateMed(i, u),
|
|
2861
|
+
onRemove: () => removeMed(i)
|
|
2862
|
+
},
|
|
2863
|
+
med.id
|
|
2864
|
+
)) }),
|
|
2865
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2866
|
+
AddEntityDialog,
|
|
2867
|
+
{
|
|
2868
|
+
open,
|
|
2869
|
+
onClose: () => setOpen(false),
|
|
2870
|
+
title: "Add Medication",
|
|
2871
|
+
recentlyUsedSlot: recentlyUsedChips.length > 0 ? recentlyUsedChips : void 0,
|
|
2872
|
+
actionSlot: /* @__PURE__ */ jsxRuntime.jsx(
|
|
2873
|
+
"button",
|
|
2874
|
+
{
|
|
2875
|
+
type: "button",
|
|
2876
|
+
disabled: !query.trim(),
|
|
2877
|
+
onClick: handleSave,
|
|
2878
|
+
className: cn(
|
|
2879
|
+
"rounded-md px-5 py-2 text-sm font-semibold bg-primary text-primary-foreground hover:bg-primary/90 transition-opacity cursor-pointer",
|
|
2880
|
+
!query.trim() && "opacity-40 cursor-not-allowed"
|
|
2881
|
+
),
|
|
2882
|
+
children: "Save Medicine"
|
|
2883
|
+
}
|
|
2884
|
+
),
|
|
2885
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
|
|
2886
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2887
|
+
Input,
|
|
2888
|
+
{
|
|
2889
|
+
ref: searchInputRef,
|
|
2890
|
+
value: query,
|
|
2891
|
+
onChange: (e) => handleQueryChange(e.target.value),
|
|
2892
|
+
placeholder: "Search medicines...",
|
|
2893
|
+
className: "border border-border rounded-md focus-visible:ring-0"
|
|
2894
|
+
}
|
|
2895
|
+
),
|
|
2896
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground text-center py-2", children: "Searching\u2026" }),
|
|
2897
|
+
!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(
|
|
2898
|
+
SearchResultCard,
|
|
2899
|
+
{
|
|
2900
|
+
result: r,
|
|
2901
|
+
isAdding: addingResultId === r.id,
|
|
2902
|
+
onSelect: () => handleResultClick(r)
|
|
2903
|
+
},
|
|
2904
|
+
r.id
|
|
2905
|
+
)) })
|
|
2906
|
+
] })
|
|
2907
|
+
}
|
|
2908
|
+
)
|
|
2909
|
+
] });
|
|
2910
|
+
};
|
|
2911
|
+
var AddMedicationWidget = ({ fieldId }) => {
|
|
2912
|
+
const { value, setValue } = useField(fieldId);
|
|
2913
|
+
const handlers = useFieldHandlers(fieldId);
|
|
2914
|
+
const { medications: suggestedMedications } = useAISuggestions();
|
|
2915
|
+
const safeValue = Array.isArray(value) ? value : [];
|
|
2916
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2917
|
+
AddMedicationField,
|
|
2918
|
+
{
|
|
2919
|
+
value: safeValue,
|
|
2920
|
+
onChange: setValue,
|
|
2921
|
+
onSearch: handlers.onSearch,
|
|
2922
|
+
recentlyUsed: handlers.recentlyUsed,
|
|
2923
|
+
suggestedMedications
|
|
2924
|
+
}
|
|
2925
|
+
);
|
|
2926
|
+
};
|
|
2927
|
+
var ADD_FEEDBACK_MS2 = 320;
|
|
2928
|
+
function mapToInvestigation(result) {
|
|
2929
|
+
return {
|
|
2930
|
+
...result,
|
|
2931
|
+
label: result.name,
|
|
2932
|
+
is_custom: false,
|
|
2933
|
+
fromAI: false
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
var AddInvestigationField = ({
|
|
2937
|
+
label = "Investigations",
|
|
2938
|
+
value,
|
|
2939
|
+
onChange,
|
|
2940
|
+
onSearch,
|
|
2941
|
+
recentlyUsed = []
|
|
2942
|
+
}) => {
|
|
2943
|
+
const [open, setOpen] = React11.useState(false);
|
|
2944
|
+
const [query, setQuery] = React11.useState("");
|
|
2945
|
+
const [results, setResults] = React11.useState([]);
|
|
2946
|
+
const [loading, setLoading] = React11.useState(false);
|
|
2947
|
+
const [addingId, setAddingId] = React11.useState(null);
|
|
2948
|
+
const debounceRef = React11.useRef(null);
|
|
2949
|
+
const valueRef = React11.useRef([]);
|
|
2950
|
+
const searchInputRef = React11.useRef(null);
|
|
2951
|
+
valueRef.current = value;
|
|
2952
|
+
const addedIds = new Set(value.map((inv) => inv.id));
|
|
2953
|
+
React11.useEffect(() => {
|
|
2954
|
+
if (!open) {
|
|
2955
|
+
setQuery("");
|
|
2956
|
+
setResults([]);
|
|
2957
|
+
setAddingId(null);
|
|
2958
|
+
}
|
|
2959
|
+
}, [open]);
|
|
2960
|
+
function handleQueryChange(q) {
|
|
2961
|
+
setQuery(q);
|
|
2962
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
2963
|
+
if (!q.trim()) {
|
|
2964
|
+
setResults([]);
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2967
|
+
debounceRef.current = setTimeout(async () => {
|
|
2968
|
+
setLoading(true);
|
|
2969
|
+
try {
|
|
2970
|
+
const res = await onSearch(q);
|
|
2971
|
+
setResults(res);
|
|
2972
|
+
} finally {
|
|
2973
|
+
setLoading(false);
|
|
2974
|
+
}
|
|
2975
|
+
}, 500);
|
|
2976
|
+
}
|
|
2977
|
+
function addInvestigation(result) {
|
|
2978
|
+
if (!addedIds.has(result.id)) {
|
|
2979
|
+
onChange([...valueRef.current, mapToInvestigation(result)]);
|
|
2980
|
+
}
|
|
2981
|
+
setQuery("");
|
|
2982
|
+
setResults([]);
|
|
2983
|
+
}
|
|
2984
|
+
function handleResultClick(result) {
|
|
2985
|
+
if (addedIds.has(result.id)) return;
|
|
2986
|
+
setAddingId(result.id);
|
|
2987
|
+
setTimeout(() => {
|
|
2988
|
+
addInvestigation(result);
|
|
2989
|
+
setAddingId(null);
|
|
2990
|
+
setTimeout(() => searchInputRef.current?.focus(), 0);
|
|
2991
|
+
}, ADD_FEEDBACK_MS2);
|
|
2992
|
+
}
|
|
2993
|
+
function removeInvestigation(id) {
|
|
2994
|
+
onChange(value.filter((inv) => inv.id !== id));
|
|
2995
|
+
}
|
|
2996
|
+
function handleRecentlyUsedToggle(result) {
|
|
2997
|
+
if (addedIds.has(result.id)) {
|
|
2998
|
+
removeInvestigation(result.id);
|
|
2999
|
+
} else {
|
|
3000
|
+
addInvestigation(result);
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
const recentlyUsedChips = recentlyUsed.map((r) => {
|
|
3004
|
+
const isAdded = addedIds.has(r.id);
|
|
3005
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3006
|
+
"button",
|
|
3007
|
+
{
|
|
3008
|
+
type: "button",
|
|
3009
|
+
onClick: () => handleRecentlyUsedToggle(r),
|
|
3010
|
+
className: cn(
|
|
3011
|
+
"rounded-full border text-xs px-3 py-1 transition-colors",
|
|
3012
|
+
isAdded ? "border-green-300 bg-green-50 text-green-700" : "border-border bg-white hover:bg-gray-50"
|
|
3013
|
+
),
|
|
3014
|
+
children: [
|
|
3015
|
+
isAdded && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1", children: "\u2713" }),
|
|
3016
|
+
r.name
|
|
3017
|
+
]
|
|
3018
|
+
},
|
|
3019
|
+
r.id
|
|
3020
|
+
);
|
|
3021
|
+
});
|
|
3022
|
+
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: [
|
|
3023
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
|
|
3024
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: label }),
|
|
3025
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3026
|
+
"button",
|
|
3027
|
+
{
|
|
3028
|
+
type: "button",
|
|
3029
|
+
onClick: () => setOpen(true),
|
|
3030
|
+
className: "text-sm font-semibold text-purple-600 hover:text-purple-800 cursor-pointer",
|
|
3031
|
+
children: "ADD"
|
|
3032
|
+
}
|
|
3033
|
+
)
|
|
3034
|
+
] }),
|
|
3035
|
+
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(
|
|
3036
|
+
"div",
|
|
3037
|
+
{
|
|
3038
|
+
className: "flex items-center justify-between px-3 py-2 bg-white hover:bg-gray-50",
|
|
3039
|
+
children: [
|
|
3040
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: inv.name }),
|
|
3041
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3042
|
+
"button",
|
|
3043
|
+
{
|
|
3044
|
+
type: "button",
|
|
3045
|
+
onClick: () => removeInvestigation(inv.id),
|
|
3046
|
+
className: "text-muted-foreground hover:text-red-500 transition-colors cursor-pointer",
|
|
3047
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
|
|
3048
|
+
}
|
|
3049
|
+
)
|
|
3050
|
+
]
|
|
3051
|
+
},
|
|
3052
|
+
inv.id
|
|
3053
|
+
)) }),
|
|
3054
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3055
|
+
AddEntityDialog,
|
|
3056
|
+
{
|
|
3057
|
+
open,
|
|
3058
|
+
onClose: () => setOpen(false),
|
|
3059
|
+
title: "Add Investigation",
|
|
3060
|
+
recentlyUsedSlot: recentlyUsedChips.length > 0 ? recentlyUsedChips : void 0,
|
|
3061
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
3062
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3063
|
+
Input,
|
|
3064
|
+
{
|
|
3065
|
+
ref: searchInputRef,
|
|
3066
|
+
value: query,
|
|
3067
|
+
onChange: (e) => handleQueryChange(e.target.value),
|
|
3068
|
+
placeholder: "Search for an investigation...",
|
|
3069
|
+
className: "border border-border rounded-md focus-visible:ring-0"
|
|
3070
|
+
}
|
|
3071
|
+
),
|
|
3072
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground py-1", children: "Searching\u2026" }),
|
|
3073
|
+
!loading && results.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border border-border rounded-md overflow-hidden", children: results.map((r) => {
|
|
3074
|
+
const isAdding = addingId === r.id;
|
|
3075
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3076
|
+
"button",
|
|
3077
|
+
{
|
|
3078
|
+
type: "button",
|
|
3079
|
+
onClick: () => handleResultClick(r),
|
|
3080
|
+
className: cn(
|
|
3081
|
+
"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",
|
|
3082
|
+
isAdding ? "bg-green-50 border-green-200" : "hover:bg-gray-50"
|
|
3083
|
+
),
|
|
3084
|
+
children: [
|
|
3085
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: r.name }),
|
|
3086
|
+
isAdding && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4 shrink-0 text-green-600" })
|
|
3087
|
+
]
|
|
3088
|
+
},
|
|
3089
|
+
r.id
|
|
3090
|
+
);
|
|
3091
|
+
}) })
|
|
3092
|
+
] })
|
|
3093
|
+
}
|
|
3094
|
+
)
|
|
3095
|
+
] });
|
|
3096
|
+
};
|
|
3097
|
+
var AddInvestigationWidget = ({ fieldId }) => {
|
|
3098
|
+
const { value, setValue } = useField(fieldId);
|
|
3099
|
+
const handlers = useFieldHandlers(fieldId);
|
|
3100
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3101
|
+
AddInvestigationField,
|
|
3102
|
+
{
|
|
3103
|
+
value: value ?? [],
|
|
3104
|
+
onChange: setValue,
|
|
3105
|
+
onSearch: handlers.onSearch,
|
|
3106
|
+
recentlyUsed: handlers.recentlyUsed
|
|
3107
|
+
}
|
|
3108
|
+
);
|
|
3109
|
+
};
|
|
3110
|
+
var ADD_FEEDBACK_MS3 = 320;
|
|
3111
|
+
function mapToProcedure(result) {
|
|
3112
|
+
return {
|
|
3113
|
+
...result,
|
|
3114
|
+
label: result.name,
|
|
3115
|
+
is_custom: false,
|
|
3116
|
+
fromAI: false
|
|
3117
|
+
};
|
|
3118
|
+
}
|
|
3119
|
+
var AddProcedureField = ({
|
|
3120
|
+
label = "Procedures",
|
|
3121
|
+
value,
|
|
3122
|
+
onChange,
|
|
3123
|
+
onSearch,
|
|
3124
|
+
recentlyUsed = []
|
|
3125
|
+
}) => {
|
|
3126
|
+
const [open, setOpen] = React11.useState(false);
|
|
3127
|
+
const [query, setQuery] = React11.useState("");
|
|
3128
|
+
const [results, setResults] = React11.useState([]);
|
|
3129
|
+
const [loading, setLoading] = React11.useState(false);
|
|
3130
|
+
const [addingId, setAddingId] = React11.useState(null);
|
|
3131
|
+
const debounceRef = React11.useRef(null);
|
|
3132
|
+
const valueRef = React11.useRef([]);
|
|
3133
|
+
const searchInputRef = React11.useRef(null);
|
|
3134
|
+
valueRef.current = value;
|
|
3135
|
+
const addedIds = new Set(value.map((proc) => proc.id));
|
|
3136
|
+
React11.useEffect(() => {
|
|
3137
|
+
if (!open) {
|
|
3138
|
+
setQuery("");
|
|
3139
|
+
setResults([]);
|
|
3140
|
+
setAddingId(null);
|
|
3141
|
+
}
|
|
3142
|
+
}, [open]);
|
|
3143
|
+
function handleQueryChange(q) {
|
|
3144
|
+
setQuery(q);
|
|
3145
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
3146
|
+
if (!q.trim()) {
|
|
3147
|
+
setResults([]);
|
|
3148
|
+
return;
|
|
3149
|
+
}
|
|
3150
|
+
debounceRef.current = setTimeout(async () => {
|
|
3151
|
+
setLoading(true);
|
|
3152
|
+
try {
|
|
3153
|
+
const res = await onSearch(q);
|
|
3154
|
+
setResults(res);
|
|
3155
|
+
} finally {
|
|
3156
|
+
setLoading(false);
|
|
3157
|
+
}
|
|
3158
|
+
}, 500);
|
|
3159
|
+
}
|
|
3160
|
+
function addProcedure(result) {
|
|
3161
|
+
if (!addedIds.has(result.id)) {
|
|
3162
|
+
onChange([...valueRef.current, mapToProcedure(result)]);
|
|
3163
|
+
}
|
|
3164
|
+
setQuery("");
|
|
3165
|
+
setResults([]);
|
|
3166
|
+
}
|
|
3167
|
+
function handleResultClick(result) {
|
|
3168
|
+
if (addedIds.has(result.id)) return;
|
|
3169
|
+
setAddingId(result.id);
|
|
3170
|
+
setTimeout(() => {
|
|
3171
|
+
addProcedure(result);
|
|
3172
|
+
setAddingId(null);
|
|
3173
|
+
setTimeout(() => searchInputRef.current?.focus(), 0);
|
|
3174
|
+
}, ADD_FEEDBACK_MS3);
|
|
3175
|
+
}
|
|
3176
|
+
function removeProcedure(id) {
|
|
3177
|
+
onChange(value.filter((proc) => proc.id !== id));
|
|
3178
|
+
}
|
|
3179
|
+
function handleRecentlyUsedToggle(result) {
|
|
3180
|
+
if (addedIds.has(result.id)) {
|
|
3181
|
+
removeProcedure(result.id);
|
|
3182
|
+
} else {
|
|
3183
|
+
addProcedure(result);
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
const recentlyUsedChips = recentlyUsed.map((r) => {
|
|
3187
|
+
const isAdded = addedIds.has(r.id);
|
|
3188
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3189
|
+
"button",
|
|
3190
|
+
{
|
|
3191
|
+
type: "button",
|
|
3192
|
+
onClick: () => handleRecentlyUsedToggle(r),
|
|
3193
|
+
className: cn(
|
|
3194
|
+
"rounded-full border text-xs px-3 py-1 transition-colors",
|
|
3195
|
+
isAdded ? "border-green-300 bg-green-50 text-green-700" : "border-border bg-white hover:bg-gray-50"
|
|
3196
|
+
),
|
|
3197
|
+
children: [
|
|
3198
|
+
isAdded && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mr-1", children: "\u2713" }),
|
|
3199
|
+
r.name
|
|
3200
|
+
]
|
|
3201
|
+
},
|
|
3202
|
+
r.id
|
|
3203
|
+
);
|
|
3204
|
+
});
|
|
3205
|
+
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: [
|
|
3206
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-3", children: [
|
|
3207
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-semibold", children: label }),
|
|
3208
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3209
|
+
"button",
|
|
3210
|
+
{
|
|
3211
|
+
type: "button",
|
|
3212
|
+
onClick: () => setOpen(true),
|
|
3213
|
+
className: "text-sm font-semibold text-purple-600 hover:text-purple-800 cursor-pointer",
|
|
3214
|
+
children: "ADD"
|
|
3215
|
+
}
|
|
3216
|
+
)
|
|
3217
|
+
] }),
|
|
3218
|
+
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(
|
|
3219
|
+
"div",
|
|
3220
|
+
{
|
|
3221
|
+
className: "flex items-center justify-between px-3 py-2 bg-white hover:bg-gray-50",
|
|
3222
|
+
children: [
|
|
3223
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm", children: proc.name }),
|
|
3224
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3225
|
+
"button",
|
|
3226
|
+
{
|
|
3227
|
+
type: "button",
|
|
3228
|
+
onClick: () => removeProcedure(proc.id),
|
|
3229
|
+
className: "text-muted-foreground hover:text-red-500 transition-colors cursor-pointer",
|
|
3230
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "h-4 w-4" })
|
|
3231
|
+
}
|
|
3232
|
+
)
|
|
3233
|
+
]
|
|
3234
|
+
},
|
|
3235
|
+
proc.id
|
|
3236
|
+
)) }),
|
|
3237
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3238
|
+
AddEntityDialog,
|
|
3239
|
+
{
|
|
3240
|
+
open,
|
|
3241
|
+
onClose: () => setOpen(false),
|
|
3242
|
+
title: "Add Procedure",
|
|
3243
|
+
recentlyUsedSlot: recentlyUsedChips.length > 0 ? recentlyUsedChips : void 0,
|
|
3244
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
3245
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3246
|
+
Input,
|
|
3247
|
+
{
|
|
3248
|
+
ref: searchInputRef,
|
|
3249
|
+
value: query,
|
|
3250
|
+
onChange: (e) => handleQueryChange(e.target.value),
|
|
3251
|
+
placeholder: "Search for a procedure...",
|
|
3252
|
+
className: "border border-border rounded-md focus-visible:ring-0"
|
|
3253
|
+
}
|
|
3254
|
+
),
|
|
3255
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground py-1", children: "Searching\u2026" }),
|
|
3256
|
+
!loading && results.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border border-border rounded-md overflow-hidden", children: results.map((r) => {
|
|
3257
|
+
const isAdding = addingId === r.id;
|
|
3258
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3259
|
+
"button",
|
|
3260
|
+
{
|
|
3261
|
+
type: "button",
|
|
3262
|
+
onClick: () => handleResultClick(r),
|
|
3263
|
+
className: cn(
|
|
3264
|
+
"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",
|
|
3265
|
+
isAdding ? "bg-green-50 border-green-200" : "hover:bg-gray-50"
|
|
3266
|
+
),
|
|
3267
|
+
children: [
|
|
3268
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate", children: r.name }),
|
|
3269
|
+
isAdding && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-4 w-4 shrink-0 text-green-600" })
|
|
3270
|
+
]
|
|
3271
|
+
},
|
|
3272
|
+
r.id
|
|
3273
|
+
);
|
|
3274
|
+
}) })
|
|
3275
|
+
] })
|
|
3276
|
+
}
|
|
3277
|
+
)
|
|
3278
|
+
] });
|
|
3279
|
+
};
|
|
3280
|
+
var AddProcedureWidget = ({ fieldId }) => {
|
|
3281
|
+
const { value, setValue } = useField(fieldId);
|
|
3282
|
+
const handlers = useFieldHandlers(fieldId);
|
|
3283
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3284
|
+
AddProcedureField,
|
|
3285
|
+
{
|
|
3286
|
+
value: value ?? [],
|
|
3287
|
+
onChange: setValue,
|
|
3288
|
+
onSearch: handlers.onSearch,
|
|
3289
|
+
recentlyUsed: handlers.recentlyUsed
|
|
3290
|
+
}
|
|
3291
|
+
);
|
|
3292
|
+
};
|
|
3293
|
+
function getStatusBasedClass(status) {
|
|
3294
|
+
switch ((status || "").toLowerCase()) {
|
|
3295
|
+
case "high":
|
|
3296
|
+
return "bg-red-100 text-red-500";
|
|
3297
|
+
case "medium":
|
|
3298
|
+
return "bg-yellow-100 text-yellow-600";
|
|
3299
|
+
case "low":
|
|
3300
|
+
return "bg-green-100 text-green-600";
|
|
3301
|
+
default:
|
|
3302
|
+
return "bg-gray-200 text-gray-600";
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
function DiagnosisCard({ item, defaultOpen = false }) {
|
|
3306
|
+
const [open, setOpen] = React11.useState(defaultOpen);
|
|
3307
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border border-gray-200 rounded-lg bg-[#F2F2F2] mb-2 last:mb-0 overflow-hidden", children: [
|
|
3308
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3309
|
+
"button",
|
|
3310
|
+
{
|
|
3311
|
+
type: "button",
|
|
3312
|
+
onClick: () => setOpen((o) => !o),
|
|
3313
|
+
className: cn(
|
|
3314
|
+
"w-full text-left flex items-center gap-2 py-2 px-3 rounded-lg",
|
|
3315
|
+
"cursor-pointer hover:bg-gray-200/60 transition-colors"
|
|
3316
|
+
),
|
|
3317
|
+
children: [
|
|
3318
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3319
|
+
lucideReact.ChevronRight,
|
|
3320
|
+
{
|
|
3321
|
+
className: cn("h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform", open && "rotate-90")
|
|
3322
|
+
}
|
|
3323
|
+
),
|
|
3324
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
3325
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500 font-medium", children: item.category.charAt(0).toUpperCase() + item.category.slice(1) }),
|
|
3326
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-black font-semibold mt-0.5 leading-tight", children: item.condition })
|
|
3327
|
+
] })
|
|
3328
|
+
]
|
|
3329
|
+
}
|
|
3330
|
+
),
|
|
3331
|
+
open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 px-3 pb-3 pt-0 pl-9", children: [
|
|
3332
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-xs font-semibold text-gray-500", children: [
|
|
3333
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "LIKELIHOOD" }),
|
|
3334
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "SEVERITY" }),
|
|
3335
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "TREATABILITY" })
|
|
3336
|
+
] }),
|
|
3337
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between gap-2", children: [
|
|
3338
|
+
/* @__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 }),
|
|
3339
|
+
/* @__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 }),
|
|
3340
|
+
/* @__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 })
|
|
3341
|
+
] }),
|
|
3342
|
+
item.keyFeatures && item.keyFeatures.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-1", children: [
|
|
3343
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs font-semibold text-gray-500 mb-0.5 uppercase tracking-wider", children: "Key Features" }),
|
|
3344
|
+
/* @__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)) })
|
|
3345
|
+
] }),
|
|
3346
|
+
item.nextSteps && item.nextSteps.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-1", children: [
|
|
3347
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs font-semibold text-gray-500 mb-0.5 uppercase tracking-wider", children: "Next Steps" }),
|
|
3348
|
+
/* @__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)) })
|
|
3349
|
+
] })
|
|
3350
|
+
] })
|
|
3351
|
+
] });
|
|
3352
|
+
}
|
|
3353
|
+
var DifferentialDiagnosis = ({
|
|
3354
|
+
differentialDiagnosis,
|
|
3355
|
+
className = "",
|
|
3356
|
+
height = "300px"
|
|
3357
|
+
}) => {
|
|
3358
|
+
const hasItems = Array.isArray(differentialDiagnosis) && differentialDiagnosis.length > 0;
|
|
3359
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3360
|
+
"div",
|
|
3361
|
+
{
|
|
3362
|
+
className: cn(
|
|
3363
|
+
"rounded-md overflow-hidden flex flex-col border border-[#D0D0D0]",
|
|
3364
|
+
className
|
|
3365
|
+
),
|
|
3366
|
+
children: [
|
|
3367
|
+
/* @__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" }) }),
|
|
3368
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3369
|
+
"div",
|
|
3370
|
+
{
|
|
3371
|
+
className: cn(
|
|
3372
|
+
"relative bg-white rounded-b-md border-t border-[#D0D0D0] overflow-y-auto p-3",
|
|
3373
|
+
"focus-visible:outline-none"
|
|
3374
|
+
),
|
|
3375
|
+
style: { minHeight: height, maxHeight: height },
|
|
3376
|
+
children: [
|
|
3377
|
+
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." }),
|
|
3378
|
+
/* @__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" })
|
|
3379
|
+
]
|
|
3380
|
+
}
|
|
3381
|
+
)
|
|
3382
|
+
]
|
|
3383
|
+
}
|
|
3384
|
+
);
|
|
3385
|
+
};
|
|
3386
|
+
var DifferentialDiagnosisWidget = ({ fieldId }) => {
|
|
3387
|
+
const { value } = useField(fieldId);
|
|
3388
|
+
const items = Array.isArray(value) ? value : [];
|
|
3389
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DifferentialDiagnosis, { differentialDiagnosis: items });
|
|
3390
|
+
};
|
|
3391
|
+
var Spinner2 = React11__namespace.forwardRef(
|
|
3392
|
+
({ className, size = "md", ...props }, ref) => {
|
|
3393
|
+
const sizeClasses = {
|
|
3394
|
+
sm: "h-4 w-4 border-2",
|
|
3395
|
+
md: "h-6 w-6 border-2",
|
|
3396
|
+
lg: "h-8 w-8 border-3"
|
|
3397
|
+
};
|
|
3398
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3399
|
+
"div",
|
|
3400
|
+
{
|
|
3401
|
+
ref,
|
|
3402
|
+
className: cn(
|
|
3403
|
+
"animate-spin rounded-full border-primary border-t-transparent",
|
|
3404
|
+
sizeClasses[size],
|
|
3405
|
+
className
|
|
3406
|
+
),
|
|
3407
|
+
...props,
|
|
3408
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", children: "Loading..." })
|
|
3409
|
+
}
|
|
3410
|
+
);
|
|
3411
|
+
}
|
|
3412
|
+
);
|
|
3413
|
+
Spinner2.displayName = "Spinner";
|
|
3414
|
+
function buildItems(data) {
|
|
3415
|
+
const items = [];
|
|
3416
|
+
const hasBP = data.blood_pressure_systolic != null || data.blood_pressure_diastolic != null;
|
|
3417
|
+
if (hasBP) {
|
|
3418
|
+
const sys = data.blood_pressure_systolic ?? "\u2013";
|
|
3419
|
+
const dia = data.blood_pressure_diastolic ?? "\u2013";
|
|
3420
|
+
items.push({ label: "BP", value: `${sys}/${dia}` });
|
|
3421
|
+
}
|
|
3422
|
+
if (data.heart_rate != null)
|
|
3423
|
+
items.push({ label: "Heart Rate", value: `${data.heart_rate} bpm` });
|
|
3424
|
+
if (data.respiratory_rate != null)
|
|
3425
|
+
items.push({ label: "Resp. Rate", value: `${data.respiratory_rate}/min` });
|
|
3426
|
+
if (data.oxygen_saturation != null)
|
|
3427
|
+
items.push({ label: "SpO\u2082", value: `${data.oxygen_saturation}%` });
|
|
3428
|
+
if (data.temperature != null)
|
|
3429
|
+
items.push({ label: "Temp", value: `${data.temperature} \xB0F` });
|
|
3430
|
+
if (data.height != null)
|
|
3431
|
+
items.push({ label: "Height", value: `${data.height} cm` });
|
|
3432
|
+
if (data.weight != null)
|
|
3433
|
+
items.push({ label: "Weight", value: `${data.weight} kg` });
|
|
3434
|
+
if (data.bmi != null)
|
|
3435
|
+
items.push({ label: "BMI", value: `${data.bmi}` });
|
|
3436
|
+
if (data.blood_sugar != null)
|
|
3437
|
+
items.push({ label: "Blood Sugar", value: `${data.blood_sugar} mg/dL` });
|
|
3438
|
+
if (data.circumference_of_head_in_cms != null)
|
|
3439
|
+
items.push({ label: "Head Circ.", value: `${data.circumference_of_head_in_cms} cm` });
|
|
3440
|
+
if (data.circumference_of_waist_in_cms != null)
|
|
3441
|
+
items.push({ label: "Waist Circ.", value: `${data.circumference_of_waist_in_cms} cm` });
|
|
3442
|
+
return items;
|
|
3443
|
+
}
|
|
3444
|
+
function formatRecordedAt(iso) {
|
|
3445
|
+
if (!iso) return "";
|
|
3446
|
+
try {
|
|
3447
|
+
return new Date(iso).toLocaleString(void 0, {
|
|
3448
|
+
day: "2-digit",
|
|
3449
|
+
month: "short",
|
|
3450
|
+
year: "numeric",
|
|
3451
|
+
hour: "2-digit",
|
|
3452
|
+
minute: "2-digit"
|
|
3453
|
+
});
|
|
3454
|
+
} catch {
|
|
3455
|
+
return iso;
|
|
3456
|
+
}
|
|
3457
|
+
}
|
|
3458
|
+
function VitalChip({ label, value }) {
|
|
3459
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-0 rounded-md min-w-fit px-1.5", children: [
|
|
3460
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-sm text-gray-500 uppercase tracking-wide leading-none", children: label }),
|
|
3461
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-gray-900 leading-tight", children: value })
|
|
3462
|
+
] });
|
|
3463
|
+
}
|
|
3464
|
+
var Vitals = ({
|
|
3465
|
+
data,
|
|
3466
|
+
loading = false,
|
|
3467
|
+
error = null,
|
|
3468
|
+
onRefresh,
|
|
3469
|
+
className
|
|
3470
|
+
}) => {
|
|
3471
|
+
const items = data ? buildItems(data) : [];
|
|
3472
|
+
const hasItems = items.length > 0;
|
|
3473
|
+
const recordedAt = data?.recorded_at ? formatRecordedAt(data.recorded_at) : null;
|
|
3474
|
+
const notes = data?.notes?.trim() || null;
|
|
3475
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3476
|
+
"div",
|
|
3477
|
+
{
|
|
3478
|
+
className: cn(
|
|
3479
|
+
"rounded-md overflow-hidden border border-[#D0D0D0] bg-white px-2 py-2",
|
|
3480
|
+
className
|
|
3481
|
+
),
|
|
3482
|
+
children: loading && !hasItems ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 py-1", children: [
|
|
3483
|
+
/* @__PURE__ */ jsxRuntime.jsx(Spinner2, { size: "sm" }),
|
|
3484
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Loading vitals\u2026" })
|
|
3485
|
+
] }) : 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: [
|
|
3486
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-x-3 gap-y-1", children: [
|
|
3487
|
+
/* @__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)) }),
|
|
3488
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-end gap-0.5 shrink-0", children: [
|
|
3489
|
+
recordedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] text-slate-500", children: recordedAt }),
|
|
3490
|
+
onRefresh && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3491
|
+
"button",
|
|
3492
|
+
{
|
|
3493
|
+
type: "button",
|
|
3494
|
+
onClick: onRefresh,
|
|
3495
|
+
disabled: loading,
|
|
3496
|
+
className: cn(
|
|
3497
|
+
"flex items-center gap-0.5 text-[10px] font-medium text-slate-600",
|
|
3498
|
+
"hover:text-slate-900 transition-colors disabled:opacity-50 cursor-pointer"
|
|
3499
|
+
),
|
|
3500
|
+
"aria-label": "Refresh vitals",
|
|
3501
|
+
children: [
|
|
3502
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: cn("h-2.5 w-2.5", loading && "animate-spin") }),
|
|
3503
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Refresh" })
|
|
3504
|
+
]
|
|
3505
|
+
}
|
|
3506
|
+
)
|
|
3507
|
+
] })
|
|
3508
|
+
] }),
|
|
3509
|
+
notes && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-gray-600 pt-1.5 mt-0.5", children: notes })
|
|
3510
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-2 py-1", children: [
|
|
3511
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "No vitals recorded." }),
|
|
3512
|
+
onRefresh && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3513
|
+
"button",
|
|
3514
|
+
{
|
|
3515
|
+
type: "button",
|
|
3516
|
+
onClick: onRefresh,
|
|
3517
|
+
disabled: loading,
|
|
3518
|
+
className: cn(
|
|
3519
|
+
"flex items-center gap-0.5 text-[10px] font-medium text-slate-600",
|
|
3520
|
+
"hover:text-slate-900 transition-colors disabled:opacity-50 shrink-0"
|
|
3521
|
+
),
|
|
3522
|
+
"aria-label": "Refresh vitals",
|
|
3523
|
+
children: [
|
|
3524
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.RefreshCw, { className: cn("h-2.5 w-2.5", loading && "animate-spin") }),
|
|
3525
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Refresh" })
|
|
3526
|
+
]
|
|
3527
|
+
}
|
|
3528
|
+
)
|
|
3529
|
+
] })
|
|
3530
|
+
}
|
|
3531
|
+
);
|
|
3532
|
+
};
|
|
3533
|
+
var VitalsWidget = ({ fieldId }) => {
|
|
3534
|
+
const { value, setValue, fieldDef } = useField(fieldId);
|
|
3535
|
+
const handlers = useFieldHandlers(fieldId);
|
|
3536
|
+
const [loading, setLoading] = React11.useState(false);
|
|
3537
|
+
const [error, setError] = React11.useState(null);
|
|
3538
|
+
console.log("[VitalsWidget] fieldId:", fieldId, "| onFetch present:", !!handlers.onFetch, "| value:", value);
|
|
3539
|
+
const fetchVitals = React11.useCallback(async () => {
|
|
3540
|
+
if (!handlers.onFetch) return;
|
|
3541
|
+
setLoading(true);
|
|
3542
|
+
setError(null);
|
|
3543
|
+
try {
|
|
3544
|
+
const data = await handlers.onFetch();
|
|
3545
|
+
console.log("[VitalsWidget] fetched data:", data);
|
|
3546
|
+
setValue(data);
|
|
3547
|
+
} catch {
|
|
3548
|
+
setError("Failed to load vitals. Please try again.");
|
|
3549
|
+
} finally {
|
|
3550
|
+
setLoading(false);
|
|
3551
|
+
}
|
|
3552
|
+
}, [handlers, setValue]);
|
|
3553
|
+
React11.useEffect(() => {
|
|
3554
|
+
fetchVitals();
|
|
3555
|
+
}, []);
|
|
3556
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
3557
|
+
Vitals,
|
|
3558
|
+
{
|
|
3559
|
+
label: fieldDef?.label ?? "Vitals",
|
|
3560
|
+
data: value ?? null,
|
|
3561
|
+
loading,
|
|
3562
|
+
error,
|
|
3563
|
+
onRefresh: handlers.onFetch ? fetchVitals : void 0
|
|
3564
|
+
}
|
|
3565
|
+
);
|
|
3566
|
+
};
|
|
3567
|
+
function normalizeOptions(response) {
|
|
3568
|
+
if (Array.isArray(response)) {
|
|
3569
|
+
return response.map((o) => ({ label: String(o.label), value: String(o.value) }));
|
|
3570
|
+
}
|
|
3571
|
+
if (response?.specializations?.length) {
|
|
3572
|
+
return response.specializations.map((s) => ({ label: s, value: s }));
|
|
3573
|
+
}
|
|
3574
|
+
return [];
|
|
3575
|
+
}
|
|
3576
|
+
function normalizeValue(value) {
|
|
3577
|
+
if (!value || !Array.isArray(value)) return [];
|
|
3578
|
+
return value.map((item) => {
|
|
3579
|
+
if (item && typeof item === "object" && "specialization" in item) {
|
|
3580
|
+
const s = item.specialization;
|
|
3581
|
+
const u = item.is_urgent;
|
|
3582
|
+
return { specialization: String(s), is_urgent: Boolean(u) };
|
|
3583
|
+
}
|
|
3584
|
+
return { specialization: String(item), is_urgent: false };
|
|
3585
|
+
});
|
|
3586
|
+
}
|
|
3587
|
+
var ReferralWidget = ({ fieldId }) => {
|
|
3588
|
+
const { fieldDef, value, setValue, setTouched, error, disabled, touched } = useField(fieldId);
|
|
3589
|
+
const handlers = useFieldHandlers(fieldId);
|
|
3590
|
+
const [options, setOptions] = React11.useState([]);
|
|
3591
|
+
const [loading, setLoading] = React11.useState(false);
|
|
3592
|
+
const { state } = useForm();
|
|
3593
|
+
const showError = !!error && (touched || state.submitAttempted);
|
|
3594
|
+
const selectedItems = normalizeValue(value);
|
|
3595
|
+
const availableOptions = options.filter(
|
|
3596
|
+
(opt) => !selectedItems.some((item) => item.specialization === opt.value)
|
|
3597
|
+
);
|
|
3598
|
+
const loadOptions = React11.useCallback(async () => {
|
|
3599
|
+
if (handlers.onFetchOptions) {
|
|
3600
|
+
setLoading(true);
|
|
3601
|
+
try {
|
|
3602
|
+
const res = await handlers.onFetchOptions();
|
|
3603
|
+
setOptions(normalizeOptions(res));
|
|
3604
|
+
} finally {
|
|
3605
|
+
setLoading(false);
|
|
3606
|
+
}
|
|
3607
|
+
return;
|
|
3608
|
+
}
|
|
3609
|
+
const def = fieldDef;
|
|
3610
|
+
if (def?.options?.length) {
|
|
3611
|
+
setOptions(def.options.map((o) => ({ label: String(o.label), value: String(o.value) })));
|
|
3612
|
+
} else if (def?.dataSource) {
|
|
3613
|
+
setLoading(true);
|
|
3614
|
+
fetchOptions(def.dataSource.source, def.dataSource.params).then((opts) => setOptions(opts.map((o) => ({ label: String(o.label), value: String(o.value) })))).finally(() => setLoading(false));
|
|
3615
|
+
} else {
|
|
3616
|
+
setOptions([]);
|
|
3617
|
+
}
|
|
3618
|
+
}, [fieldDef, handlers]);
|
|
3619
|
+
React11.useEffect(() => {
|
|
3620
|
+
if (!fieldDef) return;
|
|
3621
|
+
loadOptions();
|
|
3622
|
+
}, [fieldDef, loadOptions]);
|
|
3623
|
+
const handleAdd = React11.useCallback(
|
|
3624
|
+
(specialization) => {
|
|
3625
|
+
const next = [...selectedItems, { specialization, is_urgent: false }];
|
|
3626
|
+
setValue(next);
|
|
3627
|
+
setTouched();
|
|
3628
|
+
},
|
|
3629
|
+
[selectedItems, setValue, setTouched]
|
|
3630
|
+
);
|
|
3631
|
+
const handleRemove = React11.useCallback(
|
|
3632
|
+
(specialization) => {
|
|
3633
|
+
const next = selectedItems.filter((item) => item.specialization !== specialization);
|
|
3634
|
+
setValue(next);
|
|
3635
|
+
setTouched();
|
|
3636
|
+
},
|
|
3637
|
+
[selectedItems, setValue, setTouched]
|
|
3638
|
+
);
|
|
3639
|
+
const handleToggleUrgent = React11.useCallback(
|
|
3640
|
+
(specialization) => {
|
|
3641
|
+
const next = selectedItems.map(
|
|
3642
|
+
(item) => item.specialization === specialization ? { ...item, is_urgent: !item.is_urgent } : item
|
|
3643
|
+
);
|
|
3644
|
+
setValue(next);
|
|
3645
|
+
setTouched();
|
|
3646
|
+
},
|
|
3647
|
+
[selectedItems, setValue, setTouched]
|
|
3648
|
+
);
|
|
3649
|
+
if (!fieldDef) return null;
|
|
3650
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
3651
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: fieldId, children: [
|
|
3652
|
+
"Referral to ",
|
|
3653
|
+
fieldDef.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
|
|
3654
|
+
] }),
|
|
3655
|
+
loading && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: "Loading..." }),
|
|
3656
|
+
!loading && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3657
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3658
|
+
Select,
|
|
3659
|
+
{
|
|
3660
|
+
disabled,
|
|
3661
|
+
value: "",
|
|
3662
|
+
onValueChange: (val) => {
|
|
3663
|
+
if (val) {
|
|
3664
|
+
handleAdd(val);
|
|
3665
|
+
}
|
|
3666
|
+
},
|
|
3667
|
+
children: [
|
|
3668
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { id: fieldId, className: showError ? "border-red-500" : "", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { placeholder: "Select specializations..." }) }),
|
|
3669
|
+
/* @__PURE__ */ jsxRuntime.jsxs(SelectContent, { children: [
|
|
3670
|
+
availableOptions.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value)),
|
|
3671
|
+
availableOptions.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-2 px-2 text-sm text-muted-foreground", children: "All specializations selected" })
|
|
3672
|
+
] })
|
|
3673
|
+
]
|
|
3674
|
+
},
|
|
3675
|
+
`referral-${selectedItems.map((i) => i.specialization).join(",")}`
|
|
3676
|
+
),
|
|
3677
|
+
selectedItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-wrap gap-1.5 mt-1.5", children: selectedItems.map((item) => {
|
|
3678
|
+
const isUrgent = item.is_urgent;
|
|
3679
|
+
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";
|
|
3680
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3681
|
+
"div",
|
|
3682
|
+
{
|
|
3683
|
+
className: chipClass,
|
|
3684
|
+
onClick: (e) => {
|
|
3685
|
+
if (e.target instanceof HTMLElement && e.target.closest("button")) return;
|
|
3686
|
+
handleToggleUrgent(item.specialization);
|
|
3687
|
+
},
|
|
3688
|
+
children: [
|
|
3689
|
+
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" }),
|
|
3690
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate max-w-[120px]", children: item.specialization }),
|
|
3691
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3692
|
+
"button",
|
|
3693
|
+
{
|
|
3694
|
+
type: "button",
|
|
3695
|
+
onClick: (e) => {
|
|
3696
|
+
e.stopPropagation();
|
|
3697
|
+
handleRemove(item.specialization);
|
|
3698
|
+
},
|
|
3699
|
+
className: "ml-0.5 text-blue-600 hover:text-blue-800 focus:outline-none flex-shrink-0 text-sm leading-none",
|
|
3700
|
+
"aria-label": `Remove ${item.specialization}`,
|
|
3701
|
+
children: "\xD7"
|
|
3702
|
+
}
|
|
3703
|
+
)
|
|
3704
|
+
]
|
|
3705
|
+
},
|
|
3706
|
+
item.specialization
|
|
3707
|
+
);
|
|
3708
|
+
}) }),
|
|
3709
|
+
selectedItems.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "No specializations selected" }),
|
|
3710
|
+
selectedItems.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mt-1.5", children: "Click a chip to mark as urgent" })
|
|
3711
|
+
] }),
|
|
3712
|
+
showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
|
|
3713
|
+
] });
|
|
3714
|
+
};
|
|
3715
|
+
var Card = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3716
|
+
"div",
|
|
3717
|
+
{
|
|
3718
|
+
ref,
|
|
3719
|
+
className: cn(
|
|
3720
|
+
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
|
3721
|
+
className
|
|
3722
|
+
),
|
|
3723
|
+
...props
|
|
3724
|
+
}
|
|
3725
|
+
));
|
|
3726
|
+
Card.displayName = "Card";
|
|
3727
|
+
var CardHeader = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3728
|
+
"div",
|
|
3729
|
+
{
|
|
3730
|
+
ref,
|
|
3731
|
+
className: cn("flex flex-col space-y-1.5 p-6", className),
|
|
3732
|
+
...props
|
|
3733
|
+
}
|
|
3734
|
+
));
|
|
3735
|
+
CardHeader.displayName = "CardHeader";
|
|
3736
|
+
var CardTitle = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3737
|
+
"h3",
|
|
3738
|
+
{
|
|
3739
|
+
ref,
|
|
3740
|
+
className: cn(
|
|
3741
|
+
"text-2xl font-semibold leading-none tracking-tight",
|
|
3742
|
+
className
|
|
3743
|
+
),
|
|
3744
|
+
...props
|
|
3745
|
+
}
|
|
3746
|
+
));
|
|
3747
|
+
CardTitle.displayName = "CardTitle";
|
|
3748
|
+
var CardDescription = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3749
|
+
"p",
|
|
3750
|
+
{
|
|
3751
|
+
ref,
|
|
3752
|
+
className: cn("text-sm text-muted-foreground", className),
|
|
3753
|
+
...props
|
|
3754
|
+
}
|
|
3755
|
+
));
|
|
3756
|
+
CardDescription.displayName = "CardDescription";
|
|
3757
|
+
var CardContent = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx("div", { ref, className: cn("p-6 pt-0", className), ...props }));
|
|
3758
|
+
CardContent.displayName = "CardContent";
|
|
3759
|
+
var CardFooter = React11__namespace.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3760
|
+
"div",
|
|
3761
|
+
{
|
|
3762
|
+
ref,
|
|
3763
|
+
className: cn("flex items-center p-6 pt-0", className),
|
|
3764
|
+
...props
|
|
3765
|
+
}
|
|
3766
|
+
));
|
|
3767
|
+
CardFooter.displayName = "CardFooter";
|
|
3768
|
+
var FOLLOWUP_TYPE_OPTIONS = [
|
|
3769
|
+
{ value: "no followup", label: "No Follow-up" },
|
|
3770
|
+
{ value: "one time", label: "One Time" }
|
|
3771
|
+
];
|
|
3772
|
+
var UNIT_OPTIONS = [
|
|
3773
|
+
{ value: "days", label: "Days" },
|
|
3774
|
+
{ value: "weeks", label: "Weeks" },
|
|
3775
|
+
{ value: "months", label: "Months" }
|
|
3776
|
+
];
|
|
3777
|
+
function durationToDays(valueNum, unit) {
|
|
3778
|
+
const today = /* @__PURE__ */ new Date();
|
|
3779
|
+
today.setHours(0, 0, 0, 0);
|
|
3780
|
+
let target;
|
|
3781
|
+
if (unit === "days") target = dateFns.addDays(today, valueNum);
|
|
3782
|
+
else if (unit === "weeks") target = dateFns.addWeeks(today, valueNum);
|
|
3783
|
+
else target = dateFns.addMonths(today, valueNum);
|
|
3784
|
+
return Math.max(0, dateFns.differenceInCalendarDays(target, today));
|
|
3785
|
+
}
|
|
3786
|
+
function dateToDays(date) {
|
|
3787
|
+
const today = /* @__PURE__ */ new Date();
|
|
3788
|
+
today.setHours(0, 0, 0, 0);
|
|
3789
|
+
const d = new Date(date);
|
|
3790
|
+
d.setHours(0, 0, 0, 0);
|
|
3791
|
+
return Math.max(0, dateFns.differenceInCalendarDays(d, today));
|
|
3792
|
+
}
|
|
3793
|
+
function daysToDate(days) {
|
|
3794
|
+
const today = /* @__PURE__ */ new Date();
|
|
3795
|
+
today.setHours(0, 0, 0, 0);
|
|
3796
|
+
return dateFns.addDays(today, days);
|
|
3797
|
+
}
|
|
3798
|
+
function normalizeFollowup(value) {
|
|
3799
|
+
if (value === false || value === null || value === void 0) return false;
|
|
3800
|
+
if (typeof value !== "object") return false;
|
|
3801
|
+
const o = value;
|
|
3802
|
+
if (o.followupType !== "one time") return false;
|
|
3803
|
+
if (typeof o.value === "string" && o.unit === "days") {
|
|
3804
|
+
return { followupType: "one time", value: o.value, unit: "days" };
|
|
3805
|
+
}
|
|
3806
|
+
const today = /* @__PURE__ */ new Date();
|
|
3807
|
+
today.setHours(0, 0, 0, 0);
|
|
3808
|
+
if (o.mode === "date" && typeof o.date === "string") {
|
|
3809
|
+
const d = parseLocalDate(o.date);
|
|
3810
|
+
if (d) {
|
|
3811
|
+
const days2 = dateToDays(d);
|
|
3812
|
+
return { followupType: "one time", value: String(days2), unit: "days" };
|
|
3813
|
+
}
|
|
3814
|
+
}
|
|
3815
|
+
const val = o.value != null ? Number(String(o.value)) : 7;
|
|
3816
|
+
const u = o.unit === "weeks" || o.unit === "months" ? o.unit : "days";
|
|
3817
|
+
const days = durationToDays(Number.isNaN(val) ? 7 : val, u);
|
|
3818
|
+
return { followupType: "one time", value: String(days), unit: "days" };
|
|
3819
|
+
}
|
|
3820
|
+
function getDefaultDate() {
|
|
3821
|
+
const d = /* @__PURE__ */ new Date();
|
|
3822
|
+
d.setDate(d.getDate() + 7);
|
|
3823
|
+
return d;
|
|
3824
|
+
}
|
|
3825
|
+
function parseLocalDate(dateString) {
|
|
3826
|
+
if (!dateString) return null;
|
|
3827
|
+
try {
|
|
3828
|
+
return dateFns.parseISO(dateString);
|
|
3829
|
+
} catch {
|
|
3830
|
+
const parts = dateString.split("-");
|
|
3831
|
+
if (parts.length === 3) {
|
|
3832
|
+
const y = parseInt(parts[0], 10);
|
|
3833
|
+
const m = parseInt(parts[1], 10) - 1;
|
|
3834
|
+
const d = parseInt(parts[2], 10);
|
|
3835
|
+
if (!Number.isNaN(y) && !Number.isNaN(m) && !Number.isNaN(d)) {
|
|
3836
|
+
return new Date(y, m, d);
|
|
3837
|
+
}
|
|
3838
|
+
}
|
|
3839
|
+
return null;
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
var FollowupWidget = ({ fieldId }) => {
|
|
3843
|
+
const { fieldDef, value, setValue, setTouched, error, disabled, visible } = useField(fieldId);
|
|
3844
|
+
const followup = normalizeFollowup(value);
|
|
3845
|
+
const [calendarOpen, setCalendarOpen] = React11.useState(false);
|
|
3846
|
+
const currentFollowupType = followup ? followup.followupType : "no followup";
|
|
3847
|
+
const hasFollowup = followup !== false;
|
|
3848
|
+
const [displayMode, setDisplayMode] = React11.useState("duration");
|
|
3849
|
+
const [displayUnit, setDisplayUnit] = React11.useState("days");
|
|
3850
|
+
const daysNum = hasFollowup ? parseInt(followup.value, 10) || 7 : 7;
|
|
3851
|
+
const currentDate = hasFollowup ? daysToDate(daysNum) : null;
|
|
3852
|
+
const followupMode = displayMode;
|
|
3853
|
+
const currentValue = hasFollowup && displayUnit === "days" ? followup.value : hasFollowup ? String(
|
|
3854
|
+
displayUnit === "weeks" ? Math.round(daysNum / 7) || 1 : Math.round(daysNum / 30) || 1
|
|
3855
|
+
) : "7";
|
|
3856
|
+
const currentUnit = displayUnit;
|
|
3857
|
+
const handleFollowupTypeChange = React11.useCallback(
|
|
3858
|
+
(newType) => {
|
|
3859
|
+
setTouched();
|
|
3860
|
+
if (newType === "no followup") {
|
|
3861
|
+
setValue(false);
|
|
3862
|
+
setCalendarOpen(false);
|
|
3863
|
+
return;
|
|
3864
|
+
}
|
|
3865
|
+
setValue({
|
|
3866
|
+
followupType: "one time",
|
|
3867
|
+
value: hasFollowup ? followup.value : "7",
|
|
3868
|
+
unit: "days"
|
|
3869
|
+
});
|
|
3870
|
+
},
|
|
3871
|
+
[followup, hasFollowup, setValue, setTouched]
|
|
3872
|
+
);
|
|
3873
|
+
const handleModeChange = React11.useCallback((newMode) => {
|
|
3874
|
+
setDisplayMode(newMode);
|
|
3875
|
+
if (newMode === "date") setCalendarOpen(false);
|
|
3876
|
+
}, []);
|
|
3877
|
+
const handleValueChange = React11.useCallback(
|
|
3878
|
+
(newValue) => {
|
|
3879
|
+
setTouched();
|
|
3880
|
+
const num = parseInt(newValue, 10);
|
|
3881
|
+
const min = displayUnit === "days" ? 0 : 1;
|
|
3882
|
+
const valid = (Number.isNaN(num) ? min : num) >= min || newValue === "";
|
|
3883
|
+
if (!valid && newValue !== "") {
|
|
3884
|
+
setValue({ followupType: "one time", value: String(displayUnit === "days" ? 0 : durationToDays(1, displayUnit)), unit: "days" });
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3887
|
+
const days = durationToDays(Number.isNaN(num) ? displayUnit === "days" ? 0 : 1 : num, displayUnit);
|
|
3888
|
+
setValue({ followupType: "one time", value: String(days), unit: "days" });
|
|
3889
|
+
},
|
|
3890
|
+
[displayUnit, setValue, setTouched]
|
|
3891
|
+
);
|
|
3892
|
+
const handleUnitChange = React11.useCallback((newUnit) => {
|
|
3893
|
+
setDisplayUnit(newUnit);
|
|
3894
|
+
}, []);
|
|
3895
|
+
const handleDateSelect = React11.useCallback(
|
|
3896
|
+
(selected) => {
|
|
3897
|
+
setTouched();
|
|
3898
|
+
setCalendarOpen(false);
|
|
3899
|
+
if (!selected) return;
|
|
3900
|
+
const days = dateToDays(selected);
|
|
3901
|
+
setValue({ followupType: "one time", value: String(days), unit: "days" });
|
|
3902
|
+
},
|
|
3903
|
+
[setValue, setTouched]
|
|
3904
|
+
);
|
|
3905
|
+
if (!visible || !fieldDef) return null;
|
|
3906
|
+
const minDate = /* @__PURE__ */ new Date();
|
|
3907
|
+
minDate.setHours(0, 0, 0, 0);
|
|
3908
|
+
const selectedDate = currentDate || getDefaultDate();
|
|
3909
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Card, { className: cn(error && "border-destructive"), children: /* @__PURE__ */ jsxRuntime.jsxs(CardContent, { className: "space-y-1 p-2", children: [
|
|
3910
|
+
/* @__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: [
|
|
3911
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-xs font-semibold text-muted-foreground", children: "Follow-up" }),
|
|
3912
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3913
|
+
Select,
|
|
3914
|
+
{
|
|
3915
|
+
value: currentFollowupType,
|
|
3916
|
+
onValueChange: (v) => handleFollowupTypeChange(v),
|
|
3917
|
+
disabled,
|
|
3918
|
+
children: [
|
|
3919
|
+
/* @__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, {}) }),
|
|
3920
|
+
/* @__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)) })
|
|
3921
|
+
]
|
|
3922
|
+
}
|
|
3923
|
+
)
|
|
3924
|
+
] }) }),
|
|
3925
|
+
currentFollowupType !== "no followup" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
3926
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
|
|
3927
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { className: "text-xs font-medium text-muted-foreground", children: "Schedule by:" }),
|
|
3928
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3929
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
|
|
3930
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3931
|
+
"input",
|
|
3932
|
+
{
|
|
3933
|
+
type: "radio",
|
|
3934
|
+
name: `followup-mode-${fieldId}`,
|
|
3935
|
+
value: "duration",
|
|
3936
|
+
checked: followupMode === "duration",
|
|
3937
|
+
onChange: () => handleModeChange("duration"),
|
|
3938
|
+
className: "h-4 w-4 text-primary focus:ring-primary",
|
|
3939
|
+
disabled
|
|
3940
|
+
}
|
|
3941
|
+
),
|
|
3942
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Duration" })
|
|
3943
|
+
] }),
|
|
3944
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
|
|
3945
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3946
|
+
"input",
|
|
3947
|
+
{
|
|
3948
|
+
type: "radio",
|
|
3949
|
+
name: `followup-mode-${fieldId}`,
|
|
3950
|
+
value: "date",
|
|
3951
|
+
checked: followupMode === "date",
|
|
3952
|
+
onChange: () => handleModeChange("date"),
|
|
3953
|
+
className: "h-4 w-4 text-primary focus:ring-primary",
|
|
3954
|
+
disabled
|
|
3955
|
+
}
|
|
3956
|
+
),
|
|
3957
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: "Date" })
|
|
3958
|
+
] })
|
|
3959
|
+
] })
|
|
3960
|
+
] }),
|
|
3961
|
+
followupMode === "duration" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
3962
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-semibold text-muted-foreground", children: "In" }),
|
|
3963
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3964
|
+
"input",
|
|
3965
|
+
{
|
|
3966
|
+
type: "number",
|
|
3967
|
+
min: currentUnit === "days" ? 0 : 1,
|
|
3968
|
+
value: currentValue,
|
|
3969
|
+
onChange: (e) => handleValueChange(e.target.value),
|
|
3970
|
+
disabled,
|
|
3971
|
+
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",
|
|
3972
|
+
placeholder: "7"
|
|
3973
|
+
}
|
|
3974
|
+
),
|
|
3975
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3976
|
+
Select,
|
|
3977
|
+
{
|
|
3978
|
+
value: currentUnit,
|
|
3979
|
+
onValueChange: (v) => handleUnitChange(v),
|
|
3980
|
+
disabled,
|
|
3981
|
+
children: [
|
|
3982
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectTrigger, { className: "w-[100px] h-8 text-xs", children: /* @__PURE__ */ jsxRuntime.jsx(SelectValue, { className: "text-xs" }) }),
|
|
3983
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectContent, { children: UNIT_OPTIONS.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(SelectItem, { value: opt.value, className: "text-xs", children: opt.label }, opt.value)) })
|
|
3984
|
+
]
|
|
3985
|
+
}
|
|
3986
|
+
)
|
|
3987
|
+
] }),
|
|
3988
|
+
followupMode === "date" && /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open: calendarOpen, onOpenChange: setCalendarOpen, children: [
|
|
3989
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3990
|
+
Button,
|
|
3991
|
+
{
|
|
3992
|
+
type: "button",
|
|
3993
|
+
variant: "outline",
|
|
3994
|
+
className: "w-full justify-start text-left font-normal h-9",
|
|
3995
|
+
disabled,
|
|
3996
|
+
children: [
|
|
3997
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "mr-2 h-4 w-4 text-muted-foreground" }),
|
|
3998
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: hasFollowup && currentDate ? dateFns.format(currentDate, "MMM d, yyyy") : "Select date" })
|
|
3999
|
+
]
|
|
4000
|
+
}
|
|
4001
|
+
) }),
|
|
4002
|
+
/* @__PURE__ */ jsxRuntime.jsx(PopoverContent, { className: "w-auto p-0", align: "start", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
4003
|
+
Calendar,
|
|
4004
|
+
{
|
|
4005
|
+
mode: "single",
|
|
4006
|
+
selected: selectedDate,
|
|
4007
|
+
onSelect: handleDateSelect,
|
|
4008
|
+
disabled: (date) => date < minDate,
|
|
4009
|
+
initialFocus: true
|
|
4010
|
+
}
|
|
4011
|
+
) })
|
|
4012
|
+
] }),
|
|
4013
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
4014
|
+
"One-time appointment",
|
|
4015
|
+
" ",
|
|
4016
|
+
followupMode === "duration" ? `in ${currentValue} ${currentUnit}` : hasFollowup && currentDate ? `on ${dateFns.format(currentDate, "MMM d, yyyy")}` : "date to be selected"
|
|
4017
|
+
] })
|
|
4018
|
+
] }),
|
|
4019
|
+
error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: error })
|
|
4020
|
+
] }) });
|
|
4021
|
+
};
|
|
4022
|
+
var FieldRenderer = ({ fieldId }) => {
|
|
4023
|
+
const { fieldDef, visible } = useField(fieldId);
|
|
4024
|
+
if (!visible || !fieldDef) {
|
|
4025
|
+
return null;
|
|
4026
|
+
}
|
|
4027
|
+
switch (fieldDef.type) {
|
|
4028
|
+
case "text":
|
|
4029
|
+
case "number":
|
|
4030
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TextWidget, { fieldId });
|
|
4031
|
+
case "textarea":
|
|
4032
|
+
case "richtext":
|
|
4033
|
+
return /* @__PURE__ */ jsxRuntime.jsx(TextWidget, { fieldId });
|
|
4034
|
+
case "date":
|
|
4035
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DateWidget, { fieldId });
|
|
4036
|
+
case "datetime":
|
|
4037
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DateTimeWidget, { fieldId });
|
|
4038
|
+
case "select":
|
|
4039
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SelectWidget, { fieldId });
|
|
4040
|
+
case "radio":
|
|
4041
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RadioWidget, { fieldId });
|
|
4042
|
+
case "checkbox":
|
|
4043
|
+
return /* @__PURE__ */ jsxRuntime.jsx(CheckboxWidget, { fieldId });
|
|
4044
|
+
case "multiselect":
|
|
4045
|
+
return /* @__PURE__ */ jsxRuntime.jsx(MultiSelectWidget, { fieldId });
|
|
4046
|
+
case "repeatable":
|
|
4047
|
+
return /* @__PURE__ */ jsxRuntime.jsx(RepeatableWidget, { fieldId });
|
|
4048
|
+
case "image_upload":
|
|
4049
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ImageUploadWidget, { fieldId });
|
|
4050
|
+
case "signature":
|
|
4051
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SignatureUploadWidget, { fieldId });
|
|
4052
|
+
case "editable_table":
|
|
4053
|
+
return /* @__PURE__ */ jsxRuntime.jsx(EditableTableWidget, { fieldId });
|
|
4054
|
+
case "medications":
|
|
4055
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AddMedicationWidget, { fieldId });
|
|
4056
|
+
case "investigations":
|
|
4057
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AddInvestigationWidget, { fieldId });
|
|
4058
|
+
case "procedures":
|
|
4059
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AddProcedureWidget, { fieldId });
|
|
4060
|
+
case "differential_diagnosis":
|
|
4061
|
+
return /* @__PURE__ */ jsxRuntime.jsx(DifferentialDiagnosisWidget, { fieldId });
|
|
4062
|
+
case "vitals":
|
|
4063
|
+
return /* @__PURE__ */ jsxRuntime.jsx(VitalsWidget, { fieldId });
|
|
4064
|
+
case "referral":
|
|
4065
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ReferralWidget, { fieldId });
|
|
4066
|
+
case "followup":
|
|
4067
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FollowupWidget, { fieldId });
|
|
4068
|
+
case "static_text": {
|
|
4069
|
+
const def = fieldDef;
|
|
4070
|
+
const size = def.size ?? "regular";
|
|
4071
|
+
const labelClass = size === "large" ? "text-base" : "text-sm";
|
|
4072
|
+
const descClass = size === "large" ? "text-sm" : "text-xs";
|
|
4073
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `${labelClass} text-muted-foreground`, children: [
|
|
4074
|
+
def.label,
|
|
4075
|
+
def.description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: `mt-1 ${descClass} opacity-90`, children: def.description })
|
|
4076
|
+
] });
|
|
4077
|
+
}
|
|
4078
|
+
default:
|
|
4079
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-red-500", children: [
|
|
4080
|
+
"Unknown field type: ",
|
|
4081
|
+
fieldDef.type
|
|
4082
|
+
] });
|
|
4083
|
+
}
|
|
4084
|
+
};
|
|
4085
|
+
var Section = ({ title, children }) => {
|
|
4086
|
+
const [expanded, setExpanded] = React11.useState(true);
|
|
4087
|
+
const handleToggle = () => setExpanded((prev) => !prev);
|
|
4088
|
+
const contentClassName = cn(
|
|
4089
|
+
"overflow-hidden transition-[height] duration-200 ease-in-out",
|
|
4090
|
+
!expanded && "hidden"
|
|
4091
|
+
);
|
|
4092
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 border rounded-lg bg-white shadow-sm overflow-hidden", children: [
|
|
4093
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4094
|
+
"button",
|
|
4095
|
+
{
|
|
4096
|
+
type: "button",
|
|
4097
|
+
onClick: handleToggle,
|
|
4098
|
+
className: "w-full flex items-center justify-between gap-2 p-4 text-left border-b hover:bg-gray-50/80 transition-colors",
|
|
4099
|
+
children: [
|
|
4100
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-medium", children: title }),
|
|
4101
|
+
/* @__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" }) })
|
|
4102
|
+
]
|
|
4103
|
+
}
|
|
4104
|
+
),
|
|
4105
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: contentClassName, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 pt-4 space-y-4 border-t-0", children }) })
|
|
4106
|
+
] });
|
|
4107
|
+
};
|
|
4108
|
+
var ColumnLayout = ({
|
|
4109
|
+
columns,
|
|
4110
|
+
label,
|
|
4111
|
+
children
|
|
4112
|
+
}) => {
|
|
4113
|
+
const gridCols = Math.max(1, Math.min(columns, 12));
|
|
4114
|
+
const gridClass = `grid gap-4 w-full`;
|
|
4115
|
+
const style = {
|
|
4116
|
+
gridTemplateColumns: `repeat(${gridCols}, minmax(0, 1fr))`
|
|
4117
|
+
};
|
|
4118
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6", children: [
|
|
4119
|
+
label != null && label !== "" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2 text-sm font-medium text-gray-700", children: label }),
|
|
4120
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: gridClass, style, children })
|
|
4121
|
+
] });
|
|
4122
|
+
};
|
|
4123
|
+
var DynamicForm = () => {
|
|
4124
|
+
const store = useFormStore();
|
|
4125
|
+
const schema = store.getSchema();
|
|
4126
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
|
|
4127
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold", children: schema.title }) }),
|
|
4128
|
+
schema.layout.map((node) => {
|
|
4129
|
+
if (node.type === "section") {
|
|
4130
|
+
return /* @__PURE__ */ jsxRuntime.jsx(Section, { title: node.title, children: node.children.map(
|
|
4131
|
+
(child, index) => typeof child === "string" ? /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId: child }, child) : child.type === "column_layout" ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
4132
|
+
ColumnLayout,
|
|
4133
|
+
{
|
|
4134
|
+
columns: child.columns,
|
|
4135
|
+
label: child.label,
|
|
4136
|
+
children: child.children.map((fieldId) => /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }, fieldId))
|
|
4137
|
+
},
|
|
4138
|
+
child.id
|
|
4139
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(React11__namespace.default.Fragment, {}, child.id ?? index)
|
|
4140
|
+
) }, node.id);
|
|
4141
|
+
}
|
|
4142
|
+
if (node.type === "column_layout") {
|
|
4143
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
4144
|
+
ColumnLayout,
|
|
4145
|
+
{
|
|
4146
|
+
columns: node.columns,
|
|
4147
|
+
label: node.label,
|
|
4148
|
+
children: node.children.map((fieldId) => /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }, fieldId))
|
|
4149
|
+
},
|
|
4150
|
+
node.id
|
|
4151
|
+
);
|
|
4152
|
+
}
|
|
4153
|
+
return null;
|
|
4154
|
+
})
|
|
4155
|
+
] });
|
|
4156
|
+
};
|
|
4157
|
+
var SUGGESTION_KEYS = /* @__PURE__ */ new Set(["medications", "investigations", "procedures"]);
|
|
4158
|
+
function shallowEqualKeys(a, b) {
|
|
4159
|
+
const keysA = Object.keys(a);
|
|
4160
|
+
const keysB = Object.keys(b);
|
|
4161
|
+
if (keysA.length !== keysB.length) return false;
|
|
4162
|
+
for (const k of keysA) {
|
|
4163
|
+
if (!keysB.includes(k) || a[k] !== b[k]) return false;
|
|
4164
|
+
}
|
|
4165
|
+
return true;
|
|
4166
|
+
}
|
|
4167
|
+
function normalizeAIMedications(arr) {
|
|
4168
|
+
let parsed = arr;
|
|
4169
|
+
if (typeof arr === "string") {
|
|
4170
|
+
try {
|
|
4171
|
+
parsed = JSON.parse(arr);
|
|
4172
|
+
} catch {
|
|
4173
|
+
return [];
|
|
4174
|
+
}
|
|
4175
|
+
}
|
|
4176
|
+
if (!Array.isArray(parsed)) return [];
|
|
4177
|
+
return parsed.filter((item) => item != null && typeof item === "object" && "id" in item && "brand_name" in item);
|
|
4178
|
+
}
|
|
4179
|
+
function normalizeDifferentialDiagnosis(val) {
|
|
4180
|
+
let parsed = val;
|
|
4181
|
+
if (typeof val === "string") {
|
|
4182
|
+
try {
|
|
4183
|
+
parsed = JSON.parse(val);
|
|
4184
|
+
} catch {
|
|
4185
|
+
return [];
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4189
|
+
const result = [];
|
|
4190
|
+
for (const [category, group] of Object.entries(parsed)) {
|
|
4191
|
+
if (!group || typeof group !== "object") continue;
|
|
4192
|
+
const conditions = Array.isArray(group.conditions) ? group.conditions : [];
|
|
4193
|
+
for (const cond of conditions) {
|
|
4194
|
+
if (!cond || typeof cond !== "object") continue;
|
|
4195
|
+
const {
|
|
4196
|
+
condition,
|
|
4197
|
+
likelihood,
|
|
4198
|
+
severity,
|
|
4199
|
+
treatability,
|
|
4200
|
+
keyFeatures,
|
|
4201
|
+
nextSteps
|
|
4202
|
+
} = cond;
|
|
4203
|
+
if (!condition || !likelihood || !severity || !treatability) continue;
|
|
4204
|
+
result.push({
|
|
4205
|
+
category,
|
|
4206
|
+
condition,
|
|
4207
|
+
likelihood,
|
|
4208
|
+
severity,
|
|
4209
|
+
treatability,
|
|
4210
|
+
keyFeatures: Array.isArray(keyFeatures) ? keyFeatures : void 0,
|
|
4211
|
+
nextSteps: Array.isArray(nextSteps) ? nextSteps : void 0
|
|
4212
|
+
});
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
return result;
|
|
4216
|
+
}
|
|
4217
|
+
if (!Array.isArray(parsed)) return [];
|
|
4218
|
+
return parsed.filter(
|
|
4219
|
+
(item) => item != null && typeof item === "object" && "category" in item && "condition" in item && "likelihood" in item && "severity" in item && "treatability" in item
|
|
4220
|
+
);
|
|
4221
|
+
}
|
|
4222
|
+
var SmartForm = ({
|
|
4223
|
+
aiAnalysisResult
|
|
4224
|
+
}) => {
|
|
4225
|
+
const store = useFormStore();
|
|
4226
|
+
const schema = store.getSchema();
|
|
4227
|
+
const prevAiResultRef = React11.useRef(void 0);
|
|
4228
|
+
const aiSuggestions = React11.useMemo(() => {
|
|
4229
|
+
if (aiAnalysisResult == null) return { medications: [] };
|
|
4230
|
+
const medications = normalizeAIMedications(aiAnalysisResult.medications);
|
|
4231
|
+
return { medications };
|
|
4232
|
+
}, [aiAnalysisResult]);
|
|
4233
|
+
React11.useEffect(() => {
|
|
4234
|
+
if (aiAnalysisResult == null || Object.keys(aiAnalysisResult).length === 0) return;
|
|
4235
|
+
const prev = prevAiResultRef.current;
|
|
4236
|
+
if (prev != null && prev === aiAnalysisResult && shallowEqualKeys(prev, aiAnalysisResult)) return;
|
|
4237
|
+
prevAiResultRef.current = { ...aiAnalysisResult };
|
|
4238
|
+
const DD_KEYS = /* @__PURE__ */ new Set(["differentialDiagnosis", "differentialdiagnosis"]);
|
|
4239
|
+
const toApply = {};
|
|
4240
|
+
for (const [key, val] of Object.entries(aiAnalysisResult)) {
|
|
4241
|
+
if (!SUGGESTION_KEYS.has(key) && !DD_KEYS.has(key)) toApply[key] = val;
|
|
4242
|
+
}
|
|
4243
|
+
const rawDd = aiAnalysisResult.differentialDiagnosis ?? aiAnalysisResult.differentialdiagnosis;
|
|
4244
|
+
if (rawDd != null) {
|
|
4245
|
+
toApply.differentialdiagnosis = normalizeDifferentialDiagnosis(rawDd);
|
|
4246
|
+
}
|
|
4247
|
+
if (Object.keys(toApply).length > 0) store.setValues(toApply, { touch: false });
|
|
4248
|
+
}, [store, aiAnalysisResult]);
|
|
4249
|
+
const fieldIds = [];
|
|
4250
|
+
function collect(child) {
|
|
4251
|
+
if (typeof child === "string") {
|
|
4252
|
+
fieldIds.push(child);
|
|
4253
|
+
} else if (child.type === "column_layout") {
|
|
4254
|
+
child.children.forEach((id) => fieldIds.push(id));
|
|
4255
|
+
}
|
|
4256
|
+
}
|
|
4257
|
+
schema.layout.forEach((node) => {
|
|
4258
|
+
if (node.type === "section") {
|
|
4259
|
+
node.children.forEach(collect);
|
|
4260
|
+
} else if (node.type === "column_layout") {
|
|
4261
|
+
node.children.forEach((id) => fieldIds.push(id));
|
|
4262
|
+
}
|
|
4263
|
+
});
|
|
4264
|
+
return /* @__PURE__ */ jsxRuntime.jsx(AISuggestionsProvider, { value: aiSuggestions, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-4 gap-2 w-full items-start", children: [
|
|
4265
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-1 flex flex-col gap-2", children: fieldIds.map((fieldId) => {
|
|
4266
|
+
const fieldDef = store.getFieldDef(fieldId);
|
|
4267
|
+
if (!fieldDef) return null;
|
|
4268
|
+
const position = fieldDef.position ?? "center";
|
|
4269
|
+
if (position !== "left") return null;
|
|
4270
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }, fieldId);
|
|
4271
|
+
}) }),
|
|
4272
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-2 grid grid-cols-2 gap-x-2 space-y-2", children: fieldIds.map((fieldId) => {
|
|
4273
|
+
const fieldDef = store.getFieldDef(fieldId);
|
|
4274
|
+
if (!fieldDef) return null;
|
|
4275
|
+
const position = fieldDef.position ?? "center";
|
|
4276
|
+
if (position !== "center") return null;
|
|
4277
|
+
const colSpanRaw = fieldDef.colSpan ?? 1;
|
|
4278
|
+
const colSpan = colSpanRaw === 2 ? 2 : 1;
|
|
4279
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: colSpan === 2 ? "col-span-2" : "col-span-1", children: /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }) }, fieldId);
|
|
4280
|
+
}) }),
|
|
4281
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-1 flex flex-col gap-2", children: fieldIds.map((fieldId) => {
|
|
4282
|
+
const fieldDef = store.getFieldDef(fieldId);
|
|
4283
|
+
if (!fieldDef) return null;
|
|
4284
|
+
const position = fieldDef.position ?? "center";
|
|
4285
|
+
if (position !== "right") return null;
|
|
4286
|
+
return /* @__PURE__ */ jsxRuntime.jsx(FieldRenderer, { fieldId }, fieldId);
|
|
4287
|
+
}) })
|
|
4288
|
+
] }) });
|
|
2408
4289
|
};
|
|
2409
4290
|
var ReadOnlyText = ({ fieldDef, value }) => {
|
|
2410
4291
|
if (!fieldDef) return null;
|
|
@@ -2932,6 +4813,9 @@ function createUploadHandler(options) {
|
|
|
2932
4813
|
};
|
|
2933
4814
|
}
|
|
2934
4815
|
|
|
4816
|
+
exports.AddInvestigationField = AddInvestigationField;
|
|
4817
|
+
exports.AddMedicationField = AddMedicationField;
|
|
4818
|
+
exports.DifferentialDiagnosis = DifferentialDiagnosis;
|
|
2935
4819
|
exports.DynamicForm = DynamicForm;
|
|
2936
4820
|
exports.EMAIL_REGEX = EMAIL_REGEX;
|
|
2937
4821
|
exports.FieldRenderer = FieldRenderer;
|
|
@@ -2944,9 +4828,11 @@ exports.ReadOnlyImageUpload = ReadOnlyImageUpload;
|
|
|
2944
4828
|
exports.ReadOnlySignature = ReadOnlySignature;
|
|
2945
4829
|
exports.ReadOnlyTable = ReadOnlyTable;
|
|
2946
4830
|
exports.SignatureUploadWidget = SignatureUploadWidget;
|
|
4831
|
+
exports.SmartForm = SmartForm;
|
|
2947
4832
|
exports.createUploadHandler = createUploadHandler;
|
|
2948
4833
|
exports.evaluateRules = evaluateRules;
|
|
2949
4834
|
exports.useField = useField;
|
|
4835
|
+
exports.useFieldHandlers = useFieldHandlers;
|
|
2950
4836
|
exports.useForm = useForm;
|
|
2951
4837
|
exports.useFormStore = useFormStore;
|
|
2952
4838
|
exports.validateField = validateField;
|