matter-management.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. "use client"
  2. import type React from "react"
  3. import {useMemo, useState} from "react"
  4. import dayjs from "dayjs"
  5. import {Button, Card, Col, Flex, Form, Input, Popconfirm, Row, Select, Space, Table, Tag} from "antd"
  6. import {BadgeAlertIcon as Alert, FileText} from "lucide-react"
  7. import globalMessage from "@/app/_modules/globalMessage";
  8. type Level = "红色" | "橙色" | "黄色" | "蓝色"
  9. type WarnType = "燃气" | "供水" | "电力" | "交通" | "综合"
  10. type MatterItemStatus = "启用" | "禁用" | "作废"
  11. export type MatterItem = {
  12. id: string
  13. code: string
  14. name: string
  15. type: WarnType
  16. level: Level
  17. status: MatterItemStatus
  18. createdAt: string
  19. updatedAt: string
  20. }
  21. export type ReasonEntry = {
  22. id: string
  23. type: WarnType
  24. level: Level
  25. reason: string
  26. createdAt: string
  27. }
  28. export type MatterManagementProps = {
  29. matters: MatterItem[]
  30. setMatters: React.Dispatch<React.SetStateAction<MatterItem[]>>
  31. reasons: ReasonEntry[]
  32. setReasons: React.Dispatch<React.SetStateAction<ReasonEntry[]>>
  33. }
  34. const LEVELS: Level[] = ["红色", "橙色", "黄色", "蓝色"]
  35. const TYPES: WarnType[] = ["燃气", "供水", "电力", "交通", "综合"]
  36. function levelColor(lvl: Level) {
  37. switch (lvl) {
  38. case "红色":
  39. return "red"
  40. case "橙色":
  41. return "orange"
  42. case "黄色":
  43. return "gold"
  44. case "蓝色":
  45. return "blue"
  46. default:
  47. return "default"
  48. }
  49. }
  50. function genId(prefix = "id") {
  51. return `${prefix}_${Math.random().toString(36).slice(2, 10)}`
  52. }
  53. function autoCode(type: WarnType, level: Level, seq: number) {
  54. const date = dayjs().format("YYYYMMDD")
  55. const t = { 燃气: "GAS", 供水: "WTR", 电力: "ELE", 交通: "TRF", 综合: "COM" }[type]
  56. const l = { 红色: "R", 橙色: "O", 黄色: "Y", 蓝色: "B" }[level]
  57. return `YW-${t}-${l}-${date}-${seq.toString().padStart(3, "0")}`
  58. }
  59. export default function MatterManagement({ matters, setMatters, reasons, setReasons }: MatterManagementProps) {
  60. const [form] = Form.useForm<{ name: string; type: WarnType; level: Level }>()
  61. const [seq, setSeq] = useState(matters.length + 1)
  62. const type = Form.useWatch("type", form)
  63. const level = Form.useWatch("level", form)
  64. const computedCode = useMemo(() => {
  65. if (!type || !level) return ""
  66. return autoCode(type, level, seq)
  67. }, [type, level, seq])
  68. return (
  69. <Space direction="vertical" size={16} className="w-full">
  70. <Card
  71. title={
  72. <Space>
  73. <FileText />
  74. <span>预警清单管理</span>
  75. </Space>
  76. }
  77. >
  78. <Row gutter={12}>
  79. <Col xs={24} md={10}>
  80. <Card size="small" title="新增/修改事项">
  81. <Form
  82. form={form}
  83. layout="vertical"
  84. initialValues={{ type: "综合", level: "黄色" }}
  85. onFinish={(vals) => {
  86. const item: MatterItem = {
  87. id: genId("matter"),
  88. code: computedCode || autoCode(vals.type, vals.level, seq),
  89. name: vals.name,
  90. type: vals.type,
  91. level: vals.level,
  92. status: "启用",
  93. createdAt: new Date().toISOString(),
  94. updatedAt: new Date().toISOString(),
  95. }
  96. setMatters((prev) => [item, ...prev])
  97. setSeq((s) => s + 1)
  98. form.resetFields()
  99. globalMessage.success("已新增事项")
  100. }}
  101. >
  102. <Form.Item label="事项名称" name="name" rules={[{ required: true }]}>
  103. <Input placeholder="事项名称" />
  104. </Form.Item>
  105. <Flex gap={12} wrap="wrap">
  106. <Form.Item label="专项类型" name="type" rules={[{ required: true }]} style={{ flex: 1 }}>
  107. <Select options={TYPES.map((t) => ({ label: t, value: t }))} />
  108. </Form.Item>
  109. <Form.Item label="预警等级" name="level" rules={[{ required: true }]} style={{ width: 180 }}>
  110. <Select options={LEVELS.map((l) => ({ label: l, value: l }))} />
  111. </Form.Item>
  112. </Flex>
  113. <Form.Item label="事项编码(自动生成)">
  114. <Input value={computedCode} readOnly />
  115. </Form.Item>
  116. <Space>
  117. <Button type="primary" htmlType="submit">
  118. 保存
  119. </Button>
  120. <Button onClick={() => form.resetFields()}>重置</Button>
  121. </Space>
  122. </Form>
  123. </Card>
  124. </Col>
  125. <Col xs={24} md={14}>
  126. <Card size="small" title="事项信息列表">
  127. <Table<MatterItem>
  128. size="small"
  129. rowKey="id"
  130. dataSource={matters}
  131. pagination={{ pageSize: 6 }}
  132. columns={[
  133. { title: "名称", dataIndex: "name" },
  134. { title: "编码", dataIndex: "code" },
  135. { title: "类型", dataIndex: "type" },
  136. { title: "等级", dataIndex: "level" },
  137. {
  138. title: "状态",
  139. dataIndex: "status",
  140. render: (s: MatterItemStatus) => (
  141. <Tag color={s === "启用" ? "success" : s === "禁用" ? "default" : "error"}>{s}</Tag>
  142. ),
  143. },
  144. {
  145. title: "操作",
  146. render: (_, r) => (
  147. <Space>
  148. <Button
  149. size="small"
  150. onClick={() =>
  151. setMatters((prev) =>
  152. prev.map((m) =>
  153. m.id === r.id
  154. ? {
  155. ...m,
  156. status: m.status === "启用" ? ("禁用" as const) : ("启用" as const),
  157. updatedAt: new Date().toISOString(),
  158. }
  159. : m,
  160. ),
  161. )
  162. }
  163. >
  164. {r.status === "启用" ? "禁用" : "启用"}
  165. </Button>
  166. <Popconfirm
  167. title="确定作废该事项?"
  168. onConfirm={() =>
  169. setMatters((prev) =>
  170. prev.map((m) =>
  171. m.id === r.id ? { ...m, status: "作废", updatedAt: new Date().toISOString() } : m,
  172. ),
  173. )
  174. }
  175. >
  176. <Button size="small" danger>
  177. 作废
  178. </Button>
  179. </Popconfirm>
  180. </Space>
  181. ),
  182. },
  183. ]}
  184. />
  185. </Card>
  186. </Col>
  187. </Row>
  188. </Card>
  189. <Card
  190. title={
  191. <Space>
  192. <Alert />
  193. <span>预警原因库管理</span>
  194. </Space>
  195. }
  196. >
  197. <Row gutter={12}>
  198. <Col xs={24} md={10}>
  199. <Card size="small" title="新增原因">
  200. <Form
  201. layout="vertical"
  202. onFinish={(vals: any) => {
  203. const item: ReasonEntry = {
  204. id: genId("reason"),
  205. type: vals.type,
  206. level: vals.level,
  207. reason: vals.reason,
  208. createdAt: new Date().toISOString(),
  209. }
  210. setReasons((prev) => [item, ...prev])
  211. globalMessage.success("已添加原因")
  212. }}
  213. initialValues={{ type: "综合", level: "黄色" }}
  214. >
  215. <Form.Item label="类型" name="type" rules={[{ required: true }]}>
  216. <Select options={TYPES.map((t) => ({ label: t, value: t }))} />
  217. </Form.Item>
  218. <Form.Item label="等级" name="level" rules={[{ required: true }]}>
  219. <Select options={LEVELS.map((l) => ({ label: l, value: l }))} />
  220. </Form.Item>
  221. <Form.Item label="原因描述" name="reason" rules={[{ required: true }]}>
  222. <Input.TextArea rows={3} placeholder="维护原因项,供处置人员选择" />
  223. </Form.Item>
  224. <Button type="primary" htmlType="submit">
  225. 添加
  226. </Button>
  227. </Form>
  228. </Card>
  229. </Col>
  230. <Col xs={24} md={14}>
  231. <Card size="small" title="原因库">
  232. <Table<ReasonEntry>
  233. size="small"
  234. rowKey="id"
  235. dataSource={reasons}
  236. pagination={{ pageSize: 6 }}
  237. columns={[
  238. { title: "类型", dataIndex: "type" },
  239. { title: "等级", dataIndex: "level" },
  240. { title: "原因", dataIndex: "reason" },
  241. { title: "创建时间", dataIndex: "createdAt", render: (t) => dayjs(t).format("YYYY-MM-DD") },
  242. ]}
  243. />
  244. </Card>
  245. </Col>
  246. </Row>
  247. </Card>
  248. </Space>
  249. )
  250. }