feat: complete Jane App feature set with Telehealth, Retail POS, Memberships, Multi-Discipline Charting, Waitlist & RBAC

This commit is contained in:
2026-09-05 15:15:35 -07:00
parent e681f066bb
commit dd5057d4e3
9 changed files with 1641 additions and 102 deletions
+83 -5
View File
@@ -1,18 +1,27 @@
'use client';
import React, { useState } from 'react';
import { SoapNote, Patient, Provider, SpinalAdjustmentEntry, CptCodeItem, Icd10CodeItem } from '@/types/clinical';
import {
SoapNote,
Patient,
Provider,
SpinalAdjustmentEntry,
CptCodeItem,
Icd10CodeItem,
ClinicalDiscipline,
} from '@/types/clinical';
import { SpineVisualizer } from '@/components/ui/SpineVisualizer';
import { AmbientAudioRecorder } from '@/components/ui/AmbientAudioRecorder';
import { STANDARD_CPT_CODES, STANDARD_ICD10_CODES } from '@/lib/mock-data';
import { STANDARD_CPT_CODES, STANDARD_ICD10_CODES, DISCIPLINE_PRESETS } from '@/lib/mock-data';
import {
FileText,
CheckCircle,
Copy,
DollarSign,
ChevronLeft,
Tag,
ShieldCheck,
Video,
Layers,
} from 'lucide-react';
interface SoapChartEditorProps {
@@ -21,6 +30,7 @@ interface SoapChartEditorProps {
initialSoapNote?: SoapNote;
onSaveSoapNote: (note: SoapNote) => void;
onGenerateSuperbill: (note: SoapNote) => void;
onLaunchTelehealth: () => void;
onBackToCalendar: () => void;
}
@@ -30,8 +40,13 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
initialSoapNote,
onSaveSoapNote,
onGenerateSuperbill,
onLaunchTelehealth,
onBackToCalendar,
}) => {
const [discipline, setDiscipline] = useState<ClinicalDiscipline>(
initialSoapNote?.discipline || 'chiropractic'
);
const [subjective, setSubjective] = useState(
initialSoapNote?.subjective ||
`Patient presents for scheduled visit ${patient.carePlan.completedVisits + 1} of ${
@@ -93,6 +108,23 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
if (matchedCpts.length > 0) setSelectedCpt(matchedCpts);
};
const handleSwitchDiscipline = (newDiscipline: ClinicalDiscipline) => {
setDiscipline(newDiscipline);
const preset = DISCIPLINE_PRESETS[newDiscipline];
if (preset) {
setSubjective(preset.sampleSubjective);
setObjective(preset.sampleObjective);
setAssessment(preset.sampleAssessment);
setPlan(preset.samplePlan);
const matchedIcds = STANDARD_ICD10_CODES.filter((icd) => preset.icd10Codes.includes(icd.code));
if (matchedIcds.length > 0) setSelectedIcd10(matchedIcds);
const matchedCpts = STANDARD_CPT_CODES.filter((cpt) => preset.cptCodes.includes(cpt.code));
if (matchedCpts.length > 0) setSelectedCpt(matchedCpts);
}
};
const handleToggleAdjustment = (entry: SpinalAdjustmentEntry) => {
setAdjustments((prev) => {
const exists = prev.some((a) => a.vertebra === entry.vertebra);
@@ -125,6 +157,7 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
providerName: provider.name,
date: '2026-09-05',
status: 'signed',
discipline,
vasScore,
subjective,
objective,
@@ -150,6 +183,7 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
providerName: provider.name,
date: '2026-09-05',
status: isSigned ? 'signed' : 'draft',
discipline,
vasScore,
subjective,
objective,
@@ -187,6 +221,11 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
<span className="text-xs px-2.5 py-0.5 rounded-full bg-sky-50 text-sky-800 border border-sky-200 font-semibold">
Care Plan: Visit {patient.carePlan.completedVisits + 1} of {patient.carePlan.totalVisits}
</span>
{patient.activeMembership && (
<span className="text-xs px-2 py-0.5 rounded-full bg-emerald-50 text-emerald-800 border border-emerald-200 font-bold">
{patient.activeMembership}
</span>
)}
{isSigned ? (
<span className="text-xs px-2.5 py-0.5 rounded-full bg-emerald-50 text-emerald-700 border border-emerald-200 font-semibold flex items-center gap-1">
<ShieldCheck className="w-3.5 h-3.5" /> Signed &amp; Locked
@@ -209,7 +248,16 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
</div>
{/* Action buttons */}
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2">
<button
type="button"
onClick={onLaunchTelehealth}
className="px-3.5 py-2 rounded-lg bg-sky-100 hover:bg-sky-200 text-sky-900 text-xs font-bold flex items-center gap-1.5 transition border border-sky-200"
>
<Video className="w-3.5 h-3.5 text-sky-700" />
Launch Telehealth
</button>
<button
type="button"
onClick={handleCloneLastNote}
@@ -244,10 +292,40 @@ export const SoapChartEditor: React.FC<SoapChartEditorProps> = ({
</div>
</div>
{/* Multi-Discipline Template Presets Bar */}
<div className="bg-white border border-slate-200 rounded-xl p-3 shadow-xs flex flex-col sm:flex-row sm:items-center justify-between gap-3 text-xs">
<div className="flex items-center gap-2 font-bold text-slate-700">
<Layers className="w-4 h-4 text-sky-700" />
<span>Clinical Discipline Template:</span>
</div>
<div className="flex items-center gap-1 bg-slate-100 p-1 rounded-lg border border-slate-200">
{[
{ id: 'chiropractic', label: '🦴 Chiropractic (Spine CMT)' },
{ id: 'physical_therapy', label: '🏃 Physical Therapy & Rehab' },
{ id: 'acupuncture', label: '🪡 Acupuncture Meridian' },
{ id: 'massage_therapy', label: '💆 Medical Massage' },
].map((d) => (
<button
key={d.id}
type="button"
onClick={() => handleSwitchDiscipline(d.id as ClinicalDiscipline)}
className={`px-3 py-1.5 rounded-md font-semibold transition whitespace-nowrap ${
discipline === d.id
? 'bg-white text-sky-800 shadow-xs border border-slate-200/80'
: 'text-slate-600 hover:text-slate-900'
}`}
>
{d.label}
</button>
))}
</div>
</div>
{/* Ambient AI Scribe */}
<AmbientAudioRecorder onApplyExtractedSoap={handleApplyExtractedSoap} />
{/* Spine Subluxation Visualizer */}
{/* Spine Subluxation Visualizer (shown prominently for chiropractic & physical therapy) */}
<SpineVisualizer
adjustments={adjustments}
onToggleAdjustment={handleToggleAdjustment}