safety-eval-service-frontend/src/pages/Container/QualificationReview/SiteReviewNotice/index.js

421 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import React, { useState, useEffect } from "react";
import dayjs from "dayjs";
import { Form, Table, Button, Modal, Input, Select, DatePicker, Card, Row, Col, Statistic, message, Tag } from "antd";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { tools } from "@cqsjjb/jjb-common-lib";
import { NS_QUAL_REVIEW } from "~/enumerate/namespace";
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { ON_SITE_REVIEW_STATUS_MAP } from "~/enumerate/constant";
import AttachmentUpload from "~/components/AttachmentUpload";
const { router } = tools;
const SiteReviewNotice = (props) => {
const [searchForm] = Form.useForm();
const [noticeForm] = Form.useForm();
const [resultForm] = Form.useForm();
const [noticeModalOpen, setNoticeModalOpen] = useState(false);
const [resultModalOpen, setResultModalOpen] = useState(false);
const [currentEditId, setCurrentEditId] = useState(null);
const [currentResultRow, setCurrentResultRow] = useState(null);
const {
qualReview,
queryQualOnSiteReviewPage,
queryQualOnSiteReviewStatistics,
addQualOnSiteReview,
updateQualOnSiteReview,
uploadQualOnSiteReviewResult,
fetchQualOnSiteReviewDetail,
fetchQualOnSiteReviewResultDetail,
queryOrgSelectList,
resetModelState,
} = props;
const {
qualOnSiteReviewList = [],
qualOnSiteReviewTotal = 0,
qualOnSiteReviewStats = {},
orgSelectList = [],
siteReviewNoticeLoading,
siteReviewStatisticsLoading,
siteReviewSaveLoading,
siteReviewResultLoading,
} = qualReview || {};
useEffect(() => {
queryQualOnSiteReviewStatistics();
queryOrgSelectList();
searchForm.setFieldsValue(router.query);
handleSearch();
}, []);
useEffect(() => {
if (!noticeModalOpen) {
resetModelState(NS_QUAL_REVIEW, { siteReviewSaveLoading: false });
}
}, [noticeModalOpen]);
useEffect(() => {
if (!resultModalOpen) {
resetModelState(NS_QUAL_REVIEW, { siteReviewResultLoading: false });
}
}, [resultModalOpen]);
const handleSearch = () => {
queryQualOnSiteReviewPage({
...router.query,
});
};
const handleSearchSubmit = (values) => {
const { dateRange, ...rest } = values;
router.query = {
...router.query,
...rest,
...(dateRange && dateRange[0] && { reviewDateStartBegin: dateRange[0].format("YYYY-MM-DD") }),
...(dateRange && dateRange[1] && { reviewDateStartEnd: dateRange[1].format("YYYY-MM-DD") }),
current: 1,
size: 10,
};
handleSearch();
};
const handleReset = (values) => {
router.query = { ...values, current: 1, size: 10 , reviewDateStartBegin: undefined, reviewDateStartEnd: undefined };
handleSearch();
};
const handleOpenNoticeModal = () => {
setCurrentEditId(null);
noticeForm.resetFields();
setNoticeModalOpen(true);
};
const handleEditNotice = (record) => {
setCurrentEditId(record.id);
fetchQualOnSiteReviewDetail({ id: record.id }).then((res) => {
if (res?.success !== false) {
const detail = res.data;
noticeForm.setFieldsValue({
orgId: detail.orgId,
reviewDateStart: detail.reviewDateStart ? dayjs(detail.reviewDateStart) : null,
reviewLocation: detail.reviewLocation,
reviewMembers: detail.reviewMembers,
reviewLeader: detail.reviewLeader,
materialRequirements: detail.materialRequirements,
supervisorContact: detail.supervisorContact,
contactPhone: detail.contactPhone,
});
}
});
setNoticeModalOpen(true);
};
const handleSubmitNotice = () => {
noticeForm.validateFields().then((values) => {
const payload = {
...values,
reviewDateStart: values.reviewDateStart.format("YYYY-MM-DD HH:mm:ss"),
status: "AGENCY_PENDING_CONFIRMATION",
};
const action = currentEditId
? updateQualOnSiteReview({ ...payload, id: currentEditId })
: addQualOnSiteReview(payload);
action.then((res) => {
if (res?.success !== false) {
setNoticeModalOpen(false);
message.success(currentEditId ? "现场审查通知已修改" : "现场审查通知已下发机构端");
queryQualOnSiteReviewStatistics();
handleSearch();
}
});
}).catch(() => {});
};
const handleOpenResultModal = (record) => {
setCurrentResultRow(record);
resultForm.resetFields();
resultForm.setFieldsValue({
institution: record.unitName,
reviewDateStart: record.reviewDateStart,
});
fetchQualOnSiteReviewResultDetail({ id: record.id }).then((res) => {
if (res?.success !== false && res?.data) {
const detail = res.data;
resultForm.setFieldsValue({
reviewLeader: detail.reviewLeader || "",
reviewResult: detail.reviewResult,
reviewOpinion: detail.reviewOpinion || "",
fileList: (detail.fileList || []).map((item) => ({
url: item.url,
uid: item.id,
status: "done",
name: item.name,
})),
});
}
});
setResultModalOpen(true);
};
const handleSubmitResult = () => {
resultForm.validateFields().then((values) => {
const payload = {
id: currentResultRow.id,
...values,
};
uploadQualOnSiteReviewResult(payload).then((res) => {
if (res?.success !== false) {
setResultModalOpen(false);
message.success("审查结果已保存并上传");
queryQualOnSiteReviewStatistics();
handleSearch();
}
});
}).catch(() => {});
};
const columns = [
{ title: "机构名称", dataIndex: "unitName", ellipsis: true },
{
title: "现场审查时间", dataIndex: "reviewDateStart",
},
{ title: "审查地点", dataIndex: "reviewLocation", ellipsis: true },
{ title: "审查组成员", dataIndex: "reviewMembers", ellipsis: true },
{
title: "状态", dataIndex: "status",
render: (status) => {
const cfg = ON_SITE_REVIEW_STATUS_MAP[status];
return cfg ? <Tag color={cfg.color}>{cfg.label}</Tag> : (status || "-");
},
},
{
title: "操作", width: 220, fixed: "right",
render: (_, record) => (
<TableAction>
<Button type="link" size="small" onClick={() => handleEditNotice(record)}>
修改
</Button>
<Button type="link" size="small" onClick={() => handleOpenResultModal(record)}>
审查结果上传
</Button>
</TableAction>
),
},
];
return (
<PageLayout
title="现场审查通知"
extra={
<Button type="primary" onClick={handleOpenNoticeModal}>
下发现场审查通知
</Button>
}
>
<Row gutter={16} style={{ marginBottom: 16 }}>
<Col span={8}>
<Card size="small" loading={siteReviewStatisticsLoading}>
<Statistic title="通知总数" value={qualOnSiteReviewStats.totalCount ?? "-"} suffix="条" />
<div style={{ fontSize: 12, color: "#999", marginTop: 4 }}>{qualOnSiteReviewStats.annualCumulativeCount ?? 0} 条本年度累计</div>
</Card>
</Col>
<Col span={8}>
<Card size="small" loading={siteReviewStatisticsLoading}>
<Statistic title="待开展审查" value={qualOnSiteReviewStats.pendingReviewNextThirtyDaysCount ?? ""} valueStyle={{ color: "#2563eb" }} suffix="条" />
<div style={{ fontSize: 12, color: "#999", marginTop: 4 }}>未来30日内</div>
</Card>
</Col>
<Col span={8}>
<Card size="small" loading={siteReviewStatisticsLoading}>
<Statistic title="已完成" value={qualOnSiteReviewStats.completedArchivedCount ?? "-"} valueStyle={{ color: "#059669" }} suffix="条" />
<div style={{ fontSize: 12, color: "#999", marginTop: 4 }}>审查结果已归档</div>
</Card>
</Col>
</Row>
<SearchForm
form={searchForm}
loading={siteReviewNoticeLoading}
formLine={[
<Form.Item key="unitName" name="unitName">
<ControlWrapper.Input label="机构名称" placeholder="请输入机构名称" allowClear />
</Form.Item>,
<Form.Item key="status" name="status">
<ControlWrapper.Select label="审查状态" placeholder="全部" allowClear style={{ width: "100%" }}>
{Object.entries(ON_SITE_REVIEW_STATUS_MAP).map(([value, cfg]) => (
<Select.Option key={value} value={value}>{cfg.label}</Select.Option>
))}
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="dateRange" name="dateRange">
<ControlWrapper.DatePicker.RangePicker label="审查日期" placeholder={["开始日期", "结束日期"]} />
</Form.Item>,
]}
onFinish={handleSearchSubmit}
onReset={handleReset}
style={{ marginBottom: 16 }}
/>
<Table
rowKey="id"
columns={columns}
dataSource={qualOnSiteReviewList}
loading={siteReviewNoticeLoading}
scroll={{ y: props.scrollY, x: 1200 }}
pagination={{
total: qualOnSiteReviewTotal,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
current: Number(router.query.current) || 1,
pageSize: Number(router.query.size) || 10,
onChange: (page, pageSize) => {
router.query = { ...router.query, current: page, size: pageSize };
handleSearch();
},
}}
/>
{/* 下发现场审查通知 弹窗 */}
<Modal
title={currentEditId ? "修改现场审查通知" : "下发现场审查通知"}
open={noticeModalOpen}
onCancel={() => setNoticeModalOpen(false)}
onOk={handleSubmitNotice}
okText={currentEditId ? "保存修改" : "确认下发"}
confirmLoading={siteReviewSaveLoading}
width={760}
destroyOnHidden
>
<Form form={noticeForm} layout="vertical">
<Row gutter={16}>
<Col span={12}>
<Form.Item name="orgId" label="受审查机构" rules={[{ required: true, message: "请选择" }]}>
<Select placeholder="请选择" showSearch optionFilterProp="label">
{(orgSelectList || []).map((item) => (
<Select.Option key={item.id} value={item.id} label={item.unitName}>
{item.unitName}
</Select.Option>
))}
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="reviewDateStart" label="现场审查开始时间" rules={[{ required: true, message: "请选择" }]}>
<DatePicker style={{ width: "100%" }} showTime />
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="reviewLocation" label="审查地点" rules={[{ required: true, message: "请输入" }]}>
<Input placeholder="机构办公场所、档案室及设备存放场所" maxLength={200} />
</Form.Item>
</Col>
{/* <Col span={12}>
<Form.Item name="reviewLeader" label="审查组长">
<Input placeholder="请输入" maxLength={50} />
</Form.Item>
</Col> */}
</Row>
<Form.Item name="reviewMembers" label="审查组成员名单" rules={[{ required: true, message: "请输入" }]}>
<Input placeholder="请输入审查组成员名单" maxLength={100} />
</Form.Item>
<Form.Item name="materialRequirements" label="材料准备要求">
<Input.TextArea
rows={4}
placeholder="请准备机构资质申请材料原件、人员档案、社保材料、办公场所和档案室证明、设备清单、过程控制制度及项目档案等资料。"
maxLength={500}
/>
</Form.Item>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="supervisorContact" label="监管联系人">
<Input placeholder="请输入监管联系人" maxLength={50} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="contactPhone" label="联系电话" rules={[{ pattern: /^(\d{3,4}-?\d{7,8}|\d{11})$/, message: "请输入正确的联系电话" }]}>
<Input placeholder="请输入联系电话" maxLength={20} />
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
{/* 审查结果上传弹窗 */}
<Modal
title="上传审查结果"
open={resultModalOpen}
onCancel={() => setResultModalOpen(false)}
onOk={handleSubmitResult}
okText="保存审查结果"
confirmLoading={siteReviewResultLoading}
width={760}
destroyOnHidden
>
<div style={{ fontSize: 13, color: "#666", marginBottom: 16 }}>
上传现场审查结果文件并保存审查结论和意见
</div>
<Form form={resultForm} layout="vertical">
<Row gutter={16}>
<Col span={12}>
<Form.Item name="institution" label="受审查机构">
<Input disabled />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="reviewDateStart" label="现场审查日期">
<Input disabled />
</Form.Item>
</Col>
</Row>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="reviewResult" label="审查结论" rules={[{ required: true, message: "请选择" }]}>
<Select placeholder="请选择">
<Select.Option value={true}>审查通过</Select.Option>
<Select.Option value={false}>不通过</Select.Option>
</Select>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="reviewLeader" label="审查组长">
<Input placeholder="请输入" />
</Form.Item>
</Col>
</Row>
<AttachmentUpload
name="fileList"
label="审查结果文件"
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png"
maxCount={10}
extra="支持上传审查记录、审查意见或现场审查结果文件限定pdf和图片最多10个。"
/>
<Form.Item name="reviewOpinion" label="审查意见">
<Input.TextArea rows={4} placeholder="填写现场审查发现、整改要求和结论依据" />
</Form.Item>
</Form>
</Modal>
</PageLayout>
);
};
export default Connect([NS_QUAL_REVIEW], true)(AntdTableFuncControl(SiteReviewNotice));