685 lines
28 KiB
TypeScript
685 lines
28 KiB
TypeScript
import type {Route} from "../../../.react-router/types/app/pages/dashboard/+types/Home";
|
|
import {useEffect, useState} from "react";
|
|
import {useNavigate, useOutletContext} from "react-router";
|
|
import {
|
|
type DoctorDataWithPermission,
|
|
type DoctorTeamInfo,
|
|
type OutletContextType,
|
|
type PatientBookingInfo,
|
|
type PatientData,
|
|
type TreatmentInfoWithDoctorInfo,
|
|
type WardInfo
|
|
} from "~/utils/models.ts";
|
|
import {Badge, Button, Card, Grid, Group, Modal, PasswordInput, Stack, Table, Text} from "@mantine/core";
|
|
import {
|
|
apiChangeMyPassword,
|
|
apiGetPatientBookingList,
|
|
apiGetTreatmentRecords,
|
|
apiPatientGetCurrentWard
|
|
} from "~/utils/hms_api.ts";
|
|
import {showErrorMessage, showInfoMessage} from "~/utils/utils.ts";
|
|
import {iconMStyle, marginLeftRight, marginRound, marginTop} from "~/styles.ts";
|
|
import {useForm} from "@mantine/form";
|
|
import {BookingCategory, DashboardPageType, DoctorGrade, UserType} from "~/utils/hms_enums.ts";
|
|
import {confirmEditDoctor, confirmEditPatient} from "~/components/subs/confirms.tsx";
|
|
import EditIcon from "mdi-react/EditIcon";
|
|
import LockResetIcon from "mdi-react/LockResetIcon";
|
|
import ArrowRightIcon from "mdi-react/ArrowRightIcon";
|
|
import {TruncatedText} from "~/components/subs/TruncatedText.tsx";
|
|
import {PatientInfoDisplay} from "~/components/subs/PatientInfoDisplay.tsx";
|
|
import {TeamInfoDisplay} from "~/components/subs/TeamInfoDisplay.tsx";
|
|
import {DoctorInfoDisplay} from "~/components/subs/DoctorInfoDisplay.tsx";
|
|
import DoctorTeamsSimple from "~/components/subs/DoctorTeamsSimple.tsx";
|
|
import { ResponsiveTableContainer } from "~/components/subs/ResponsiveTableContainer";
|
|
|
|
export function meta({}: Route.MetaArgs) {
|
|
return [
|
|
{ title: "Home" },
|
|
{ name: "description", content: "Dashboard Home" },
|
|
];
|
|
}
|
|
|
|
interface ChangePasswordModalProps {
|
|
opened: boolean;
|
|
onClose: () => void;
|
|
onSuccess: () => void;
|
|
}
|
|
|
|
function ChangePasswordModal({ opened, onClose, onSuccess }: ChangePasswordModalProps) {
|
|
const form = useForm({
|
|
initialValues: {
|
|
old_password: '',
|
|
new_password: '',
|
|
confirm_password: '',
|
|
},
|
|
validate: {
|
|
old_password: (value) => (!value ? 'Current password is required' : null),
|
|
new_password: (value) => (!value ? 'New password is required' : value.length < 6 ? 'Password must be at least 6 characters' : null),
|
|
confirm_password: (value, values) => (value !== values.new_password ? 'Passwords do not match' : null),
|
|
},
|
|
});
|
|
|
|
const handleSubmit = (values: typeof form.values) => {
|
|
apiChangeMyPassword(values.old_password, values.new_password)
|
|
.then(res => {
|
|
if (res.success) {
|
|
showInfoMessage('', 'Password changed successfully', 3000);
|
|
form.reset();
|
|
onClose();
|
|
onSuccess();
|
|
} else {
|
|
showErrorMessage(res.message, 'Failed to change password');
|
|
}
|
|
})
|
|
.catch(err => {
|
|
showErrorMessage(err.toString(), 'Failed to change password');
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Modal opened={opened} onClose={onClose} title="Change Password" centered>
|
|
<form onSubmit={form.onSubmit(handleSubmit)}>
|
|
<Stack>
|
|
<PasswordInput
|
|
label="Current Password"
|
|
placeholder="Enter your current password"
|
|
withAsterisk
|
|
{...form.getInputProps('old_password')}
|
|
/>
|
|
<PasswordInput
|
|
label="New Password"
|
|
placeholder="Enter new password"
|
|
withAsterisk
|
|
{...form.getInputProps('new_password')}
|
|
/>
|
|
<PasswordInput
|
|
label="Confirm New Password"
|
|
placeholder="Confirm new password"
|
|
withAsterisk
|
|
{...form.getInputProps('confirm_password')}
|
|
/>
|
|
<Group justify="flex-end" mt="md">
|
|
<Button variant="outline" onClick={onClose}>Cancel</Button>
|
|
<Button type="submit">Change Password</Button>
|
|
</Group>
|
|
</Stack>
|
|
</form>
|
|
</Modal>
|
|
);
|
|
}
|
|
|
|
interface PatientHomeProps {
|
|
patientData: PatientData;
|
|
refreshUserInfo: () => void;
|
|
}
|
|
|
|
function PatientHome({ patientData, refreshUserInfo }: PatientHomeProps) {
|
|
const navigate = useNavigate();
|
|
const [changePasswordOpened, setChangePasswordOpened] = useState(false);
|
|
const [currentWard, setCurrentWard] = useState<WardInfo | null>(null);
|
|
const [loadingWard, setLoadingWard] = useState(false);
|
|
const [bookings, setBookings] = useState<PatientBookingInfo[]>([]);
|
|
const [loadingBookings, setLoadingBookings] = useState(false);
|
|
const [treatments, setTreatments] = useState<TreatmentInfoWithDoctorInfo[]>([]);
|
|
const [loadingTreatments, setLoadingTreatments] = useState(false);
|
|
const { changePage } = useOutletContext<OutletContextType>();
|
|
|
|
useEffect(() => {
|
|
fetchCurrentWard();
|
|
fetchRecentBookings();
|
|
fetchRecentTreatments();
|
|
}, []);
|
|
|
|
const fetchCurrentWard = () => {
|
|
setLoadingWard(true);
|
|
apiPatientGetCurrentWard()
|
|
.then(res => {
|
|
if (res.success && res.data.ward) {
|
|
setCurrentWard(res.data.ward);
|
|
} else {
|
|
setCurrentWard(null);
|
|
}
|
|
})
|
|
.catch(err => {
|
|
showErrorMessage(err.toString(), 'Error');
|
|
})
|
|
.finally(() => {
|
|
setLoadingWard(false);
|
|
});
|
|
};
|
|
|
|
const fetchRecentBookings = () => {
|
|
setLoadingBookings(true);
|
|
apiGetPatientBookingList(1)
|
|
.then(res => {
|
|
if (res.success) {
|
|
const recentBookings = [...res.data.appointments]
|
|
.sort((a, b) => b.id - a.id)
|
|
.slice(0, 5);
|
|
setBookings(recentBookings);
|
|
} else {
|
|
showErrorMessage(res.message, 'Failed to load appointments');
|
|
}
|
|
})
|
|
.catch(err => {
|
|
showErrorMessage(err.toString(), 'Error');
|
|
})
|
|
.finally(() => {
|
|
setLoadingBookings(false);
|
|
});
|
|
};
|
|
|
|
const fetchRecentTreatments = () => {
|
|
setLoadingTreatments(true);
|
|
apiGetTreatmentRecords(1, patientData.id, null, null)
|
|
.then(res => {
|
|
if (res.success) {
|
|
// Get only the 10 most recent treatments
|
|
const recentTreatments = [...res.data.treatments]
|
|
.sort((a, b) => b.treated_at - a.treated_at)
|
|
.slice(0, 10);
|
|
setTreatments(recentTreatments);
|
|
} else {
|
|
showErrorMessage(res.message, 'Failed to load treatments');
|
|
}
|
|
})
|
|
.catch(err => {
|
|
showErrorMessage(err.toString(), 'Error');
|
|
})
|
|
.finally(() => {
|
|
setLoadingTreatments(false);
|
|
});
|
|
};
|
|
|
|
const handleEditInfo = () => {
|
|
confirmEditPatient(patientData, () => {
|
|
refreshUserInfo();
|
|
});
|
|
};
|
|
|
|
const getStatusBadge = (booking: PatientBookingInfo) => {
|
|
if (booking.discharged) {
|
|
return <Badge color="purple">Discharged</Badge>;
|
|
} else if (booking.admitted) {
|
|
return <Badge color="blue">Admitted</Badge>;
|
|
} else if (booking.approved) {
|
|
if (booking.assigned_team === null) {
|
|
return <Badge color="red">Rejected</Badge>;
|
|
} else {
|
|
return <Badge color="green">Approved</Badge>;
|
|
}
|
|
} else {
|
|
return <Badge color="yellow">Pending</Badge>;
|
|
}
|
|
};
|
|
|
|
const getCategoryDisplay = (category: string) => {
|
|
const key = Object.entries(BookingCategory).find(([_, value]) => value === category)?.[0];
|
|
return key ? key.replace(/_/g, ' ') : category;
|
|
};
|
|
|
|
const bookingRows = bookings.map((booking) => (
|
|
<Table.Tr key={booking.id}>
|
|
<Table.Td>{booking.id}</Table.Td>
|
|
<Table.Td>{getCategoryDisplay(booking.category)}</Table.Td>
|
|
<Table.Td>{new Date(booking.appointment_time * 1000).toLocaleString('en-GB').replace(',', '')}</Table.Td>
|
|
<Table.Td>
|
|
<TruncatedText
|
|
text={booking.description}
|
|
title={`Description - Appointment #${booking.id}`}
|
|
/>
|
|
</Table.Td>
|
|
<Table.Td>{getStatusBadge(booking)}</Table.Td>
|
|
<Table.Td>
|
|
{booking.assigned_team ? (
|
|
<TeamInfoDisplay teamId={booking.assigned_team} />
|
|
) : (
|
|
<Text size="sm" c="dimmed">Not Assigned</Text>
|
|
)}
|
|
</Table.Td>
|
|
<Table.Td>
|
|
{booking.feedback ? (
|
|
<TruncatedText
|
|
text={booking.feedback}
|
|
title={`Feedback - Appointment #${booking.id}`}
|
|
/>
|
|
) : (
|
|
<Text>-</Text>
|
|
)}
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
));
|
|
|
|
const treatmentRows = treatments.map((treatment) => (
|
|
<Table.Tr key={treatment.id}>
|
|
<Table.Td>{treatment.id}</Table.Td>
|
|
<Table.Td>
|
|
<DoctorInfoDisplay doctorId={treatment.doctor_id} />
|
|
</Table.Td>
|
|
<Table.Td>
|
|
{treatment.team ? (
|
|
<TeamInfoDisplay teamId={treatment.team.id} />
|
|
) : (
|
|
<Text size="sm" c="dimmed">Not Assigned</Text>
|
|
)}
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<TruncatedText
|
|
text={treatment.treat_info}
|
|
title={`Treatment Info - Record #${treatment.id}`}
|
|
/>
|
|
</Table.Td>
|
|
<Table.Td>{new Date(treatment.treated_at * 1000).toLocaleString('en-GB').replace(',', '')}</Table.Td>
|
|
</Table.Tr>
|
|
));
|
|
|
|
return (
|
|
<Stack>
|
|
<Card padding="lg" radius="md" withBorder>
|
|
<Card.Section withBorder>
|
|
<Group justify="space-between" style={marginRound}>
|
|
<Stack gap="xs">
|
|
<Text size="xl" fw={700}>Hi, {patientData.title} {patientData.name}</Text>
|
|
<Text size="sm" c="dimmed">Welcome to the Hospital Management System</Text>
|
|
</Stack>
|
|
<Group>
|
|
<Button
|
|
leftSection={<EditIcon style={iconMStyle} />}
|
|
onClick={handleEditInfo}
|
|
>
|
|
Edit Profile
|
|
</Button>
|
|
<Button
|
|
leftSection={<LockResetIcon style={iconMStyle} />}
|
|
onClick={() => setChangePasswordOpened(true)}
|
|
>
|
|
Change Password
|
|
</Button>
|
|
</Group>
|
|
</Group>
|
|
</Card.Section>
|
|
|
|
<Card.Section>
|
|
<Grid style={marginRound}>
|
|
<Grid.Col span={{ base: 12, md: 6 }}>
|
|
<Stack gap="sm">
|
|
<Group>
|
|
<Text fw={500} w={100}>Full Name:</Text>
|
|
<Text>{patientData.title} {patientData.name}</Text>
|
|
</Group>
|
|
<Group>
|
|
<Text fw={500} w={100}>Gender:</Text>
|
|
<Text>{patientData.gender}</Text>
|
|
</Group>
|
|
<Group>
|
|
<Text fw={500} w={100}>Birth Date:</Text>
|
|
<Text>{patientData.birth_date}</Text>
|
|
</Group>
|
|
</Stack>
|
|
</Grid.Col>
|
|
<Grid.Col span={{ base: 12, md: 6 }}>
|
|
<Stack gap="sm">
|
|
<Group>
|
|
<Text fw={500} w={100}>Email:</Text>
|
|
<Text>{patientData.email}</Text>
|
|
</Group>
|
|
<Group>
|
|
<Text fw={500} w={100}>Phone:</Text>
|
|
<Text>{patientData.phone}</Text>
|
|
</Group>
|
|
<Group>
|
|
<Text fw={500} w={100}>Address:</Text>
|
|
<Text>{patientData.address}, {patientData.postcode}</Text>
|
|
</Group>
|
|
</Stack>
|
|
</Grid.Col>
|
|
</Grid>
|
|
</Card.Section>
|
|
</Card>
|
|
|
|
<Card padding="lg" radius="md" withBorder>
|
|
<Card.Section withBorder>
|
|
<Group style={{...marginLeftRight, ...marginTop}}>
|
|
<Text size="lg" fw={700} mb="md">Current Ward</Text>
|
|
</Group>
|
|
</Card.Section>
|
|
|
|
<Card.Section>
|
|
{loadingWard ? (
|
|
<Group style={marginRound}>
|
|
<Text>Loading...</Text>
|
|
</Group>
|
|
) : currentWard ? (
|
|
<Grid style={marginRound}>
|
|
<Grid.Col span={6}>
|
|
<Stack gap="sm">
|
|
<Group>
|
|
<Text fw={500} w={120}>Ward ID:</Text>
|
|
<Text>{currentWard.id}</Text>
|
|
</Group>
|
|
<Group>
|
|
<Text fw={500} w={120}>Ward Name:</Text>
|
|
<Text>{currentWard.name}</Text>
|
|
</Group>
|
|
</Stack>
|
|
</Grid.Col>
|
|
<Grid.Col span={6}>
|
|
<Stack gap="sm">
|
|
<Group>
|
|
<Text fw={500} w={120}>Ward Type:</Text>
|
|
<Text>{currentWard.type.replace(/_/g, ' ')}</Text>
|
|
</Group>
|
|
<Group>
|
|
<Text fw={500} w={120}>Occupancy:</Text>
|
|
<Text>{currentWard.current_occupancy} / {currentWard.total_capacity}</Text>
|
|
</Group>
|
|
</Stack>
|
|
</Grid.Col>
|
|
</Grid>
|
|
) : (
|
|
<Group style={marginRound}>
|
|
<Text c="dimmed">You are not currently admitted to any ward.</Text>
|
|
</Group>
|
|
)}
|
|
</Card.Section>
|
|
</Card>
|
|
|
|
<Card padding="lg" radius="md" withBorder>
|
|
<Group justify="space-between" mb="md">
|
|
<Text size="lg" fw={700}>Recent Appointments</Text>
|
|
<Button
|
|
rightSection={<ArrowRightIcon style={iconMStyle} />}
|
|
variant="subtle"
|
|
onClick={() => changePage(DashboardPageType.PatientBooking)}
|
|
>
|
|
View All
|
|
</Button>
|
|
</Group>
|
|
|
|
<Card.Section withBorder>
|
|
<Group style={marginRound}>
|
|
{loadingBookings ? (
|
|
<Text>Loading...</Text>
|
|
) : bookings.length > 0 ? (
|
|
<ResponsiveTableContainer minWidth={600}>
|
|
<Table striped highlightOnHover withColumnBorders withTableBorder>
|
|
<Table.Thead>
|
|
<Table.Tr>
|
|
<Table.Th>ID</Table.Th>
|
|
<Table.Th>Category</Table.Th>
|
|
<Table.Th>Appointment Time</Table.Th>
|
|
<Table.Th>Description</Table.Th>
|
|
<Table.Th>Status</Table.Th>
|
|
<Table.Th>Team</Table.Th>
|
|
<Table.Th>Feedback</Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>{bookingRows}</Table.Tbody>
|
|
</Table>
|
|
</ResponsiveTableContainer>
|
|
) : (
|
|
<Text c="dimmed">No appointments found.</Text>
|
|
)}
|
|
</Group>
|
|
</Card.Section>
|
|
</Card>
|
|
|
|
<Card padding="lg" radius="md" withBorder>
|
|
<Group justify="space-between" mb="md">
|
|
<Text size="lg" fw={700}>Recent Treatments</Text>
|
|
<Button
|
|
rightSection={<ArrowRightIcon style={iconMStyle} />}
|
|
variant="subtle"
|
|
onClick={() => changePage(DashboardPageType.TreatmentRecord, `/dashboard/treatment_record/${patientData.id}`)}
|
|
>
|
|
View All
|
|
</Button>
|
|
</Group>
|
|
|
|
<Card.Section withBorder>
|
|
<Group style={marginRound}>
|
|
{loadingTreatments ? (
|
|
<Text>Loading...</Text>
|
|
) : treatments.length > 0 ? (
|
|
<ResponsiveTableContainer minWidth={600}>
|
|
<Table striped highlightOnHover withColumnBorders withTableBorder>
|
|
<Table.Thead>
|
|
<Table.Tr>
|
|
<Table.Th>ID</Table.Th>
|
|
<Table.Th>Doctor</Table.Th>
|
|
<Table.Th>Medical Team</Table.Th>
|
|
<Table.Th>Treatment Info</Table.Th>
|
|
<Table.Th>Treatment Date</Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>{treatmentRows}</Table.Tbody>
|
|
</Table>
|
|
</ResponsiveTableContainer>
|
|
) : (
|
|
<Text c="dimmed">No treatment records found.</Text>
|
|
)}
|
|
</Group>
|
|
</Card.Section>
|
|
</Card>
|
|
|
|
<ChangePasswordModal
|
|
opened={changePasswordOpened}
|
|
onClose={() => setChangePasswordOpened(false)}
|
|
onSuccess={() => {navigate("/dashboard/account/login")}}
|
|
/>
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
interface DoctorHomeProps {
|
|
doctorData: DoctorDataWithPermission;
|
|
doctorTeams: DoctorTeamInfo[];
|
|
refreshUserInfo: () => void;
|
|
}
|
|
|
|
function DoctorHome({ doctorData, doctorTeams, refreshUserInfo }: DoctorHomeProps) {
|
|
const [changePasswordOpened, setChangePasswordOpened] = useState(false);
|
|
const [assignedPatients, setAssignedPatients] = useState<any[]>([]);
|
|
const [loadingPatients, setLoadingPatients] = useState(false);
|
|
const { changePage } = useOutletContext<OutletContextType>();
|
|
const navigate = useNavigate();
|
|
|
|
useEffect(() => {
|
|
fetchAssignedPatients();
|
|
}, []);
|
|
|
|
const fetchAssignedPatients = () => {
|
|
setLoadingPatients(true);
|
|
apiGetTreatmentRecords(1, null, doctorData.id, null)
|
|
.then(res => {
|
|
if (res.success) {
|
|
const uniquePatientIds = new Set();
|
|
const patientTreatments = res.data.treatments
|
|
.filter(treatment => {
|
|
if (!uniquePatientIds.has(treatment.patient_id)) {
|
|
uniquePatientIds.add(treatment.patient_id);
|
|
return true;
|
|
}
|
|
return false;
|
|
})
|
|
.slice(0, 15); // Get only the first 15 unique patients
|
|
|
|
setAssignedPatients(patientTreatments);
|
|
} else {
|
|
showErrorMessage(res.message, 'Failed to load patient assignments');
|
|
}
|
|
})
|
|
.catch(err => {
|
|
showErrorMessage(err.toString(), 'Error');
|
|
})
|
|
.finally(() => {
|
|
setLoadingPatients(false);
|
|
});
|
|
};
|
|
|
|
const handleEditInfo = () => {
|
|
confirmEditDoctor(doctorData, () => {
|
|
refreshUserInfo();
|
|
});
|
|
};
|
|
|
|
const patientRows = assignedPatients.map((treatment) => (
|
|
<Table.Tr key={treatment.id}>
|
|
<Table.Td>
|
|
<PatientInfoDisplay patientId={treatment.patient_id} />
|
|
</Table.Td>
|
|
<Table.Td>
|
|
<TruncatedText
|
|
text={treatment.treat_info}
|
|
title={`Treatment Info - Record #${treatment.id}`}
|
|
/>
|
|
</Table.Td>
|
|
<Table.Td>{new Date(treatment.treated_at * 1000).toLocaleString('en-GB').replace(',', '')}</Table.Td>
|
|
</Table.Tr>
|
|
));
|
|
|
|
return (
|
|
<Stack>
|
|
<Card padding="lg" radius="md" withBorder>
|
|
<Card.Section withBorder>
|
|
<Group justify="space-between" style={marginRound}>
|
|
<Stack gap="xs">
|
|
<Text size="xl" fw={700}>Hi, {doctorData.title} {doctorData.name}</Text>
|
|
<Text size="sm" c="dimmed">Welcome to the Hospital Management System</Text>
|
|
</Stack>
|
|
<Group>
|
|
<Button
|
|
leftSection={<EditIcon style={iconMStyle} />}
|
|
onClick={handleEditInfo}
|
|
>
|
|
Edit Profile
|
|
</Button>
|
|
<Button
|
|
leftSection={<LockResetIcon style={iconMStyle} />}
|
|
onClick={() => setChangePasswordOpened(true)}
|
|
>
|
|
Change Password
|
|
</Button>
|
|
</Group>
|
|
</Group>
|
|
</Card.Section>
|
|
|
|
<Card.Section>
|
|
<Grid style={marginRound}>
|
|
<Grid.Col span={{ base: 12, md: 6 }}>
|
|
<Stack gap="sm">
|
|
<Group>
|
|
<Text fw={500} w={100}>Full Name:</Text>
|
|
<Text>{doctorData.title} {doctorData.name}</Text>
|
|
</Group>
|
|
<Group>
|
|
<Text fw={500} w={100}>Grade:</Text>
|
|
<Text>{DoctorGrade[doctorData.grade]}</Text>
|
|
</Group>
|
|
<Group>
|
|
<Text fw={500} w={100}>Gender:</Text>
|
|
<Text>{doctorData.gender}</Text>
|
|
</Group>
|
|
</Stack>
|
|
</Grid.Col>
|
|
<Grid.Col span={{ base: 12, md: 6 }}>
|
|
<Stack gap="sm">
|
|
<Group>
|
|
<Text fw={500} w={100}>Birth Date:</Text>
|
|
<Text>{doctorData.birth_date}</Text>
|
|
</Group>
|
|
<Group>
|
|
<Text fw={500} w={100}>Email:</Text>
|
|
<Text>{doctorData.email}</Text>
|
|
</Group>
|
|
<Group>
|
|
<Text fw={500} w={100}>Phone:</Text>
|
|
<Text>{doctorData.phone}</Text>
|
|
</Group>
|
|
</Stack>
|
|
</Grid.Col>
|
|
</Grid>
|
|
</Card.Section>
|
|
</Card>
|
|
|
|
<DoctorTeamsSimple doctorTeams={doctorTeams} />
|
|
|
|
<Card padding="lg" radius="md" withBorder>
|
|
<Group justify="space-between" mb="md">
|
|
<Text size="lg" fw={700}>Recent Patients</Text>
|
|
<Button
|
|
rightSection={<ArrowRightIcon style={iconMStyle} />}
|
|
variant="subtle"
|
|
onClick={() => changePage(DashboardPageType.DoctorTreatment)}
|
|
>
|
|
View All
|
|
</Button>
|
|
</Group>
|
|
|
|
<Card.Section withBorder>
|
|
<Group style={marginRound}>
|
|
{loadingPatients ? (
|
|
<Text>Loading...</Text>
|
|
) : assignedPatients.length > 0 ? (
|
|
<ResponsiveTableContainer minWidth={600}>
|
|
<Table striped highlightOnHover withColumnBorders withTableBorder>
|
|
<Table.Thead>
|
|
<Table.Tr>
|
|
<Table.Th>Patient</Table.Th>
|
|
<Table.Th>Last Treatment</Table.Th>
|
|
<Table.Th>Treatment Date</Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>{patientRows}</Table.Tbody>
|
|
</Table>
|
|
</ResponsiveTableContainer>
|
|
) : (
|
|
<Text c="dimmed">No patients assigned yet.</Text>
|
|
)}
|
|
</Group>
|
|
</Card.Section>
|
|
</Card>
|
|
|
|
<ChangePasswordModal
|
|
opened={changePasswordOpened}
|
|
onClose={() => setChangePasswordOpened(false)}
|
|
onSuccess={() => {navigate("/dashboard/account/login")}}
|
|
/>
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
export default function Component() {
|
|
const { loginUserInfo, refreshMyInfo } = useOutletContext<OutletContextType>();
|
|
|
|
if (!loginUserInfo) {
|
|
return (
|
|
<Stack>
|
|
<Text size="1.5em" fw={700} style={marginLeftRight}>Dashboard</Text>
|
|
<Text>Loading user information...</Text>
|
|
</Stack>
|
|
);
|
|
}
|
|
|
|
const isDoctor = loginUserInfo.user_type === UserType.DOCTOR;
|
|
|
|
return (
|
|
<Stack>
|
|
{isDoctor && loginUserInfo.doctor_data ? (
|
|
<DoctorHome
|
|
doctorData={loginUserInfo.doctor_data}
|
|
doctorTeams={loginUserInfo.doctor_teams || []}
|
|
refreshUserInfo={refreshMyInfo}
|
|
/>
|
|
) : loginUserInfo.patient_data ? (
|
|
<PatientHome
|
|
patientData={loginUserInfo.patient_data}
|
|
refreshUserInfo={refreshMyInfo}
|
|
/>
|
|
) : (
|
|
<Text>Unable to load user profile information.</Text>
|
|
)}
|
|
</Stack>
|
|
);
|
|
}
|