From c62e958e91744065ba08d441b5bced2f17d7a220 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Wed, 17 Jun 2026 10:57:52 +0330 Subject: [PATCH] Create check list --- src/components/Select.tsx | 95 ++++++++----------- src/langs/fa.json | 4 +- .../task-detail/TaskDetailActionBar.tsx | 7 +- .../task-detail/TaskDetailToolbar.tsx | 27 ++++++ .../checklist/TaskDetailChecklistForm.tsx | 60 ++++++++++++ .../checklist/TaskDetailChecklistPopover.tsx | 19 ++++ .../task-detail/checklist/constants.ts | 6 ++ .../components/task-detail/checklist/types.ts | 4 + 8 files changed, 162 insertions(+), 60 deletions(-) create mode 100644 src/pages/taskmanager/components/task-detail/checklist/TaskDetailChecklistForm.tsx create mode 100644 src/pages/taskmanager/components/task-detail/checklist/TaskDetailChecklistPopover.tsx create mode 100644 src/pages/taskmanager/components/task-detail/checklist/constants.ts create mode 100644 src/pages/taskmanager/components/task-detail/checklist/types.ts diff --git a/src/components/Select.tsx b/src/components/Select.tsx index e70c0de..8fa0d21 100644 --- a/src/components/Select.tsx +++ b/src/components/Select.tsx @@ -1,62 +1,45 @@ -import { FC, SelectHTMLAttributes } from 'react' -import { clx } from '../helpers/utils' -import { ArrowDown2 } from 'iconsax-react' -import Error from './Error' +import { ArrowDown2 } from "iconsax-react"; +import { FC, SelectHTMLAttributes } from "react"; +import { clx } from "../helpers/utils"; +import Error from "./Error"; export type ItemsSelectType = { - value: string, - label: string, -} + value: string; + label: string; +}; type Props = { - className?: string, - items: ItemsSelectType[], - error_text?: string, - placeholder?: string, - label?: string, -} & SelectHTMLAttributes + className?: string; + items: ItemsSelectType[]; + error_text?: string; + placeholder?: string; + label?: string; + labelClassName?: string; +} & SelectHTMLAttributes; const Select: FC = (props: Props) => { - return ( -
- { - props.label && - - } - - - { - props.error_text && props.error_text !== '' ? - - : null - } -
+ return ( +
+ {props.label && } +
+ + +
+ {props.error_text && props.error_text !== "" ? : null} +
+ ); +}; - ) -} - -export default Select \ No newline at end of file +export default Select; diff --git a/src/langs/fa.json b/src/langs/fa.json index 7da556d..c693a8c 100644 --- a/src/langs/fa.json +++ b/src/langs/fa.json @@ -1001,7 +1001,9 @@ "new_label": "برچسب جدید", "label_title": "عنوان", "choose_custom_color": "انتخاب رنگ دلخواه", - "members": "اعضا" + "members": "اعضا", + "copy_from": "کپی از", + "none": "هیچ کدام" } } } diff --git a/src/pages/taskmanager/components/task-detail/TaskDetailActionBar.tsx b/src/pages/taskmanager/components/task-detail/TaskDetailActionBar.tsx index e4b5df5..da2b7e9 100644 --- a/src/pages/taskmanager/components/task-detail/TaskDetailActionBar.tsx +++ b/src/pages/taskmanager/components/task-detail/TaskDetailActionBar.tsx @@ -22,9 +22,10 @@ type Props = { onTabChange: (tab: TaskDetailTab) => void; labelsPopover?: ReactNode; usersPopover?: ReactNode; + checklistPopover?: ReactNode; }; -const TaskDetailActionBar: FC = ({ activeTab, onTabChange, labelsPopover, usersPopover }) => { +const TaskDetailActionBar: FC = ({ activeTab, onTabChange, labelsPopover, usersPopover, checklistPopover }) => { const { t } = useTranslation("global"); return ( @@ -43,11 +44,11 @@ const TaskDetailActionBar: FC = ({ activeTab, onTabChange, labelsPopover, ); - if (id === "labels" || id === "users") { + if (id === "labels" || id === "users" || id === "checklist") { return (
{button} - {id === "labels" ? labelsPopover : usersPopover} + {id === "labels" ? labelsPopover : id === "users" ? usersPopover : checklistPopover}
); } diff --git a/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx b/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx index 1c712a9..536717c 100644 --- a/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx +++ b/src/pages/taskmanager/components/task-detail/TaskDetailToolbar.tsx @@ -1,4 +1,7 @@ import { type FC, useState } from "react"; +import { DEFAULT_CHECKLISTS } from "./checklist/constants"; +import TaskDetailChecklistPopover from "./checklist/TaskDetailChecklistPopover"; +import type { TaskChecklist } from "./checklist/types"; import { DEFAULT_LABELS } from "./labels/constants"; import TaskDetailLabelsPopover from "./labels/TaskDetailLabelsPopover"; import type { LabelsView, TaskLabel } from "./labels/types"; @@ -18,11 +21,13 @@ const TaskDetailToolbar: FC = ({ activeTab, onTabChange }) => { const [labels, setLabels] = useState(DEFAULT_LABELS); const [selectedLabelIds, setSelectedLabelIds] = useState>(new Set(["1"])); const [selectedUserIds, setSelectedUserIds] = useState>(new Set(["1"])); + const [checklists, setChecklists] = useState(DEFAULT_CHECKLISTS); const selectedLabels = labels.filter((label) => selectedLabelIds.has(label.id)); const selectedUsers = DEFAULT_USERS.filter((user) => selectedUserIds.has(user.id)); const isLabelsOpen = activeTab === "labels"; const isUsersOpen = activeTab === "users"; + const isChecklistOpen = activeTab === "checklist"; const handleTabChange = (tab: TaskDetailTab) => { if (tab === "labels" && activeTab !== "labels") { @@ -66,6 +71,19 @@ const TaskDetailToolbar: FC = ({ activeTab, onTabChange }) => { setLabelsView("list"); }; + const handleChecklistClose = () => { + if (isChecklistOpen) onTabChange("checklist"); + }; + + const handleChecklistCreate = (title: string, _copyFromId: string) => { + const newChecklist: TaskChecklist = { + id: crypto.randomUUID(), + title, + }; + setChecklists((prev) => [...prev, newChecklist]); + handleChecklistClose(); + }; + return (
= ({ activeTab, onTabChange }) => { ) : null } + checklistPopover={ + isChecklistOpen ? ( + + ) : null + } /> diff --git a/src/pages/taskmanager/components/task-detail/checklist/TaskDetailChecklistForm.tsx b/src/pages/taskmanager/components/task-detail/checklist/TaskDetailChecklistForm.tsx new file mode 100644 index 0000000..1d71bc5 --- /dev/null +++ b/src/pages/taskmanager/components/task-detail/checklist/TaskDetailChecklistForm.tsx @@ -0,0 +1,60 @@ +import { ArrowRight2 } from "iconsax-react"; +import { type FC, useState } from "react"; +import { useTranslation } from "react-i18next"; +import Button from "../../../../../components/Button"; +import Input from "../../../../../components/Input"; +import Select from "../../../../../components/Select"; +import type { TaskChecklist } from "./types"; + +type Props = { + checklists: TaskChecklist[]; + onClose: () => void; + onSubmit: (title: string, copyFromId: string) => void; +}; + +const TaskDetailChecklistForm: FC = ({ checklists, onClose, onSubmit }) => { + const { t } = useTranslation("global"); + const [title, setTitle] = useState(""); + const [copyFromId, setCopyFromId] = useState(""); + + const copyFromItems = [{ value: "", label: t("taskmanager.task_detail.none") }, ...checklists.map((checklist) => ({ value: checklist.id, label: checklist.title }))]; + + const handleSubmit = () => { + const trimmed = title.trim(); + if (!trimmed) return; + onSubmit(trimmed, copyFromId); + }; + + return ( +
+
+ +

{t("taskmanager.task_detail.checklist")}

+
+ + setTitle(e.target.value)} className="h-8 bg-white/63 border-white" labelClassName="text-xs" /> + +