Components
Top-level feature pages are the *Module.js files; everything else is either a subcomponent of a module or a shared primitive in components/common/.
Top-level layout
App.js
Holds the module switch and the chrome (sidebar + header). Pseudocode:
function App() {
const { isAuthenticated } = useAuth();
const [activeModule, setActiveModule] = useState('dashboard');
if (!isAuthenticated) return <AuthModule />;
return (
<div className="flex min-h-screen">
<Sidebar active={activeModule} onSelect={setActiveModule} />
<main className="flex-1 p-6">
{activeModule === 'dashboard' && <Dashboard />}
{activeModule === 'patients' && <PatientsModule />}
{activeModule === 'appointments' && <AppointmentsModule />}
{activeModule === 'prescriptions' && <PrescriptionsModule />}
{activeModule === 'reminders' && <RemindersModule />}
{activeModule === 'medical' && <MedicalModule />}
{activeModule === 'inventory' && <InventoryDashboard />}
{activeModule === 'billing' && <BillingModule />}
{activeModule === 'reports' && <ReportsModule />}
</main>
</div>
);
}
components/auth/AuthModule.js
Unauthenticated entry point. Two tabs: Login and Register. Calls useAuth().login(email, password) or the owner-registration service.
components/layout/Sidebar.js
- Reads the current tenant from
TenantContextto render clinic name/logo at the top. - Renders nav items with icons and an active highlight.
- Footer shows clinic address/phone/email.
- Exposes a callback
onSelect(moduleKey)soAppcan update state.
components/tenant/TenantSwitcher.js
Dropdown in the top-right of App.js. Lists every tenant from TenantContext and calls switchTenant(id) on click.
Dashboard module
Dashboard.js
- On mount, calls
services.patients.list(),services.appointments.list(),services.invoices.list()in parallel. - Normalizes results with
lib/normalizers.js. - Derives stats:
- Total patients.
- Appointments this week.
- Pending revenue = sum of
invoice.balance. - Completed visits.
- Renders
RemindersWidgetin the side column.
RemindersWidget.js
Displays the upcoming reminders for the current tenant. Stubbed against services.patients/mock data today; ready to be wired to a dedicated reminders endpoint.
Patients module
PatientsModule.js
- Table of pets with columns: Name, Species, Breed, Age, Owner, Status, Last Visit, Actions.
- "Add Patient" opens
PatientFormin a modal. - Row click opens
PatientProfile. - Patient creation/update goes through
services.patients.create(...)/services.patients.update(...)and updates local state on success.
PatientForm.js
Form for both "Add" and "Edit" modes. Uses useState for each field; no form library. Submits to the right service method based on whether an id is present.
PatientProfile.js
Detail pane — shows demographics, medical flags, allergies, appointments history (fetched from services.appointments filtered to the pet).
PatientDetails.js / PatientEditForm.js
Alternate/legacy detail and edit components. One can be retired during consolidation.
Appointments module
AppointmentsModule.js
Paginated list of upcoming and past appointments with status badges. Row actions let a staffer check in, complete or cancel an appointment — each maps to services.appointments.updateStatus(...).
AppointmentForm.js
- Scheduling form — pet picker, client picker (auto-filled from pet), vet picker, appointment type, start/end date-time, reason.
booking_sourcedefaults toclinic_portal.
Billing module
BillingModule.js
- Two tabs: Invoices and Payments.
- Invoice list shows number, client, grand total, balance, status badge.
- Inline "Record payment" action creates a
Paymentagainst the invoice viaservices.invoices.recordPayment().
Inventory module
InventoryDashboard.js
Parent layout with nav tabs: Medicines, Movements, Add medicine.
MedicineInventory.js
Table of current stock. Fetches from services.pharmacy.
AddMedicineForm.js, StockAdjustmentForm.js, StockAdjustmentModal.js, StockHistoryModal.js, StockMovementHistory.js
Form and modal components for CRUD on inventory items and stock movements.
Prescriptions module
PrescriptionsModule.js
List of prescriptions per patient.
PrescriptionForm.js / DispenseMedicine.js
Issue a prescription and mark it as dispensed against inventory.
Reminders module
RemindersModule.js
List of reminders with filters by type (vaccination, deworming, follow_up, payment_due, general) and status (pending, sent, dismissed, failed). Allows marking as "dismissed" or "sent".
Medical records module
MedicalModule.js
Stub today — placeholder for clinical notes and treatment plans. A future expansion target.
Reports module
ReportsModule.js
Stub today. Expected to surface aggregate metrics per month (revenue, appointments, species mix).
Shared primitives
common/Badge.js
Renders a pill with colour derived from the status:
<Badge status="paid" /> // green
<Badge status="partial" /> // amber
<Badge status="void" /> // gray
common/Button.js
A styled button wrapping <button>. Variants: primary, secondary, ghost, danger.
common/Table.js
Reusable table component with:
columns— array of{ key, label, render?(row) }.rows— data.onRowClick?(row)— optional.- Basic pagination when
rows.length > pageSize.
Patterns
- Data fetching happens in the top-level module component and is passed to children as props. Sub-components don't call services themselves.
- Forms use local
useState; no Formik/React Hook Form. For a future large-form overhaul, adopting RHF would reduce boilerplate. - Modals are implemented as portals to
<body>via React's nativecreatePortal. CSS comes from Tailwind utilities (fixed inset-0 bg-black/50 …). - Empty / loading / error states are handled inline inside each module with conditional rendering. Extracting a
<DataState>wrapper would be a nice refactor.