diff --git a/.env b/.env index 9f3227d..f91670f 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ VITE_TOKEN_NAME = 'admin_token' VITE_REFRESH_TOKEN_NAME = 'admin_refresh_token' -VITE_BASE_URL = 'https://api.danakcorp.com' +#VITE_BASE_URL = 'https://api.danakcorp.com' -#VITE_BASE_URL = 'http://192.168.1.107:3500' \ No newline at end of file +VITE_BASE_URL = 'http://192.168.99.131:3500' \ No newline at end of file diff --git a/src/langs/fa.json b/src/langs/fa.json index e3f8897..ab83ec6 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -1077,7 +1077,14 @@ "stop_tracking": "پایان", "time_history": "سابقه", "no_time_entries": "هنوز زمانی ثبت نشده", - "tracking_active": "در حال اجرا" + "tracking_active": "در حال اجرا", + "comments": "کامنت‌ها", + "add_comment": "ثبت کامنت", + "comment_placeholder": "کامنت خود را بنویسید...", + "no_comments": "هنوز کامنتی ثبت نشده", + "unknown_user": "کاربر", + "related_ticket": "تیکت مرتبط", + "view_ticket": "مشاهده تیکت" } } } diff --git a/src/pages/taskmanager/components/Task.tsx b/src/pages/taskmanager/components/Task.tsx index 1aca8c9..8cbf669 100644 --- a/src/pages/taskmanager/components/Task.tsx +++ b/src/pages/taskmanager/components/Task.tsx @@ -1,5 +1,8 @@ import { AttachCircle, Calendar, TickSquare } from "iconsax-react"; import { useEffect, useRef, type FC } from "react"; +import { Link } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { Pages } from "../../../config/Pages"; import type { Task as TaskType } from "../types"; type Props = { @@ -9,6 +12,7 @@ type Props = { }; const Task: FC = ({ task, isDragging, onClick }) => { + const { t } = useTranslation("global"); const suppressClickRef = useRef(false); useEffect(() => { @@ -37,6 +41,15 @@ const Task: FC = ({ task, isDragging, onClick }) => { {task.tag}
{task.title}
+ {task.ticketId ? ( + e.stopPropagation()} + className="mt-2 inline-flex items-center text-[10px] text-primary hover:underline" + > + {t("taskmanager.task_detail.related_ticket")} + + ) : null}
diff --git a/src/pages/taskmanager/components/task-detail/TaskDetailMetadataPreview.tsx b/src/pages/taskmanager/components/task-detail/TaskDetailMetadataPreview.tsx index d6e0c94..e869be0 100644 --- a/src/pages/taskmanager/components/task-detail/TaskDetailMetadataPreview.tsx +++ b/src/pages/taskmanager/components/task-detail/TaskDetailMetadataPreview.tsx @@ -1,7 +1,9 @@ import { Add } from "iconsax-react"; import { type FC } from "react"; +import { Link } from "react-router-dom"; import type DateObject from "react-date-object"; import { useTranslation } from "react-i18next"; +import { Pages } from "../../../../config/Pages"; import UserAvatar from "../UserAvatar"; import { formatDateRange } from "./date/utils"; import type { TaskLabel } from "./labels/types"; @@ -12,11 +14,12 @@ type Props = { selectedUsers: TaskUser[]; startDate: DateObject | null; endDate: DateObject | null; + ticketId?: string | null; onAddLabel?: () => void; onAddUser?: () => void; }; -const TaskDetailMetadataPreview: FC = ({ selectedLabels, selectedUsers, startDate, endDate, onAddLabel, onAddUser }) => { +const TaskDetailMetadataPreview: FC = ({ selectedLabels, selectedUsers, startDate, endDate, ticketId, onAddLabel, onAddUser }) => { const { t } = useTranslation("global"); const dateLabel = formatDateRange(startDate, endDate); @@ -76,6 +79,18 @@ const TaskDetailMetadataPreview: FC = ({ selectedLabels, selectedUsers, s ) : null}
+ + {ticketId ? ( +
+
{t("taskmanager.task_detail.related_ticket")}
+ + {t("taskmanager.task_detail.view_ticket")} + +
+ ) : null}
); }; diff --git a/src/pages/taskmanager/components/task-detail/TaskDetailModal.tsx b/src/pages/taskmanager/components/task-detail/TaskDetailModal.tsx index dbee528..7666da0 100644 --- a/src/pages/taskmanager/components/task-detail/TaskDetailModal.tsx +++ b/src/pages/taskmanager/components/task-detail/TaskDetailModal.tsx @@ -10,6 +10,7 @@ import type { TaskDetailType } from "../../task/types/TaskTypes"; import type { Column, Task } from "../../types"; import AttachmentList from "./attachment/List"; import CheckLists from "./checklist/List"; +import CommentList from "./comment/List"; import TimeList from "./time/List"; import TaskDetailCoverColorBar from "./cover/TaskDetailCoverColorBar"; import TaskDetailDescription from "./TaskDetailDescription"; @@ -218,6 +219,8 @@ const TaskDetailModal: FC = ({ open, task, columns, projectId, onClose, o /> + + )} diff --git a/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx b/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx index 4730c46..b8c16ce 100644 --- a/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx +++ b/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx @@ -397,6 +397,7 @@ const TaskDetailToolbar: FC = ({ activeTab, onTabChange, taskDetail, task selectedUsers={selectedUsers} startDate={startDate} endDate={endDate} + ticketId={taskDetail?.ticketId} onAddLabel={handleAddLabelClick} onAddUser={handleAddUserClick} /> diff --git a/src/pages/taskmanager/components/task-detail/comment/CommentItem.tsx b/src/pages/taskmanager/components/task-detail/comment/CommentItem.tsx new file mode 100644 index 0000000..3bc0221 --- /dev/null +++ b/src/pages/taskmanager/components/task-detail/comment/CommentItem.tsx @@ -0,0 +1,128 @@ +import { Edit2 } from "iconsax-react"; +import moment from "moment-jalaali"; +import { type FC, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { toast } from "../../../../../components/Toast"; +import TrashWithConfrim from "../../../../../components/TrashWithConfrim"; +import { ErrorType } from "../../../../../helpers/types"; +import { useDeleteTaskComment, useUpdateTaskComment } from "../../../task/hooks/useTaskCommentData"; +import type { TaskCommentType } from "../../../task/types/TaskCommentTypes"; +import UserAvatar from "../../UserAvatar"; + +type Props = { + comment: TaskCommentType; + taskId: string; +}; + +const CommentItem: FC = ({ comment, taskId }) => { + const { t } = useTranslation("global"); + const [isEditing, setIsEditing] = useState(false); + const [content, setContent] = useState(comment.content); + const updateComment = useUpdateTaskComment(); + const deleteComment = useDeleteTaskComment(); + + const authorName = comment.user + ? `${comment.user.firstName} ${comment.user.lastName}`.trim() + : t("taskmanager.task_detail.unknown_user"); + + const handleSave = () => { + const trimmedContent = content.trim(); + if (!trimmedContent || trimmedContent === comment.content) { + setIsEditing(false); + setContent(comment.content); + return; + } + + updateComment.mutate( + { id: comment.id, params: { content: trimmedContent }, taskId }, + { + onSuccess: () => { + toast(t("success"), "success"); + setIsEditing(false); + }, + onError: (error: ErrorType) => { + toast(error.response?.data?.error.message[0], "error"); + }, + }, + ); + }; + + const handleCancel = () => { + setContent(comment.content); + setIsEditing(false); + }; + + const handleDelete = () => { + deleteComment.mutate( + { id: comment.id, taskId }, + { + onSuccess: () => { + toast(t("success"), "success"); + }, + onError: (error: ErrorType) => { + toast(error.response?.data?.error.message[0], "error"); + }, + }, + ); + }; + + return ( +
+
+ + +
+
+
{authorName}
+
+ {moment(comment.createdAt).format("jYYYY/jMM/jDD HH:mm")} +
+
+ + {isEditing ? ( +
+