2025-04-30 17:28:58 +01:00

206 lines
9.0 KiB
TypeScript

import type {Route} from "../../../.react-router/types/app/pages/dashboard/+types/WardManagement";
import {useEffect, useMemo, useState} from "react";
import {DashboardPageType} from "~/utils/hms_enums.ts";
import {useNavigate, useOutletContext} from "react-router";
import {type OutletContextType, SORT_SYMBOLS, type WardInfo, type WardListInfo, WardTypes} from "~/utils/models.ts";
import {Accordion, ActionIcon, Button, Card, Group, Pagination, Stack, Table, Tabs, Text} from "@mantine/core";
import {apiGetWardList} from "~/utils/hms_api.ts";
import {showErrorMessage} from "~/utils/utils.ts";
import {iconMStyle, marginLeftRight, marginRightBottom, marginRound, marginTopBottom, textCenter} from "~/styles.ts";
import PencilIcon from "mdi-react/PencilIcon";
import DeleteIcon from "mdi-react/DeleteIcon";
import {confirmCheckWardPatients, confirmDeleteWard, confirmEditOrCreateWard} from "~/components/subs/confirms.tsx";
import AddIcon from "mdi-react/AddIcon";
import {WardManageTableFilter} from "~/components/subs/WardManageTableFilter.tsx";
import EyeIcon from "mdi-react/EyeIcon";
import FilterIcon from "mdi-react/FilterIcon";
import {ResponsiveTableContainer} from "~/components/subs/ResponsiveTableContainer.tsx";
export function meta({}: Route.MetaArgs) {
return [
{ title: "Medical Teams Management" },
{ name: "description", content: "Medical Teams Management" },
];
}
export default function Component() {
const navigate = useNavigate();
const [refreshingWardList, setRefreshingWardList] = useState<boolean>(false)
const [wardInfo, setWardInfo] = useState<WardListInfo>({wards: [], total_pages: 1})
const [sortKey, setSortKey] = useState<string>("")
const [sortDesc, setSortDesc] = useState<boolean>(true)
const [currPage, setCurrPage] = useState<number>(1)
const [filterTypes, setFilterTypes] = useState<string[]>([])
const [filterOccupancy, setFilterOccupancy] = useState<[number, number]>([0, 12])
const [filterCapacity, setFilterCapacity] = useState<[number, number]>([1, 12])
const { loginUserInfo, refreshMyInfo } = useOutletContext<OutletContextType>();
const updateFilter = (types: string[], occupancy: [number, number], capacity: [number, number]) => {
setFilterTypes(types)
setFilterOccupancy(occupancy)
setFilterCapacity(capacity)
}
useEffect(() => {
refreshWardList()
}, []);
useEffect(() => {
refreshWardList()
}, [currPage]);
const refreshWardList = () => {
setRefreshingWardList(true)
apiGetWardList(currPage).then(res => {
if (!res.success) {
showErrorMessage(res.message, "Get Ward List Failed")
return
}
setWardInfo(res.data)
})
.catch(err => {})
.finally(() => { setRefreshingWardList(false) })
}
const handleSort = (key: string) => {
if (sortKey === key) {
setSortDesc(!sortDesc)
} else {
setSortKey(key)
setSortDesc(false) // 默认升序
}
}
const sortedWards = useMemo<WardInfo[]>(() => {
let data = [...wardInfo.wards]
data = data.filter((w) => {
// 类型过滤:如果 filterTypes 为空数组则不过滤
const okType = filterTypes.length === 0 || filterTypes.includes(w.type)
// occupancy 和 capacity 的区间过滤
const okOcc =
w.current_occupancy >= filterOccupancy[0] &&
w.current_occupancy <= filterOccupancy[1]
const okCap =
w.total_capacity >= filterCapacity[0] &&
w.total_capacity <= filterCapacity[1]
return okType && okOcc && okCap
})
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
}
return 0
})
return data
}, [wardInfo.wards, sortKey, sortDesc, filterTypes, filterOccupancy[0], filterOccupancy[1], filterCapacity[0],
filterCapacity[1]])
const rows = sortedWards.map((ward) => (
<Table.Tr key={ward.id}>
<Table.Td>{ward.id}</Table.Td>
<Table.Td>{ward.name}</Table.Td>
<Table.Td>{WardTypes[ward.type]}</Table.Td>
<Table.Td>{ward.current_occupancy}</Table.Td>
<Table.Td>{ward.total_capacity}</Table.Td>
<Table.Td>
<Group>
<ActionIcon onClick={() => { confirmCheckWardPatients(ward.id, refreshWardList) }}>
<EyeIcon style={iconMStyle}/>
</ActionIcon>
<ActionIcon onClick={() => { confirmEditOrCreateWard(ward, refreshWardList) }}>
<PencilIcon style={iconMStyle}/>
</ActionIcon>
<ActionIcon color="red" onClick={() => { confirmDeleteWard(ward.id, ward.name, refreshWardList) }}>
<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}>Wards Management</Text>
<Button
leftSection={<AddIcon style={iconMStyle}/>}
onClick={() => confirmEditOrCreateWard(null, refreshWardList)}
>Add Ward</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>
<WardManageTableFilter 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")}>
Ward ID{" "}
{sortKey === "id" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]}
</Table.Th>
<Table.Th onClick={() => handleSort("name")}>
Ward Name{" "}
{sortKey === "name" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]}
</Table.Th>
<Table.Th onClick={() => handleSort("type")}>
Ward Type{" "}
{sortKey === "type" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]}
</Table.Th>
<Table.Th onClick={() => handleSort("current_occupancy")}>
Current Occupancy{" "}
{sortKey === "current_occupancy" && SORT_SYMBOLS[sortDesc ? "desc" : "asc"]}
</Table.Th>
<Table.Th onClick={() => handleSort("total_capacity")}>
Capacity{" "}
{sortKey === "total_capacity" && 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={wardInfo.total_pages} value={currPage} onChange={setCurrPage} mt="sm"
style={{justifyItems: "flex-end", ...marginRightBottom}}/>
</Card.Section>
</Card>
</Stack>
);
}