Create check list
This commit is contained in:
+32
-49
@@ -1,62 +1,45 @@
|
|||||||
import { FC, SelectHTMLAttributes } from 'react'
|
import { ArrowDown2 } from "iconsax-react";
|
||||||
import { clx } from '../helpers/utils'
|
import { FC, SelectHTMLAttributes } from "react";
|
||||||
import { ArrowDown2 } from 'iconsax-react'
|
import { clx } from "../helpers/utils";
|
||||||
import Error from './Error'
|
import Error from "./Error";
|
||||||
|
|
||||||
export type ItemsSelectType = {
|
export type ItemsSelectType = {
|
||||||
value: string,
|
value: string;
|
||||||
label: string,
|
label: string;
|
||||||
}
|
};
|
||||||
type Props = {
|
type Props = {
|
||||||
className?: string,
|
className?: string;
|
||||||
items: ItemsSelectType[],
|
items: ItemsSelectType[];
|
||||||
error_text?: string,
|
error_text?: string;
|
||||||
placeholder?: string,
|
placeholder?: string;
|
||||||
label?: string,
|
label?: string;
|
||||||
} & SelectHTMLAttributes<HTMLSelectElement>
|
labelClassName?: string;
|
||||||
|
} & SelectHTMLAttributes<HTMLSelectElement>;
|
||||||
|
|
||||||
const Select: FC<Props> = (props: Props) => {
|
const Select: FC<Props> = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<div className='w-full relative'>
|
<div className="w-full relative">
|
||||||
{
|
{props.label && <label className={clx("text-sm", props.labelClassName)}>{props.label}</label>}
|
||||||
props.label &&
|
<div className={clx("relative", props.label && "mt-1")}>
|
||||||
<label className='text-sm'>
|
<select {...props} className={clx("w-full text-black block border appearance-none border-border px-2.5 h-10 text-sm rounded-2.5 bg-gray", props.className)}>
|
||||||
{props.label}
|
{props.placeholder && (
|
||||||
</label>
|
<option value="" disabled selected={!props.value}>
|
||||||
}
|
{props.placeholder}
|
||||||
<select {...props} className={clx(
|
</option>
|
||||||
'w-full text-black block border appearance-none border-border px-2.5 h-10 text-sm rounded-2.5 bg-gray',
|
)}
|
||||||
props.className,
|
{props.items?.map((item) => {
|
||||||
props.label && 'mt-1'
|
|
||||||
)}>
|
|
||||||
{
|
|
||||||
props.placeholder &&
|
|
||||||
<option value="" disabled selected={!props.value}>{props.placeholder}</option>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
props.items?.map((item) => {
|
|
||||||
return (
|
return (
|
||||||
<option key={item.value} value={item.value}>
|
<option key={item.value} value={item.value}>
|
||||||
{item.label}
|
{item.label}
|
||||||
</option>
|
</option>
|
||||||
)
|
);
|
||||||
})
|
})}
|
||||||
}
|
|
||||||
</select>
|
</select>
|
||||||
<ArrowDown2 size={16} color='black' className={clx(
|
<ArrowDown2 size={16} color="black" className="absolute z-0 top-0 bottom-0 my-auto left-2" />
|
||||||
'absolute z-0 top-3 left-2',
|
|
||||||
props.label && 'top-10'
|
|
||||||
)} />
|
|
||||||
{
|
|
||||||
props.error_text && props.error_text !== '' ?
|
|
||||||
<Error
|
|
||||||
errorText={props.error_text}
|
|
||||||
/>
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
|
{props.error_text && props.error_text !== "" ? <Error errorText={props.error_text} /> : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
)
|
export default Select;
|
||||||
}
|
|
||||||
|
|
||||||
export default Select
|
|
||||||
|
|||||||
+3
-1
@@ -1001,7 +1001,9 @@
|
|||||||
"new_label": "برچسب جدید",
|
"new_label": "برچسب جدید",
|
||||||
"label_title": "عنوان",
|
"label_title": "عنوان",
|
||||||
"choose_custom_color": "انتخاب رنگ دلخواه",
|
"choose_custom_color": "انتخاب رنگ دلخواه",
|
||||||
"members": "اعضا"
|
"members": "اعضا",
|
||||||
|
"copy_from": "کپی از",
|
||||||
|
"none": "هیچ کدام"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,9 +22,10 @@ type Props = {
|
|||||||
onTabChange: (tab: TaskDetailTab) => void;
|
onTabChange: (tab: TaskDetailTab) => void;
|
||||||
labelsPopover?: ReactNode;
|
labelsPopover?: ReactNode;
|
||||||
usersPopover?: ReactNode;
|
usersPopover?: ReactNode;
|
||||||
|
checklistPopover?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailActionBar: FC<Props> = ({ activeTab, onTabChange, labelsPopover, usersPopover }) => {
|
const TaskDetailActionBar: FC<Props> = ({ activeTab, onTabChange, labelsPopover, usersPopover, checklistPopover }) => {
|
||||||
const { t } = useTranslation("global");
|
const { t } = useTranslation("global");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -43,11 +44,11 @@ const TaskDetailActionBar: FC<Props> = ({ activeTab, onTabChange, labelsPopover,
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (id === "labels" || id === "users") {
|
if (id === "labels" || id === "users" || id === "checklist") {
|
||||||
return (
|
return (
|
||||||
<div key={id} className="relative">
|
<div key={id} className="relative">
|
||||||
{button}
|
{button}
|
||||||
{id === "labels" ? labelsPopover : usersPopover}
|
{id === "labels" ? labelsPopover : id === "users" ? usersPopover : checklistPopover}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { type FC, useState } from "react";
|
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 { 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";
|
||||||
@@ -18,11 +21,13 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
|||||||
const [labels, setLabels] = useState<TaskLabel[]>(DEFAULT_LABELS);
|
const [labels, setLabels] = useState<TaskLabel[]>(DEFAULT_LABELS);
|
||||||
const [selectedLabelIds, setSelectedLabelIds] = useState<Set<string>>(new Set(["1"]));
|
const [selectedLabelIds, setSelectedLabelIds] = useState<Set<string>>(new Set(["1"]));
|
||||||
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 selectedLabels = labels.filter((label) => selectedLabelIds.has(label.id));
|
const selectedLabels = labels.filter((label) => selectedLabelIds.has(label.id));
|
||||||
const selectedUsers = DEFAULT_USERS.filter((user) => selectedUserIds.has(user.id));
|
const selectedUsers = DEFAULT_USERS.filter((user) => selectedUserIds.has(user.id));
|
||||||
const isLabelsOpen = activeTab === "labels";
|
const isLabelsOpen = activeTab === "labels";
|
||||||
const isUsersOpen = activeTab === "users";
|
const isUsersOpen = activeTab === "users";
|
||||||
|
const isChecklistOpen = activeTab === "checklist";
|
||||||
|
|
||||||
const handleTabChange = (tab: TaskDetailTab) => {
|
const handleTabChange = (tab: TaskDetailTab) => {
|
||||||
if (tab === "labels" && activeTab !== "labels") {
|
if (tab === "labels" && activeTab !== "labels") {
|
||||||
@@ -66,6 +71,19 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
|||||||
setLabelsView("list");
|
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 (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<TaskDetailActionBar
|
<TaskDetailActionBar
|
||||||
@@ -88,6 +106,15 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
|||||||
<TaskDetailUsersPopover users={DEFAULT_USERS} selectedIds={selectedUserIds} onToggle={handleUserToggle} />
|
<TaskDetailUsersPopover users={DEFAULT_USERS} selectedIds={selectedUserIds} onToggle={handleUserToggle} />
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
checklistPopover={
|
||||||
|
isChecklistOpen ? (
|
||||||
|
<TaskDetailChecklistPopover
|
||||||
|
checklists={checklists}
|
||||||
|
onClose={handleChecklistClose}
|
||||||
|
onSubmit={handleChecklistCreate}
|
||||||
|
/>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TaskDetailMetadataPreview selectedLabels={selectedLabels} selectedUsers={selectedUsers} />
|
<TaskDetailMetadataPreview selectedLabels={selectedLabels} selectedUsers={selectedUsers} />
|
||||||
|
|||||||
@@ -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<Props> = ({ 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 (
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<div className="flex items-center justify-center relative">
|
||||||
|
<Button type="button" onClick={onClose} className="absolute right-0 size-7 bg-transparent text-inherit w-auto h-auto p-0 border-0 rounded-none shadow-none" aria-label={t("back")}>
|
||||||
|
<ArrowRight2 size={16} color="#292D32" />
|
||||||
|
</Button>
|
||||||
|
<h3 className="text-xs font-bold">{t("taskmanager.task_detail.checklist")}</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Input label={t("taskmanager.task_detail.label_title")} value={title} onChange={(e) => setTitle(e.target.value)} className="h-8 bg-white/63 border-white" labelClassName="text-xs" />
|
||||||
|
|
||||||
|
<Select
|
||||||
|
label={t("taskmanager.task_detail.copy_from")}
|
||||||
|
labelClassName="text-xs"
|
||||||
|
value={copyFromId}
|
||||||
|
onChange={(e) => setCopyFromId(e.target.value)}
|
||||||
|
items={copyFromItems}
|
||||||
|
className="h-8 bg-white/63 border-white text-xs rounded-xl"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button type="button" onClick={onClose} className="h-8 bg-[#E8E4F0] text-[#292D32] rounded-xl text-xs w-[95px]">
|
||||||
|
{t("cancel")}
|
||||||
|
</Button>
|
||||||
|
<Button type="button" onClick={handleSubmit} disabled={!title.trim()} className="h-8 bg-[#292D32] text-white rounded-xl text-xs disabled:opacity-50 w-[95px]">
|
||||||
|
{t("save")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskDetailChecklistForm;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { type FC } from "react";
|
||||||
|
import TaskDetailChecklistForm from "./TaskDetailChecklistForm";
|
||||||
|
import type { TaskChecklist } from "./types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
checklists: TaskChecklist[];
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (title: string, copyFromId: string) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TaskDetailChecklistPopover: FC<Props> = ({ checklists, onClose, onSubmit }) => {
|
||||||
|
return (
|
||||||
|
<div className="absolute top-full right-0 mt-2 z-30 w-[300px] bg-white/90 backdrop-blur-md rounded-2xl p-4 shadow-lg border border-white/80">
|
||||||
|
<TaskDetailChecklistForm checklists={checklists} onClose={onClose} onSubmit={onSubmit} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskDetailChecklistPopover;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import type { TaskChecklist } from "./types";
|
||||||
|
|
||||||
|
export const DEFAULT_CHECKLISTS: TaskChecklist[] = [
|
||||||
|
{ id: "1", title: "چک لیست پروژه" },
|
||||||
|
{ id: "2", title: "چک لیست انتشار" },
|
||||||
|
];
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export type TaskChecklist = {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user