Remark
This commit is contained in:
@@ -33,8 +33,14 @@ const TaskDetailModal: FC<Props> = ({ open, task, statusLabel, onClose }) => {
|
|||||||
const updateTask = useUpdateTask();
|
const updateTask = useUpdateTask();
|
||||||
const deleteTask = useDeleteTask();
|
const deleteTask = useDeleteTask();
|
||||||
|
|
||||||
const taskDetail: TaskDetailType | undefined =
|
const rawTaskDetail = getTaskDetail.data?.data?.task ?? getTaskDetail.data?.data;
|
||||||
getTaskDetail.data?.data?.task ?? getTaskDetail.data?.data;
|
const taskDetail: TaskDetailType | undefined = rawTaskDetail
|
||||||
|
? {
|
||||||
|
...rawTaskDetail,
|
||||||
|
id: rawTaskDetail.id ?? task?.id ?? "",
|
||||||
|
labels: rawTaskDetail.labels ?? rawTaskDetail.remarks,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
@@ -128,7 +134,12 @@ const TaskDetailModal: FC<Props> = ({ open, task, statusLabel, onClose }) => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<TaskDetailToolbar activeTab={activeTab} onTabChange={handleTabChange} taskDetail={taskDetail} />
|
<TaskDetailToolbar
|
||||||
|
activeTab={activeTab}
|
||||||
|
onTabChange={handleTabChange}
|
||||||
|
taskDetail={taskDetail}
|
||||||
|
taskId={task.id}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<TaskDetailDescription
|
<TaskDetailDescription
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { type FC, useEffect, useState } from "react";
|
import { type FC, useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { toast } from "react-toastify";
|
||||||
import TaskDetailAttachmentPopover from "./attachment/TaskDetailAttachmentPopover";
|
import TaskDetailAttachmentPopover from "./attachment/TaskDetailAttachmentPopover";
|
||||||
import { DEFAULT_DATE_RANGE } from "./date/constants";
|
import { DEFAULT_DATE_RANGE } from "./date/constants";
|
||||||
import TaskDetailDatePopover from "./date/TaskDetailDatePopover";
|
import TaskDetailDatePopover from "./date/TaskDetailDatePopover";
|
||||||
@@ -9,7 +11,6 @@ import persian_fa from "react-date-object/locales/persian_fa";
|
|||||||
import { DEFAULT_CHECKLISTS } from "./checklist/constants";
|
import { DEFAULT_CHECKLISTS } from "./checklist/constants";
|
||||||
import TaskDetailChecklistPopover from "./checklist/TaskDetailChecklistPopover";
|
import TaskDetailChecklistPopover from "./checklist/TaskDetailChecklistPopover";
|
||||||
import type { TaskChecklist } from "./checklist/types";
|
import type { TaskChecklist } from "./checklist/types";
|
||||||
import { DEFAULT_LABELS } from "./labels/constants";
|
|
||||||
import TaskDetailLabelsPopover from "./labels/TaskDetailLabelsPopover";
|
import TaskDetailLabelsPopover from "./labels/TaskDetailLabelsPopover";
|
||||||
import type { LabelsView, TaskLabel } from "./labels/types";
|
import type { LabelsView, TaskLabel } from "./labels/types";
|
||||||
import TaskDetailActionBar from "./TaskDetailActionBar";
|
import TaskDetailActionBar from "./TaskDetailActionBar";
|
||||||
@@ -18,11 +19,14 @@ import type { TaskDetailTab } from "./types";
|
|||||||
import { DEFAULT_USERS } from "./users/constants";
|
import { DEFAULT_USERS } from "./users/constants";
|
||||||
import TaskDetailUsersPopover from "./users/TaskDetailUsersPopover";
|
import TaskDetailUsersPopover from "./users/TaskDetailUsersPopover";
|
||||||
import type { TaskDetailType } from "../../task/types/TaskTypes";
|
import type { TaskDetailType } from "../../task/types/TaskTypes";
|
||||||
|
import { useCreateTaskRemark } from "../../task/hooks/useTaskData";
|
||||||
|
import { ErrorType } from "../../../../helpers/types";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
activeTab: TaskDetailTab | null;
|
activeTab: TaskDetailTab | null;
|
||||||
onTabChange: (tab: TaskDetailTab) => void;
|
onTabChange: (tab: TaskDetailTab) => void;
|
||||||
taskDetail?: TaskDetailType;
|
taskDetail?: TaskDetailType;
|
||||||
|
taskId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseDate = (value?: string): DateObject | null => {
|
const parseDate = (value?: string): DateObject | null => {
|
||||||
@@ -31,10 +35,12 @@ const parseDate = (value?: string): DateObject | null => {
|
|||||||
return date.isValid ? date : null;
|
return date.isValid ? date : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail }) => {
|
const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail, taskId }) => {
|
||||||
|
const { t } = useTranslation("global");
|
||||||
|
const createTaskRemark = useCreateTaskRemark();
|
||||||
const [labelsView, setLabelsView] = useState<LabelsView>("list");
|
const [labelsView, setLabelsView] = useState<LabelsView>("list");
|
||||||
const [labels, setLabels] = useState<TaskLabel[]>(DEFAULT_LABELS);
|
const [labels, setLabels] = useState<TaskLabel[]>([]);
|
||||||
const [selectedLabelIds, setSelectedLabelIds] = useState<Set<string>>(new Set(["1"]));
|
const [selectedLabelIds, setSelectedLabelIds] = useState<Set<string>>(new Set());
|
||||||
const [selectedUserIds, setSelectedUserIds] = useState<Set<string>>(new Set(["1"]));
|
const [selectedUserIds, setSelectedUserIds] = useState<Set<string>>(new Set(["1"]));
|
||||||
const [checklists, setChecklists] = useState<TaskChecklist[]>(DEFAULT_CHECKLISTS);
|
const [checklists, setChecklists] = useState<TaskChecklist[]>(DEFAULT_CHECKLISTS);
|
||||||
const [startDate, setStartDate] = useState<DateObject | null>(DEFAULT_DATE_RANGE.startDate);
|
const [startDate, setStartDate] = useState<DateObject | null>(DEFAULT_DATE_RANGE.startDate);
|
||||||
@@ -43,9 +49,14 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail }) =>
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!taskDetail) return;
|
if (!taskDetail) return;
|
||||||
|
|
||||||
if (taskDetail.labels?.length) {
|
const taskLabels = taskDetail.labels ?? taskDetail.remarks ?? [];
|
||||||
setLabels(taskDetail.labels);
|
|
||||||
setSelectedLabelIds(new Set(taskDetail.labels.map((label) => label.id)));
|
if (taskLabels.length) {
|
||||||
|
setLabels(taskLabels);
|
||||||
|
setSelectedLabelIds(new Set(taskLabels.map((label) => label.id)));
|
||||||
|
} else {
|
||||||
|
setLabels([]);
|
||||||
|
setSelectedLabelIds(new Set());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taskDetail.users?.length) {
|
if (taskDetail.users?.length) {
|
||||||
@@ -108,14 +119,34 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail }) =>
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCreate = (title: string, color: string) => {
|
const handleCreate = (title: string, color: string) => {
|
||||||
const newLabel: TaskLabel = {
|
const effectiveTaskId = taskId || taskDetail?.id;
|
||||||
id: crypto.randomUUID(),
|
if (!effectiveTaskId) return;
|
||||||
title,
|
|
||||||
color,
|
createTaskRemark.mutate(
|
||||||
};
|
{ title, color, taskId: effectiveTaskId },
|
||||||
setLabels((prev) => [...prev, newLabel]);
|
{
|
||||||
setSelectedLabelIds((prev) => new Set([...prev, newLabel.id]));
|
onSuccess: (response) => {
|
||||||
setLabelsView("list");
|
const remark =
|
||||||
|
response.data?.remark ??
|
||||||
|
(response as { remark?: TaskLabel }).remark ??
|
||||||
|
(response.data as TaskLabel | undefined);
|
||||||
|
if (remark?.id) {
|
||||||
|
const newLabel: TaskLabel = {
|
||||||
|
id: remark.id,
|
||||||
|
title: remark.title,
|
||||||
|
color: remark.color,
|
||||||
|
};
|
||||||
|
setLabels((prev) => [...prev, newLabel]);
|
||||||
|
setSelectedLabelIds((prev) => new Set([...prev, newLabel.id]));
|
||||||
|
}
|
||||||
|
setLabelsView("list");
|
||||||
|
toast.success(t("success"));
|
||||||
|
},
|
||||||
|
onError: (error: ErrorType) => {
|
||||||
|
toast.error(error.response?.data?.error.message[0]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChecklistClose = () => {
|
const handleChecklistClose = () => {
|
||||||
@@ -163,6 +194,7 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail }) =>
|
|||||||
onViewChange={setLabelsView}
|
onViewChange={setLabelsView}
|
||||||
onToggle={handleLabelToggle}
|
onToggle={handleLabelToggle}
|
||||||
onCreate={handleCreate}
|
onCreate={handleCreate}
|
||||||
|
isSubmitting={createTaskRemark.isPending}
|
||||||
/>
|
/>
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ type Props = {
|
|||||||
onViewChange: (view: LabelsView) => void;
|
onViewChange: (view: LabelsView) => void;
|
||||||
onToggle: (id: string) => void;
|
onToggle: (id: string) => void;
|
||||||
onCreate: (title: string, color: string) => void;
|
onCreate: (title: string, color: string) => void;
|
||||||
|
isSubmitting?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailLabelsPopover: FC<Props> = ({
|
const TaskDetailLabelsPopover: FC<Props> = ({
|
||||||
@@ -19,6 +20,7 @@ const TaskDetailLabelsPopover: FC<Props> = ({
|
|||||||
onViewChange,
|
onViewChange,
|
||||||
onToggle,
|
onToggle,
|
||||||
onCreate,
|
onCreate,
|
||||||
|
isSubmitting = false,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -34,6 +36,7 @@ const TaskDetailLabelsPopover: FC<Props> = ({
|
|||||||
<TaskDetailNewLabelForm
|
<TaskDetailNewLabelForm
|
||||||
onBack={() => onViewChange("list")}
|
onBack={() => onViewChange("list")}
|
||||||
onSubmit={onCreate}
|
onSubmit={onCreate}
|
||||||
|
isSubmitting={isSubmitting}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -10,9 +10,10 @@ import { LABEL_COLOR_OPTIONS } from "./constants";
|
|||||||
type Props = {
|
type Props = {
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
onSubmit: (title: string, color: string) => void;
|
onSubmit: (title: string, color: string) => void;
|
||||||
|
isSubmitting?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailNewLabelForm: FC<Props> = ({ onBack, onSubmit }) => {
|
const TaskDetailNewLabelForm: FC<Props> = ({ onBack, onSubmit, isSubmitting = false }) => {
|
||||||
const { t } = useTranslation("global");
|
const { t } = useTranslation("global");
|
||||||
const colorInputRef = useRef<HTMLInputElement>(null);
|
const colorInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [title, setTitle] = useState("");
|
const [title, setTitle] = useState("");
|
||||||
@@ -48,7 +49,8 @@ const TaskDetailNewLabelForm: FC<Props> = ({ onBack, onSubmit }) => {
|
|||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
disabled={!title.trim()}
|
disabled={!title.trim() || isSubmitting}
|
||||||
|
isLoading={isSubmitting}
|
||||||
className="flex items-center justify-center gap-2 bg-white border border-[#D0D0D0] text-[#292D32] rounded-[6px] h-7 text-xs disabled:opacity-50"
|
className="flex items-center justify-center gap-2 bg-white border border-[#D0D0D0] text-[#292D32] rounded-[6px] h-7 text-xs disabled:opacity-50"
|
||||||
>
|
>
|
||||||
<AddCircle size={14} color="#292D32" />
|
<AddCircle size={14} color="#292D32" />
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import * as api from "../service/TaskService";
|
import * as api from "../service/TaskService";
|
||||||
import { CreateTaskType, UpdateTaskType } from "../types/TaskTypes";
|
import { CreateTaskRemarkType, CreateTaskType, UpdateTaskType } from "../types/TaskTypes";
|
||||||
|
|
||||||
export const useGetTaskDetail = (id: string, enabled = true) => {
|
export const useGetTaskDetail = (id: string, enabled = true) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -45,3 +45,15 @@ export const useDeleteTask = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useCreateTaskRemark = () => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: (variables: CreateTaskRemarkType) => api.createTaskRemark(variables),
|
||||||
|
onSuccess: (_data, variables) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["task-detail", variables.taskId] });
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["project"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
import axios from "../../../../config/axios";
|
import axios from "../../../../config/axios";
|
||||||
import { CreateTaskType, GetTaskDetailResponse, UpdateTaskType } from "../types/TaskTypes";
|
import {
|
||||||
|
CreateTaskRemarkResponse,
|
||||||
|
CreateTaskRemarkType,
|
||||||
|
CreateTaskType,
|
||||||
|
GetTaskDetailResponse,
|
||||||
|
UpdateTaskType,
|
||||||
|
} from "../types/TaskTypes";
|
||||||
|
|
||||||
export const createTask = async (params: CreateTaskType) => {
|
export const createTask = async (params: CreateTaskType) => {
|
||||||
const { data } = await axios.post(`/task-manager/tasks`, params);
|
const { data } = await axios.post(`/task-manager/tasks`, params);
|
||||||
@@ -20,3 +26,8 @@ export const deleteTask = async (id: string) => {
|
|||||||
const { data } = await axios.delete(`/task-manager/tasks/${id}`);
|
const { data } = await axios.delete(`/task-manager/tasks/${id}`);
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createTaskRemark = async (params: CreateTaskRemarkType): Promise<CreateTaskRemarkResponse> => {
|
||||||
|
const { data } = await axios.post(`/task-manager/remarks`, params);
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|||||||
@@ -25,6 +25,16 @@ export type TaskLabelType = {
|
|||||||
color: string;
|
color: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CreateTaskRemarkType = {
|
||||||
|
title: string;
|
||||||
|
color: string;
|
||||||
|
taskId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface CreateTaskRemarkResponse extends IResponse<{ remark: TaskLabelType }> {
|
||||||
|
statusCode: number;
|
||||||
|
}
|
||||||
|
|
||||||
export type TaskUserType = {
|
export type TaskUserType = {
|
||||||
id: string;
|
id: string;
|
||||||
firstName: string;
|
firstName: string;
|
||||||
@@ -61,6 +71,7 @@ export type TaskDetailType = {
|
|||||||
startDate?: string;
|
startDate?: string;
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
labels?: TaskLabelType[];
|
labels?: TaskLabelType[];
|
||||||
|
remarks?: TaskLabelType[];
|
||||||
users?: TaskUserType[];
|
users?: TaskUserType[];
|
||||||
checklists?: TaskChecklistType[];
|
checklists?: TaskChecklistType[];
|
||||||
attachments?: TaskAttachmentType[];
|
attachments?: TaskAttachmentType[];
|
||||||
|
|||||||
Reference in New Issue
Block a user