diff --git a/web/normogen-web/package-lock.json b/web/normogen-web/package-lock.json
index 8cafad0..c189c7c 100644
--- a/web/normogen-web/package-lock.json
+++ b/web/normogen-web/package-lock.json
@@ -10,6 +10,7 @@
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
+ "@mui/icons-material": "^7.3.11",
"@mui/material": "^7.3.9",
"@mui/x-charts": "^8.27.4",
"axios": "^1.13.6",
@@ -1127,26 +1128,52 @@
}
},
"node_modules/@mui/core-downloads-tracker": {
- "version": "7.3.9",
- "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.9.tgz",
- "integrity": "sha512-MOkOCTfbMJwLshlBCKJ59V2F/uaLYfmKnN76kksj6jlGUVdI25A9Hzs08m+zjBRdLv+sK7Rqdsefe8X7h/6PCw==",
+ "version": "7.3.11",
+ "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.3.11.tgz",
+ "integrity": "sha512-a7I/b/nBTdXYz2cOSlEmkQ9WWE1x8FHpqMhFPp+Y1VPFxcOw91G5ELOHARQAGSPy5V+UCgJua6K/1x70bAtQPw==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mui-org"
}
},
+ "node_modules/@mui/icons-material": {
+ "version": "7.3.11",
+ "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.3.11.tgz",
+ "integrity": "sha512-+hz5ilwHZ3djd5es3sCErLioqe/NhZcYTsV/TNXZAMdJdb23F4xzJjqnnZdnurc3S1+ietcssRNqieOhPQLZ7Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@mui/material": "^7.3.11",
+ "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@mui/material": {
- "version": "7.3.9",
- "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.9.tgz",
- "integrity": "sha512-I8yO3t4T0y7bvDiR1qhIN6iBWZOTBfVOnmLlM7K6h3dx5YX2a7rnkuXzc2UkZaqhxY9NgTnEbdPlokR1RxCNRQ==",
+ "version": "7.3.11",
+ "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.11.tgz",
+ "integrity": "sha512-yq8bPc3LxOwKRWpcjRgDkYFmpM6aKlARfESTmOQcvLYFeJwtHte2tw6hJDrb8sk8wcvpDprHEHVaoUU0MslIkw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.6",
- "@mui/core-downloads-tracker": "^7.3.9",
- "@mui/system": "^7.3.9",
+ "@mui/core-downloads-tracker": "^7.3.11",
+ "@mui/system": "^7.3.11",
"@mui/types": "^7.4.12",
- "@mui/utils": "^7.3.9",
+ "@mui/utils": "^7.3.11",
"@popperjs/core": "^2.11.8",
"@types/react-transition-group": "^4.4.12",
"clsx": "^2.1.1",
@@ -1165,7 +1192,7 @@
"peerDependencies": {
"@emotion/react": "^11.5.0",
"@emotion/styled": "^11.3.0",
- "@mui/material-pigment-css": "^7.3.9",
+ "@mui/material-pigment-css": "^7.3.11",
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
@@ -1192,13 +1219,13 @@
"license": "MIT"
},
"node_modules/@mui/private-theming": {
- "version": "7.3.9",
- "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.9.tgz",
- "integrity": "sha512-ErIyRQvsiQEq7Yvcvfw9UDHngaqjMy9P3JDPnRAaKG5qhpl2C4tX/W1S4zJvpu+feihmZJStjIyvnv6KDbIrlw==",
+ "version": "7.3.11",
+ "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.3.11.tgz",
+ "integrity": "sha512-9B+YKms0fRHbNrqp9tOT/DNbNnU5gyvJ1o3qAGXfq8GmZcbJnE3At9x07Zr/o0pkhzg4aDdwXVqe4+AcgtOCPA==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.6",
- "@mui/utils": "^7.3.9",
+ "@mui/utils": "^7.3.11",
"prop-types": "^15.8.1"
},
"engines": {
@@ -1219,9 +1246,9 @@
}
},
"node_modules/@mui/styled-engine": {
- "version": "7.3.9",
- "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.9.tgz",
- "integrity": "sha512-JqujWt5bX4okjUPGpVof/7pvgClqh7HvIbsIBIOOlCh2u3wG/Bwp4+E1bc1dXSwkrkp9WUAoNdI5HEC+5HKvMw==",
+ "version": "7.3.10",
+ "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.3.10.tgz",
+ "integrity": "sha512-WxE9SiF8xskAQqGjsp0poXCkCqsoXFEsSr0HBXfApmGHR+DBnXRp+z46Vsltg4gpPM4Z96DeAQRpeAOnhNg7Ng==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.6",
@@ -1253,16 +1280,16 @@
}
},
"node_modules/@mui/system": {
- "version": "7.3.9",
- "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.9.tgz",
- "integrity": "sha512-aL1q9am8XpRrSabv9qWf5RHhJICJql34wnrc1nz0MuOglPRYF/liN+c8VqZdTvUn9qg+ZjRVbKf4sJVFfIDtmg==",
+ "version": "7.3.11",
+ "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.11.tgz",
+ "integrity": "sha512-7izwGWdNawAKpBKcRlx7f2gFnAAjmASBWvMcyX4YYEeLOFsbfGRbUYGInvnAcUeql3rPxI7F9Ft4oY2OLRz44g==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.6",
- "@mui/private-theming": "^7.3.9",
- "@mui/styled-engine": "^7.3.9",
+ "@mui/private-theming": "^7.3.11",
+ "@mui/styled-engine": "^7.3.10",
"@mui/types": "^7.4.12",
- "@mui/utils": "^7.3.9",
+ "@mui/utils": "^7.3.11",
"clsx": "^2.1.1",
"csstype": "^3.2.3",
"prop-types": "^15.8.1"
@@ -1310,9 +1337,9 @@
}
},
"node_modules/@mui/utils": {
- "version": "7.3.9",
- "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.9.tgz",
- "integrity": "sha512-U6SdZaGbfb65fqTsH3V5oJdFj9uYwyLE2WVuNvmbggTSDBb8QHrFsqY8BN3taK9t3yJ8/BPHD/kNvLNyjwM7Yw==",
+ "version": "7.3.11",
+ "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.11.tgz",
+ "integrity": "sha512-XTjGnifwteg71/ij+0e7Y7d+hwyntMYP5wPoA/g2drdGH+Flkvjwy0OfrVpKBbaOvofq4zU/LIyUZyKgmWu18g==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.28.6",
diff --git a/web/normogen-web/package.json b/web/normogen-web/package.json
index f9501e2..fd4c3cc 100644
--- a/web/normogen-web/package.json
+++ b/web/normogen-web/package.json
@@ -13,6 +13,7 @@
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
+ "@mui/icons-material": "^7.3.11",
"@mui/material": "^7.3.9",
"@mui/x-charts": "^8.27.4",
"axios": "^1.13.6",
diff --git a/web/normogen-web/src/components/common/SeverityChip.tsx b/web/normogen-web/src/components/common/SeverityChip.tsx
new file mode 100644
index 0000000..32e9e29
--- /dev/null
+++ b/web/normogen-web/src/components/common/SeverityChip.tsx
@@ -0,0 +1,17 @@
+import { Chip } from '@mui/material';
+import { InteractionSeverity } from '../../types/api';
+
+const CONFIG: Record<
+ InteractionSeverity,
+ { color: 'error' | 'warning' | 'success' | 'default'; label: string }
+> = {
+ [InteractionSeverity.Severe]: { color: 'error', label: 'Severe' },
+ [InteractionSeverity.Moderate]: { color: 'warning', label: 'Moderate' },
+ [InteractionSeverity.Mild]: { color: 'success', label: 'Mild' },
+ [InteractionSeverity.Unknown]: { color: 'default', label: 'Unknown' },
+};
+
+export const SeverityChip = ({ severity }: { severity: InteractionSeverity }) => {
+ const cfg = CONFIG[severity] ?? CONFIG[InteractionSeverity.Unknown];
+ return ;
+};
diff --git a/web/normogen-web/src/components/health/HealthStats.tsx b/web/normogen-web/src/components/health/HealthStats.tsx
new file mode 100644
index 0000000..a76284d
--- /dev/null
+++ b/web/normogen-web/src/components/health/HealthStats.tsx
@@ -0,0 +1,309 @@
+import { useEffect, useState, type FC } from 'react';
+import {
+ Box,
+ Button,
+ Card,
+ CardContent,
+ CircularProgress,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Alert,
+ MenuItem,
+ Stack,
+ TextField,
+ Typography,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Paper,
+} from '@mui/material';
+import AddIcon from '@mui/icons-material/Add';
+import TrendUpIcon from '@mui/icons-material/TrendingUp';
+import TrendDownIcon from '@mui/icons-material/TrendingDown';
+import TrendFlatIcon from '@mui/icons-material/TrendingFlat';
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+} from 'recharts';
+import { format } from 'date-fns';
+import { useHealthStore } from '../../store/useStore';
+import { HealthStatType, type CreateHealthStatRequest, type TrendData } from '../../types/api';
+
+const STAT_TYPES = Object.values(HealthStatType);
+
+const UNIT_DEFAULTS: Record = {
+ [HealthStatType.Weight]: 'kg',
+ [HealthStatType.Height]: 'cm',
+ [HealthStatType.BloodPressure]: 'mmHg',
+ [HealthStatType.HeartRate]: 'bpm',
+ [HealthStatType.BloodSugar]: 'mg/dL',
+ [HealthStatType.Temperature]: '°C',
+ [HealthStatType.OxygenSaturation]: '%',
+ [HealthStatType.Cholesterol]: 'mg/dL',
+ [HealthStatType.Other]: '',
+};
+
+const trendIcon = (trend: TrendData['trend']) => {
+ if (trend === 'up') return ;
+ if (trend === 'down') return ;
+ return ;
+};
+
+const toLocalInput = (d: Date) => {
+ // datetime-local expects YYYY-MM-DDTHH:mm in local time.
+ const pad = (n: number) => String(n).padStart(2, '0');
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
+};
+
+export const HealthStats: FC = () => {
+ const { stats, trends, isLoading, error, loadStats, loadTrends, createStat, clearError } =
+ useHealthStore();
+
+ const [addOpen, setAddOpen] = useState(false);
+ const [saving, setSaving] = useState(false);
+ const [chartType, setChartType] = useState(HealthStatType.Weight);
+
+ const [form, setForm] = useState({
+ stat_type: HealthStatType.Weight,
+ value: 0,
+ unit: UNIT_DEFAULTS[HealthStatType.Weight],
+ measured_at: new Date().toISOString(),
+ });
+
+ useEffect(() => {
+ loadStats();
+ loadTrends();
+ }, [loadStats, loadTrends]);
+
+ const openAdd = () => {
+ const t = HealthStatType.Weight;
+ setForm({
+ stat_type: t,
+ value: 0,
+ unit: UNIT_DEFAULTS[t],
+ measured_at: new Date().toISOString(),
+ });
+ setAddOpen(true);
+ };
+
+ const submitAdd = async () => {
+ setSaving(true);
+ try {
+ await createStat(form);
+ setAddOpen(false);
+ } catch {
+ /* error surfaced via store */
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const typedTrends = trends as TrendData[];
+ const chartData = stats
+ .filter((s) => s.stat_type === chartType)
+ .sort((a, b) => new Date(a.measured_at).getTime() - new Date(b.measured_at).getTime())
+ .map((s) => ({ time: format(new Date(s.measured_at), 'MMM d'), value: s.value }));
+
+ return (
+
+
+ Health statistics
+ } onClick={openAdd}>
+ Record
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {isLoading ? (
+
+
+
+ ) : (
+ <>
+ {/* Trend summary cards */}
+ {typedTrends.length > 0 && (
+
+ {typedTrends.map((t) => (
+
+
+
+ {t.stat_type}
+
+
+ {t.average.toFixed(1)}
+ {trendIcon(t.trend)}
+
+
+ {t.min} – {t.max}
+
+
+
+ ))}
+
+ )}
+
+ {/* Chart with type selector */}
+
+
+
+ setChartType(e.target.value as HealthStatType)}
+ sx={{ minWidth: 180 }}
+ >
+ {STAT_TYPES.map((t) => (
+
+ ))}
+
+
+ {chartData.length === 0 ? (
+
+ No {chartType} readings recorded yet.
+
+ ) : (
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ {/* Recent readings table */}
+ {stats.length > 0 && (
+
+
+
+
+ Type
+ Value
+ When
+ Notes
+
+
+
+ {[...stats]
+ .sort(
+ (a, b) =>
+ new Date(b.measured_at).getTime() - new Date(a.measured_at).getTime(),
+ )
+ .slice(0, 15)
+ .map((s) => (
+
+ {s.stat_type}
+
+ {s.value} {s.unit}
+
+ {format(new Date(s.measured_at), 'MMM d, HH:mm')}
+ {s.notes ?? ''}
+
+ ))}
+
+
+
+ )}
+
+ {stats.length === 0 && typedTrends.length === 0 && (
+
+ No health readings yet — record one.
+
+ )}
+ >
+ )}
+
+ {/* Add dialog */}
+
+
+ );
+};
+
+export default HealthStats;
diff --git a/web/normogen-web/src/components/interactions/InteractionsChecker.tsx b/web/normogen-web/src/components/interactions/InteractionsChecker.tsx
new file mode 100644
index 0000000..aeee0b1
--- /dev/null
+++ b/web/normogen-web/src/components/interactions/InteractionsChecker.tsx
@@ -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([]);
+
+ 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 (
+
+
+ Drug interactions
+
+
+ Select two or more of your medications to check for known interactions.
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {medications.length === 0 ? (
+
+ Add medications first (Medications tab), then check them for interactions.
+
+ ) : (
+ <>
+
+ {medications.map((m) => {
+ const on = selected.includes(m.name);
+ return (
+ toggle(m.name)}
+ />
+ );
+ })}
+
+
+
+
+ {/* Results */}
+ {hasChecked && !isChecking && selected.length >= 2 && (
+ <>
+ {interactions.length === 0 ? (
+ } severity="success">
+ No known interactions found between the selected medications.
+
+ ) : (
+
+ } severity="warning">
+ {interactions.length} interaction{interactions.length > 1 ? 's' : ''} found.
+
+ {interactions.map((ix, i) => (
+
+
+
+
+ {ix.medications.join(' + ')}
+
+
+
+
+ {ix.description}
+
+ {ix.disclaimer && (
+
+ {ix.disclaimer}
+
+ )}
+
+
+ ))}
+
+ )}
+ >
+ )}
+ >
+ )}
+
+ );
+};
+
+export default InteractionsChecker;
diff --git a/web/normogen-web/src/components/medication/MedicationManager.tsx b/web/normogen-web/src/components/medication/MedicationManager.tsx
new file mode 100644
index 0000000..8c9dc62
--- /dev/null
+++ b/web/normogen-web/src/components/medication/MedicationManager.tsx
@@ -0,0 +1,348 @@
+import { useEffect, useState, type FC } from 'react';
+import {
+ Box,
+ Button,
+ Card,
+ CardContent,
+ CardActions,
+ Chip,
+ CircularProgress,
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ Alert,
+ IconButton,
+ List,
+ ListItem,
+ ListItemText,
+ MenuItem,
+ Stack,
+ TextField,
+ Typography,
+} from '@mui/material';
+import EditIcon from '@mui/icons-material/Edit';
+import DeleteIcon from '@mui/icons-material/Delete';
+import AddIcon from '@mui/icons-material/Add';
+import { useMedicationStore, useAuthStore } from '../../store/useStore';
+import type {
+ Medication,
+ CreateMedicationRequest,
+ UpdateMedicationRequest,
+} from '../../types/api';
+
+const ROUTES = ['oral', 'topical', 'injection', 'inhalation', 'other'] as const;
+
+const emptyCreate: CreateMedicationRequest = {
+ name: '',
+ dosage: '',
+ frequency: '',
+ route: 'oral',
+ profile_id: 'default',
+};
+
+export const MedicationManager: FC = () => {
+ const {
+ medications,
+ isLoading,
+ error,
+ loadMedications,
+ createMedication,
+ updateMedication,
+ deleteMedication,
+ clearError,
+ } = useMedicationStore();
+ const user = useAuthStore((s) => s.user);
+
+ const [createOpen, setCreateOpen] = useState(false);
+ const [createForm, setCreateForm] = useState(emptyCreate);
+ const [editTarget, setEditTarget] = useState(null);
+ const [editForm, setEditForm] = useState({});
+ const [deleteTarget, setDeleteTarget] = useState(null);
+ const [saving, setSaving] = useState(false);
+
+ useEffect(() => {
+ loadMedications();
+ }, [loadMedications]);
+
+ const openCreate = () => {
+ // TODO: real profile management — for now, source profile_id from the user,
+ // falling back to 'default' (the backend accepts any string).
+ setCreateForm({
+ ...emptyCreate,
+ profile_id: user?.profile_id ?? 'default',
+ });
+ setCreateOpen(true);
+ };
+
+ const submitCreate = async () => {
+ setSaving(true);
+ try {
+ await createMedication(createForm);
+ setCreateOpen(false);
+ } catch {
+ /* error surfaced via store */
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const openEdit = (med: Medication) => {
+ setEditTarget(med);
+ setEditForm({
+ name: med.name,
+ dosage: med.dosage,
+ frequency: med.frequency,
+ instructions: med.instructions,
+ active: med.active,
+ });
+ };
+
+ const submitEdit = async () => {
+ if (!editTarget?.medication_id) return;
+ setSaving(true);
+ try {
+ await updateMedication(editTarget.medication_id, editForm);
+ setEditTarget(null);
+ } catch {
+ /* error surfaced via store */
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const confirmDelete = async () => {
+ if (!deleteTarget?.medication_id) return;
+ setSaving(true);
+ try {
+ await deleteMedication(deleteTarget.medication_id);
+ setDeleteTarget(null);
+ } catch {
+ /* error surfaced via store */
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ return (
+
+
+ Medications
+ } onClick={openCreate}>
+ Add
+
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+ {isLoading ? (
+
+
+
+ ) : medications.length === 0 ? (
+
+ No medications yet — add one.
+
+ ) : (
+
+ {medications.map((med) => (
+
+
+
+
+
+
+ {med.name}
+ {' '}
+
+ {med.dosage} · {med.frequency}
+
+
+
+
+ {med.instructions && (
+
+ {med.instructions}
+
+ )}
+
+
+
+
+ openEdit(med)} aria-label="edit">
+
+
+ setDeleteTarget(med)}
+ aria-label="delete"
+ color="error"
+ >
+
+
+
+
+
+ ))}
+
+ )}
+
+ {/* Create dialog */}
+
+
+ {/* Edit dialog */}
+
+
+ {/* Delete confirm */}
+
+
+ );
+};
+
+export default MedicationManager;
diff --git a/web/normogen-web/src/pages/Dashboard.tsx b/web/normogen-web/src/pages/Dashboard.tsx
index 7e85b83..fb139f7 100644
--- a/web/normogen-web/src/pages/Dashboard.tsx
+++ b/web/normogen-web/src/pages/Dashboard.tsx
@@ -1,24 +1,20 @@
-import { useEffect, type FC } from 'react';
+import { useEffect, useState, type FC } from 'react';
import { useNavigate } from 'react-router-dom';
-import {
- AppBar,
- Toolbar,
- Typography,
- Button,
- Container,
- Card,
- CardContent,
- Box,
- CircularProgress,
-} from '@mui/material';
+import { AppBar, Toolbar, Typography, Button, Container, Box, Tabs, Tab } from '@mui/material';
import { useAuthStore } from '../store/useStore';
+import { MedicationManager } from '../components/medication/MedicationManager';
+import { HealthStats } from '../components/health/HealthStats';
+import { InteractionsChecker } from '../components/interactions/InteractionsChecker';
+
+type TabIndex = 0 | 1 | 2;
export const Dashboard: FC = () => {
const navigate = useNavigate();
- const { user, isAuthenticated, isLoading, loadUser, logout } = useAuthStore();
+ const { user, isAuthenticated, loadUser, logout } = useAuthStore();
+ const [tab, setTab] = useState(0);
// On mount, refresh the user record from the backend (confirms the token is
- // still valid and pulls the latest profile).
+ // still valid, and medication-create needs user.profile_id).
useEffect(() => {
if (isAuthenticated && !user) {
loadUser();
@@ -37,34 +33,32 @@ export const Dashboard: FC = () => {
Normogen
+ {user && (
+
+ {user.username}
+
+ )}
-
- {isLoading && (
-
-
-
- )}
+
+ setTab(v as TabIndex)} sx={{ mb: 3 }}>
+
+
+
+
-
-
-
- Welcome{user ? `, ${user.username}` : ''}
-
-
- You're signed in to Normogen.
-
-
- This is the dashboard shell. Medication management, health statistics,
- and drug-interaction checking UIs will be built here.
-
-
-
+
+ {tab === 0 && }
+ {tab === 1 && }
+ {tab === 2 && }
+
>
);
};
+
+export default Dashboard;