formanitor 0.0.20 → 0.0.22

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.mjs CHANGED
@@ -16,6 +16,7 @@ import { useReactTable, getCoreRowModel, flexRender } from '@tanstack/react-tabl
16
16
  import * as RadioGroupPrimitive from '@radix-ui/react-radio-group';
17
17
  import * as DialogPrimitive from '@radix-ui/react-dialog';
18
18
  import nlp from 'compromise';
19
+ import * as SliderPrimitive from '@radix-ui/react-slider';
19
20
 
20
21
  // src/core/validate.ts
21
22
  var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
@@ -2561,7 +2562,7 @@ function createCustomMedication(name, customId) {
2561
2562
  brand_name: name.trim(),
2562
2563
  generic_name: "",
2563
2564
  frequency: "custom",
2564
- is_custom: true,
2565
+ is_custom: false,
2565
2566
  duration_value: "3",
2566
2567
  duration_unit: "days",
2567
2568
  before_after: "AFTER FOOD",
@@ -6369,6 +6370,389 @@ var OphthalDiagnosisWidget = ({ fieldId }) => {
6369
6370
  ] })
6370
6371
  ] });
6371
6372
  };
6373
+ var Slider = React11.forwardRef(({ className, trackClassName, thumbClassName, ...props }, ref) => /* @__PURE__ */ jsxs(
6374
+ SliderPrimitive.Root,
6375
+ {
6376
+ ref,
6377
+ className: cn(
6378
+ "relative flex w-full touch-none select-none items-center",
6379
+ className
6380
+ ),
6381
+ ...props,
6382
+ children: [
6383
+ /* @__PURE__ */ jsx(
6384
+ SliderPrimitive.Track,
6385
+ {
6386
+ className: cn(
6387
+ "relative h-2 w-full grow overflow-hidden rounded-full bg-gray-200",
6388
+ trackClassName
6389
+ ),
6390
+ children: /* @__PURE__ */ jsx(SliderPrimitive.Range, { className: "absolute h-full bg-primary" })
6391
+ }
6392
+ ),
6393
+ /* @__PURE__ */ jsx(
6394
+ SliderPrimitive.Thumb,
6395
+ {
6396
+ className: cn(
6397
+ "block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
6398
+ thumbClassName
6399
+ )
6400
+ }
6401
+ )
6402
+ ]
6403
+ }
6404
+ ));
6405
+ Slider.displayName = SliderPrimitive.Root.displayName;
6406
+ var CONCERNS = [
6407
+ { id: "knee", label: "Knee" },
6408
+ { id: "shoulder", label: "Shoulder" },
6409
+ { id: "spine", label: "Spine" },
6410
+ { id: "musculoskeletal_pain", label: "Musculoskeletal pain" },
6411
+ { id: "hip", label: "Hip" },
6412
+ { id: "trauma", label: "Trauma" },
6413
+ { id: "other", label: "Other" }
6414
+ ];
6415
+ var SIDE_PARTS = ["knee", "shoulder", "hip"];
6416
+ var REMINDERS = {
6417
+ knee: [
6418
+ "Inspect for swelling, deformity, scars.",
6419
+ "Palpate joint line, patella, and ligaments.",
6420
+ "Assess active/passive flexion and extension.",
6421
+ "Check stability (ACL/PCL/MCL/LCL) if appropriate."
6422
+ ],
6423
+ shoulder: [
6424
+ "Inspect for asymmetry, wasting, deformity.",
6425
+ "Palpate AC joint, bicipital groove, cuff insertions.",
6426
+ "Assess active/passive ROM (flex/abd/ER/IR).",
6427
+ "Consider impingement/cuff tests if indicated."
6428
+ ],
6429
+ hip: [
6430
+ "Inspect gait and standing alignment.",
6431
+ "Palpate greater trochanter and groin tenderness.",
6432
+ "Assess ROM (flex/ext/abd/add/IR/ER).",
6433
+ "Consider special tests (FABER/FADIR) if indicated."
6434
+ ],
6435
+ spine: [
6436
+ "Inspect posture and spinal alignment.",
6437
+ "Palpate spinous/paraspinal tenderness.",
6438
+ "Assess ROM (flex/ext/lat bend/rotation).",
6439
+ "Document neuro screen if radicular symptoms."
6440
+ ],
6441
+ musculoskeletal_pain: [
6442
+ "Localize pain and radiation; identify triggers/relievers.",
6443
+ "Inspect for swelling, erythema, deformity.",
6444
+ "Assess function and weight-bearing/ADLs impact."
6445
+ ],
6446
+ trauma: [
6447
+ "Inspect for deformity, wounds, bruising.",
6448
+ "Check distal neurovascular status.",
6449
+ "Assess joint stability only if safe."
6450
+ ],
6451
+ other: [
6452
+ "Clarify the concern and affected region.",
6453
+ "Document red flags (night pain, fever, weight loss)."
6454
+ ]
6455
+ };
6456
+ var defaultValue = () => ({
6457
+ concerns: [],
6458
+ sides: {},
6459
+ painScore: 0,
6460
+ physicalExam: { inspection: "", palpation: "", rangeOfMotion: "" },
6461
+ reminders: { dismissed: false, position: { top: 96, right: 24 } }
6462
+ });
6463
+ var OrthopedicExamWidget = ({ fieldId }) => {
6464
+ const { fieldDef, value, setValue, setTouched, error, touched, disabled } = useField(fieldId);
6465
+ const { state } = useForm();
6466
+ const showError = !!error && (touched || state.submitAttempted);
6467
+ const safeValue = useMemo(() => {
6468
+ if (!value || typeof value !== "object") return defaultValue();
6469
+ const v = value;
6470
+ const concerns = Array.isArray(v.concerns) ? v.concerns : [];
6471
+ const painScore = typeof v.painScore === "number" ? v.painScore : 0;
6472
+ const physicalExam = v.physicalExam ?? defaultValue().physicalExam;
6473
+ const reminders = v.reminders ?? defaultValue().reminders;
6474
+ return {
6475
+ concerns,
6476
+ sides: v.sides ?? {},
6477
+ painScore: Math.min(10, Math.max(0, Math.round(painScore))),
6478
+ physicalExam: {
6479
+ inspection: physicalExam.inspection ?? "",
6480
+ palpation: physicalExam.palpation ?? "",
6481
+ rangeOfMotion: physicalExam.rangeOfMotion ?? ""
6482
+ },
6483
+ reminders: {
6484
+ dismissed: !!reminders.dismissed,
6485
+ position: reminders.position ?? { top: 96, right: 24 }
6486
+ }
6487
+ };
6488
+ }, [value]);
6489
+ const [panelOpen, setPanelOpen] = useState(false);
6490
+ const draggingRef = useRef(null);
6491
+ const prevConcernsLengthRef = useRef(safeValue.concerns.length);
6492
+ const reminderSections = useMemo(() => {
6493
+ return safeValue.concerns.map((id) => ({
6494
+ id,
6495
+ label: CONCERNS.find((c) => c.id === id)?.label ?? id,
6496
+ points: REMINDERS[id] ?? []
6497
+ })).filter((s) => s.points.length > 0);
6498
+ }, [safeValue.concerns]);
6499
+ const hasReminders = reminderSections.length > 0;
6500
+ useEffect(() => {
6501
+ const prevLen = prevConcernsLengthRef.current;
6502
+ const currLen = safeValue.concerns.length;
6503
+ prevConcernsLengthRef.current = currLen;
6504
+ if (currLen === 0) {
6505
+ if (safeValue.reminders.dismissed) {
6506
+ setValue({ ...safeValue, reminders: { ...safeValue.reminders, dismissed: false } });
6507
+ }
6508
+ setPanelOpen(false);
6509
+ return;
6510
+ }
6511
+ if (prevLen === 0 && currLen > 0 && !safeValue.reminders.dismissed) {
6512
+ setPanelOpen(true);
6513
+ }
6514
+ }, [safeValue, setValue]);
6515
+ useEffect(() => {
6516
+ const onMove = (e) => {
6517
+ if (!draggingRef.current) return;
6518
+ const { startX, startY, startTop, startRight } = draggingRef.current;
6519
+ const dx = e.clientX - startX;
6520
+ const dy = e.clientY - startY;
6521
+ const nextPos = { top: startTop + dy, right: startRight - dx };
6522
+ setValue({
6523
+ ...safeValue,
6524
+ reminders: {
6525
+ ...safeValue.reminders,
6526
+ position: nextPos
6527
+ }
6528
+ });
6529
+ };
6530
+ const onUp = () => {
6531
+ draggingRef.current = null;
6532
+ };
6533
+ window.addEventListener("mousemove", onMove);
6534
+ window.addEventListener("mouseup", onUp);
6535
+ return () => {
6536
+ window.removeEventListener("mousemove", onMove);
6537
+ window.removeEventListener("mouseup", onUp);
6538
+ };
6539
+ }, [safeValue, setValue]);
6540
+ const update = (next) => {
6541
+ setValue(next);
6542
+ setTouched();
6543
+ };
6544
+ const toggleConcern = (id) => {
6545
+ const selected = safeValue.concerns.includes(id);
6546
+ const nextConcerns = selected ? safeValue.concerns.filter((c) => c !== id) : [...safeValue.concerns, id];
6547
+ const nextSides = { ...safeValue.sides };
6548
+ if (!nextConcerns.includes("knee")) delete nextSides.knee;
6549
+ if (!nextConcerns.includes("shoulder")) delete nextSides.shoulder;
6550
+ if (!nextConcerns.includes("hip")) delete nextSides.hip;
6551
+ update({ ...safeValue, concerns: nextConcerns, sides: nextSides });
6552
+ };
6553
+ const toggleSide = (part, side) => {
6554
+ const current = safeValue.sides?.[part];
6555
+ const nextSides = { ...safeValue.sides };
6556
+ if (current === side) {
6557
+ delete nextSides[part];
6558
+ } else {
6559
+ nextSides[part] = side;
6560
+ }
6561
+ update({ ...safeValue, sides: nextSides });
6562
+ };
6563
+ const setPainScore = (score) => {
6564
+ update({ ...safeValue, painScore: Math.min(10, Math.max(0, Math.round(score))) });
6565
+ };
6566
+ const setPhysicalExam = (key, text) => {
6567
+ update({
6568
+ ...safeValue,
6569
+ physicalExam: {
6570
+ ...safeValue.physicalExam,
6571
+ [key]: text
6572
+ }
6573
+ });
6574
+ };
6575
+ const showSidePanel = SIDE_PARTS.some((p) => safeValue.concerns.includes(p));
6576
+ if (!fieldDef) return null;
6577
+ return /* @__PURE__ */ jsxs("div", { className: `space-y-4 relative ${disabled ? "opacity-60 pointer-events-none" : ""}`, children: [
6578
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs(Label, { className: "text-sm font-medium", children: [
6579
+ fieldDef.label,
6580
+ fieldDef.required ? /* @__PURE__ */ jsx("span", { className: "text-red-500 ml-1", children: "*" }) : null
6581
+ ] }) }),
6582
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
6583
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-semibold text-muted-foreground", children: "Concern" }),
6584
+ /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2", children: CONCERNS.map((c) => {
6585
+ const active = safeValue.concerns.includes(c.id);
6586
+ return /* @__PURE__ */ jsx(
6587
+ Button,
6588
+ {
6589
+ type: "button",
6590
+ size: "sm",
6591
+ variant: active ? "default" : "outline",
6592
+ className: "h-6 rounded-full px-3 py-1 text-xs",
6593
+ onClick: () => toggleConcern(c.id),
6594
+ children: c.label
6595
+ },
6596
+ c.id
6597
+ );
6598
+ }) })
6599
+ ] }),
6600
+ showSidePanel ? /* @__PURE__ */ jsxs(Card, { className: "p-3 space-y-3 border-dashed", children: [
6601
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-semibold text-muted-foreground", children: "Side (Left / Right / Both)" }),
6602
+ SIDE_PARTS.map((part) => {
6603
+ if (!safeValue.concerns.includes(part)) return null;
6604
+ const current = safeValue.sides?.[part];
6605
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
6606
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium capitalize", children: part }),
6607
+ /* @__PURE__ */ jsx("div", { className: "flex gap-1", children: ["left", "right", "both"].map((s) => {
6608
+ const active = current === s;
6609
+ return /* @__PURE__ */ jsx(
6610
+ Button,
6611
+ {
6612
+ type: "button",
6613
+ size: "sm",
6614
+ variant: active ? "default" : "outline",
6615
+ className: "text-[11px] px-2 py-1 h-7",
6616
+ onClick: () => toggleSide(part, s),
6617
+ children: s.charAt(0).toUpperCase() + s.slice(1)
6618
+ },
6619
+ s
6620
+ );
6621
+ }) })
6622
+ ] }, part);
6623
+ })
6624
+ ] }) : null,
6625
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
6626
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
6627
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-muted-foreground", children: "Pain score" }),
6628
+ /* @__PURE__ */ jsxs("span", { className: "text-xs font-mono", children: [
6629
+ safeValue.painScore,
6630
+ " / 10"
6631
+ ] })
6632
+ ] }),
6633
+ /* @__PURE__ */ jsx(
6634
+ Slider,
6635
+ {
6636
+ min: 0,
6637
+ max: 10,
6638
+ step: 1,
6639
+ value: [safeValue.painScore],
6640
+ onValueChange: (v) => setPainScore(v[0] ?? 0),
6641
+ trackClassName: "!bg-gray-400",
6642
+ thumbClassName: "!bg-white !border-white"
6643
+ }
6644
+ )
6645
+ ] }),
6646
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-3", children: [
6647
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
6648
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Inspection" }),
6649
+ /* @__PURE__ */ jsx(
6650
+ Textarea,
6651
+ {
6652
+ rows: 4,
6653
+ value: safeValue.physicalExam.inspection,
6654
+ onChange: (e) => setPhysicalExam("inspection", e.target.value),
6655
+ placeholder: "Gait, posture, deformity, swelling, scars...",
6656
+ className: "text-xs"
6657
+ }
6658
+ )
6659
+ ] }),
6660
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
6661
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Palpation" }),
6662
+ /* @__PURE__ */ jsx(
6663
+ Textarea,
6664
+ {
6665
+ rows: 4,
6666
+ value: safeValue.physicalExam.palpation,
6667
+ onChange: (e) => setPhysicalExam("palpation", e.target.value),
6668
+ placeholder: "Tenderness, warmth, crepitus, effusion...",
6669
+ className: "text-xs"
6670
+ }
6671
+ )
6672
+ ] }),
6673
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
6674
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: "Range of motion" }),
6675
+ /* @__PURE__ */ jsx(
6676
+ Textarea,
6677
+ {
6678
+ rows: 4,
6679
+ value: safeValue.physicalExam.rangeOfMotion,
6680
+ onChange: (e) => setPhysicalExam("rangeOfMotion", e.target.value),
6681
+ placeholder: "Flexion/extension, rotation, abduction/adduction...",
6682
+ className: "text-xs"
6683
+ }
6684
+ )
6685
+ ] })
6686
+ ] }),
6687
+ showError ? /* @__PURE__ */ jsx("p", { className: "text-xs text-red-500", children: error }) : null,
6688
+ safeValue.concerns.length > 0 && hasReminders ? panelOpen ? /* @__PURE__ */ jsx(
6689
+ "div",
6690
+ {
6691
+ style: {
6692
+ position: "fixed",
6693
+ top: safeValue.reminders.position?.top ?? 96,
6694
+ right: safeValue.reminders.position?.right ?? 24,
6695
+ zIndex: 40
6696
+ },
6697
+ children: /* @__PURE__ */ jsxs(Card, { className: "w-80 shadow-lg border-primary/30 bg-background/95 backdrop-blur-sm", children: [
6698
+ /* @__PURE__ */ jsxs(
6699
+ "div",
6700
+ {
6701
+ className: "flex items-center justify-between px-3 py-2 border-b cursor-move bg-primary/5",
6702
+ onMouseDown: (e) => {
6703
+ e.preventDefault();
6704
+ draggingRef.current = {
6705
+ startX: e.clientX,
6706
+ startY: e.clientY,
6707
+ startTop: safeValue.reminders.position?.top ?? 96,
6708
+ startRight: safeValue.reminders.position?.right ?? 24
6709
+ };
6710
+ },
6711
+ children: [
6712
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-semibold text-primary", children: "Exam reminders" }),
6713
+ /* @__PURE__ */ jsx(
6714
+ "button",
6715
+ {
6716
+ type: "button",
6717
+ className: "text-[10px] uppercase tracking-wide text-muted-foreground hover:text-destructive",
6718
+ onMouseDown: (e) => e.stopPropagation(),
6719
+ onClick: () => setPanelOpen(false),
6720
+ children: "Close"
6721
+ }
6722
+ )
6723
+ ]
6724
+ }
6725
+ ),
6726
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-2 max-h-64 overflow-auto space-y-3", children: reminderSections.map(({ id, label, points }) => /* @__PURE__ */ jsxs("div", { children: [
6727
+ /* @__PURE__ */ jsx("div", { className: "text-xs font-semibold text-primary mb-1", children: label }),
6728
+ /* @__PURE__ */ jsx("ul", { className: "list-disc list-inside space-y-1 text-[11px] text-muted-foreground", children: points.map((p) => /* @__PURE__ */ jsx("li", { children: p }, p)) })
6729
+ ] }, id)) })
6730
+ ] })
6731
+ }
6732
+ ) : /* @__PURE__ */ jsx(
6733
+ "div",
6734
+ {
6735
+ style: {
6736
+ position: "fixed",
6737
+ top: safeValue.reminders.position?.top ?? 96,
6738
+ right: safeValue.reminders.position?.right ?? 24,
6739
+ zIndex: 40
6740
+ },
6741
+ children: /* @__PURE__ */ jsx(
6742
+ Button,
6743
+ {
6744
+ type: "button",
6745
+ variant: "outline",
6746
+ size: "sm",
6747
+ className: "rounded-full px-3 py-1 text-xs border-primary/30 bg-background/95 backdrop-blur-sm shadow-md",
6748
+ onClick: () => setPanelOpen(true),
6749
+ children: "Exam reminders"
6750
+ }
6751
+ )
6752
+ }
6753
+ ) : null
6754
+ ] });
6755
+ };
6372
6756
  var FieldRenderer = ({ fieldId }) => {
6373
6757
  const { fieldDef, visible } = useField(fieldId);
6374
6758
  if (!visible || !fieldDef) {
@@ -6425,6 +6809,8 @@ var FieldRenderer = ({ fieldId }) => {
6425
6809
  return /* @__PURE__ */ jsx(OphthalmologyWidget, { fieldId });
6426
6810
  case "ophthal_diagnosis":
6427
6811
  return /* @__PURE__ */ jsx(OphthalDiagnosisWidget, { fieldId });
6812
+ case "orthopedic_exam":
6813
+ return /* @__PURE__ */ jsx(OrthopedicExamWidget, { fieldId });
6428
6814
  case "static_text": {
6429
6815
  const def = fieldDef;
6430
6816
  const size = def.size ?? "regular";