Frontend Structure
ui/
├── public/
│ └── index.html
├── src/
│ ├── App.js # Root component, module switcher
│ ├── index.js # ReactDOM.render
│ ├── index.css # Tailwind directives + globals
│ ├── reportWebVitals.js # CRA default
│ ├── setupTests.js # Jest setup
│ ├── contexts/
│ │ ├── AuthContext.js # Current user, login/logout
│ │ └── TenantContext.js # Active clinic, tenants list
│ ├── hooks/
│ │ ├── usePatients.js
│ │ └── useAppointments.js
│ ├── services/
│ │ ├── index.js # createApiServices() factory
│ │ ├── mockData.js # Static mock data
│ │ ├── core/
│ │ │ ├── httpClient.js # fetch wrapper
│ │ │ └── apiResult.js # { ok, data, error } wrapper
│ │ └── adapters/
│ │ ├── http/
│ │ │ ├── auth.service.js
│ │ │ ├── patients.service.js
│ │ │ ├── appointments.service.js
│ │ │ ├── invoices.service.js
│ │ │ ├── pharmacy.service.js
│ │ │ └── schedules.service.js
│ │ └── mock/
│ │ ├── mockDb.js
│ │ ├── auth.service.js
│ │ ├── patients.service.js
│ │ ├── appointments.service.js
│ │ ├── invoices.service.js
│ │ ├── pharmacy.service.js
│ │ └── schedules.service.js
│ ├── lib/
│ │ ├── api.js # base URL resolution, token reader
│ │ └── normalizers.js # snake_case → UI shape
│ └── components/
│ ├── auth/AuthModule.js # Login/register screen
│ ├── layout/Sidebar.js # Main nav
│ ├── dashboard/
│ │ ├── Dashboard.js
│ │ └── RemindersWidget.js
│ ├── patients/
│ │ ├── PatientsModule.js
│ │ ├── PatientForm.js
│ │ ├── PatientProfile.js
│ │ ├── PatientDetails.js
│ │ └── PatientEditForm.js
│ ├── appointments/
│ │ ├── AppointmentsModule.js
│ │ └── AppointmentForm.js
│ ├── prescriptions/
│ │ ├── PrescriptionsModule.js
│ │ ├── PrescriptionForm.js
│ │ └── DispenseMedicine.js
│ ├── reminders/RemindersModule.js
│ ├── medical/MedicalModule.js
│ ├── inventory/
│ │ ├── InventoryDashboard.js
│ │ ├── MedicineInventory.js
│ │ ├── AddMedicineForm.js
│ │ ├── StockAdjustmentForm.js
│ │ ├── StockAdjustmentModal.js
│ │ ├── StockHistoryModal.js
│ │ └── StockMovementHistory.js
│ ├── billing/BillingModule.js
│ ├── reports/ReportsModule.js
│ ├── tenant/TenantSwitcher.js
│ └── common/
│ ├── Badge.js
│ ├── Button.js
│ └── Table.js
├── tailwind.config.js
├── postcss.config.js
└── package.json
Module switcher
App.js keeps the currently active feature in local state:
const [activeModule, setActiveModule] = useState('dashboard');
// Sidebar calls setActiveModule(...) when you click a nav item
// The main area renders:
// dashboard → <Dashboard />
// patients → <PatientsModule />
// appointments → <AppointmentsModule />
// prescriptions→ <PrescriptionsModule />
// reminders → <RemindersModule />
// medical → <MedicalModule />
// inventory → <InventoryDashboard />
// billing → <BillingModule />
// reports → <ReportsModule />
There is no URL routing today — navigation is via state. Swapping in react-router is a drop-in change because every module is self-contained.
File-type conventions
*Module.js— top-level feature page (loads data, renders table/forms).*Form.js— standalone form (often shown in a modal).*Profile.js,*Details.js— detail views.*Modal.js— modal-specific components.*.service.js— adapter-layer services underservices/adapters/.use*.js— custom hooks.
Import aliases
No jsconfig.json aliases are configured today. Imports use relative paths (../../services). If this gets painful, a jsconfig.json with "baseUrl": "src" turns them into absolute imports without any build change.
Environment variables
Read at build time (CRA convention — must start with REACT_APP_):
| Variable | Default | Purpose |
|---|---|---|
REACT_APP_API_BASE_URL | Dynamic from window.location | API root (e.g. https://app.vetty.example.com/api/v1) |
REACT_APP_API_MODE | http | Set to mock for offline/demo |
Drop them into ui/.env.local for local development. Remember that CRA rebuilds when these change — restart npm start.