refactor(frontend): modularize App.tsx into components, pages, hooks, and context (#60)
- Extract App.tsx from 764 lines to ~404 lines - Create reusable components: MedDetailModal, MobileEditModal, ShareDialog, etc. - Add AppContext for global state management - Split pages: DashboardPage, MedicationsPage, SchedulePage, SettingsPage, PlannerPage - Create custom hooks: useAuth, useMedications, useSettings, useDoses, useSchedule - Add utility functions in separate modules - Fix stock status logic (>30 days = green/normal) - Fix reminder threshold calculation (use reminderDaysBefore not lowStockDays) - Fix takenBy validation (send [] instead of null) - Fix datetime format for blister start times (add Z suffix) - Style 'All OK' status as green/bold BREAKING: None - all existing functionality preserved
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
// =============================================================================
|
||||
// Core Types for MedAssist
|
||||
// =============================================================================
|
||||
|
||||
export type Blister = {
|
||||
usage: number;
|
||||
every: number;
|
||||
start: string;
|
||||
};
|
||||
|
||||
export type Medication = {
|
||||
id: number;
|
||||
name: string;
|
||||
genericName?: string | null;
|
||||
takenBy: string[];
|
||||
packCount: number;
|
||||
blistersPerPack: number;
|
||||
pillsPerBlister: number;
|
||||
looseTablets: number;
|
||||
stockAdjustment?: number;
|
||||
lastStockCorrectionAt?: string | null;
|
||||
pillWeightMg?: number | null;
|
||||
blisters: Blister[];
|
||||
imageUrl?: string | null;
|
||||
expiryDate?: string | null;
|
||||
notes?: string | null;
|
||||
intakeRemindersEnabled?: boolean;
|
||||
updatedAt: string | number | null;
|
||||
};
|
||||
|
||||
export type PlannerRow = {
|
||||
medicationId: number;
|
||||
medicationName: string;
|
||||
totalPills: number;
|
||||
plannerUsage: number;
|
||||
blisterSize: number;
|
||||
blistersNeeded: number;
|
||||
fullBlisters: number;
|
||||
loosePills: number;
|
||||
enough: boolean;
|
||||
};
|
||||
|
||||
export type RefillEntry = {
|
||||
id: number;
|
||||
packsAdded: number;
|
||||
loosePillsAdded: number;
|
||||
refillDate: string;
|
||||
};
|
||||
|
||||
export type FormBlister = {
|
||||
usage: string;
|
||||
every: string;
|
||||
startDate: string;
|
||||
startTime: string;
|
||||
};
|
||||
|
||||
export type FormState = {
|
||||
name: string;
|
||||
genericName: string;
|
||||
takenBy: string[];
|
||||
packCount: string;
|
||||
blistersPerPack: string;
|
||||
pillsPerBlister: string;
|
||||
looseTablets: string;
|
||||
pillWeightMg: string;
|
||||
expiryDate: string;
|
||||
notes: string;
|
||||
intakeRemindersEnabled: boolean;
|
||||
blisters: FormBlister[];
|
||||
};
|
||||
|
||||
export type FieldErrors = {
|
||||
name?: string;
|
||||
genericName?: string;
|
||||
takenBy?: string;
|
||||
notes?: string;
|
||||
};
|
||||
|
||||
export type Coverage = {
|
||||
name: string;
|
||||
medsLeft: number;
|
||||
daysLeft: number | null;
|
||||
depletionDate: string | null;
|
||||
depletionTime: number | null;
|
||||
nextDose: string | null;
|
||||
};
|
||||
|
||||
export type StockStatus = {
|
||||
level: "out-of-stock" | "low" | "normal" | "high";
|
||||
className: string;
|
||||
label: string;
|
||||
};
|
||||
|
||||
export type StockThresholds = {
|
||||
lowStockDays: number;
|
||||
normalStockDays: number;
|
||||
highStockDays: number;
|
||||
};
|
||||
|
||||
export type ScheduleEvent = {
|
||||
id: string;
|
||||
medName: string;
|
||||
timeStr: string;
|
||||
dateStr: string;
|
||||
usage: number;
|
||||
when: number;
|
||||
isPast: boolean;
|
||||
takenBy: string[];
|
||||
};
|
||||
|
||||
export type BlisterStock = {
|
||||
fullBlisters: number;
|
||||
openBlisterPills: number;
|
||||
loosePills: number;
|
||||
};
|
||||
|
||||
// Shared schedule types
|
||||
export type SharedMedication = {
|
||||
id: number;
|
||||
name: string;
|
||||
genericName?: string | null;
|
||||
pillWeightMg?: number | null;
|
||||
imageUrl?: string | null;
|
||||
totalPills: number;
|
||||
packCount: number;
|
||||
blistersPerPack: number;
|
||||
looseTablets: number;
|
||||
pillsPerBlister: number;
|
||||
takenBy: string[];
|
||||
blisters: Blister[];
|
||||
};
|
||||
|
||||
export type SharedScheduleData = {
|
||||
takenBy: string;
|
||||
sharedBy: string | null;
|
||||
scheduleDays: number;
|
||||
medications: SharedMedication[];
|
||||
stockThresholds?: {
|
||||
lowStockDays: number;
|
||||
};
|
||||
};
|
||||
|
||||
export type ExpiredLinkData = {
|
||||
ownerUsername: string;
|
||||
takenBy: string;
|
||||
expiredAt: string;
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Field Validation Limits (must match backend)
|
||||
// =============================================================================
|
||||
export const FIELD_LIMITS = {
|
||||
name: { min: 1, max: 100 },
|
||||
genericName: { max: 100 },
|
||||
takenBy: { max: 100 },
|
||||
notes: { max: 2000 }
|
||||
} as const;
|
||||
|
||||
// =============================================================================
|
||||
// Helper Functions for Medication Calculations
|
||||
// =============================================================================
|
||||
|
||||
type MedLike = Pick<Medication, 'packCount' | 'blistersPerPack' | 'pillsPerBlister' | 'looseTablets'> & { stockAdjustment?: number };
|
||||
|
||||
/** Calculate total pills including stockAdjustment */
|
||||
export function getMedTotal(med: MedLike): number {
|
||||
return med.packCount * med.blistersPerPack * med.pillsPerBlister + med.looseTablets + (med.stockAdjustment ?? 0);
|
||||
}
|
||||
|
||||
/** Get the base package size (without stockAdjustment) */
|
||||
export function getPackageSize(med: MedLike): number {
|
||||
return med.packCount * med.blistersPerPack * med.pillsPerBlister + med.looseTablets;
|
||||
}
|
||||
Reference in New Issue
Block a user