TaskReader.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import { useEffect, useState } from "react";
  2. import { Divider, Skeleton, Space, Tag, Typography, message } from "antd";
  3. import { CodeSandboxOutlined } from "@ant-design/icons";
  4. import type {
  5. ITaskData,
  6. ITaskResponse,
  7. ITaskUpdateRequest,
  8. } from "../../api/task";
  9. import { get, patch } from "../../request";
  10. import User from "../auth/User";
  11. import TimeShow from "../general/TimeShow";
  12. import TaskEditButton, { type TRelation } from "./TaskEditButton";
  13. import PreTask from "./PreTask";
  14. import Like from "../like/Like";
  15. import Assignees from "./Assignees";
  16. import PlanDate from "./PlanDate";
  17. import TaskTitle from "./TaskTitle";
  18. import TaskStatus from "./TaskStatus";
  19. import Description from "./Description";
  20. import Category from "./Category";
  21. import { useIntl } from "react-intl";
  22. import TaskLog from "./TaskLog";
  23. import DiscussionDrawer from "../discussion/DiscussionDrawer";
  24. const { Text } = Typography;
  25. export const Milestone = ({ task }: { task?: ITaskData }) => {
  26. const intl = useIntl();
  27. return task?.is_milestone ? (
  28. <Tag icon={<CodeSandboxOutlined />} color="error">
  29. {intl.formatMessage({ id: "labels.milestone" })}
  30. </Tag>
  31. ) : null;
  32. };
  33. interface IWidget {
  34. taskId?: string;
  35. onChange?: (data: ITaskData[]) => void;
  36. onDiscussion?: () => void;
  37. }
  38. const TaskReader = ({ taskId, onChange }: IWidget) => {
  39. const [openPreTask, setOpenPreTask] = useState(false);
  40. const [openNextTask, setOpenNextTask] = useState(false);
  41. const [task, setTask] = useState<ITaskData>();
  42. const [loading, setLoading] = useState(true);
  43. const [open, setOpen] = useState(false);
  44. useEffect(() => {
  45. async function load() {
  46. const url = `/v2/task/${taskId}`;
  47. console.info("task api request", url);
  48. setLoading(true);
  49. get<ITaskResponse>(url)
  50. .then((json) => {
  51. console.info("task api response", json);
  52. if (json.ok) {
  53. setTask(json.data);
  54. }
  55. })
  56. .finally(() => setLoading(false));
  57. }
  58. load();
  59. }, [taskId]);
  60. const updatePreTask = (type: TRelation, data: ITaskData, has: boolean) => {
  61. if (!taskId || !data) {
  62. return;
  63. }
  64. const setting: ITaskUpdateRequest = {
  65. id: taskId,
  66. studio_name: "",
  67. };
  68. if (type === "pre") {
  69. let newPre =
  70. task?.pre_task?.filter((value) => value.id !== data.id) ?? [];
  71. if (has) {
  72. newPre = [...newPre, data];
  73. }
  74. setting.pre_task_id = newPre?.map((item) => item.id).join();
  75. } else if (type === "next") {
  76. let newNext =
  77. task?.next_task?.filter((value) => value.id !== data.id) ?? [];
  78. if (has) {
  79. newNext = [...newNext, data];
  80. }
  81. setting.next_task_id = newNext?.map((item) => item.id).join();
  82. }
  83. const url = `/v2/task/${setting.id}`;
  84. console.info("api request", url, setting);
  85. patch<ITaskUpdateRequest, ITaskResponse>(url, setting).then((json) => {
  86. console.info("api response", json);
  87. if (json.ok) {
  88. message.success("Success");
  89. setTask(json.data);
  90. onChange?.([json.data]);
  91. } else {
  92. message.error(json.message);
  93. }
  94. });
  95. };
  96. return loading ? (
  97. <Skeleton active />
  98. ) : (
  99. <div>
  100. <div style={{ display: "flex", justifyContent: "space-between" }}>
  101. <Space>
  102. <TaskStatus task={task} />
  103. <Milestone task={task} />
  104. <PreTask
  105. task={task}
  106. open={openPreTask}
  107. type="pre"
  108. onChange={(data, has) => {
  109. updatePreTask("pre", data, has);
  110. setOpenPreTask(false);
  111. }}
  112. onTagClick={() => setOpenPreTask(true)}
  113. onClose={() => setOpenPreTask(false)}
  114. />
  115. <PreTask
  116. task={task}
  117. open={openNextTask}
  118. type="next"
  119. onChange={(data, has) => {
  120. updatePreTask("next", data, has);
  121. setOpenNextTask(false);
  122. }}
  123. onClose={() => setOpenNextTask(false)}
  124. onTagClick={() => setOpenNextTask(true)}
  125. />
  126. </Space>
  127. <div>
  128. <TaskEditButton
  129. task={task}
  130. onChange={(tasks: ITaskData[]) => {
  131. setTask(tasks.find((value) => value.id === taskId));
  132. onChange?.(tasks);
  133. }}
  134. onPreTask={(type: TRelation) => {
  135. if (type === "pre") {
  136. setOpenPreTask(true);
  137. } else if (type === "next") {
  138. setOpenNextTask(true);
  139. }
  140. }}
  141. />
  142. </div>
  143. </div>
  144. <TaskTitle
  145. task={task}
  146. onChange={(data) => {
  147. setTask(data[0]);
  148. onChange?.(data);
  149. }}
  150. />
  151. <div style={{ display: "flex", flexDirection: "column" }}>
  152. <Space>
  153. <User {...task?.editor} />
  154. <TimeShow updatedAt={task?.updated_at} />
  155. <Like resId={task?.id} resType="task" />
  156. </Space>
  157. <Space
  158. style={{ display: task?.type === "workflow" ? "none" : "unset" }}
  159. >
  160. <Text type="secondary" key={"2"}>
  161. 执行人
  162. </Text>
  163. <User key={"executor"} {...task?.executor} />
  164. </Space>
  165. <Space>
  166. <Text type="secondary" key={"1"}>
  167. 指派给
  168. </Text>
  169. <Assignees
  170. key={"assignees"}
  171. task={task}
  172. onChange={(data) => {
  173. setTask(data[0]);
  174. onChange?.(data);
  175. }}
  176. />
  177. </Space>
  178. <Space>
  179. <Text type="secondary">起止日期</Text>
  180. <div style={{ width: 400 }}>
  181. <PlanDate />
  182. </div>
  183. </Space>
  184. <Space>
  185. <Text type="secondary">类别</Text>
  186. <Category
  187. task={task}
  188. onChange={(data) => {
  189. setTask(data[0]);
  190. onChange?.(data);
  191. }}
  192. />
  193. </Space>
  194. </div>
  195. <Divider />
  196. <TaskLog taskId={taskId} onMore={() => setOpen(true)} />
  197. <Description
  198. task={task}
  199. onChange={(data) => {
  200. setTask(data[0]);
  201. onChange?.(data);
  202. }}
  203. onDiscussion={() => setOpen(true)}
  204. />
  205. <DiscussionDrawer
  206. open={open}
  207. onClose={() => setOpen(false)}
  208. resId={taskId}
  209. resType="task"
  210. />
  211. </div>
  212. );
  213. };
  214. export default TaskReader;