feat(pe-guide): teaching-focused redesign — grading scales, pearls, significance
User feedback: the exam steps were too generic (e.g. "Shoulder abduction
— 5/5 bilaterally" never explained what 5/5 means or HOW to test it).
The guide needs to serve as a teaching tool, not just a checkbox list.
Three changes:
1. GRADING SCALES reference card (new). Collapsible panel at the top of
each system showing the relevant scales:
- Neuro: MRC strength (0-5), DTR (0-4+), Plantar response
- MSK: Scoliometer ATR, Beighton hypermobility score
Each scale shows the grade AND its clinical meaning in a compact
table. No more orphan "5/5 bilaterally" without definition.
2. Per-component SIGNIFICANCE + PEARL fields (optional). Adolescent
neuro components enriched with:
- Significance: one-line clinical relevance (what this component is
actually for — what pathologies it detects)
- Teaching pearl: a Hutchison/Bates/Nelson-style tip that helps the
learner see past the mechanics to the reasoning
Visually distinct — pearl gets a warm amber accent, significance is
a crosshair icon under the name.
3. Method strings REWRITTEN for every adolescent strength step. Before:
"Shoulder abduction — 5/5 bilaterally". After: "Patient abducts both
arms to 90°. Examiner pushes down on each arm just above the elbow
while patient resists. Compare sides. — Holds against full resistance
— MRC 5/5 bilaterally". Same treatment for all 14 strength steps,
all 8 DTR steps, and tone/pronator-drift.
UI redesign:
- Accent bars on cards (cyan for MSK, purple for neuro) for visual
anchor
- Numbered step circles instead of "1." prefix
- HOW / NORMAL label badges on each step
- Watch-for block with red left-border for red-flag grouping
- System-level header with icon (bone for MSK, brain for neuro)
Other age groups (newborn through school-age) keep the old data shape
(steps without pearls) — they still render correctly, just without the
pearl/significance blocks. Enriching them is an incremental follow-up.
This commit is contained in:
parent
31507a4f09
commit
33bfc0bfbc
1 changed files with 174 additions and 52 deletions
|
|
@ -17,6 +17,67 @@
|
|||
|
||||
(function () {
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// GRADING SCALES — universal reference shown at top of each system
|
||||
// ────────────────────────────────────────────────────────────
|
||||
var SCALES = {
|
||||
mrc: {
|
||||
title: 'MRC strength grade (0–5)',
|
||||
icon: 'fa-hand-fist',
|
||||
rows: [
|
||||
['5', 'Normal power — holds against full resistance'],
|
||||
['4', 'Reduced — moves against gravity + some resistance'],
|
||||
['3', 'Moves against gravity only (no added resistance)'],
|
||||
['2', 'Full range with gravity eliminated (horizontal plane)'],
|
||||
['1', 'Flicker / trace contraction, no joint movement'],
|
||||
['0', 'No contraction']
|
||||
]
|
||||
},
|
||||
dtr: {
|
||||
title: 'Deep-tendon reflex grade (0–4+)',
|
||||
icon: 'fa-circle-dot',
|
||||
rows: [
|
||||
['0', 'Absent'],
|
||||
['1+', 'Hypoactive — trace, only with reinforcement'],
|
||||
['2+', 'Normal'],
|
||||
['3+', 'Brisk — may still be normal in anxious patients'],
|
||||
['4+', 'Hyperactive with sustained clonus — always abnormal']
|
||||
]
|
||||
},
|
||||
plantar: {
|
||||
title: 'Plantar response (Babinski)',
|
||||
icon: 'fa-shoe-prints',
|
||||
rows: [
|
||||
['Down-going', 'Normal in anyone ≥ 2 years'],
|
||||
['Up-going', 'Normal < 2 years; abnormal after — UMN lesion'],
|
||||
['Asymmetric', 'Always abnormal at any age']
|
||||
]
|
||||
},
|
||||
beighton: {
|
||||
title: 'Beighton hypermobility score (0–9)',
|
||||
icon: 'fa-hands',
|
||||
rows: [
|
||||
['≤ 3', 'Normal flexibility'],
|
||||
['4', 'Borderline — consider in context'],
|
||||
['≥ 5', 'Hypermobility spectrum; screen for hEDS if other features present']
|
||||
]
|
||||
},
|
||||
atr: {
|
||||
title: 'Scoliometer — angle of trunk rotation',
|
||||
icon: 'fa-ruler',
|
||||
rows: [
|
||||
['< 5°', 'Normal, no follow-up'],
|
||||
['5–6°', 'Borderline — re-check at each visit'],
|
||||
['≥ 7°', 'Refer for PA/lateral spine x-ray + orthopedic evaluation']
|
||||
]
|
||||
}
|
||||
};
|
||||
// Which scales are relevant per system
|
||||
var SYSTEM_SCALES = {
|
||||
msk: ['atr', 'beighton'],
|
||||
neuro: ['mrc', 'dtr', 'plantar']
|
||||
};
|
||||
|
||||
// ────────────────────────────────────────────────────────────
|
||||
// DATA
|
||||
// ────────────────────────────────────────────────────────────
|
||||
|
|
@ -545,9 +606,12 @@
|
|||
]
|
||||
},
|
||||
neuro: {
|
||||
overview: 'Full adult-pattern neuro exam. Screen for concussion sequelae in sports-active adolescents; frontal release signs should be absent.',
|
||||
overview: 'Full adult-pattern neuro exam across six pillars: mental status, cranial nerves, motor, reflexes, sensory, coordination/gait. In adolescents, screen concussion sequelae if sports-active; frontal release signs must be absent.',
|
||||
components: [
|
||||
{ name: 'Mental status', steps: [
|
||||
{ name: 'Mental status',
|
||||
significance: 'Detects cognitive change (concussion, substance use, mood disorder, rare neurodegenerative disease).',
|
||||
pearl: 'Attention precedes memory. A patient who can\'t attend (serial 7s, months backward) will fail memory even with intact hippocampus — distinguish before calling it a memory problem.',
|
||||
steps: [
|
||||
{ label: 'Orientation', method: 'Name, age, date, location, situation', normal: 'Oriented x 4' },
|
||||
{ label: 'Attention', method: 'Count backward from 100 by 7s (serial 7s) or months of year backward', normal: 'Intact' },
|
||||
{ label: 'Short-term memory', method: '3-item registration and recall at 5 min', normal: '3/3 recall' },
|
||||
|
|
@ -555,7 +619,10 @@
|
|||
{ label: 'Executive function', method: 'Similarities (apple/orange); interpret proverb', normal: 'Abstract, age-appropriate' }
|
||||
], abnormalHints: ['Post-concussion cognitive changes', 'Mood or personality changes', 'Subtle executive dysfunction', 'Word-finding difficulty'] },
|
||||
|
||||
{ name: 'Cranial nerves (II–XII, full formal exam)', steps: [
|
||||
{ name: 'Cranial nerves (II–XII, full formal exam)',
|
||||
significance: 'Localises brainstem, base-of-skull, and specific nerve pathology. Subtle deficits (RAPD, mild facial weakness, Horner) are easily missed — exam discipline matters.',
|
||||
pearl: 'The fastest screen for a CN deficit is asking the patient to speak, smile, look around, and swallow water. What\'s preserved in everyday function tells you what\'s likely intact — then examine formally to confirm and to catch the subtle.',
|
||||
steps: [
|
||||
{ label: 'CN I (if indicated)', method: 'Coffee or cinnamon each nostril separately', normal: 'Identifies both' },
|
||||
{ label: 'CN II — acuity', method: 'Snellen at 20ft each eye; corrective lenses if worn', normal: '20/20 or baseline' },
|
||||
{ label: 'CN II — fields', method: 'Confrontation, 4 quadrants each eye', normal: 'Full fields' },
|
||||
|
|
@ -572,33 +639,39 @@
|
|||
{ label: 'CN XII', method: 'Tongue protrusion, side to side; inspect for fasciculations/atrophy', normal: 'Midline, no atrophy or fasciculations, full movement' }
|
||||
], abnormalHints: ['Any focal cranial nerve deficit', 'Papilledema', 'RAPD', 'Nystagmus', 'Facial asymmetry', 'Tongue deviation'] },
|
||||
|
||||
{ name: 'Motor — bulk, tone, strength', steps: [
|
||||
{ label: 'Bulk inspection', method: 'Shoulders, arms, thighs, calves, interossei', normal: 'Symmetric, no atrophy' },
|
||||
{ label: 'Tone — upper', method: 'Passive range elbows, wrists; pronator drift test', normal: 'Normal tone, no drift' },
|
||||
{ label: 'Tone — lower', method: 'Passive knee and ankle range', normal: 'Normal tone, no catch' },
|
||||
{ label: 'Strength — deltoid (C5)', method: 'Shoulder abduction', normal: '5/5 bilaterally' },
|
||||
{ label: 'Strength — biceps (C5-C6)', method: 'Elbow flexion', normal: '5/5' },
|
||||
{ label: 'Strength — triceps (C7)', method: 'Elbow extension', normal: '5/5' },
|
||||
{ label: 'Strength — wrist extension (C6-C7)', method: 'Dorsiflex wrist against resistance', normal: '5/5' },
|
||||
{ label: 'Strength — finger flexion (C8)', method: 'Grip strength', normal: '5/5 symmetric' },
|
||||
{ label: 'Strength — finger abduction (T1)', method: 'Spread fingers', normal: '5/5' },
|
||||
{ label: 'Strength — hip flexion (L2-L3)', method: 'Lift thigh supine', normal: '5/5' },
|
||||
{ label: 'Strength — knee extension (L3-L4)', method: 'Straighten knee', normal: '5/5' },
|
||||
{ label: 'Strength — ankle dorsiflexion (L4-L5)', method: 'Pull toes up', normal: '5/5' },
|
||||
{ label: 'Strength — great toe extension (L5)', method: 'Dorsiflex great toe against resistance', normal: '5/5' },
|
||||
{ label: 'Strength — ankle plantarflexion (S1)', method: 'Push foot down; toe-walk', normal: '5/5 bilaterally' }
|
||||
], abnormalHints: ['Focal weakness (localize by myotome)', 'Pronator drift (subtle UMN)', 'Spasticity', 'Atrophy', 'Fasciculations'] },
|
||||
{ name: 'Motor — bulk, tone, strength',
|
||||
significance: 'Localises lesion to UMN vs LMN vs muscle vs junction. Pattern of weakness (proximal vs distal, symmetric vs focal) narrows differential.',
|
||||
pearl: 'Pronator drift is the most sensitive screen for subtle UMN weakness — a normal-feeling arm that drifts down with eyes closed still has corticospinal tract dysfunction. Always do it even when formal strength is 5/5.',
|
||||
steps: [
|
||||
{ label: 'Bulk inspection', method: 'Inspect shoulders, biceps, thighs, calves, dorsal interossei (between metacarpals) of hands.', normal: 'Symmetric bulk; no atrophy, no pseudohypertrophy' },
|
||||
{ label: 'Tone — upper', method: 'Passive flex-extend elbow and pronate-supinate wrist at slow then quick speeds. Then pronator drift: arms outstretched, palms up, eyes closed for 10 s.', normal: 'Smooth passive range; no drift, no pronation of the outstretched hand' },
|
||||
{ label: 'Tone — lower', method: 'Passive knee flexion-extension; quick ankle dorsiflexion to check for catch. Heel-slap test: roll thigh and watch for ankle swing.', normal: 'Normal resistance, no catch, symmetric' },
|
||||
{ label: 'Strength — deltoid (C5)', method: 'Patient abducts both arms to 90°. Examiner pushes down on each arm just above the elbow while patient resists. Compare sides.', normal: 'Holds against full resistance — MRC 5/5 bilaterally' },
|
||||
{ label: 'Strength — biceps (C5–C6)', method: 'Elbow flexed 90°, supinated. Examiner grasps wrist and pulls to extend while patient resists.', normal: 'Holds against full resistance — 5/5' },
|
||||
{ label: 'Strength — triceps (C7)', method: 'Elbow flexed 90°. Examiner pushes wrist toward shoulder while patient extends against resistance.', normal: 'Extends against full resistance — 5/5' },
|
||||
{ label: 'Strength — wrist extension (C6–C7)', method: 'Patient makes fist, extends wrist. Examiner pushes down on knuckles while patient holds wrist up.', normal: 'Holds against full resistance — 5/5' },
|
||||
{ label: 'Strength — finger flexion / grip (C8)', method: 'Patient grips two of examiner\'s crossed fingers as hard as possible. Compare sides.', normal: 'Strong symmetric grip — 5/5' },
|
||||
{ label: 'Strength — finger abduction (T1)', method: 'Patient spreads fingers wide. Examiner squeezes index and little fingers together while patient resists.', normal: 'Holds fingers apart — 5/5' },
|
||||
{ label: 'Strength — hip flexion (L2–L3)', method: 'Supine. Patient lifts straight leg 30° off table. Examiner pushes down on thigh just above knee while patient resists.', normal: 'Holds thigh up against full resistance — 5/5' },
|
||||
{ label: 'Strength — knee extension (L3–L4)', method: 'Sitting, knee 90°. Patient straightens knee while examiner pushes distal shin down.', normal: 'Extends against full resistance — 5/5' },
|
||||
{ label: 'Strength — ankle dorsiflexion (L4–L5)', method: 'Patient pulls toes and foot up toward shin. Examiner pushes foot down at the dorsum.', normal: 'Holds dorsiflexion against full resistance — 5/5; preserved heel-walk' },
|
||||
{ label: 'Strength — great toe extension (L5)', method: 'Patient extends great toe up while examiner pushes it down with thumb.', normal: 'Holds against full resistance — 5/5 (classic L5 test)' },
|
||||
{ label: 'Strength — ankle plantarflexion (S1)', method: 'Patient pushes foot down against examiner\'s hand at the ball. OR ask patient to toe-walk 10 steps (more sensitive — unilateral plantarflexion weakness shows immediately).', normal: 'Full power; toe-walks symmetrically — 5/5' }
|
||||
], abnormalHints: ['Focal weakness → localise by myotome', 'Pronator drift (subtle UMN, always check even with 5/5)', 'Spasticity / catch (UMN)', 'Atrophy (LMN, disuse)', 'Fasciculations (MND, ALS)', 'Pseudohypertrophy of calves (DMD in a young male)'] },
|
||||
|
||||
{ name: 'Deep tendon reflexes', steps: [
|
||||
{ label: 'Biceps (C5-C6)', method: 'Thumb on tendon, strike', normal: '2+ symmetric' },
|
||||
{ label: 'Brachioradialis (C5-C6)', method: 'Strike distal radius', normal: '2+ symmetric' },
|
||||
{ label: 'Triceps (C7)', method: 'Strike triceps tendon', normal: '2+ symmetric' },
|
||||
{ label: 'Finger flexors (Hoffmann)', method: 'Flick distal phalanx of middle finger', normal: 'No thumb flexion (negative Hoffmann)' },
|
||||
{ label: 'Patellar (L3-L4)', method: 'Knees hanging, strike tendon', normal: '2+ symmetric' },
|
||||
{ label: 'Achilles (S1)', method: 'Slight dorsiflexion, strike tendon', normal: '2+ symmetric' },
|
||||
{ label: 'Plantar response (Babinski)', method: 'Stroke lateral sole heel to toes', normal: 'Down-going bilaterally' },
|
||||
{ label: 'Ankle clonus', method: 'Rapid dorsiflexion', normal: 'No sustained clonus (≤3 beats)' }
|
||||
], abnormalHints: ['Hyperreflexia with clonus (UMN: MS, myelopathy, cord lesion)', 'Hyporeflexia (GBS, neuropathy, myopathy, hypothyroid)', 'Asymmetry', 'Positive Hoffmann (corticospinal)', 'Up-going Babinski', 'Sustained clonus'] },
|
||||
{ name: 'Deep tendon reflexes',
|
||||
significance: 'Reflex pattern (increased, decreased, asymmetric) localises UMN vs LMN vs root vs peripheral nerve. Inexpensive and fast, but asymmetry is the most informative finding.',
|
||||
pearl: 'A reinforced reflex is still a reflex. If you can\'t elicit it initially, use Jendrassik (teeth clench or pull interlocked fingers apart) to boost — absent reflexes without reinforcement aren\'t truly absent.',
|
||||
steps: [
|
||||
{ label: 'Biceps (C5–C6)', method: 'Patient\'s arm relaxed across lap. Examiner places thumb firmly on biceps tendon at the cubital fossa, strikes thumb with reflex hammer. Compare both sides sequentially.', normal: '2+ symmetric — visible contraction of biceps, slight elbow flexion' },
|
||||
{ label: 'Brachioradialis (C5–C6)', method: 'Arm relaxed. Strike the distal radius about 3 cm proximal to the wrist, on its radial (thumb) side.', normal: '2+ symmetric — elbow flexion and slight forearm supination' },
|
||||
{ label: 'Triceps (C7)', method: 'Support the patient\'s arm at the wrist with elbow at 90°. Strike the triceps tendon just above the olecranon.', normal: '2+ symmetric — triceps contraction, slight elbow extension' },
|
||||
{ label: 'Finger flexors — Hoffmann sign', method: 'Grasp the middle finger\'s distal phalanx, flick it downward quickly and release. Watch the thumb and index finger.', normal: 'Negative — no thumb flexion, no index flexion (positive = corticospinal tract dysfunction)' },
|
||||
{ label: 'Patellar (L3–L4)', method: 'Patient sits with knees hanging freely off the table. Strike the patellar tendon just below the patella.', normal: '2+ symmetric — quadriceps contraction with knee extension' },
|
||||
{ label: 'Achilles (S1)', method: 'Patient\'s knee slightly flexed and leg externally rotated, or kneeling on a chair. Slightly dorsiflex the foot and strike the Achilles tendon.', normal: '2+ symmetric — plantar flexion of the foot' },
|
||||
{ label: 'Plantar response (Babinski)', method: 'Stroke the lateral aspect of the sole firmly from the heel toward the little toe, then curve across the ball of the foot.', normal: 'Toes flex downward (plantar flexion, "down-going") bilaterally in anyone ≥ 2 years' },
|
||||
{ label: 'Ankle clonus', method: 'Knee slightly bent. Support the shin with one hand, quickly and sharply dorsiflex the foot with the other, hold in dorsiflexion.', normal: '≤ 3 non-sustained beats is acceptable; sustained rhythmic oscillation = pathological clonus (UMN)' }
|
||||
], abnormalHints: ['Hyperreflexia + sustained clonus = UMN (MS, myelopathy, cord lesion, stroke)', 'Symmetric hyporeflexia = peripheral polyneuropathy, GBS, myopathy, hypothyroid, B12 deficiency', 'Asymmetric hyporeflexia = radiculopathy at that segment', 'Hoffmann positive = corticospinal tract dysfunction at cervical cord or above', 'Up-going Babinski after age 2 = UMN (always abnormal)'] },
|
||||
|
||||
{ name: 'Sensory', steps: [
|
||||
{ label: 'Light touch — upper', method: 'Cotton wisp, dorsum of hands, eyes closed', normal: 'Intact, symmetric' },
|
||||
|
|
@ -696,48 +769,95 @@
|
|||
return;
|
||||
}
|
||||
var section = group[currentSystem];
|
||||
var accent = currentSystem === 'msk' ? '#0891b2' : '#7c3aed'; // cyan for MSK, purple for neuro
|
||||
var accentTint = currentSystem === 'msk' ? '#ecfeff' : '#f5f3ff';
|
||||
var html = '';
|
||||
html += '<div class="card" style="margin-bottom:12px;">';
|
||||
html += ' <div class="card-header"><h3><i class="fas fa-circle-info"></i> OSCE Overview — ' + esc(group.label) + ' (' + (currentSystem === 'msk' ? 'MSK' : 'Neuro') + ')</h3></div>';
|
||||
html += ' <div style="padding:12px 16px;font-size:13px;line-height:1.6;color:var(--g700);">' + esc(section.overview) + '</div>';
|
||||
|
||||
// ─ Overview banner ─
|
||||
html += '<div class="card" style="margin-bottom:14px;border-left:4px solid ' + accent + ';">';
|
||||
html += ' <div class="card-header" style="border:0;padding:14px 16px 6px;"><h3 style="margin:0;font-size:16px;color:' + accent + ';"><i class="fas fa-' + (currentSystem === 'msk' ? 'bone' : 'brain') + '"></i> ' + esc(group.label) + ' — ' + (currentSystem === 'msk' ? 'Musculoskeletal' : 'Neurologic') + '</h3></div>';
|
||||
html += ' <div style="padding:0 16px 14px;font-size:13px;line-height:1.65;color:var(--g700);">' + esc(section.overview) + '</div>';
|
||||
html += '</div>';
|
||||
|
||||
section.components.forEach(function (c, ci) {
|
||||
html += '<div class="card" style="margin-bottom:10px;">';
|
||||
html += ' <div class="card-header" style="display:flex;align-items:center;justify-content:space-between;">';
|
||||
html += ' <h3 style="margin:0;">' + esc(c.name) + '</h3>';
|
||||
html += ' <span style="font-size:11px;color:var(--g500);">' + c.steps.length + ' steps</span>';
|
||||
// ─ Grading scales reference (collapsible) ─
|
||||
var scaleKeys = SYSTEM_SCALES[currentSystem] || [];
|
||||
if (scaleKeys.length) {
|
||||
html += '<details class="card" style="margin-bottom:14px;background:' + accentTint + ';border:1px solid ' + accent + '33;">';
|
||||
html += ' <summary style="padding:10px 16px;cursor:pointer;font-weight:600;font-size:13px;color:' + accent + ';list-style:none;display:flex;align-items:center;gap:8px;"><i class="fas fa-book-medical"></i> Grading scales & reference <span style="font-weight:400;color:var(--g500);font-size:11px;">(click to expand)</span></summary>';
|
||||
html += ' <div style="padding:0 16px 14px;display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:14px;">';
|
||||
scaleKeys.forEach(function (sk) {
|
||||
var sc = SCALES[sk]; if (!sc) return;
|
||||
html += '<div style="background:#fff;border:1px solid var(--g200);border-radius:8px;padding:10px 12px;">';
|
||||
html += ' <div style="font-weight:600;font-size:12px;color:var(--g800);margin-bottom:6px;"><i class="fas ' + sc.icon + '" style="color:' + accent + ';margin-right:6px;"></i>' + esc(sc.title) + '</div>';
|
||||
html += ' <table style="width:100%;font-size:11px;line-height:1.5;border-collapse:collapse;">';
|
||||
sc.rows.forEach(function (row) {
|
||||
html += '<tr><td style="padding:2px 0;white-space:nowrap;color:' + accent + ';font-weight:600;font-family:ui-monospace,monospace;">' + esc(row[0]) + '</td><td style="padding:2px 0 2px 10px;color:var(--g700);">' + esc(row[1]) + '</td></tr>';
|
||||
});
|
||||
html += ' </table>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += ' </div>';
|
||||
html += ' <div style="padding:10px 16px;">';
|
||||
html += '</details>';
|
||||
}
|
||||
|
||||
// ─ Components ─
|
||||
section.components.forEach(function (c, ci) {
|
||||
html += '<div class="card" style="margin-bottom:12px;border-left:3px solid ' + accent + ';">';
|
||||
|
||||
// Header
|
||||
html += ' <div style="padding:12px 16px 8px;display:flex;align-items:center;justify-content:space-between;gap:10px;">';
|
||||
html += ' <h3 style="margin:0;font-size:15px;color:var(--g800);">' + esc(c.name) + '</h3>';
|
||||
html += ' <span style="font-size:10px;font-weight:600;padding:3px 8px;border-radius:10px;background:' + accent + '22;color:' + accent + ';text-transform:uppercase;letter-spacing:0.5px;white-space:nowrap;">' + c.steps.length + ' steps</span>';
|
||||
html += ' </div>';
|
||||
|
||||
// Significance line
|
||||
if (c.significance) {
|
||||
html += ' <div style="padding:0 16px 6px;font-size:12px;color:var(--g600);line-height:1.55;"><i class="fas fa-bullseye" style="color:' + accent + ';margin-right:6px;font-size:11px;"></i>' + esc(c.significance) + '</div>';
|
||||
}
|
||||
|
||||
// Teaching pearl
|
||||
if (c.pearl) {
|
||||
html += ' <div style="margin:6px 16px 10px;padding:10px 12px;background:#fef7e7;border-left:3px solid #f59e0b;border-radius:0 6px 6px 0;font-size:12px;line-height:1.6;color:#78350f;"><i class="fas fa-lightbulb" style="color:#f59e0b;margin-right:6px;"></i><em>' + esc(c.pearl) + '</em></div>';
|
||||
}
|
||||
|
||||
// Steps
|
||||
html += ' <div style="padding:4px 12px 10px;">';
|
||||
c.steps.forEach(function (s, si) {
|
||||
var key = currentSystem + '-' + ci + '-' + si;
|
||||
if (!state[key]) state[key] = { component: c.name, label: s.label, method: s.method, normal: s.normal, status: null, note: '' };
|
||||
var st = state[key].status;
|
||||
html += '<div class="pe-step" data-pe-key="' + key + '" style="padding:8px 10px;border:1px solid var(--g200);border-radius:8px;margin-bottom:6px;' + (st === 'normal' ? 'background:#ecfdf5;' : st === 'abnormal' ? 'background:#fef2f2;' : '') + '">';
|
||||
var rowBg = st === 'normal' ? '#ecfdf5' : st === 'abnormal' ? '#fef2f2' : 'transparent';
|
||||
var rowBorder = st === 'normal' ? '#a7f3d0' : st === 'abnormal' ? '#fecaca' : 'var(--g200)';
|
||||
html += '<div class="pe-step" data-pe-key="' + key + '" style="padding:10px 12px;border:1px solid ' + rowBorder + ';border-radius:8px;margin-bottom:6px;background:' + rowBg + ';transition:background 0.12s,border-color 0.12s;">';
|
||||
html += ' <div style="display:flex;align-items:flex-start;gap:10px;">';
|
||||
html += ' <div style="flex-shrink:0;width:22px;height:22px;border-radius:50%;background:' + accent + '22;color:' + accent + ';font-size:11px;font-weight:700;display:flex;align-items:center;justify-content:center;line-height:1;">' + (si + 1) + '</div>';
|
||||
html += ' <div style="flex:1;min-width:0;">';
|
||||
html += ' <div style="font-weight:600;font-size:13px;color:var(--g800);">' + (si + 1) + '. ' + esc(s.label) + '</div>';
|
||||
html += ' <div style="font-size:12px;color:var(--g600);margin-top:2px;line-height:1.5;"><strong>Method:</strong> ' + esc(s.method) + '</div>';
|
||||
html += ' <div style="font-size:12px;color:var(--g600);line-height:1.5;"><strong>Expected:</strong> ' + esc(s.normal) + '</div>';
|
||||
html += ' <div style="font-weight:600;font-size:13px;color:var(--g800);">' + esc(s.label) + '</div>';
|
||||
html += ' <div style="font-size:12px;color:var(--g600);margin-top:3px;line-height:1.55;"><span style="color:var(--g500);font-weight:600;text-transform:uppercase;letter-spacing:0.5px;font-size:10px;">How</span> ' + esc(s.method) + '</div>';
|
||||
html += ' <div style="font-size:12px;color:var(--g600);margin-top:2px;line-height:1.55;"><span style="color:#059669;font-weight:600;text-transform:uppercase;letter-spacing:0.5px;font-size:10px;">Normal</span> ' + esc(s.normal) + '</div>';
|
||||
html += ' </div>';
|
||||
html += ' <div style="display:flex;gap:3px;flex-shrink:0;">';
|
||||
html += ' <button class="visit-status-btn ' + (st === 'normal' ? 'done' : '') + '" data-pe-status="normal" data-pe-key="' + key + '">✓</button>';
|
||||
html += ' <button class="visit-status-btn ' + (st === 'abnormal' ? 'refused' : '') + '" data-pe-status="abnormal" data-pe-key="' + key + '">✗</button>';
|
||||
html += ' <button class="visit-status-btn ' + (st === null ? 'not-due' : '') + '" data-pe-status="skip" data-pe-key="' + key + '">—</button>';
|
||||
html += ' <button class="visit-status-btn ' + (st === 'normal' ? 'done' : '') + '" data-pe-status="normal" data-pe-key="' + key + '" title="Normal">✓</button>';
|
||||
html += ' <button class="visit-status-btn ' + (st === 'abnormal' ? 'refused' : '') + '" data-pe-status="abnormal" data-pe-key="' + key + '" title="Abnormal">✗</button>';
|
||||
html += ' <button class="visit-status-btn ' + (st === null ? 'not-due' : '') + '" data-pe-status="skip" data-pe-key="' + key + '" title="Skip">—</button>';
|
||||
html += ' </div>';
|
||||
html += ' </div>';
|
||||
html += ' <div class="pe-abnormal-detail" style="margin-top:6px;' + (st === 'abnormal' ? '' : 'display:none;') + '">';
|
||||
html += ' <input type="text" class="pe-note" data-pe-key="' + key + '" placeholder="Describe the abnormal finding (e.g., 4/5 strength, left pronator drift)" value="' + esc(state[key].note || '') + '" style="width:100%;padding:5px 8px;font-size:12px;border:1px solid var(--g300);border-radius:6px;">';
|
||||
html += ' <div class="pe-abnormal-detail" style="margin-top:8px;' + (st === 'abnormal' ? '' : 'display:none;') + '">';
|
||||
html += ' <input type="text" class="pe-note" data-pe-key="' + key + '" placeholder="Describe the abnormal finding (e.g., 4/5 strength, left pronator drift)" value="' + esc(state[key].note || '') + '" style="width:100%;padding:6px 10px;font-size:12px;border:1px solid #fecaca;border-radius:6px;background:#fff;">';
|
||||
html += ' </div>';
|
||||
html += '</div>';
|
||||
});
|
||||
html += ' </div>';
|
||||
|
||||
// Watch-for
|
||||
if (c.abnormalHints && c.abnormalHints.length) {
|
||||
html += '<div style="margin-top:8px;padding:8px 10px;background:var(--g50);border-radius:6px;font-size:11px;color:var(--g600);line-height:1.6;">';
|
||||
html += '<strong>Watch for:</strong> ' + c.abnormalHints.map(esc).join(' · ');
|
||||
html += '<div style="margin:0 12px 12px;padding:10px 12px;background:#fef2f2;border-left:3px solid #ef4444;border-radius:0 6px 6px 0;font-size:11px;line-height:1.7;color:#7f1d1d;">';
|
||||
html += '<div style="font-weight:600;text-transform:uppercase;letter-spacing:0.5px;color:#991b1b;margin-bottom:4px;font-size:10px;"><i class="fas fa-triangle-exclamation"></i> Watch for</div>';
|
||||
html += c.abnormalHints.map(esc).join(' · ');
|
||||
html += '</div>';
|
||||
}
|
||||
html += ' </div>'; // end padding
|
||||
html += '</div>'; // end card
|
||||
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
content.innerHTML = html;
|
||||
|
|
@ -760,7 +880,9 @@
|
|||
state[key].status = (status === 'skip') ? null : status;
|
||||
var row = btn.closest('.pe-step');
|
||||
if (row) {
|
||||
row.style.background = state[key].status === 'normal' ? '#ecfdf5' : state[key].status === 'abnormal' ? '#fef2f2' : '';
|
||||
var s = state[key].status;
|
||||
row.style.background = s === 'normal' ? '#ecfdf5' : s === 'abnormal' ? '#fef2f2' : 'transparent';
|
||||
row.style.borderColor = s === 'normal' ? '#a7f3d0' : s === 'abnormal' ? '#fecaca' : 'var(--g200)';
|
||||
row.querySelectorAll('[data-pe-status]').forEach(function (b) {
|
||||
b.classList.remove('done', 'refused', 'not-due');
|
||||
if (b.dataset.peStatus === 'normal' && state[key].status === 'normal') b.classList.add('done');
|
||||
|
|
|
|||
Loading…
Reference in a new issue