feat: full appointment feature (CRUD + Appointments tab)

Builds the complete appointment feature end-to-end, mirroring the medication
feature structure. The model existed but was orphaned dead code (no handlers,
routes, repo, or frontend) — this builds the whole stack.

Backend:
- models/appointment.rs: AppointmentData (deserializes the data blob, camelCase
  keys), AppointmentResponse (flat snake_case + From<Appointment>), Create/Update
  request structs, AppointmentRepository (create, find_by_user_filtered with
  optional status filter, find/update/delete by appointment_id). Mirrors the
  medication serialization pattern. Module added to models/mod.rs.
- handlers/appointments.rs: full CRUD (create/list/get/update/delete) returning
  AppointmentResponse. Routes wired in app.rs
  (POST/GET /api/appointments, GET/POST /:id, POST /:id/delete).
- 3 unit tests (AppointmentData deser, default status, response flatten).

Frontend:
- Appointment/Create/Update types; getAppointments/getAppointment/create/
  update/deleteAppointment in api.ts; useAppointmentStore.
- AppointmentsManager component (list with type+status chips, create/edit
  dialogs, delete confirm, empty state) as a 5th Dashboard tab.

Verified: backend fmt/clippy 0 warnings, 26 tests pass; frontend build clean,
20 tests pass.
This commit is contained in:
goose 2026-06-28 16:35:50 -03:00
parent 0921d73a6d
commit 4c4c29f72e
10 changed files with 1019 additions and 4 deletions

View file

@ -6,8 +6,9 @@ import { MedicationManager } from '../components/medication/MedicationManager';
import { HealthStats } from '../components/health/HealthStats';
import { InteractionsChecker } from '../components/interactions/InteractionsChecker';
import { ProfileEditor } from '../components/profile/ProfileEditor';
import { AppointmentsManager } from '../components/appointments/AppointmentsManager';
type TabIndex = 0 | 1 | 2 | 3;
type TabIndex = 0 | 1 | 2 | 3 | 4;
export const Dashboard: FC = () => {
const navigate = useNavigate();
@ -50,6 +51,7 @@ export const Dashboard: FC = () => {
<Tab label="Medications" />
<Tab label="Health" />
<Tab label="Interactions" />
<Tab label="Appointments" />
<Tab label="Profile" />
</Tabs>
@ -57,7 +59,8 @@ export const Dashboard: FC = () => {
{tab === 0 && <MedicationManager />}
{tab === 1 && <HealthStats />}
{tab === 2 && <InteractionsChecker />}
{tab === 3 && <ProfileEditor />}
{tab === 3 && <AppointmentsManager />}
{tab === 4 && <ProfileEditor />}
</Box>
</Container>
</>