feat(web): Phase 3b — medication/health/interactions feature UIs
Build the three core feature UIs on top of the now-wired stores + corrected API client from Phase 3a, turning the runnable shell into a usable app. Dashboard (pages/Dashboard.tsx): AppBar (Normogen + username + logout) over an MUI Tabs container (Medications | Health | Interactions), widened to lg. User loads on mount (needed for profile_id in medication create). MedicationManager (components/medication/): full CRUD. * List of medication cards (name, dosage·frequency, active chip, instructions) with edit/delete actions and an empty state. * Create dialog: name/dosage/frequency/route(select)/instructions. profile_id sourced from user.profile_id with a 'default' fallback (TODO: real profiles). Payload typed to CreateMedicationRequest. * Edit dialog (UpdateMedicationRequest shape) + delete confirm. Guards the optional medication_id field. HealthStats (components/health/): trend cards + chart + add + table. * Trend summary cards from /health-stats/trends (avg/min/max + trend arrow). * recharts LineChart of a selected stat type over measured_at, with a type selector. Timestamp field is measured_at (not timestamp). * Record dialog (stat_type/value/unit/measured_at/notes) + recent-readings table (date-fns formatted). InteractionsChecker (components/interactions/): multi-select medication names (chips) -> POST /interactions/check. Results rendered as cards with a SeverityChip, description, and disclaimer. Success/empty states. Shared SeverityChip (components/common/): maps InteractionSeverity -> MUI Chip color (severe=error, moderate=warning, mild=success, unknown=default). Added @mui/icons-material@7 (pinned to match @mui/material 7; npm tried to grab v9 which needs material ^9). Verified: npm run build clean (TS5 strict), dev server serves the app + all component modules transform. Solaria round-trip confirmed every contract the UIs call: medication create/list (200), interactions warfarin+aspirin -> severe, health-stat create (201).
This commit is contained in:
parent
ef1bb9f4be
commit
1515923996
7 changed files with 886 additions and 60 deletions
|
|
@ -0,0 +1,130 @@
|
|||
import { useState, type FC } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CircularProgress,
|
||||
Alert,
|
||||
Stack,
|
||||
Typography,
|
||||
Chip,
|
||||
} from '@mui/material';
|
||||
import WarningAmberIcon from '@mui/icons-material/WarningAmber';
|
||||
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
|
||||
import { useInteractionStore, useMedicationStore } from '../../store/useStore';
|
||||
import { SeverityChip } from '../common/SeverityChip';
|
||||
|
||||
export const InteractionsChecker: FC = () => {
|
||||
const { medications } = useMedicationStore();
|
||||
const { interactions, isChecking, error, checkInteractions, clearError } =
|
||||
useInteractionStore();
|
||||
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
|
||||
const toggle = (name: string) => {
|
||||
setSelected((prev) =>
|
||||
prev.includes(name) ? prev.filter((n) => n !== name) : [...prev, name],
|
||||
);
|
||||
};
|
||||
|
||||
const runCheck = async () => {
|
||||
if (selected.length < 2) return;
|
||||
await checkInteractions(selected);
|
||||
};
|
||||
|
||||
const hasChecked = interactions.length > 0 || !isChecking;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
Drug interactions
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
|
||||
Select two or more of your medications to check for known interactions.
|
||||
</Typography>
|
||||
|
||||
{error && (
|
||||
<Alert severity="error" sx={{ mb: 2 }} onClose={clearError}>
|
||||
{error}
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{medications.length === 0 ? (
|
||||
<Typography color="text.secondary" sx={{ py: 4, textAlign: 'center' }}>
|
||||
Add medications first (Medications tab), then check them for interactions.
|
||||
</Typography>
|
||||
) : (
|
||||
<>
|
||||
<Stack direction="row" spacing={1} sx={{ flexWrap: 'wrap', gap: 1, mb: 2 }}>
|
||||
{medications.map((m) => {
|
||||
const on = selected.includes(m.name);
|
||||
return (
|
||||
<Chip
|
||||
key={m.medication_id}
|
||||
label={m.name}
|
||||
color={on ? 'primary' : 'default'}
|
||||
variant={on ? 'filled' : 'outlined'}
|
||||
onClick={() => toggle(m.name)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={runCheck}
|
||||
disabled={selected.length < 2 || isChecking}
|
||||
sx={{ mb: 3 }}
|
||||
>
|
||||
{isChecking ? <CircularProgress size={24} /> : 'Check interactions'}
|
||||
</Button>
|
||||
|
||||
{/* Results */}
|
||||
{hasChecked && !isChecking && selected.length >= 2 && (
|
||||
<>
|
||||
{interactions.length === 0 ? (
|
||||
<Alert icon={<CheckCircleIcon fontSize="inherit" />} severity="success">
|
||||
No known interactions found between the selected medications.
|
||||
</Alert>
|
||||
) : (
|
||||
<Stack spacing={2}>
|
||||
<Alert icon={<WarningAmberIcon fontSize="inherit" />} severity="warning">
|
||||
{interactions.length} interaction{interactions.length > 1 ? 's' : ''} found.
|
||||
</Alert>
|
||||
{interactions.map((ix, i) => (
|
||||
<Card key={i} variant="outlined">
|
||||
<CardContent>
|
||||
<Stack
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
sx={{ mb: 1 }}
|
||||
>
|
||||
<Typography variant="subtitle1" fontWeight={600}>
|
||||
{ix.medications.join(' + ')}
|
||||
</Typography>
|
||||
<SeverityChip severity={ix.severity} />
|
||||
</Stack>
|
||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||
{ix.description}
|
||||
</Typography>
|
||||
{ix.disclaimer && (
|
||||
<Typography variant="caption" color="text.secondary" fontStyle="italic">
|
||||
{ix.disclaimer}
|
||||
</Typography>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default InteractionsChecker;
|
||||
Loading…
Add table
Add a link
Reference in a new issue