ActionPanel.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  1. import React from 'react';
  2. import { Button, Tooltip, Modal, message } from 'antd';
  3. import { useDispatch, useSelector } from 'react-redux';
  4. import {
  5. deleteWorkThunk,
  6. lockWorkInWorklistThunk,
  7. } from '@/states/patient/worklist/slices/workSlice';
  8. import { deleteWorkThunk as deleteWorkThunkFromHistory, lockWorkInhistorylistThunk } from '@/states/patient/worklist/slices/history';
  9. import { switchToSendPanel } from '@/states/patient/worklist/slices/historyPanelSwitchSlice';
  10. import { FormattedMessage } from 'react-intl';
  11. import { AppDispatch, RootState, useAppSelector } from '@/states/store';
  12. import Icon from '@/components/Icon';
  13. import DiagnosticReport from '../DiagnosticReport';
  14. import { Popup } from 'antd-mobile';
  15. import { setVisible } from '@/states/patient/DiagnosticReport/slice';
  16. import EditTaskModal from './EditTaskModal';
  17. import { openEditModal } from '@/states/patient/edit/editFormSlice';
  18. import { setBusinessFlow } from '@/states/BusinessFlowSlice';
  19. import { setSourceTask, setRegisterInfo } from '@/states/patient/reregister/reregisterSlice';
  20. import { mapTaskToRegisterInfo } from '@/domain/patient/taskToRegister';
  21. import { showNotImplemented } from '@/utils/notificationHelper';
  22. import { string } from 'zod';
  23. interface ActionButtonProps {
  24. icon: React.ReactNode;
  25. tooltip: React.ReactNode;
  26. onClick?: () => void;
  27. 'data-testid'?: string;
  28. }
  29. const ActionButton: React.FC<ActionButtonProps> = ({
  30. icon,
  31. tooltip,
  32. onClick,
  33. 'data-testid': dataTestId,
  34. }) => (
  35. <Tooltip title={tooltip}>
  36. <Button icon={icon} onClick={onClick} style={{ width: '2.5rem' }} data-testid={dataTestId} />
  37. </Tooltip>
  38. );
  39. const ActionPanel: React.FC = () => {
  40. const dispatch = useDispatch<AppDispatch>();
  41. const workSelectedIds = useSelector(
  42. (state: RootState) => state.workSelection.selectedIds
  43. );
  44. const historySelectedIds = useSelector(
  45. (state: RootState) => state.historySelection.selectedIds
  46. );
  47. const visible = useSelector(
  48. (state: RootState) => state.diagnosticReport.visible
  49. );
  50. const themeType = useAppSelector((state) => state.theme.themeType);
  51. const currentKey = useSelector(
  52. (state: RootState) => state.BusinessFlow.currentKey
  53. );
  54. const workEntities = useSelector(
  55. (state: RootState) => state.workEntities.data
  56. );
  57. const workEntitiesFromHistory = useSelector(
  58. (state: RootState) => state.historyEntities.data
  59. );
  60. const getSelectedWorkIds = () => {
  61. const selectedIds =
  62. currentKey === 'worklist' ? workSelectedIds : historySelectedIds;
  63. return selectedIds;
  64. };
  65. const getDeleteThunk = (): typeof deleteWorkThunk => {
  66. return currentKey === 'worklist'
  67. ? deleteWorkThunk
  68. : deleteWorkThunkFromHistory;
  69. };
  70. const getLockThunk = () => {
  71. return currentKey === 'worklist'
  72. ? lockWorkInWorklistThunk
  73. : lockWorkInhistorylistThunk;
  74. };
  75. const getSelectedWorks = () => {
  76. const selectedIds = getSelectedWorkIds();
  77. if (currentKey === 'worklist') {
  78. return workEntities.find((w) => selectedIds.includes(w.StudyID));
  79. } else if (currentKey === 'historylist') {
  80. return workEntitiesFromHistory.find((w) =>
  81. selectedIds.includes(w.StudyID)
  82. );
  83. }
  84. };
  85. // 使用 worklist 或 history 的选中项,取决于哪个有值
  86. const selectedIds = getSelectedWorkIds();
  87. const handleDelete = () => {
  88. if (selectedIds.length === 0) {
  89. message.warning('请先选择要删除的项目');
  90. return;
  91. }
  92. //判断是否锁定
  93. const selectedWork = getSelectedWorks();
  94. if (selectedWork?.StudyLock === 'Locked') {
  95. message.warning('锁定状态不可删除');
  96. return;
  97. }
  98. Modal.confirm({
  99. title: '确认删除',
  100. content: `确定要删除选中的 ${selectedIds.length} 个项目吗?此操作不可撤销。`,
  101. okText: '确认删除',
  102. cancelText: '取消',
  103. okButtonProps: {
  104. danger: true,
  105. 'data-testid': 'modal-confirm-delete'
  106. },
  107. cancelButtonProps: {
  108. 'data-testid': 'modal-cancel-delete'
  109. },
  110. centered: true,
  111. onOk: () => {
  112. const delThunk = getDeleteThunk();
  113. dispatch(delThunk(selectedIds));
  114. },
  115. });
  116. };
  117. const handleSend = () => {
  118. dispatch(switchToSendPanel());
  119. };
  120. const handleShowReport = () => {
  121. dispatch(setVisible(true));
  122. };
  123. const getWorksFromWorklistOrHistory = () => {
  124. return currentKey === 'worklist' ? workEntities : workEntitiesFromHistory;
  125. };
  126. const handleEdit = () => {
  127. const selectedIds = getSelectedWorkIds();
  128. if (selectedIds.length === 0) {
  129. message.warning('请先选择要编辑的任务');
  130. return;
  131. }
  132. if (selectedIds.length > 1) {
  133. message.warning('只能编辑一个任务');
  134. return;
  135. }
  136. const works = getWorksFromWorklistOrHistory();
  137. const task = works.find((item) => item.StudyID === selectedIds[0]);
  138. if (task) {
  139. // 通过 dispatch action 传递数据到 slice
  140. dispatch(openEditModal(task));
  141. }
  142. };
  143. const handleReRegister = async () => {
  144. const selectedIds = getSelectedWorkIds();
  145. // 验证选择
  146. if (selectedIds.length === 0) {
  147. message.warning('请先选择要重新注册的项目');
  148. return;
  149. }
  150. if (selectedIds.length > 1) {
  151. message.warning('只能重新注册一个项目');
  152. return;
  153. }
  154. const works = getWorksFromWorklistOrHistory();
  155. const task = works.find((item) => item.StudyID === selectedIds[0]);
  156. // 验证任务数据
  157. if (!task) {
  158. message.error('找不到选中的任务数据');
  159. return;
  160. }
  161. // 验证必要字段
  162. if (!task.PatientName || !task.PatientID) {
  163. message.error('任务数据不完整,缺少必要信息');
  164. return;
  165. }
  166. try {
  167. // 设置源任务数据
  168. await dispatch(setSourceTask(task));
  169. // 将Task数据映射为RegisterInfo
  170. const registerInfo = mapTaskToRegisterInfo(task);
  171. console.log(`study转换后的注册信息:${JSON.stringify(registerInfo)}`)
  172. // 验证映射后的数据
  173. if (!registerInfo.patient_name || !registerInfo.patient_id) {
  174. throw new Error('数据映射失败,缺少必要信息');
  175. }
  176. console.log(`开始设置注册信息`);
  177. // 设置注册信息
  178. await dispatch(setRegisterInfo(registerInfo));
  179. console.log(`开始切换到注册页面`);
  180. // 切换到注册页面
  181. await dispatch(setBusinessFlow('register'));
  182. message.success('已切换到注册页面,表单已预填充');
  183. } catch (error) {
  184. console.error('ReRegister error:', error);
  185. const errorMessage = error instanceof Error ? error.message : '重新注册失败,请重试';
  186. message.error(`重新注册失败: ${errorMessage}`);
  187. }
  188. };
  189. const handleLock = () => {
  190. const selectedIds = getSelectedWorkIds();
  191. // 2. 检查是否有选中项
  192. if (selectedIds.length === 0) {
  193. message.warning('请先选择要锁定/解锁的项目');
  194. return;
  195. }
  196. // 3. 获取第一个选中项的锁定状态
  197. const works = getWorksFromWorklistOrHistory();
  198. const selectedItem = works.find((item) => item.StudyID === selectedIds[0]);
  199. if (!selectedItem) return;
  200. // 4. 根据当前状态切换
  201. const newLockState =
  202. selectedItem.StudyLock === 'Locked' ? 'Unlocked' : 'Locked';
  203. const lockThunk = getLockThunk();
  204. // 为每个选中项执行锁定/解锁操作
  205. selectedIds.forEach((studyId) => {
  206. console.log(
  207. `锁定,触发action ,目标 studyid是 ${studyId},新状态是 ${newLockState}`
  208. );
  209. dispatch(lockThunk({ studyId, lock: newLockState }));
  210. });
  211. };
  212. return (
  213. <div className="flex flex-wrap gap-2 w-full">
  214. <ActionButton
  215. icon={
  216. <Icon
  217. module="module-patient"
  218. name="Delete"
  219. userId="base"
  220. theme="default"
  221. size="2x"
  222. state="normal"
  223. />
  224. }
  225. tooltip={
  226. <FormattedMessage
  227. id="actionPanel.deleteTask"
  228. defaultMessage="actionPanel.deleteTask"
  229. />
  230. }
  231. onClick={handleDelete}
  232. data-testid="delete-button"
  233. />
  234. <ActionButton
  235. icon={
  236. <Icon
  237. module="module-patient"
  238. name="EditPatient"
  239. userId="base"
  240. theme="default"
  241. size="2x"
  242. state="normal"
  243. />
  244. }
  245. tooltip={
  246. <FormattedMessage
  247. id="actionPanel.editPatient"
  248. defaultMessage="actionPanel.editPatient"
  249. />
  250. }
  251. onClick={handleEdit}
  252. />
  253. <ActionButton
  254. icon={
  255. <Icon
  256. module="module-patient"
  257. name="Protect"
  258. userId="base"
  259. theme="default"
  260. size="2x"
  261. state="normal"
  262. />
  263. }
  264. tooltip={
  265. <FormattedMessage
  266. id="actionPanel.lockTask"
  267. defaultMessage="actionPanel.lockTask"
  268. />
  269. }
  270. onClick={handleLock}
  271. />
  272. <ActionButton
  273. icon={
  274. <Icon
  275. module="module-patient"
  276. name="RIS"
  277. userId="base"
  278. theme="default"
  279. size="2x"
  280. state="normal"
  281. />
  282. }
  283. tooltip={
  284. <FormattedMessage
  285. id="actionPanel.risSync"
  286. defaultMessage="actionPanel.risSync"
  287. />
  288. }
  289. onClick={() => showNotImplemented('')}
  290. />
  291. {currentKey === 'historylist' && (
  292. <ActionButton
  293. icon={
  294. <Icon
  295. module="module-patient"
  296. name="ReRegister"
  297. userId="base"
  298. theme="default"
  299. size="2x"
  300. state="normal"
  301. />
  302. }
  303. tooltip={
  304. <FormattedMessage
  305. id="actionPanel.reRegister"
  306. defaultMessage="actionPanel.reRegister"
  307. />
  308. }
  309. onClick={handleReRegister}
  310. />)}
  311. <ActionButton
  312. icon={
  313. <Icon
  314. module="module-patient"
  315. name="Sort"
  316. userId="base"
  317. theme="default"
  318. size="2x"
  319. state="normal"
  320. />
  321. }
  322. tooltip={
  323. <FormattedMessage
  324. id="actionPanel.sortList"
  325. defaultMessage="actionPanel.sortList"
  326. />
  327. }
  328. onClick={() => showNotImplemented('')}
  329. />
  330. <ActionButton
  331. icon={
  332. <Icon
  333. module="module-patient"
  334. name="Swap"
  335. userId="base"
  336. theme="default"
  337. size="2x"
  338. state="normal"
  339. />
  340. }
  341. tooltip={
  342. <FormattedMessage
  343. id="actionPanel.imageExchange"
  344. defaultMessage="actionPanel.imageExchange"
  345. />
  346. }
  347. onClick={() => showNotImplemented('')}
  348. />
  349. {currentKey === 'historylist' && (
  350. <ActionButton
  351. icon={
  352. <Icon
  353. module="module-patient"
  354. name="Send"
  355. userId="base"
  356. theme="default"
  357. size="2x"
  358. state="normal"
  359. />
  360. }
  361. tooltip={
  362. <FormattedMessage
  363. id="actionPanel.send"
  364. defaultMessage="actionPanel.send"
  365. />
  366. }
  367. onClick={handleSend}
  368. />
  369. )}
  370. {/* 我们不需要导出功能 */}
  371. {/* <ActionButton
  372. icon={
  373. <Icon
  374. module="module-patient"
  375. name="Export"
  376. userId="base"
  377. theme="default"
  378. size="2x"
  379. state="normal"
  380. />
  381. }
  382. tooltip={
  383. <FormattedMessage
  384. id="actionPanel.export"
  385. defaultMessage="actionPanel.export"
  386. />
  387. }
  388. onClick={() => showNotImplemented('')}
  389. /> */}
  390. {/* 我们不需要导入功能 */}
  391. {/* <ActionButton
  392. icon={
  393. <Icon
  394. module="module-patient"
  395. name="Import"
  396. userId="base"
  397. theme="default"
  398. size="2x"
  399. state="normal"
  400. />
  401. }
  402. tooltip={
  403. <FormattedMessage
  404. id="actionPanel.import"
  405. defaultMessage="actionPanel.import"
  406. />
  407. }
  408. onClick={() => showNotImplemented('')}
  409. /> */}
  410. <ActionButton
  411. icon={
  412. <Icon
  413. module="module-patient"
  414. name="report"
  415. userId="base"
  416. theme={themeType}
  417. size="2x"
  418. state="normal"
  419. width={40}
  420. height={40}
  421. />
  422. }
  423. tooltip={
  424. <FormattedMessage
  425. id="actionPanel.showReport"
  426. defaultMessage="actionPanel.showReport"
  427. />
  428. }
  429. onClick={handleShowReport}
  430. />
  431. <Popup
  432. visible={visible}
  433. onMaskClick={() => dispatch(setVisible(false))}
  434. position="right"
  435. bodyStyle={{ width: '100vw', height: '100vh' }}
  436. >
  437. <DiagnosticReport />
  438. </Popup>
  439. <EditTaskModal />
  440. </div>
  441. );
  442. };
  443. export default ActionPanel;