Odontogram Tier 2 Implementation Plan
Odontogram Tier 2 Implementation Plan
Section titled “Odontogram Tier 2 Implementation Plan”For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Implement Tier 2 clinical charting features: Periogram View Toggle, Print-to-PDF export, and Tooth History Timeline, driven entirely by TDD.
Architecture:
- Tooth History Timeline: Enhance the existing history section in
ToothPanel.tsxinto a vertical timeline. We will add visual timeline nodes, and assume the API providescreated_atanddoctor_name(or mock them). - Print-to-PDF: Introduce a
@media printblock inOdontogram.css(orindex.css) that hides interactive panels/buttons, expands the chart, and reveals a hidden-by-default signature line. Add a Print button to the UI. - Periogram View: Add a view toggle to
Odontogram.tsx(Odontogram vs. Periodontogram). Create aPeriogramplaceholder component that renders a specialized grid for pocket depths, BOP, and recession.
Architecture Diagram:
graph TD classDef main fill:#007AFF0D,stroke:#007AFF,stroke-width:1px; classDef secondary fill:#00000008,stroke:#0000001A,stroke-width:1px;
subgraph OdontogramView ["Odontogram View"] direction TB O[Odontogram Orchestrator]:::main --> T[ToothSVG Chart]:::main O --> P[ToothPanel]:::main P --> H[History Timeline]:::main end
subgraph PeriogramView ["Periogram View"] direction TB O --> Peri[Periogram Grid]:::secondary end
subgraph PrintMode ["Print Mode"] direction TB O --> PR[Print Styles / Signature Line]:::secondary endTech Stack: React, TypeScript, Vitest, CSS @media print.
Task 1: Tooth History Timeline UI
Section titled “Task 1: Tooth History Timeline UI”Files:
-
Modify:
[ToothPanel.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/ToothPanel.tsx) -
Modify:
[ToothPanel.test.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/__tests__/ToothPanel.test.tsx) -
Modify:
[Odontogram.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/Odontogram.tsx) -
Step 1: Write the failing test
import React from 'react';import { render, screen } from '@testing-library/react';import { ToothPanel } from '../ToothPanel';
describe('Tooth History Timeline', () => { it('renders a chronological timeline with date and doctor name', () => { const historyData = [ { id: '1', tooth_fdi: 16, surfaces: ['O'], condition: 'caries', status: 'planned', created_at: '2026-06-25T10:00:00Z', doctor_name: 'Dr. House' } ];
const { container } = render( <ToothPanel patientId="123" isOpen={true} onClose={() => {}} selectedTooth={{ fdi: 16 }} catalog={[]} onSave={() => {}} history={historyData} /> );
// Expecting to find the timeline elements expect(screen.getByText('Dr. House')).toBeInTheDocument();
// Ensure the history list has a timeline line class const timelineContainer = container.querySelector('.border-l-2'); expect(timelineContainer).toBeInTheDocument(); });});- Step 2: Run test to verify it fails
Run: npm run test frontend/src/components/dental/__tests__/ToothPanel.test.tsx
Expected: FAIL.
- Step 3: Write minimal implementation
Update ChartFinding interface in Odontogram.tsx to include optional created_at?: string and doctor_name?: string.
In ToothPanel.tsx:
Replace the existing flat history map with a vertical timeline layout:
<div className="flex flex-col gap-0 border-l-2 border-border ml-2 pl-4 relative"> {historyEntries.map(item => ( <div key={item.id} className="relative mb-6"> <div className="absolute -left-[21px] top-1 w-3 h-3 bg-muted border-2 border-border rounded-full" /> <div className="text-xs text-muted-foreground mb-1"> {item.created_at ? new Date(item.created_at).toLocaleDateString() : 'Sin fecha'} • {item.doctor_name || 'Dr. Clínico'} </div> <div className="bg-muted/50 border border-border rounded-xl p-3 text-sm"> <div className="font-bold">{item.condition}</div> <div className="text-muted-foreground text-xs">{item.status}</div> </div> </div> ))}</div>- Step 4: Run test to verify it passes
Run: npm run test frontend/src/components/dental/__tests__/ToothPanel.test.tsx
Expected: PASS.
- Step 5: Commit
git add frontend/src/components/dental/ToothPanel.tsx frontend/src/components/dental/__tests__/ToothPanel.test.tsx frontend/src/components/dental/Odontogram.tsxgit commit -m "feat: implement chronological tooth history timeline in panel"Task 2: Print-to-PDF Styling and Action
Section titled “Task 2: Print-to-PDF Styling and Action”Files:
-
Modify:
[Odontogram.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/Odontogram.tsx) -
Modify:
[index.css](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/index.css) -
Modify:
[Odontogram.test.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/__tests__/Odontogram.test.tsx) -
Step 1: Write the failing test
import React from 'react';import { render, screen } from '@testing-library/react';import { Odontogram } from '../Odontogram';import { MemoryRouter } from 'react-router-dom';import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
describe('Odontogram Print', () => { it('renders a print button and a hidden signature line that becomes visible on print', () => { const { container } = render( <QueryClientProvider client={queryClient}> <MemoryRouter> <Odontogram patientId="123" patientName="Ana" /> </MemoryRouter> </QueryClientProvider> );
const printBtn = screen.getByText(/Imprimir/i); expect(printBtn).toBeInTheDocument();
const signature = container.querySelector('.print-signature'); expect(signature).toBeInTheDocument(); });});- Step 2: Run test to verify it fails
Run: npm run test frontend/src/components/dental/__tests__/Odontogram.test.tsx
Expected: FAIL.
- Step 3: Write minimal implementation
In Odontogram.tsx:
Add a “Imprimir” button next to the dentition toggles that calls window.print().
At the bottom of the component, add:
<div className="print-signature hidden mt-16 justify-around w-full"> <div className="text-center border-t border-black pt-2 w-1/3">Firma del Doctor</div> <div className="text-center border-t border-black pt-2 w-1/3">Firma del Paciente</div></div>In index.css:
@media print { .btn-icon, button, .PlanBuilderBar, .ToothPanel { display: none !important; } .print-signature { display: flex !important; } body { background: white; } .OdontogramContainer { border: none !important; box-shadow: none !important; }}- Step 4: Run test to verify it passes
Run: npm run test frontend/src/components/dental/__tests__/Odontogram.test.tsx
Expected: PASS.
- Step 5: Commit
git add frontend/src/components/dental/Odontogram.tsx frontend/src/index.css frontend/src/components/dental/__tests__/Odontogram.test.tsxgit commit -m "feat: add print-to-pdf layout and signature line"Task 3: Periogram View Toggle & Placeholder
Section titled “Task 3: Periogram View Toggle & Placeholder”Files:
-
Modify:
[Odontogram.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/Odontogram.tsx) -
Create:
[Periogram.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/Periogram.tsx) -
Test:
[Odontogram.test.tsx](file:///home/ernestnumar/Escritorio/ClinicFlow/frontend/src/components/dental/__tests__/Odontogram.test.tsx) -
Step 1: Write the failing test
import React from 'react';import { render, screen, fireEvent } from '@testing-library/react';import { Odontogram } from '../Odontogram';import { MemoryRouter } from 'react-router-dom';import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
describe('Periogram Toggle', () => { it('toggles between Odontograma and Periodontograma views', () => { render( <QueryClientProvider client={queryClient}> <MemoryRouter> <Odontogram patientId="123" /> </MemoryRouter> </QueryClientProvider> );
// Should start in Odontograma expect(screen.getByText('Odontograma')).toBeInTheDocument();
// Find toggle and switch const perioBtn = screen.getByRole('button', { name: /Periodontograma/i }); fireEvent.click(perioBtn);
// Periogram UI should render expect(screen.getByText('Métricas Periodontales')).toBeInTheDocument(); });});- Step 2: Run test to verify it fails
Run: npm run test frontend/src/components/dental/__tests__/Odontogram.test.tsx
Expected: FAIL.
- Step 3: Write minimal implementation
Create Periogram.tsx:
import React from 'react';
export const Periogram: React.FC = () => { return ( <div className="w-full min-h-[500px] flex items-center justify-center bg-muted/20 rounded-xl border border-border"> <div className="text-center"> <h3 className="text-xl font-bold mb-2">Métricas Periodontales</h3> <p className="text-muted-foreground">Tabla de recesión, bolsa periodontal y sangrado (Próximamente)</p> </div> </div> );};In Odontogram.tsx:
Add state: const [activeView, setActiveView] = useState<'odontogram' | 'periogram'>('odontogram');
Add toggle buttons above the dentitionMode toggles:
<div className="flex gap-4 mb-4 border-b border-border pb-4 w-full"> <button onClick={() => setActiveView('odontogram')} className={activeView === 'odontogram' ? 'font-bold' : ''}>Odontograma</button> <button onClick={() => setActiveView('periogram')} className={activeView === 'periogram' ? 'font-bold' : ''}>Periodontograma</button></div>Conditionally render <FullOdontogram /> or <Periogram />.
- Step 4: Run test to verify it passes
Run: npm run test frontend/src/components/dental/__tests__/Odontogram.test.tsx
Expected: PASS.
- Step 5: Commit
git add frontend/src/components/dental/Odontogram.tsx frontend/src/components/dental/Periogram.tsx frontend/src/components/dental/__tests__/Odontogram.test.tsxgit commit -m "feat: implement periogram view toggle and placeholder component"