253 lines
10 KiB
TypeScript
253 lines
10 KiB
TypeScript
import type {Route} from "../../../.react-router/types/app/pages/dashboard/+types/MedicalTeamsManagement";
|
|
import {useEffect, useMemo, useState} from "react";
|
|
import {DashboardPageType} from "~/utils/hms_enums.ts";
|
|
import {useNavigate, useOutletContext} from "react-router";
|
|
import {
|
|
type DoctorDataWithPermission,
|
|
type DoctorTeamInfo,
|
|
type OutletContextType,
|
|
SORT_SYMBOLS
|
|
} from "~/utils/models.ts";
|
|
import {Accordion, ActionIcon, Button, Card, Group, Pagination, Stack, Table, Text, Tooltip} from "@mantine/core";
|
|
import {apiGetDoctorsList, apiGetTeamList} from "~/utils/hms_api.ts";
|
|
import {showErrorMessage} from "~/utils/utils.ts";
|
|
import {iconMStyle, marginLeftRight, marginRightBottom, marginTopBottom} from "~/styles.ts";
|
|
import PencilIcon from "mdi-react/PencilIcon";
|
|
import DeleteIcon from "mdi-react/DeleteIcon";
|
|
import {confirmDeleteTeam, confirmEditOrCreateTeam, confirmViewTeamMembers} from "~/components/subs/confirms.tsx";
|
|
import AddIcon from "mdi-react/AddIcon";
|
|
import EyeIcon from "mdi-react/EyeIcon";
|
|
import {MedicalTeamManageFilter} from "~/components/subs/MedicalTeamManageFilter.tsx";
|
|
import HistoryIcon from "mdi-react/HistoryIcon";
|
|
import FilterIcon from "mdi-react/FilterIcon";
|
|
import { ResponsiveTableContainer } from "~/components/subs/ResponsiveTableContainer";
|
|
|
|
export function meta({}: Route.MetaArgs) {
|
|
return [
|
|
{ title: "Medical Teams Management" },
|
|
{ name: "description", content: "Medical Teams Management" },
|
|
];
|
|
}
|
|
|
|
export default function Component() {
|
|
const navigate = useNavigate();
|
|
const [refreshingTeamList, setRefreshingTeamList] = useState<boolean>(false);
|
|
const [teamInfo, setTeamInfo] = useState<{teams: DoctorTeamInfo[], total_pages: number}>({teams: [], total_pages: 1});
|
|
const [doctorsList, setDoctorsList] = useState<DoctorDataWithPermission[]>([]);
|
|
const [loadingDoctors, setLoadingDoctors] = useState<boolean>(false);
|
|
|
|
const [sortKey, setSortKey] = useState<string>("");
|
|
const [sortDesc, setSortDesc] = useState<boolean>(true);
|
|
const [currPage, setCurrPage] = useState<number>(1);
|
|
|
|
const [filterDepartments, setFilterDepartments] = useState<string[]>([]);
|
|
const [filterAdminTeam, setFilterAdminTeam] = useState<number>(-1);
|
|
|
|
const { changePage } = useOutletContext<OutletContextType>();
|
|
|
|
useEffect(() => {
|
|
refreshTeamList();
|
|
loadDoctorsList();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
refreshTeamList();
|
|
}, [currPage]);
|
|
|
|
const updateFilter = (departments: string[], adminTeam: number) => {
|
|
setFilterDepartments(departments);
|
|
setFilterAdminTeam(adminTeam);
|
|
};
|
|
|
|
const loadDoctorsList = () => {
|
|
setLoadingDoctors(true);
|
|
apiGetDoctorsList(1).then(res => {
|
|
if (!res.success) {
|
|
showErrorMessage(res.message, "Failed to get doctors list");
|
|
return;
|
|
}
|
|
setDoctorsList(res.data.doctors);
|
|
})
|
|
.catch(err => {})
|
|
.finally(() => { setLoadingDoctors(false); });
|
|
};
|
|
|
|
const refreshTeamList = () => {
|
|
setRefreshingTeamList(true);
|
|
apiGetTeamList(currPage).then(res => {
|
|
if (!res.success) {
|
|
showErrorMessage(res.message, "Failed to get medical teams list");
|
|
return;
|
|
}
|
|
setTeamInfo(res.data);
|
|
})
|
|
.catch(err => {})
|
|
.finally(() => { setRefreshingTeamList(false); });
|
|
};
|
|
|
|
const handleSort = (key: string) => {
|
|
if (sortKey === key) {
|
|
setSortDesc(!sortDesc);
|
|
} else {
|
|
setSortKey(key);
|
|
setSortDesc(false); // Default ascending
|
|
}
|
|
};
|
|
|
|
const sortedTeams = useMemo(() => {
|
|
let data = [...teamInfo.teams];
|
|
|
|
data = data.filter((team) => {
|
|
const okDepartment = filterDepartments.length === 0 || filterDepartments.includes(team.department);
|
|
const okAdmin = filterAdminTeam === -1 || (filterAdminTeam === 1 ? team.is_admin_team : !team.is_admin_team);
|
|
return okDepartment && okAdmin;
|
|
});
|
|
|
|
if (!sortKey) return data;
|
|
|
|
data.sort((a, b) => {
|
|
const valA = a[sortKey];
|
|
const valB = b[sortKey];
|
|
|
|
if (typeof valA === 'string' && typeof valB === 'string') {
|
|
const cmp = valA.localeCompare(valB);
|
|
return sortDesc ? -cmp : cmp;
|
|
}
|
|
|
|
if (typeof valA === 'number' && typeof valB === 'number') {
|
|
return sortDesc ? valB - valA : valA - valB;
|
|
}
|
|
|
|
if (typeof valA === 'boolean' && typeof valB === 'boolean') {
|
|
return sortDesc ? (valB ? 1 : -1) : (valA ? 1 : -1);
|
|
}
|
|
|
|
return 0;
|
|
});
|
|
|
|
return data;
|
|
}, [teamInfo.teams, sortKey, sortDesc, filterDepartments, filterAdminTeam]);
|
|
|
|
const handleCreateTeam = () => {
|
|
confirmEditOrCreateTeam(null, doctorsList, refreshTeamList);
|
|
};
|
|
|
|
const handleEditTeam = (team: DoctorTeamInfo) => {
|
|
confirmEditOrCreateTeam(team, doctorsList, refreshTeamList);
|
|
};
|
|
|
|
const handleDeleteTeam = (teamId: number, department: string) => {
|
|
confirmDeleteTeam(teamId, department, refreshTeamList);
|
|
};
|
|
|
|
const handleViewTeamMembers = (team: DoctorTeamInfo) => {
|
|
confirmViewTeamMembers(team);
|
|
};
|
|
|
|
const rows = sortedTeams.map((team) => (
|
|
<Table.Tr key={team.id}>
|
|
<Table.Td>{team.id}</Table.Td>
|
|
<Table.Td>{team.department.replace(/_/g, ' ')}</Table.Td>
|
|
<Table.Td>
|
|
{team.members.find(m => m.id === team.consultant_id)
|
|
? `${team.members.find(m => m.id === team.consultant_id)?.title || ''} ${team.members.find(m => m.id === team.consultant_id)?.name || ''}`
|
|
: `ID: ${team.consultant_id}`}
|
|
</Table.Td>
|
|
<Table.Td>{team.members.length}</Table.Td>
|
|
<Table.Td>{team.is_admin_team ? 'Yes' : 'No'}</Table.Td>
|
|
<Table.Td>
|
|
<Group>
|
|
<Tooltip label="Treatment Record" withArrow>
|
|
<ActionIcon onClick={() => { changePage(DashboardPageType.TreatmentRecord, `/dashboard/treatment_record/t${team.id}`) }}>
|
|
<HistoryIcon style={iconMStyle}/>
|
|
</ActionIcon>
|
|
</Tooltip>
|
|
<ActionIcon onClick={() => handleViewTeamMembers(team)}>
|
|
<EyeIcon style={iconMStyle}/>
|
|
</ActionIcon>
|
|
<ActionIcon onClick={() => handleEditTeam(team)}>
|
|
<PencilIcon style={iconMStyle}/>
|
|
</ActionIcon>
|
|
<ActionIcon color="red" onClick={() => handleDeleteTeam(team.id, team.department.replace(/_/g, ' '))}>
|
|
<DeleteIcon style={iconMStyle}/>
|
|
</ActionIcon>
|
|
</Group>
|
|
</Table.Td>
|
|
</Table.Tr>
|
|
));
|
|
|
|
return (
|
|
<Stack>
|
|
<Group justify="space-between" align="center" style={marginLeftRight}>
|
|
<Text size="1.5em" fw={700}>Medical Teams Management</Text>
|
|
<Button
|
|
leftSection={<AddIcon style={iconMStyle}/>}
|
|
onClick={handleCreateTeam}
|
|
loading={loadingDoctors}
|
|
>Add Medical Team</Button>
|
|
</Group>
|
|
|
|
<Card padding="lg" radius="md" withBorder style={marginTopBottom}>
|
|
<Card.Section withBorder>
|
|
<Accordion variant="filled" chevronPosition="left" defaultValue="advanced-filter">
|
|
<Accordion.Item value="advanced-filter">
|
|
<Accordion.Control>
|
|
<Group>
|
|
<FilterIcon style={iconMStyle} />
|
|
<Text>Filters</Text>
|
|
</Group>
|
|
</Accordion.Control>
|
|
<Accordion.Panel>
|
|
<MedicalTeamManageFilter onChange={updateFilter}/>
|
|
</Accordion.Panel>
|
|
</Accordion.Item>
|
|
</Accordion>
|
|
</Card.Section>
|
|
|
|
<Card.Section>
|
|
<ResponsiveTableContainer minWidth={600}>
|
|
<Table striped highlightOnHover withColumnBorders withTableBorder>
|
|
<Table.Thead>
|
|
<Table.Tr>
|
|
<Table.Th onClick={() => handleSort("id")}>
|
|
Team ID{" "}
|
|
{sortKey === "id" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]}
|
|
</Table.Th>
|
|
<Table.Th onClick={() => handleSort("department")}>
|
|
Department{" "}
|
|
{sortKey === "department" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]}
|
|
</Table.Th>
|
|
<Table.Th onClick={() => handleSort("consultant_id")}>
|
|
Consultant{" "}
|
|
{sortKey === "consultant_id" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]}
|
|
</Table.Th>
|
|
<Table.Th>
|
|
Members Count
|
|
</Table.Th>
|
|
<Table.Th onClick={() => handleSort("is_admin_team")}>
|
|
Admin Team{" "}
|
|
{sortKey === "is_admin_team" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]}
|
|
</Table.Th>
|
|
<Table.Th>
|
|
Operations
|
|
</Table.Th>
|
|
</Table.Tr>
|
|
</Table.Thead>
|
|
<Table.Tbody>{rows}</Table.Tbody>
|
|
</Table>
|
|
</ResponsiveTableContainer>
|
|
|
|
<Pagination
|
|
withEdges
|
|
total={teamInfo.total_pages}
|
|
value={currPage}
|
|
onChange={setCurrPage}
|
|
mt="sm"
|
|
style={{justifyItems: "flex-end", ...marginRightBottom}}
|
|
/>
|
|
</Card.Section>
|
|
</Card>
|
|
</Stack>
|
|
);
|
|
}
|