272 lines
8.1 KiB
TypeScript
272 lines
8.1 KiB
TypeScript
import { useState, useCallback, useMemo } from 'react';
|
||
import {
|
||
Button, DatePicker, Form, Input, message, Modal, Popconfirm,
|
||
Result, Select, Space, Table, Tag,
|
||
} from 'antd';
|
||
import type { ColumnsType } from 'antd/es/table';
|
||
import dayjs from 'dayjs';
|
||
|
||
import {
|
||
diagnosisApi,
|
||
type Diagnosis,
|
||
type CreateDiagnosisReq,
|
||
type UpdateDiagnosisReq,
|
||
DIAGNOSIS_TYPE_OPTIONS,
|
||
DIAGNOSIS_STATUS_OPTIONS,
|
||
DIAGNOSIS_TYPE_COLOR,
|
||
DIAGNOSIS_STATUS_COLOR,
|
||
DIAGNOSIS_TYPE_LABEL,
|
||
DIAGNOSIS_STATUS_LABEL,
|
||
} from '../../api/health/diagnoses';
|
||
import { PageContainer } from '../../components/PageContainer';
|
||
import { usePermission } from '../../hooks/usePermission';
|
||
|
||
export default function DiagnosisList() {
|
||
const { hasPermission } = usePermission('health.health-data.list');
|
||
|
||
const [data, setData] = useState<Diagnosis[]>([]);
|
||
const [total, setTotal] = useState(0);
|
||
const [page, setPage] = useState(1);
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
const [patientId, setPatientId] = useState('');
|
||
const [searchInput, setSearchInput] = useState('');
|
||
|
||
const [modalOpen, setModalOpen] = useState(false);
|
||
const [editRecord, setEditRecord] = useState<Diagnosis | null>(null);
|
||
const [submitting, setSubmitting] = useState(false);
|
||
const [form] = Form.useForm();
|
||
|
||
const pageSize = 20;
|
||
|
||
const fetchData = useCallback(async (pid: string, p: number) => {
|
||
if (!pid) return;
|
||
setLoading(true);
|
||
try {
|
||
const resp = await diagnosisApi.list(pid, { page: p, page_size: pageSize });
|
||
setData(resp.data);
|
||
setTotal(resp.total);
|
||
setPage(p);
|
||
} catch {
|
||
message.error('加载诊断记录失败');
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, []);
|
||
|
||
const handleSearch = () => {
|
||
const pid = searchInput.trim();
|
||
if (!pid) {
|
||
message.warning('请输入患者 ID');
|
||
return;
|
||
}
|
||
setPatientId(pid);
|
||
fetchData(pid, 1);
|
||
};
|
||
|
||
const handleCreate = () => {
|
||
setEditRecord(null);
|
||
form.resetFields();
|
||
form.setFieldsValue({ patient_id: patientId, diagnosis_type: 'primary', status: 'active' });
|
||
setModalOpen(true);
|
||
};
|
||
|
||
const handleEdit = (record: Diagnosis) => {
|
||
setEditRecord(record);
|
||
form.setFieldsValue({
|
||
icd_code: record.icd_code,
|
||
diagnosis_name: record.diagnosis_name,
|
||
diagnosis_type: record.diagnosis_type,
|
||
diagnosed_date: record.diagnosed_date ? dayjs(record.diagnosed_date) : undefined,
|
||
status: record.status,
|
||
diagnosed_by: record.diagnosed_by,
|
||
notes: record.notes,
|
||
});
|
||
setModalOpen(true);
|
||
};
|
||
|
||
const handleSubmit = async () => {
|
||
try {
|
||
const values = await form.validateFields();
|
||
const req = {
|
||
...values,
|
||
diagnosed_date: values.diagnosed_date?.format('YYYY-MM-DD'),
|
||
};
|
||
|
||
setSubmitting(true);
|
||
if (editRecord) {
|
||
await diagnosisApi.update(editRecord.id, {
|
||
...req,
|
||
version: editRecord.version,
|
||
} as UpdateDiagnosisReq & { version: number });
|
||
message.success('诊断记录已更新');
|
||
} else {
|
||
await diagnosisApi.create(req.patient_id, req as CreateDiagnosisReq);
|
||
message.success('诊断记录已创建');
|
||
}
|
||
setModalOpen(false);
|
||
fetchData(patientId, page);
|
||
} catch {
|
||
// validation
|
||
} finally {
|
||
setSubmitting(false);
|
||
}
|
||
};
|
||
|
||
const handleDelete = async (record: Diagnosis) => {
|
||
try {
|
||
await diagnosisApi.delete(record.id, record.version);
|
||
message.success('诊断记录已删除');
|
||
fetchData(patientId, page);
|
||
} catch {
|
||
message.error('删除失败');
|
||
}
|
||
};
|
||
|
||
const columns: ColumnsType<Diagnosis> = useMemo(() => [
|
||
{
|
||
title: 'ICD 编码',
|
||
dataIndex: 'icd_code',
|
||
width: 110,
|
||
},
|
||
{
|
||
title: '诊断名称',
|
||
dataIndex: 'diagnosis_name',
|
||
width: 200,
|
||
ellipsis: true,
|
||
},
|
||
{
|
||
title: '类型',
|
||
dataIndex: 'diagnosis_type',
|
||
width: 100,
|
||
render: (v: string) => (
|
||
<Tag color={DIAGNOSIS_TYPE_COLOR[v] ?? 'default'}>
|
||
{DIAGNOSIS_TYPE_LABEL[v] ?? v}
|
||
</Tag>
|
||
),
|
||
},
|
||
{
|
||
title: '确诊日期',
|
||
dataIndex: 'diagnosed_date',
|
||
width: 110,
|
||
render: (v: string) => v ? dayjs(v).format('YYYY-MM-DD') : '-',
|
||
},
|
||
{
|
||
title: '状态',
|
||
dataIndex: 'status',
|
||
width: 80,
|
||
render: (v: string) => (
|
||
<Tag color={DIAGNOSIS_STATUS_COLOR[v] ?? 'default'}>
|
||
{DIAGNOSIS_STATUS_LABEL[v] ?? v}
|
||
</Tag>
|
||
),
|
||
},
|
||
{
|
||
title: '备注',
|
||
dataIndex: 'notes',
|
||
width: 160,
|
||
ellipsis: true,
|
||
render: (v: string) => v ?? '-',
|
||
},
|
||
{
|
||
title: '操作',
|
||
width: 140,
|
||
render: (_, record) => (
|
||
<Space>
|
||
<Button size="small" onClick={() => handleEdit(record)}>编辑</Button>
|
||
<Popconfirm title="确定删除此诊断记录?" onConfirm={() => handleDelete(record)}>
|
||
<Button size="small" danger>删除</Button>
|
||
</Popconfirm>
|
||
</Space>
|
||
),
|
||
},
|
||
], [patientId, page]);
|
||
|
||
if (!hasPermission) {
|
||
return <Result status="403" title="权限不足" subTitle="您没有查看诊断记录的权限" />;
|
||
}
|
||
|
||
return (
|
||
<PageContainer
|
||
title="诊断记录"
|
||
actions={patientId ? <Button type="primary" onClick={handleCreate}>添加诊断</Button> : undefined}
|
||
>
|
||
<Space style={{ marginBottom: 16 }} wrap>
|
||
<Input.Search
|
||
placeholder="输入患者 ID 搜索"
|
||
value={searchInput}
|
||
onChange={(e) => setSearchInput(e.target.value)}
|
||
onSearch={handleSearch}
|
||
style={{ width: 360 }}
|
||
enterButton="查询"
|
||
/>
|
||
{patientId && (
|
||
<Tag color="blue">当前患者: {patientId}</Tag>
|
||
)}
|
||
</Space>
|
||
|
||
{!patientId ? (
|
||
<Result title="请输入患者 ID 查询诊断记录" />
|
||
) : (
|
||
<Table<Diagnosis>
|
||
rowKey="id"
|
||
columns={columns}
|
||
dataSource={data}
|
||
loading={loading}
|
||
pagination={{
|
||
current: page,
|
||
pageSize,
|
||
total,
|
||
showTotal: (t) => `共 ${t} 条`,
|
||
onChange: (p) => fetchData(patientId, p),
|
||
}}
|
||
/>
|
||
)}
|
||
|
||
<Modal
|
||
title={editRecord ? '编辑诊断' : '添加诊断'}
|
||
open={modalOpen}
|
||
onOk={handleSubmit}
|
||
onCancel={() => setModalOpen(false)}
|
||
confirmLoading={submitting}
|
||
width={600}
|
||
>
|
||
<Form form={form} layout="vertical">
|
||
{!editRecord && (
|
||
<Form.Item name="patient_id" label="患者 ID" rules={[{ required: true }]}>
|
||
<Input disabled />
|
||
</Form.Item>
|
||
)}
|
||
<Space style={{ width: '100%' }} size="middle">
|
||
<Form.Item name="icd_code" label="ICD 编码" rules={[{ required: true, message: '请输入 ICD 编码' }]} style={{ width: 200 }}>
|
||
<Input placeholder="如:N18.9" />
|
||
</Form.Item>
|
||
<Form.Item name="diagnosis_name" label="诊断名称" rules={[{ required: true, message: '请输入诊断名称' }]} style={{ width: 320 }}>
|
||
<Input placeholder="如:慢性肾脏病" />
|
||
</Form.Item>
|
||
</Space>
|
||
<Space style={{ width: '100%' }} size="middle">
|
||
<Form.Item name="diagnosis_type" label="类型" style={{ width: 200 }}>
|
||
<Select options={DIAGNOSIS_TYPE_OPTIONS} />
|
||
</Form.Item>
|
||
<Form.Item name="diagnosed_date" label="确诊日期" rules={[{ required: true, message: '请选择确诊日期' }]} style={{ width: 260 }}>
|
||
<DatePicker style={{ width: '100%' }} />
|
||
</Form.Item>
|
||
</Space>
|
||
<Space style={{ width: '100%' }} size="middle">
|
||
<Form.Item name="status" label="状态" style={{ width: 200 }}>
|
||
<Select options={DIAGNOSIS_STATUS_OPTIONS} />
|
||
</Form.Item>
|
||
<Form.Item name="diagnosed_by" label="确诊医生 ID" style={{ width: 320 }}>
|
||
<Input placeholder="医生 UUID(可选)" />
|
||
</Form.Item>
|
||
</Space>
|
||
<Form.Item name="notes" label="备注">
|
||
<Input.TextArea rows={2} />
|
||
</Form.Item>
|
||
</Form>
|
||
</Modal>
|
||
</PageContainer>
|
||
);
|
||
}
|