Compare commits

...

2 Commits

Author SHA1 Message Date
hamid zarghami 19d60a9225 update style default modal + added date in modal
deploy to danak / build_and_deploy (push) Has been cancelled
2026-06-17 16:40:08 +03:30
hamid zarghami 73d1ad3492 attachments list 2026-06-17 16:33:01 +03:30
12 changed files with 211 additions and 17 deletions
+4
View File
@@ -0,0 +1,4 @@
{
"printWidth": 200,
"singleAttributePerLine": true
}
+3 -3
View File
@@ -26,9 +26,9 @@ const DefaulModal: FC<Props> = (props: Props) => {
{ {
props.open && ( props.open && (
<Fragment> <Fragment>
<div style={{ maxWidth: props.width }} className='xl:justify-center xl:items-center items-end flex overflow-x-hidden overflow-y-auto fixed inset-0 z-[60] h-auto top-0 bottom-0 m-auto outline-none focus:outline-none xl:max-w-xl mx-auto'> <div style={{ maxWidth: props.width }} className='xl:justify-center xl:items-center items-end flex overflow-hidden fixed inset-0 z-[60] h-auto top-0 bottom-0 m-auto outline-none focus:outline-none xl:max-w-xl mx-auto'>
<div className='relative xl:h-full h-[80%] bottom-0 left-0 flex xl:items-center sm:h-auto w-full xl:my-6 xl:p-2'> <div className='relative max-h-[85vh] bottom-0 left-0 flex xl:items-center items-end w-full xl:my-6 xl:p-2'>
<div className='border-0 h-auto p-5 lg:min-w-full overflow-y-auto rounded-3xl rounded-b-none xl:rounded-b-3xl relative flex flex-col w-full modalGlass2 outline-none focus:outline-none'> <div className='border-0 max-h-[85vh] p-5 lg:min-w-full overflow-y-auto rounded-3xl rounded-b-none xl:rounded-b-3xl relative flex flex-col w-full modalGlass2 outline-none focus:outline-none'>
{ {
+3
View File
@@ -995,6 +995,7 @@
"user_management": "مدیریت کاربران", "user_management": "مدیریت کاربران",
"checklist": "چک لیست", "checklist": "چک لیست",
"attachment": "ضمیمه", "attachment": "ضمیمه",
"date": "تاریخ",
"date_settings": "تنظیمات تاریخ", "date_settings": "تنظیمات تاریخ",
"description": "توضیحات", "description": "توضیحات",
"description_placeholder": "توضیحات خود را اضافه کنید", "description_placeholder": "توضیحات خود را اضافه کنید",
@@ -1005,6 +1006,8 @@
"copy_from": "کپی از", "copy_from": "کپی از",
"none": "هیچ کدام", "none": "هیچ کدام",
"link": "لینک", "link": "لینک",
"files": "فایل ها",
"links": "لینک ها",
"or": "یا", "or": "یا",
"insert": "درج", "insert": "درج",
"start_date": "تاریخ شروع", "start_date": "تاریخ شروع",
@@ -1,20 +1,25 @@
import { Add } from "iconsax-react"; import { Add } from "iconsax-react";
import { type FC } from "react"; import { type FC } from "react";
import type DateObject from "react-date-object";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import AvatarImage from "../../../../assets/images/avatar_image.png"; import AvatarImage from "../../../../assets/images/avatar_image.png";
import { formatDateRange } from "./date/utils";
import type { TaskLabel } from "./labels/types"; import type { TaskLabel } from "./labels/types";
import type { TaskUser } from "./users/types"; import type { TaskUser } from "./users/types";
type Props = { type Props = {
selectedLabels: TaskLabel[]; selectedLabels: TaskLabel[];
selectedUsers: TaskUser[]; selectedUsers: TaskUser[];
startDate: DateObject | null;
endDate: DateObject | null;
}; };
const TaskDetailMetadataPreview: FC<Props> = ({ selectedLabels, selectedUsers }) => { const TaskDetailMetadataPreview: FC<Props> = ({ selectedLabels, selectedUsers, startDate, endDate }) => {
const { t } = useTranslation("global"); const { t } = useTranslation("global");
const dateLabel = formatDateRange(startDate, endDate);
return ( return (
<div className="mt-4 grid grid-cols-2 gap-4"> <div className="mt-7 flex gap-10">
<div> <div>
<div className="text-xs font-medium mb-2">{t("taskmanager.task_detail.labels")}</div> <div className="text-xs font-medium mb-2">{t("taskmanager.task_detail.labels")}</div>
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
@@ -32,7 +37,10 @@ const TaskDetailMetadataPreview: FC<Props> = ({ selectedLabels, selectedUsers })
className="size-7 rounded-lg border border-[#D0D0D0] bg-white/60 flex items-center justify-center cursor-pointer" className="size-7 rounded-lg border border-[#D0D0D0] bg-white/60 flex items-center justify-center cursor-pointer"
aria-label={t("taskmanager.task_detail.new_label")} aria-label={t("taskmanager.task_detail.new_label")}
> >
<Add size={14} color="#292D32" /> <Add
size={14}
color="#292D32"
/>
</button> </button>
</div> </div>
</div> </div>
@@ -41,8 +49,15 @@ const TaskDetailMetadataPreview: FC<Props> = ({ selectedLabels, selectedUsers })
<div className="text-xs font-medium mb-2">{t("taskmanager.users")}</div> <div className="text-xs font-medium mb-2">{t("taskmanager.users")}</div>
<div className="flex items-center gap-2 flex-wrap"> <div className="flex items-center gap-2 flex-wrap">
{selectedUsers.map((user) => ( {selectedUsers.map((user) => (
<div key={user.id} className="size-8 rounded-full bg-[#D0D0D0] overflow-hidden"> <div
<img src={user.avatar ?? AvatarImage} alt={user.name} className="size-full object-cover" /> key={user.id}
className="size-8 rounded-full bg-[#D0D0D0] overflow-hidden"
>
<img
src={user.avatar ?? AvatarImage}
alt={user.name}
className="size-full object-cover"
/>
</div> </div>
))} ))}
<button <button
@@ -50,10 +65,18 @@ const TaskDetailMetadataPreview: FC<Props> = ({ selectedLabels, selectedUsers })
className="size-7 rounded-full border border-[#D0D0D0] bg-white/60 flex items-center justify-center cursor-pointer" className="size-7 rounded-full border border-[#D0D0D0] bg-white/60 flex items-center justify-center cursor-pointer"
aria-label={t("taskmanager.users")} aria-label={t("taskmanager.users")}
> >
<Add size={14} color="#292D32" /> <Add
size={14}
color="#292D32"
/>
</button> </button>
</div> </div>
</div> </div>
<div>
<div className="text-xs font-medium mb-2">{t("taskmanager.task_detail.date")}</div>
{dateLabel ? <span className="inline-flex items-center h-7 px-3 rounded-lg text-xs font-medium bg-[#E8E8E8] text-[#292D32]">{dateLabel}</span> : null}
</div>
</div> </div>
); );
}; };
@@ -1,6 +1,7 @@
import { type FC, useEffect, useState } from "react"; import { type FC, useEffect, useState } from "react";
import DefaulModal from "../../../../components/DefaulModal"; import DefaulModal from "../../../../components/DefaulModal";
import type { Task } from "../../types"; import type { Task } from "../../types";
import AttachmentList from "./attachment/List";
import CheckLists from "./checklist/List"; import CheckLists from "./checklist/List";
import TaskDetailDescription from "./TaskDetailDescription"; import TaskDetailDescription from "./TaskDetailDescription";
import TaskDetailHeader from "./TaskDetailHeader"; import TaskDetailHeader from "./TaskDetailHeader";
@@ -43,6 +44,8 @@ const TaskDetailModal: FC<Props> = ({ open, task, statusLabel, onClose }) => {
<TaskDetailDescription value={description} onChange={setDescription} /> <TaskDetailDescription value={description} onChange={setDescription} />
<AttachmentList />
<CheckLists /> <CheckLists />
</DefaulModal> </DefaulModal>
); );
@@ -1,5 +1,6 @@
import { type FC, useState } from "react"; import { type FC, useState } from "react";
import TaskDetailAttachmentPopover from "./attachment/TaskDetailAttachmentPopover"; import TaskDetailAttachmentPopover from "./attachment/TaskDetailAttachmentPopover";
import { DEFAULT_DATE_RANGE } from "./date/constants";
import TaskDetailDatePopover from "./date/TaskDetailDatePopover"; import TaskDetailDatePopover from "./date/TaskDetailDatePopover";
import type DateObject from "react-date-object"; import type DateObject from "react-date-object";
import { DEFAULT_CHECKLISTS } from "./checklist/constants"; import { DEFAULT_CHECKLISTS } from "./checklist/constants";
@@ -25,6 +26,8 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
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 [checklists, setChecklists] = useState<TaskChecklist[]>(DEFAULT_CHECKLISTS);
const [startDate, setStartDate] = useState<DateObject | null>(DEFAULT_DATE_RANGE.startDate);
const [endDate, setEndDate] = useState<DateObject | null>(DEFAULT_DATE_RANGE.endDate);
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));
@@ -101,7 +104,9 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
if (isDateOpen) onTabChange("date"); if (isDateOpen) onTabChange("date");
}; };
const handleDateSubmit = (_data: { startDate: DateObject | null; endDate: DateObject | null }) => { const handleDateSubmit = (data: { startDate: DateObject | null; endDate: DateObject | null }) => {
setStartDate(data.startDate);
setEndDate(data.endDate);
handleDateClose(); handleDateClose();
}; };
@@ -148,7 +153,12 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
} }
/> />
<TaskDetailMetadataPreview selectedLabels={selectedLabels} selectedUsers={selectedUsers} /> <TaskDetailMetadataPreview
selectedLabels={selectedLabels}
selectedUsers={selectedUsers}
startDate={startDate}
endDate={endDate}
/>
</div> </div>
); );
}; };
@@ -0,0 +1,54 @@
import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react";
import { Document, Link2, More } from "iconsax-react";
import { type FC } from "react";
import { timeAgo } from "../../../../../config/func";
type Props = {
title: string;
createdAt: string;
type: "file" | "link";
url?: string;
onEdit: () => void;
onDelete: () => void;
};
const AttachmentItem: FC<Props> = ({ title, createdAt, type, url, onEdit, onDelete }) => {
return (
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-2 flex-1 min-w-0">
<div className="shrink-0 size-9 bg-white rounded-[10px] flex items-center justify-center">
{type === "file" ? <Document size={20} color="#292D32" /> : <Link2 size={20} color="#292D32" />}
</div>
<div className="min-w-0">
{type === "link" && url ? (
<a href={url} target="_blank" rel="noopener noreferrer" className="text-xs font-bold text-[#0047FF] underline truncate block">
{title}
</a>
) : (
<div className="text-xs font-bold truncate">{title}</div>
)}
<div className="text-[11px] mt-0.5">{timeAgo(createdAt)}</div>
</div>
</div>
<Popover className="relative shrink-0">
<PopoverButton className="size-7 bg-white/60 rounded-[8px] flex items-center justify-center outline-none">
<More size={18} color="#292D32" />
</PopoverButton>
<PopoverPanel anchor="bottom end" className="z-[80] mt-1 rounded-[10px] bg-white shadow-md text-right p-3">
<button type="button" onClick={onEdit} className="w-full text-sm text-right">
ویرایش
</button>
<button type="button" onClick={onDelete} className="w-full mt-3 text-sm text-right text-[#FF0000]">
پاک کردن
</button>
</PopoverPanel>
</Popover>
</div>
);
};
export default AttachmentItem;
@@ -0,0 +1,75 @@
import { type FC } from "react";
import { useTranslation } from "react-i18next";
import AttachmentItem from "./AttachmentItem";
import type { TaskAttachment } from "./types";
const AttachmentList: FC = () => {
const { t } = useTranslation("global");
const attachments: TaskAttachment[] = [
{
id: "1",
title: "file.pdf",
fileName: "file.pdf",
createdAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
},
{
id: "2",
title: "لینک طرح",
url: "https://example.com",
createdAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(),
},
];
const files = attachments.filter((item) => item.fileName);
const links = attachments.filter((item) => item.url);
if (attachments.length === 0) return null;
return (
<div className="mt-6">
<div className="text-sm font-bold">{t("taskmanager.task_detail.attachment")}</div>
{files.length > 0 && (
<div className="mt-4">
<div className="text-sm">{t("taskmanager.task_detail.files")}</div>
<div className="mt-3 flex flex-col gap-3">
{files.map((item) => (
<AttachmentItem
key={item.id}
title={item.fileName ?? item.title}
createdAt={item.createdAt ?? ""}
type="file"
onEdit={() => {}}
onDelete={() => {}}
/>
))}
</div>
</div>
)}
{links.length > 0 && (
<div className="mt-4">
<div className="text-sm">{t("taskmanager.task_detail.links")}</div>
<div className="mt-3 flex flex-col gap-3">
{links.map((item) => (
<AttachmentItem
key={item.id}
title={item.title}
createdAt={item.createdAt}
type="link"
url={item.url}
onEdit={() => {}}
onDelete={() => {}}
/>
))}
</div>
</div>
)}
</div>
);
};
export default AttachmentList;
@@ -1,6 +1,8 @@
export type TaskAttachment = { export type TaskAttachment = {
id: string; id: string;
title: string; title: string;
linkId: string; createdAt: string;
linkId?: string;
fileName?: string; fileName?: string;
url?: string;
}; };
@@ -7,17 +7,13 @@ import { Calendar } from "react-multi-date-picker";
import Button from "../../../../../components/Button"; import Button from "../../../../../components/Button";
import TaskDetailLabelCheckbox from "../labels/TaskDetailLabelCheckbox"; import TaskDetailLabelCheckbox from "../labels/TaskDetailLabelCheckbox";
import type { TaskDateField } from "./types"; import type { TaskDateField } from "./types";
import { formatDateLabel } from "./utils";
type Props = { type Props = {
onClose: () => void; onClose: () => void;
onSubmit: (data: { startDate: DateObject | null; endDate: DateObject | null }) => void; onSubmit: (data: { startDate: DateObject | null; endDate: DateObject | null }) => void;
}; };
const formatDateLabel = (date: DateObject | null) => {
if (!date) return "";
return date.convert(persian, persian_fa).format("D MMMM");
};
const TaskDetailDateForm: FC<Props> = ({ onClose, onSubmit }) => { const TaskDetailDateForm: FC<Props> = ({ onClose, onSubmit }) => {
const { t } = useTranslation("global"); const { t } = useTranslation("global");
const [startDate, setStartDate] = useState<DateObject | null>(null); const [startDate, setStartDate] = useState<DateObject | null>(null);
@@ -0,0 +1,8 @@
import DateObject from "react-date-object";
import persian from "react-date-object/calendars/persian";
import persian_fa from "react-date-object/locales/persian_fa";
export const DEFAULT_DATE_RANGE = {
startDate: new DateObject({ calendar: persian, locale: persian_fa, year: 1404, month: 2, day: 1 }),
endDate: new DateObject({ calendar: persian, locale: persian_fa, year: 1404, month: 2, day: 20 }),
};
@@ -0,0 +1,16 @@
import DateObject from "react-date-object";
import persian from "react-date-object/calendars/persian";
import persian_fa from "react-date-object/locales/persian_fa";
export const formatDateLabel = (date: DateObject | null) => {
if (!date) return "";
return date.convert(persian, persian_fa).format("D MMMM");
};
export const formatDateRange = (startDate: DateObject | null, endDate: DateObject | null) => {
const start = formatDateLabel(startDate);
const end = formatDateLabel(endDate);
if (start && end) return `${start} - ${end}`;
return start || end;
};