formanitor 0.0.12 → 0.0.15
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 +813 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +60 -2
- package/dist/index.d.ts +60 -2
- package/dist/index.mjs +810 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -16,6 +16,9 @@ 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
18
|
var DialogPrimitive = require('@radix-ui/react-dialog');
|
|
19
|
+
var nlp = require('compromise');
|
|
20
|
+
|
|
21
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
19
22
|
|
|
20
23
|
function _interopNamespace(e) {
|
|
21
24
|
if (e && e.__esModule) return e;
|
|
@@ -42,6 +45,7 @@ var CheckboxPrimitive__namespace = /*#__PURE__*/_interopNamespace(CheckboxPrimit
|
|
|
42
45
|
var PopoverPrimitive__namespace = /*#__PURE__*/_interopNamespace(PopoverPrimitive);
|
|
43
46
|
var RadioGroupPrimitive__namespace = /*#__PURE__*/_interopNamespace(RadioGroupPrimitive);
|
|
44
47
|
var DialogPrimitive__namespace = /*#__PURE__*/_interopNamespace(DialogPrimitive);
|
|
48
|
+
var nlp__default = /*#__PURE__*/_interopDefault(nlp);
|
|
45
49
|
|
|
46
50
|
// src/core/validate.ts
|
|
47
51
|
var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
@@ -54,6 +58,11 @@ function validateField(value, field) {
|
|
|
54
58
|
if (Array.isArray(value) && value.length === 0) {
|
|
55
59
|
return "This field is required";
|
|
56
60
|
}
|
|
61
|
+
if (field.type === "smart_textarea" && typeof value === "object" && !Array.isArray(value)) {
|
|
62
|
+
if (!value.text || value.text.trim() === "") {
|
|
63
|
+
return "This field is required";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
57
66
|
}
|
|
58
67
|
if (value === null || value === void 0 || value === "") {
|
|
59
68
|
return null;
|
|
@@ -241,6 +250,7 @@ var FormStore = class {
|
|
|
241
250
|
} else {
|
|
242
251
|
if (field.type === "repeatable") values[field.id] = [];
|
|
243
252
|
else if (field.type === "checkbox") values[field.id] = [];
|
|
253
|
+
else if (field.type === "smart_textarea") values[field.id] = { text: "", extractedKeywords: [] };
|
|
244
254
|
else values[field.id] = null;
|
|
245
255
|
}
|
|
246
256
|
});
|
|
@@ -388,6 +398,15 @@ var FormStore = class {
|
|
|
388
398
|
getSerializedValuesForSubmission() {
|
|
389
399
|
return this.serializeValuesForSubmission(this.state.values);
|
|
390
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Raw form values as stored internally, without any serialization or
|
|
403
|
+
* transformation. Useful when consumers need access to richer structures
|
|
404
|
+
* (e.g. smart_textarea keyword metadata) alongside the flattened values
|
|
405
|
+
* returned by getSerializedValuesForSubmission.
|
|
406
|
+
*/
|
|
407
|
+
getRawValues() {
|
|
408
|
+
return { ...this.state.values };
|
|
409
|
+
}
|
|
391
410
|
serializeValuesForSubmission(values) {
|
|
392
411
|
const result = { ...values };
|
|
393
412
|
this.schema.fields.forEach((field) => {
|
|
@@ -395,6 +414,11 @@ var FormStore = class {
|
|
|
395
414
|
delete result[field.id];
|
|
396
415
|
return;
|
|
397
416
|
}
|
|
417
|
+
if (field.type === "smart_textarea") {
|
|
418
|
+
const raw = values[field.id];
|
|
419
|
+
result[field.id] = raw?.text ?? null;
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
398
422
|
if (field.type === "image_upload" || field.type === "signature") {
|
|
399
423
|
const raw = values[field.id];
|
|
400
424
|
if (raw !== void 0 && raw !== null && raw !== "") {
|
|
@@ -685,6 +709,7 @@ function useForm() {
|
|
|
685
709
|
availableTransitions: store.getAvailableTransitions(),
|
|
686
710
|
hasPersistence: store.hasPersistence(),
|
|
687
711
|
getSerializedValues: store.getSerializedValuesForSubmission.bind(store),
|
|
712
|
+
getRawValues: store.getRawValues.bind(store),
|
|
688
713
|
markSubmitAttempted: store.markSubmitAttempted.bind(store),
|
|
689
714
|
flushPendingUploads: store.flushPendingUploads.bind(store)
|
|
690
715
|
};
|
|
@@ -4019,6 +4044,788 @@ var FollowupWidget = ({ fieldId }) => {
|
|
|
4019
4044
|
error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-destructive", children: error })
|
|
4020
4045
|
] }) });
|
|
4021
4046
|
};
|
|
4047
|
+
function useSmartKeywords(text, onSearch, debounceDelay = 1e3) {
|
|
4048
|
+
console.log("[useSmartKeywords] render", {
|
|
4049
|
+
textLength: text?.length ?? 0,
|
|
4050
|
+
hasOnSearch: typeof onSearch === "function",
|
|
4051
|
+
debounceDelay
|
|
4052
|
+
});
|
|
4053
|
+
const [extractedNouns, setExtractedNouns] = React11.useState([]);
|
|
4054
|
+
const [matchedConditions, setMatchedConditions] = React11.useState([]);
|
|
4055
|
+
const [isProcessing, setIsProcessing] = React11.useState(false);
|
|
4056
|
+
const [error, setError] = React11.useState(null);
|
|
4057
|
+
const timerRef = React11.useRef(null);
|
|
4058
|
+
const abortRef = React11.useRef(0);
|
|
4059
|
+
const processText = React11.useCallback(
|
|
4060
|
+
async (input, runId) => {
|
|
4061
|
+
if (!input || input.trim().length === 0) {
|
|
4062
|
+
console.log("[useSmartKeywords] skip empty input", {
|
|
4063
|
+
runId,
|
|
4064
|
+
inputLength: input?.length ?? 0
|
|
4065
|
+
});
|
|
4066
|
+
setExtractedNouns([]);
|
|
4067
|
+
setMatchedConditions([]);
|
|
4068
|
+
setIsProcessing(false);
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
4071
|
+
console.log("[useSmartKeywords] processText start", {
|
|
4072
|
+
runId,
|
|
4073
|
+
inputLength: input.length
|
|
4074
|
+
});
|
|
4075
|
+
setIsProcessing(true);
|
|
4076
|
+
setError(null);
|
|
4077
|
+
try {
|
|
4078
|
+
const nouns = extractNouns(input);
|
|
4079
|
+
const uniqueNouns = Array.from(new Set(nouns));
|
|
4080
|
+
console.log("[useSmartKeywords] extracted nouns", {
|
|
4081
|
+
runId,
|
|
4082
|
+
nouns,
|
|
4083
|
+
uniqueNouns
|
|
4084
|
+
});
|
|
4085
|
+
if (runId !== abortRef.current) return;
|
|
4086
|
+
setExtractedNouns(uniqueNouns);
|
|
4087
|
+
if (uniqueNouns.length === 0) {
|
|
4088
|
+
console.log("[useSmartKeywords] no nouns, skipping search", { runId });
|
|
4089
|
+
setMatchedConditions([]);
|
|
4090
|
+
setIsProcessing(false);
|
|
4091
|
+
return;
|
|
4092
|
+
}
|
|
4093
|
+
const searchResults = await Promise.all(
|
|
4094
|
+
uniqueNouns.map(async (noun) => {
|
|
4095
|
+
const normalised = noun.replace(/\s+/g, " ").replace(/^[\s-]+/, "").trim();
|
|
4096
|
+
try {
|
|
4097
|
+
console.log("[useSmartKeywords] onSearch call", { runId, noun, normalised });
|
|
4098
|
+
const results = await onSearch(normalised);
|
|
4099
|
+
console.log("[useSmartKeywords] onSearch result", {
|
|
4100
|
+
runId,
|
|
4101
|
+
noun,
|
|
4102
|
+
normalised,
|
|
4103
|
+
count: Array.isArray(results) ? results.length : 0
|
|
4104
|
+
});
|
|
4105
|
+
const first = Array.isArray(results) && results.length > 0 ? [results[0]] : [];
|
|
4106
|
+
return { keyword: normalised, matches: first };
|
|
4107
|
+
} catch (err) {
|
|
4108
|
+
console.error("[useSmartKeywords] onSearch error", { runId, noun, err });
|
|
4109
|
+
return { keyword: normalised || noun, matches: [] };
|
|
4110
|
+
}
|
|
4111
|
+
})
|
|
4112
|
+
);
|
|
4113
|
+
if (runId !== abortRef.current) return;
|
|
4114
|
+
const allMatches = searchResults.flatMap(
|
|
4115
|
+
({ keyword, matches }) => matches.map((record) => ({
|
|
4116
|
+
extractedKeyword: keyword,
|
|
4117
|
+
matchedCondition: {
|
|
4118
|
+
id: record.id,
|
|
4119
|
+
name: record.name,
|
|
4120
|
+
created_at: record.created_at,
|
|
4121
|
+
updated_at: record.updated_at
|
|
4122
|
+
}
|
|
4123
|
+
}))
|
|
4124
|
+
);
|
|
4125
|
+
console.log("[useSmartKeywords] allMatches computed", {
|
|
4126
|
+
runId,
|
|
4127
|
+
matchesCount: allMatches.length
|
|
4128
|
+
});
|
|
4129
|
+
setMatchedConditions(allMatches);
|
|
4130
|
+
} catch (err) {
|
|
4131
|
+
if (runId !== abortRef.current) return;
|
|
4132
|
+
console.error("[useSmartKeywords] processText error", { runId, err });
|
|
4133
|
+
setError(err instanceof Error ? err.message : "Keyword extraction failed");
|
|
4134
|
+
} finally {
|
|
4135
|
+
if (runId === abortRef.current) {
|
|
4136
|
+
console.log("[useSmartKeywords] processText end", { runId });
|
|
4137
|
+
setIsProcessing(false);
|
|
4138
|
+
}
|
|
4139
|
+
}
|
|
4140
|
+
},
|
|
4141
|
+
[onSearch]
|
|
4142
|
+
);
|
|
4143
|
+
React11.useEffect(() => {
|
|
4144
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
4145
|
+
const runId = ++abortRef.current;
|
|
4146
|
+
console.log("[useSmartKeywords] schedule debounce", {
|
|
4147
|
+
runId,
|
|
4148
|
+
debounceDelay,
|
|
4149
|
+
textLength: text?.length ?? 0
|
|
4150
|
+
});
|
|
4151
|
+
timerRef.current = setTimeout(() => {
|
|
4152
|
+
console.log("[useSmartKeywords] debounce fired", { runId });
|
|
4153
|
+
processText(text, runId);
|
|
4154
|
+
}, debounceDelay);
|
|
4155
|
+
return () => {
|
|
4156
|
+
if (timerRef.current) clearTimeout(timerRef.current);
|
|
4157
|
+
};
|
|
4158
|
+
}, [text, debounceDelay, processText]);
|
|
4159
|
+
return { extractedNouns, matchedConditions, isProcessing, error };
|
|
4160
|
+
}
|
|
4161
|
+
function extractNouns(text) {
|
|
4162
|
+
const doc = nlp__default.default(text);
|
|
4163
|
+
const rawNouns = doc.nouns().out("array");
|
|
4164
|
+
const cleaned = rawNouns.map((n) => n.replace(/[^\w\s-]/g, "").trim().toLowerCase()).filter((n) => n.length > 2);
|
|
4165
|
+
console.log("[useSmartKeywords] extractNouns", {
|
|
4166
|
+
inputLength: text?.length ?? 0,
|
|
4167
|
+
rawNouns,
|
|
4168
|
+
cleaned
|
|
4169
|
+
});
|
|
4170
|
+
return cleaned;
|
|
4171
|
+
}
|
|
4172
|
+
var SmartTextareaWidget = ({ fieldId }) => {
|
|
4173
|
+
const { fieldDef, value, setValue, setTouched, error, disabled, touched } = useField(fieldId);
|
|
4174
|
+
const { state } = useForm();
|
|
4175
|
+
const handler = useFieldHandlers(fieldId);
|
|
4176
|
+
const def = fieldDef;
|
|
4177
|
+
const structured = value;
|
|
4178
|
+
const textValue = structured?.text ?? "";
|
|
4179
|
+
const showError = !!error && (touched || state.submitAttempted);
|
|
4180
|
+
const onSearch = React11.useCallback(
|
|
4181
|
+
(query) => handler.onSearch(query),
|
|
4182
|
+
[handler]
|
|
4183
|
+
);
|
|
4184
|
+
const { matchedConditions, isProcessing } = useSmartKeywords(
|
|
4185
|
+
textValue,
|
|
4186
|
+
onSearch,
|
|
4187
|
+
def?.debounceDelay ?? 1e3
|
|
4188
|
+
);
|
|
4189
|
+
const currentKeywords = structured?.extractedKeywords ?? [];
|
|
4190
|
+
const nextKeywords = React11.useMemo(() => {
|
|
4191
|
+
if (matchedConditions.length === 0 && currentKeywords.length === 0) return currentKeywords;
|
|
4192
|
+
if (matchedConditions.length === currentKeywords.length && matchedConditions.every(
|
|
4193
|
+
(m, i) => m.matchedCondition.id === currentKeywords[i]?.matchedCondition.id && m.extractedKeyword === currentKeywords[i]?.extractedKeyword
|
|
4194
|
+
)) {
|
|
4195
|
+
return currentKeywords;
|
|
4196
|
+
}
|
|
4197
|
+
return matchedConditions;
|
|
4198
|
+
}, [matchedConditions, currentKeywords]);
|
|
4199
|
+
const writeValue = React11.useCallback(
|
|
4200
|
+
(text, keywords) => {
|
|
4201
|
+
const next = { text, extractedKeywords: keywords };
|
|
4202
|
+
setValue(next);
|
|
4203
|
+
},
|
|
4204
|
+
[setValue]
|
|
4205
|
+
);
|
|
4206
|
+
React11__namespace.default.useEffect(() => {
|
|
4207
|
+
if (nextKeywords !== currentKeywords) {
|
|
4208
|
+
writeValue(textValue, nextKeywords);
|
|
4209
|
+
}
|
|
4210
|
+
}, [nextKeywords]);
|
|
4211
|
+
const handleTextChange = React11.useCallback(
|
|
4212
|
+
(e) => {
|
|
4213
|
+
writeValue(e.target.value, currentKeywords);
|
|
4214
|
+
},
|
|
4215
|
+
[writeValue, currentKeywords]
|
|
4216
|
+
);
|
|
4217
|
+
if (!def) return null;
|
|
4218
|
+
const height = def.height;
|
|
4219
|
+
const theme = getThemeConfig("textarea", def.theme);
|
|
4220
|
+
const wrapperClass = theme?.wrapperClassName ?? "space-y-2";
|
|
4221
|
+
const labelClass = theme?.labelClassName;
|
|
4222
|
+
const inputClass = cn(theme?.inputClassName, showError && "border-red-500");
|
|
4223
|
+
const labelContent = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
4224
|
+
def.label,
|
|
4225
|
+
" ",
|
|
4226
|
+
def.required && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" }),
|
|
4227
|
+
isProcessing && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "inline-block ml-1.5 h-3 w-3 animate-spin text-muted-foreground" })
|
|
4228
|
+
] });
|
|
4229
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: wrapperClass, children: [
|
|
4230
|
+
theme ? /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: fieldId, className: labelClass, children: labelContent }) : /* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: fieldId, children: labelContent }),
|
|
4231
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4232
|
+
Textarea,
|
|
4233
|
+
{
|
|
4234
|
+
id: fieldId,
|
|
4235
|
+
value: textValue,
|
|
4236
|
+
onChange: handleTextChange,
|
|
4237
|
+
onBlur: setTouched,
|
|
4238
|
+
disabled,
|
|
4239
|
+
placeholder: def.placeholder,
|
|
4240
|
+
className: inputClass,
|
|
4241
|
+
...height != null ? { height } : {}
|
|
4242
|
+
}
|
|
4243
|
+
),
|
|
4244
|
+
showError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-500", children: error })
|
|
4245
|
+
] });
|
|
4246
|
+
};
|
|
4247
|
+
var DEFAULT_DRAFT = {
|
|
4248
|
+
gpal: { G: 0, P: 0, A: 0, L: 0 },
|
|
4249
|
+
lmp: "",
|
|
4250
|
+
is_pregnant: false,
|
|
4251
|
+
eddUSG: "",
|
|
4252
|
+
primaryEDD: "lmp",
|
|
4253
|
+
complications: "",
|
|
4254
|
+
outcome: null
|
|
4255
|
+
};
|
|
4256
|
+
function pad2(n) {
|
|
4257
|
+
return String(n).padStart(2, "0");
|
|
4258
|
+
}
|
|
4259
|
+
function formatDdMmYyyy(iso) {
|
|
4260
|
+
const [y, m, d] = iso.split("-");
|
|
4261
|
+
if (!y || !m || !d) return iso;
|
|
4262
|
+
return `${pad2(Number(d))}/${pad2(Number(m))}/${y}`;
|
|
4263
|
+
}
|
|
4264
|
+
function todayIso() {
|
|
4265
|
+
const d = /* @__PURE__ */ new Date();
|
|
4266
|
+
const y = d.getFullYear();
|
|
4267
|
+
const m = pad2(d.getMonth() + 1);
|
|
4268
|
+
const day = pad2(d.getDate());
|
|
4269
|
+
return `${y}-${m}-${day}`;
|
|
4270
|
+
}
|
|
4271
|
+
function isValidIsoDate(val) {
|
|
4272
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(val)) return false;
|
|
4273
|
+
const dt = /* @__PURE__ */ new Date(`${val}T00:00:00.000Z`);
|
|
4274
|
+
return !Number.isNaN(dt.getTime());
|
|
4275
|
+
}
|
|
4276
|
+
function safeJsonParse(val) {
|
|
4277
|
+
try {
|
|
4278
|
+
return JSON.parse(val);
|
|
4279
|
+
} catch {
|
|
4280
|
+
return null;
|
|
4281
|
+
}
|
|
4282
|
+
}
|
|
4283
|
+
function clampInt(val, min, max) {
|
|
4284
|
+
if (!Number.isFinite(val)) return min;
|
|
4285
|
+
return Math.max(min, Math.min(max, Math.trunc(val)));
|
|
4286
|
+
}
|
|
4287
|
+
function parseLegacyString(input) {
|
|
4288
|
+
const result = {};
|
|
4289
|
+
const gpalMatch = input.match(/G(\d+)\s*P(\d+)\s*A(\d+)\s*L(\d+)/i);
|
|
4290
|
+
if (gpalMatch) {
|
|
4291
|
+
const [, g, p, a, l] = gpalMatch;
|
|
4292
|
+
result.gpal = {
|
|
4293
|
+
G: clampInt(Number(g), 0, 20),
|
|
4294
|
+
P: clampInt(Number(p), 0, 20),
|
|
4295
|
+
A: clampInt(Number(a), 0, 20),
|
|
4296
|
+
L: clampInt(Number(l), 0, 20)
|
|
4297
|
+
};
|
|
4298
|
+
}
|
|
4299
|
+
const lmpMatch = input.match(/LMP:\s*([^\n]+)/i);
|
|
4300
|
+
if (lmpMatch?.[1]) result.lmp = lmpMatch[1].trim();
|
|
4301
|
+
const eddUsgMatch = input.match(/EDD\s*\(USG\):\s*([^\n]+)/i);
|
|
4302
|
+
if (eddUsgMatch?.[1]) result.eddUSG = eddUsgMatch[1].trim();
|
|
4303
|
+
const primaryMatch = input.match(/Primary\s*EDD:\s*(lmp|usg)/i);
|
|
4304
|
+
if (primaryMatch?.[1]) result.primaryEDD = primaryMatch[1].toLowerCase();
|
|
4305
|
+
const compMatch = input.match(/Complications:\s*([^\n]+)/i);
|
|
4306
|
+
if (compMatch?.[1]) result.complications = compMatch[1].trim();
|
|
4307
|
+
return result;
|
|
4308
|
+
}
|
|
4309
|
+
function normalizeToDraft(value) {
|
|
4310
|
+
if (!value) return { draft: { ...DEFAULT_DRAFT }, isNewEntry: true };
|
|
4311
|
+
let parsed = value;
|
|
4312
|
+
if (typeof value === "string") {
|
|
4313
|
+
const json = safeJsonParse(value);
|
|
4314
|
+
parsed = json ?? value;
|
|
4315
|
+
}
|
|
4316
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed) && "gpal" in parsed && parsed.gpal && typeof parsed.gpal === "object") {
|
|
4317
|
+
const gpal = parsed.gpal ?? {};
|
|
4318
|
+
const edd = parsed.edd ?? null;
|
|
4319
|
+
const eddUSG = (edd && typeof edd === "object" ? edd.usg : void 0) ?? parsed.eddUSG ?? "";
|
|
4320
|
+
const primaryEDD = (edd && typeof edd === "object" ? edd.primary : void 0) ?? parsed.primaryEDD ?? "lmp";
|
|
4321
|
+
const lmp = parsed.lmp ?? "";
|
|
4322
|
+
const complications = parsed.complications ?? "";
|
|
4323
|
+
const outcome = parsed.outcome ?? null;
|
|
4324
|
+
const is_pregnant = !!parsed.is_pregnant;
|
|
4325
|
+
return {
|
|
4326
|
+
isNewEntry: false,
|
|
4327
|
+
draft: {
|
|
4328
|
+
gpal: {
|
|
4329
|
+
G: clampInt(Number(gpal.G ?? 0), 0, 20),
|
|
4330
|
+
P: clampInt(Number(gpal.P ?? 0), 0, 20),
|
|
4331
|
+
A: clampInt(Number(gpal.A ?? 0), 0, 20),
|
|
4332
|
+
L: clampInt(Number(gpal.L ?? 0), 0, 20)
|
|
4333
|
+
},
|
|
4334
|
+
lmp: typeof lmp === "string" ? lmp : "",
|
|
4335
|
+
is_pregnant,
|
|
4336
|
+
eddUSG: typeof eddUSG === "string" ? eddUSG : "",
|
|
4337
|
+
primaryEDD: primaryEDD === "usg" ? "usg" : "lmp",
|
|
4338
|
+
complications: typeof complications === "string" ? complications : "",
|
|
4339
|
+
outcome: outcome && typeof outcome === "object" ? {
|
|
4340
|
+
type: outcome.type ?? "",
|
|
4341
|
+
date: typeof outcome.date === "string" ? outcome.date : "",
|
|
4342
|
+
notes: typeof outcome.notes === "string" ? outcome.notes : "",
|
|
4343
|
+
is_final: !!outcome.is_final
|
|
4344
|
+
} : null
|
|
4345
|
+
}
|
|
4346
|
+
};
|
|
4347
|
+
}
|
|
4348
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4349
|
+
const gpalFromFlat = {
|
|
4350
|
+
G: clampInt(Number(parsed.G ?? parsed.g ?? 0), 0, 20),
|
|
4351
|
+
P: clampInt(Number(parsed.P ?? parsed.p ?? 0), 0, 20),
|
|
4352
|
+
A: clampInt(Number(parsed.A ?? parsed.a ?? 0), 0, 20),
|
|
4353
|
+
L: clampInt(Number(parsed.L ?? parsed.l ?? 0), 0, 20)
|
|
4354
|
+
};
|
|
4355
|
+
return {
|
|
4356
|
+
isNewEntry: false,
|
|
4357
|
+
draft: {
|
|
4358
|
+
...DEFAULT_DRAFT,
|
|
4359
|
+
gpal: gpalFromFlat,
|
|
4360
|
+
lmp: typeof parsed.lmp === "string" ? parsed.lmp : "",
|
|
4361
|
+
eddUSG: typeof parsed.eddUSG === "string" ? parsed.eddUSG : "",
|
|
4362
|
+
primaryEDD: parsed.primaryEDD === "usg" ? "usg" : "lmp",
|
|
4363
|
+
complications: typeof parsed.complications === "string" ? parsed.complications : "",
|
|
4364
|
+
outcome: parsed.outcome && typeof parsed.outcome === "object" ? {
|
|
4365
|
+
type: parsed.outcome.type ?? "",
|
|
4366
|
+
date: typeof parsed.outcome.date === "string" ? parsed.outcome.date : "",
|
|
4367
|
+
notes: typeof parsed.outcome.notes === "string" ? parsed.outcome.notes : "",
|
|
4368
|
+
is_final: !!parsed.outcome.is_final
|
|
4369
|
+
} : null,
|
|
4370
|
+
is_pregnant: !!parsed.is_pregnant
|
|
4371
|
+
}
|
|
4372
|
+
};
|
|
4373
|
+
}
|
|
4374
|
+
if (typeof parsed === "string") {
|
|
4375
|
+
const legacy = parseLegacyString(parsed);
|
|
4376
|
+
return { isNewEntry: false, draft: { ...DEFAULT_DRAFT, ...legacy } };
|
|
4377
|
+
}
|
|
4378
|
+
return { draft: { ...DEFAULT_DRAFT }, isNewEntry: true };
|
|
4379
|
+
}
|
|
4380
|
+
function computeEddFromLmp(lmpIso) {
|
|
4381
|
+
if (!isValidIsoDate(lmpIso)) return null;
|
|
4382
|
+
const base = /* @__PURE__ */ new Date(`${lmpIso}T00:00:00.000Z`);
|
|
4383
|
+
base.setUTCDate(base.getUTCDate() + 280);
|
|
4384
|
+
const y = base.getUTCFullYear();
|
|
4385
|
+
const m = pad2(base.getUTCMonth() + 1);
|
|
4386
|
+
const d = pad2(base.getUTCDate());
|
|
4387
|
+
return `${y}-${m}-${d}`;
|
|
4388
|
+
}
|
|
4389
|
+
function diffDaysUtc(fromIso, toIso) {
|
|
4390
|
+
if (!isValidIsoDate(fromIso) || !isValidIsoDate(toIso)) return null;
|
|
4391
|
+
const a = (/* @__PURE__ */ new Date(`${fromIso}T00:00:00.000Z`)).getTime();
|
|
4392
|
+
const b = (/* @__PURE__ */ new Date(`${toIso}T00:00:00.000Z`)).getTime();
|
|
4393
|
+
return Math.floor((b - a) / (1e3 * 60 * 60 * 24));
|
|
4394
|
+
}
|
|
4395
|
+
function computeGestationalAgeFromLmp(lmpIso, today) {
|
|
4396
|
+
const days = diffDaysUtc(lmpIso, today);
|
|
4397
|
+
if (days == null || days < 0) return null;
|
|
4398
|
+
const weeks = Math.floor(days / 7);
|
|
4399
|
+
const rem = days % 7;
|
|
4400
|
+
return { weeks, days: rem, totalDays: days, basedOn: "lmp" };
|
|
4401
|
+
}
|
|
4402
|
+
function computeGestationalAgeFromUsgEdd(eddUsgIso, today) {
|
|
4403
|
+
const daysToEdd = diffDaysUtc(today, eddUsgIso);
|
|
4404
|
+
if (daysToEdd == null) return null;
|
|
4405
|
+
const daysElapsed = 280 - daysToEdd;
|
|
4406
|
+
if (daysElapsed < 0) return null;
|
|
4407
|
+
const weeks = Math.floor(daysElapsed / 7);
|
|
4408
|
+
const rem = daysElapsed % 7;
|
|
4409
|
+
return { weeks, days: rem, totalDays: daysElapsed, basedOn: "usg" };
|
|
4410
|
+
}
|
|
4411
|
+
function computeTrimester(ga) {
|
|
4412
|
+
if (!ga) return null;
|
|
4413
|
+
if (ga.weeks < 13) return 1;
|
|
4414
|
+
if (ga.weeks < 27) return 2;
|
|
4415
|
+
if (ga.weeks < 42) return 3;
|
|
4416
|
+
return null;
|
|
4417
|
+
}
|
|
4418
|
+
function buildStored(draft, today) {
|
|
4419
|
+
const lmpIso = isValidIsoDate(draft.lmp) ? draft.lmp : null;
|
|
4420
|
+
const eddUsgIso = isValidIsoDate(draft.eddUSG) ? draft.eddUSG : null;
|
|
4421
|
+
if (!draft.is_pregnant) {
|
|
4422
|
+
return {
|
|
4423
|
+
gpal: draft.gpal,
|
|
4424
|
+
lmp: lmpIso,
|
|
4425
|
+
is_pregnant: false,
|
|
4426
|
+
edd: null,
|
|
4427
|
+
gestationalAge: null,
|
|
4428
|
+
trimester: null,
|
|
4429
|
+
complications: draft.complications.trim() ? draft.complications : null,
|
|
4430
|
+
outcome: draft.outcome
|
|
4431
|
+
};
|
|
4432
|
+
}
|
|
4433
|
+
const eddFromLmp = lmpIso ? computeEddFromLmp(lmpIso) : null;
|
|
4434
|
+
const gaFromLmp = lmpIso ? computeGestationalAgeFromLmp(lmpIso, today) : null;
|
|
4435
|
+
const gaFromUsg = eddUsgIso ? computeGestationalAgeFromUsgEdd(eddUsgIso, today) : null;
|
|
4436
|
+
const primary = draft.primaryEDD;
|
|
4437
|
+
const gaPrimary = primary === "usg" && gaFromUsg ? gaFromUsg : gaFromLmp ? gaFromLmp : null;
|
|
4438
|
+
return {
|
|
4439
|
+
gpal: draft.gpal,
|
|
4440
|
+
lmp: lmpIso,
|
|
4441
|
+
is_pregnant: true,
|
|
4442
|
+
edd: { lmp: eddFromLmp, usg: eddUsgIso, primary },
|
|
4443
|
+
gestationalAge: gaPrimary,
|
|
4444
|
+
trimester: computeTrimester(gaPrimary),
|
|
4445
|
+
complications: draft.complications.trim() ? draft.complications : null,
|
|
4446
|
+
outcome: draft.outcome
|
|
4447
|
+
};
|
|
4448
|
+
}
|
|
4449
|
+
function stripGpalSuffix(label) {
|
|
4450
|
+
return label.replace(/\s*\(G-P-A-L\)\s*$/i, "").trim();
|
|
4451
|
+
}
|
|
4452
|
+
function useDebouncedCommit(commit, delayMs) {
|
|
4453
|
+
const timeoutRef = React11.useRef(null);
|
|
4454
|
+
const latestCommit = React11.useRef(commit);
|
|
4455
|
+
latestCommit.current = commit;
|
|
4456
|
+
React11.useEffect(() => {
|
|
4457
|
+
return () => {
|
|
4458
|
+
if (timeoutRef.current != null) window.clearTimeout(timeoutRef.current);
|
|
4459
|
+
};
|
|
4460
|
+
}, []);
|
|
4461
|
+
return (next) => {
|
|
4462
|
+
if (timeoutRef.current != null) window.clearTimeout(timeoutRef.current);
|
|
4463
|
+
timeoutRef.current = window.setTimeout(() => latestCommit.current(next), delayMs);
|
|
4464
|
+
};
|
|
4465
|
+
}
|
|
4466
|
+
var ObstetricHistoryWidget = ({ fieldId }) => {
|
|
4467
|
+
const { fieldDef, value, setValue, disabled } = useField(fieldId);
|
|
4468
|
+
const [{ draft, isNewEntry }, setDraftState] = React11.useState(() => normalizeToDraft(value));
|
|
4469
|
+
const [lmpError, setLmpError] = React11.useState(null);
|
|
4470
|
+
const [primaryEddError, setPrimaryEddError] = React11.useState(null);
|
|
4471
|
+
const [activeGpalKey, setActiveGpalKey] = React11.useState(null);
|
|
4472
|
+
const refs = {
|
|
4473
|
+
G: React11.useRef(null),
|
|
4474
|
+
P: React11.useRef(null),
|
|
4475
|
+
A: React11.useRef(null),
|
|
4476
|
+
L: React11.useRef(null)
|
|
4477
|
+
};
|
|
4478
|
+
const today = React11.useMemo(() => todayIso(), []);
|
|
4479
|
+
React11.useEffect(() => {
|
|
4480
|
+
const next = normalizeToDraft(value);
|
|
4481
|
+
const currStr = JSON.stringify(buildStored(draft, today));
|
|
4482
|
+
const nextStr = JSON.stringify(buildStored(next.draft, today));
|
|
4483
|
+
if (currStr !== nextStr) setDraftState(next);
|
|
4484
|
+
}, [value]);
|
|
4485
|
+
const commitImmediate = (nextDraft) => {
|
|
4486
|
+
const stored = buildStored(nextDraft, today);
|
|
4487
|
+
setValue(JSON.stringify(stored));
|
|
4488
|
+
};
|
|
4489
|
+
const commitDebounced = useDebouncedCommit(commitImmediate, 500);
|
|
4490
|
+
const storedComputed = React11.useMemo(() => buildStored(draft, today), [draft, today]);
|
|
4491
|
+
const rightPanelEnabled = draft.is_pregnant && (!!storedComputed.lmp || !!storedComputed.edd?.usg);
|
|
4492
|
+
const headerLabel = fieldDef?.label ? stripGpalSuffix(fieldDef.label) : "Obstetric History";
|
|
4493
|
+
const setDraft = (updater, persist) => {
|
|
4494
|
+
setDraftState((prev) => {
|
|
4495
|
+
const nextDraft = updater(prev.draft);
|
|
4496
|
+
const nextState = { ...prev, draft: nextDraft };
|
|
4497
|
+
if (persist === "immediate") commitImmediate(nextDraft);
|
|
4498
|
+
else commitDebounced(nextDraft);
|
|
4499
|
+
return nextState;
|
|
4500
|
+
});
|
|
4501
|
+
};
|
|
4502
|
+
const sanitizeGpalInput = (raw) => {
|
|
4503
|
+
const digits = raw.replace(/[^\d]/g, "").slice(0, 2);
|
|
4504
|
+
if (!digits) return 0;
|
|
4505
|
+
return clampInt(Number(digits), 0, 20);
|
|
4506
|
+
};
|
|
4507
|
+
const handleGpalChange = (key, raw) => {
|
|
4508
|
+
const prevWasEmpty = draft.gpal[key] === 0 && activeGpalKey === key;
|
|
4509
|
+
const val = sanitizeGpalInput(raw);
|
|
4510
|
+
setDraft((prev) => ({ ...prev, gpal: { ...prev.gpal, [key]: val } }), "debounce");
|
|
4511
|
+
const digitsOnly = raw.replace(/[^\d]/g, "");
|
|
4512
|
+
const shouldAutoTab = digitsOnly.length === 1 && (prevWasEmpty || String(draft.gpal[key]).length >= 1);
|
|
4513
|
+
if (shouldAutoTab) {
|
|
4514
|
+
const order = ["G", "P", "A", "L"];
|
|
4515
|
+
const idx = order.indexOf(key);
|
|
4516
|
+
const nextKey = order[idx + 1];
|
|
4517
|
+
if (nextKey) refs[nextKey].current?.focus();
|
|
4518
|
+
}
|
|
4519
|
+
};
|
|
4520
|
+
const handleLmpChange = (nextIso) => {
|
|
4521
|
+
setPrimaryEddError(null);
|
|
4522
|
+
if (nextIso && nextIso > today) {
|
|
4523
|
+
setLmpError("LMP cannot be in the future");
|
|
4524
|
+
return;
|
|
4525
|
+
}
|
|
4526
|
+
setLmpError(null);
|
|
4527
|
+
setDraft((prev) => ({ ...prev, lmp: nextIso }), "debounce");
|
|
4528
|
+
};
|
|
4529
|
+
const handlePregStatus = (isPregnant) => {
|
|
4530
|
+
setLmpError(null);
|
|
4531
|
+
setPrimaryEddError(null);
|
|
4532
|
+
setDraft((prev) => ({ ...prev, is_pregnant: isPregnant }), "immediate");
|
|
4533
|
+
};
|
|
4534
|
+
const handleEddUsgChange = (nextIso) => {
|
|
4535
|
+
setPrimaryEddError(null);
|
|
4536
|
+
setDraft((prev) => ({ ...prev, eddUSG: nextIso }), "debounce");
|
|
4537
|
+
};
|
|
4538
|
+
const handlePrimaryEdd = (next) => {
|
|
4539
|
+
if (next === "usg" && !isValidIsoDate(draft.eddUSG)) {
|
|
4540
|
+
setPrimaryEddError("Please enter EDD from USG first");
|
|
4541
|
+
return;
|
|
4542
|
+
}
|
|
4543
|
+
setPrimaryEddError(null);
|
|
4544
|
+
setDraft((prev) => ({ ...prev, primaryEDD: next }), "immediate");
|
|
4545
|
+
};
|
|
4546
|
+
const ensureOutcome = () => {
|
|
4547
|
+
if (draft.outcome) return;
|
|
4548
|
+
setDraft(
|
|
4549
|
+
(prev) => ({
|
|
4550
|
+
...prev,
|
|
4551
|
+
outcome: { type: "", date: "", notes: "", is_final: false }
|
|
4552
|
+
}),
|
|
4553
|
+
"immediate"
|
|
4554
|
+
);
|
|
4555
|
+
};
|
|
4556
|
+
const clearOutcome = () => {
|
|
4557
|
+
setDraft((prev) => ({ ...prev, outcome: null }), "immediate");
|
|
4558
|
+
};
|
|
4559
|
+
const updateOutcomeImmediate = (updater) => {
|
|
4560
|
+
setDraft(
|
|
4561
|
+
(prev) => {
|
|
4562
|
+
const o = prev.outcome ?? { type: "", date: "", notes: "", is_final: false };
|
|
4563
|
+
return { ...prev, outcome: updater(o) };
|
|
4564
|
+
},
|
|
4565
|
+
"immediate"
|
|
4566
|
+
);
|
|
4567
|
+
};
|
|
4568
|
+
const updateOutcomeNotesDebounced = (notes) => {
|
|
4569
|
+
setDraft(
|
|
4570
|
+
(prev) => {
|
|
4571
|
+
const o = prev.outcome ?? { type: "", date: "", notes: "", is_final: false };
|
|
4572
|
+
return { ...prev, outcome: { ...o, notes } };
|
|
4573
|
+
},
|
|
4574
|
+
"debounce"
|
|
4575
|
+
);
|
|
4576
|
+
};
|
|
4577
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(Card, { className: cn("w-full", disabled && "opacity-70 pointer-events-none"), children: [
|
|
4578
|
+
/* @__PURE__ */ jsxRuntime.jsx(CardHeader, { className: "pb-3", children: /* @__PURE__ */ jsxRuntime.jsx(CardTitle, { className: "text-base font-semibold", children: headerLabel }) }),
|
|
4579
|
+
/* @__PURE__ */ jsxRuntime.jsxs(CardContent, { className: "space-y-4", children: [
|
|
4580
|
+
isNewEntry && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-900", children: "No previous maternity episode found. Please fill obstetric details to start tracking." }),
|
|
4581
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4 items-start", children: [
|
|
4582
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
4583
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-white p-4", children: [
|
|
4584
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs font-medium text-muted-foreground mb-2", children: "G-P-A-L" }),
|
|
4585
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-4 gap-3", children: ["G", "P", "A", "L"].map((k) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1", children: [
|
|
4586
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `${fieldId}-gpal-${k}`, className: "text-[11px] text-muted-foreground", children: k }),
|
|
4587
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4588
|
+
Input,
|
|
4589
|
+
{
|
|
4590
|
+
ref: refs[k],
|
|
4591
|
+
id: `${fieldId}-gpal-${k}`,
|
|
4592
|
+
inputMode: "numeric",
|
|
4593
|
+
pattern: "\\d*",
|
|
4594
|
+
maxLength: 2,
|
|
4595
|
+
value: String(draft.gpal[k] ?? 0),
|
|
4596
|
+
onFocus: () => {
|
|
4597
|
+
setActiveGpalKey(k);
|
|
4598
|
+
const el = refs[k].current;
|
|
4599
|
+
if (el) el.select();
|
|
4600
|
+
},
|
|
4601
|
+
onChange: (e) => handleGpalChange(k, e.target.value),
|
|
4602
|
+
onKeyDown: (e) => {
|
|
4603
|
+
const allowed = ["Backspace", "Delete", "ArrowLeft", "ArrowRight", "Tab", "Home", "End"];
|
|
4604
|
+
if (allowed.includes(e.key)) return;
|
|
4605
|
+
if (/^\d$/.test(e.key)) return;
|
|
4606
|
+
e.preventDefault();
|
|
4607
|
+
},
|
|
4608
|
+
className: "text-center text-base"
|
|
4609
|
+
}
|
|
4610
|
+
)
|
|
4611
|
+
] }, k)) })
|
|
4612
|
+
] }),
|
|
4613
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-3", children: [
|
|
4614
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 rounded-lg border bg-white p-4", children: [
|
|
4615
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `${fieldId}-lmp`, className: cn("text-sm", lmpError && "text-red-600"), children: "Last Menstrual Period (LMP)" }),
|
|
4616
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4617
|
+
Input,
|
|
4618
|
+
{
|
|
4619
|
+
id: `${fieldId}-lmp`,
|
|
4620
|
+
type: "date",
|
|
4621
|
+
max: today,
|
|
4622
|
+
value: draft.lmp,
|
|
4623
|
+
onChange: (e) => handleLmpChange(e.target.value),
|
|
4624
|
+
className: cn(lmpError && "border-red-500")
|
|
4625
|
+
}
|
|
4626
|
+
),
|
|
4627
|
+
lmpError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-600", children: lmpError })
|
|
4628
|
+
] }),
|
|
4629
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 rounded-lg border bg-white p-4", children: [
|
|
4630
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium", children: "Pregnancy Status" }),
|
|
4631
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
4632
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4633
|
+
"button",
|
|
4634
|
+
{
|
|
4635
|
+
type: "button",
|
|
4636
|
+
onClick: () => handlePregStatus(false),
|
|
4637
|
+
className: cn(
|
|
4638
|
+
"flex-1 rounded-md border px-3 py-2 text-xs",
|
|
4639
|
+
!draft.is_pregnant ? "bg-green-600 text-white border-green-600" : "bg-white"
|
|
4640
|
+
),
|
|
4641
|
+
children: "Not Pregnant"
|
|
4642
|
+
}
|
|
4643
|
+
),
|
|
4644
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4645
|
+
"button",
|
|
4646
|
+
{
|
|
4647
|
+
type: "button",
|
|
4648
|
+
onClick: () => handlePregStatus(true),
|
|
4649
|
+
className: cn(
|
|
4650
|
+
"flex-1 rounded-md border px-3 py-2 text-xs",
|
|
4651
|
+
draft.is_pregnant ? "bg-green-600 text-white border-green-600" : "bg-white"
|
|
4652
|
+
),
|
|
4653
|
+
children: "Pregnant"
|
|
4654
|
+
}
|
|
4655
|
+
)
|
|
4656
|
+
] })
|
|
4657
|
+
] })
|
|
4658
|
+
] }),
|
|
4659
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 rounded-lg border bg-white p-4", children: [
|
|
4660
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `${fieldId}-complications`, className: "text-sm", children: "Previous Pregnancy Complications" }),
|
|
4661
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4662
|
+
Textarea,
|
|
4663
|
+
{
|
|
4664
|
+
id: `${fieldId}-complications`,
|
|
4665
|
+
placeholder: "e.g. Pre-eclampsia, Gestational diabetes, C-section, etc.",
|
|
4666
|
+
value: draft.complications,
|
|
4667
|
+
onChange: (e) => setDraft((prev) => ({ ...prev, complications: e.target.value }), "debounce"),
|
|
4668
|
+
className: "min-h-[140px]"
|
|
4669
|
+
}
|
|
4670
|
+
)
|
|
4671
|
+
] })
|
|
4672
|
+
] }),
|
|
4673
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-white p-4 min-h-[260px]", children: [
|
|
4674
|
+
!draft.is_pregnant && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full flex items-center justify-center text-center text-sm text-muted-foreground px-6 py-10", children: 'Only for "Pregnant Women" and enter LMP to calculate gestational age and EDD' }),
|
|
4675
|
+
draft.is_pregnant && !rightPanelEnabled && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full flex items-center justify-center text-center text-sm text-muted-foreground px-6 py-10", children: "Enter LMP and/or EDD (USG) to enable gestational age and EDD calculations." }),
|
|
4676
|
+
draft.is_pregnant && rightPanelEnabled && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
4677
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border p-4", children: [
|
|
4678
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
4679
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-semibold text-blue-700", children: "Expected Delivery Date (EDD)" }),
|
|
4680
|
+
storedComputed.gestationalAge?.basedOn && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "rounded-full bg-blue-100 px-2 py-1 text-[11px] text-blue-700", children: [
|
|
4681
|
+
"Based on ",
|
|
4682
|
+
storedComputed.gestationalAge.basedOn.toUpperCase()
|
|
4683
|
+
] })
|
|
4684
|
+
] }),
|
|
4685
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-3", children: [
|
|
4686
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground", children: "Gestational Age:" }),
|
|
4687
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-lg font-semibold", children: storedComputed.gestationalAge ? `${storedComputed.gestationalAge.weeks} weeks ${storedComputed.gestationalAge.days} days` : "\u2014" }),
|
|
4688
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground mt-1", children: storedComputed.trimester ? `Trimester ${storedComputed.trimester}` : "Trimester \u2014" })
|
|
4689
|
+
] })
|
|
4690
|
+
] }),
|
|
4691
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4692
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground", children: "EDD (from LMP):" }),
|
|
4693
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-md border bg-muted/20 px-3 py-2 text-sm font-semibold", children: storedComputed.edd?.lmp ? formatDdMmYyyy(storedComputed.edd.lmp) : "\u2014" })
|
|
4694
|
+
] }),
|
|
4695
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4696
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `${fieldId}-edd-usg`, className: "text-sm", children: "EDD (from USG):" }),
|
|
4697
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4698
|
+
Input,
|
|
4699
|
+
{
|
|
4700
|
+
id: `${fieldId}-edd-usg`,
|
|
4701
|
+
type: "date",
|
|
4702
|
+
value: draft.eddUSG,
|
|
4703
|
+
onChange: (e) => handleEddUsgChange(e.target.value)
|
|
4704
|
+
}
|
|
4705
|
+
)
|
|
4706
|
+
] }),
|
|
4707
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4708
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium", children: "Primary EDD:" }),
|
|
4709
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
4710
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4711
|
+
"button",
|
|
4712
|
+
{
|
|
4713
|
+
type: "button",
|
|
4714
|
+
onClick: () => handlePrimaryEdd("lmp"),
|
|
4715
|
+
className: cn(
|
|
4716
|
+
"flex-1 rounded-md border px-3 py-2 text-sm",
|
|
4717
|
+
draft.primaryEDD === "lmp" ? "bg-blue-600 text-white border-blue-600" : "bg-white"
|
|
4718
|
+
),
|
|
4719
|
+
children: "LMP"
|
|
4720
|
+
}
|
|
4721
|
+
),
|
|
4722
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4723
|
+
"button",
|
|
4724
|
+
{
|
|
4725
|
+
type: "button",
|
|
4726
|
+
onClick: () => handlePrimaryEdd("usg"),
|
|
4727
|
+
className: cn(
|
|
4728
|
+
"flex-1 rounded-md border px-3 py-2 text-sm",
|
|
4729
|
+
draft.primaryEDD === "usg" ? "bg-blue-600 text-white border-blue-600" : "bg-white"
|
|
4730
|
+
),
|
|
4731
|
+
children: "USG"
|
|
4732
|
+
}
|
|
4733
|
+
)
|
|
4734
|
+
] }),
|
|
4735
|
+
primaryEddError && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-red-600", children: primaryEddError })
|
|
4736
|
+
] })
|
|
4737
|
+
] })
|
|
4738
|
+
] })
|
|
4739
|
+
] }),
|
|
4740
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-lg border bg-yellow-50/60 p-4", children: [
|
|
4741
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
4742
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-semibold", children: "Pregnancy Outcome (Optional - To Close Episode)" }),
|
|
4743
|
+
draft.outcome && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "text-sm text-red-600 hover:underline", onClick: clearOutcome, children: "Clear" })
|
|
4744
|
+
] }),
|
|
4745
|
+
!draft.outcome && /* @__PURE__ */ jsxRuntime.jsx(
|
|
4746
|
+
"button",
|
|
4747
|
+
{
|
|
4748
|
+
type: "button",
|
|
4749
|
+
onClick: ensureOutcome,
|
|
4750
|
+
className: "mt-3 w-full rounded-md border border-yellow-300 bg-yellow-100 px-3 py-2 text-sm font-medium",
|
|
4751
|
+
children: "+ Add Pregnancy Outcome"
|
|
4752
|
+
}
|
|
4753
|
+
),
|
|
4754
|
+
draft.outcome && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 space-y-3", children: [
|
|
4755
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4756
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Label, { className: "text-sm", children: [
|
|
4757
|
+
"Outcome Type ",
|
|
4758
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
|
|
4759
|
+
] }),
|
|
4760
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
4761
|
+
"select",
|
|
4762
|
+
{
|
|
4763
|
+
className: "w-full rounded-md border px-3 py-2 text-sm bg-white",
|
|
4764
|
+
value: draft.outcome.type,
|
|
4765
|
+
onChange: (e) => updateOutcomeImmediate((o) => ({ ...o, type: e.target.value })),
|
|
4766
|
+
children: [
|
|
4767
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "", children: "Select outcome..." }),
|
|
4768
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "delivery", children: "Normal Delivery" }),
|
|
4769
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "cesarean", children: "Cesarean Section" }),
|
|
4770
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "abortion", children: "Abortion" }),
|
|
4771
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "miscarriage", children: "Miscarriage" }),
|
|
4772
|
+
/* @__PURE__ */ jsxRuntime.jsx("option", { value: "stillbirth", children: "Stillbirth" })
|
|
4773
|
+
]
|
|
4774
|
+
}
|
|
4775
|
+
)
|
|
4776
|
+
] }),
|
|
4777
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4778
|
+
/* @__PURE__ */ jsxRuntime.jsxs(Label, { htmlFor: `${fieldId}-outcome-date`, className: "text-sm", children: [
|
|
4779
|
+
"Date ",
|
|
4780
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-500", children: "*" })
|
|
4781
|
+
] }),
|
|
4782
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4783
|
+
Input,
|
|
4784
|
+
{
|
|
4785
|
+
id: `${fieldId}-outcome-date`,
|
|
4786
|
+
type: "date",
|
|
4787
|
+
max: today,
|
|
4788
|
+
value: draft.outcome.date,
|
|
4789
|
+
onChange: (e) => updateOutcomeImmediate((o) => ({ ...o, date: e.target.value }))
|
|
4790
|
+
}
|
|
4791
|
+
),
|
|
4792
|
+
draft.outcome.date && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs text-muted-foreground", children: [
|
|
4793
|
+
"Selected: ",
|
|
4794
|
+
formatDdMmYyyy(draft.outcome.date)
|
|
4795
|
+
] })
|
|
4796
|
+
] }),
|
|
4797
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
4798
|
+
/* @__PURE__ */ jsxRuntime.jsx(Label, { htmlFor: `${fieldId}-outcome-notes`, className: "text-sm", children: "Notes" }),
|
|
4799
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4800
|
+
Textarea,
|
|
4801
|
+
{
|
|
4802
|
+
id: `${fieldId}-outcome-notes`,
|
|
4803
|
+
placeholder: "e.g. Normal vaginal delivery, healthy baby boy, 3.2kg",
|
|
4804
|
+
value: draft.outcome.notes,
|
|
4805
|
+
onChange: (e) => updateOutcomeNotesDebounced(e.target.value),
|
|
4806
|
+
className: "min-h-[90px]"
|
|
4807
|
+
}
|
|
4808
|
+
)
|
|
4809
|
+
] }),
|
|
4810
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border bg-white p-3", children: [
|
|
4811
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
|
|
4812
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4813
|
+
"input",
|
|
4814
|
+
{
|
|
4815
|
+
type: "checkbox",
|
|
4816
|
+
checked: !!draft.outcome.is_final,
|
|
4817
|
+
onChange: (e) => updateOutcomeImmediate((o) => ({ ...o, is_final: e.target.checked }))
|
|
4818
|
+
}
|
|
4819
|
+
),
|
|
4820
|
+
"Close this pregnancy episode"
|
|
4821
|
+
] }),
|
|
4822
|
+
draft.outcome.is_final && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 rounded-md border border-red-300 bg-red-50 px-3 py-2 text-xs text-red-800", children: "Checking this will mark the pregnancy as completed and is intended to close the maternity episode." })
|
|
4823
|
+
] })
|
|
4824
|
+
] })
|
|
4825
|
+
] })
|
|
4826
|
+
] })
|
|
4827
|
+
] });
|
|
4828
|
+
};
|
|
4022
4829
|
var FieldRenderer = ({ fieldId }) => {
|
|
4023
4830
|
const { fieldDef, visible } = useField(fieldId);
|
|
4024
4831
|
if (!visible || !fieldDef) {
|
|
@@ -4065,6 +4872,10 @@ var FieldRenderer = ({ fieldId }) => {
|
|
|
4065
4872
|
return /* @__PURE__ */ jsxRuntime.jsx(ReferralWidget, { fieldId });
|
|
4066
4873
|
case "followup":
|
|
4067
4874
|
return /* @__PURE__ */ jsxRuntime.jsx(FollowupWidget, { fieldId });
|
|
4875
|
+
case "smart_textarea":
|
|
4876
|
+
return /* @__PURE__ */ jsxRuntime.jsx(SmartTextareaWidget, { fieldId });
|
|
4877
|
+
case "obstetric_history":
|
|
4878
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ObstetricHistoryWidget, { fieldId });
|
|
4068
4879
|
case "static_text": {
|
|
4069
4880
|
const def = fieldDef;
|
|
4070
4881
|
const size = def.size ?? "regular";
|
|
@@ -4829,12 +5640,14 @@ exports.ReadOnlySignature = ReadOnlySignature;
|
|
|
4829
5640
|
exports.ReadOnlyTable = ReadOnlyTable;
|
|
4830
5641
|
exports.SignatureUploadWidget = SignatureUploadWidget;
|
|
4831
5642
|
exports.SmartForm = SmartForm;
|
|
5643
|
+
exports.SmartTextareaWidget = SmartTextareaWidget;
|
|
4832
5644
|
exports.createUploadHandler = createUploadHandler;
|
|
4833
5645
|
exports.evaluateRules = evaluateRules;
|
|
4834
5646
|
exports.useField = useField;
|
|
4835
5647
|
exports.useFieldHandlers = useFieldHandlers;
|
|
4836
5648
|
exports.useForm = useForm;
|
|
4837
5649
|
exports.useFormStore = useFormStore;
|
|
5650
|
+
exports.useSmartKeywords = useSmartKeywords;
|
|
4838
5651
|
exports.validateField = validateField;
|
|
4839
5652
|
exports.validateForm = validateForm;
|
|
4840
5653
|
//# sourceMappingURL=index.cjs.map
|