This commit is contained in:
@@ -72,6 +72,82 @@ tbody tr {
|
|||||||
.rmdp-container {
|
.rmdp-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-wrapper {
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: none;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-calendar {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-header {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-header-values {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #292d32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-arrow {
|
||||||
|
border-color: #292d32;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-week-day {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #8c90a3;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-day {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-day span {
|
||||||
|
font-size: 11px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-day.rmdp-deactive span {
|
||||||
|
color: #c4c4c4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-day.rmdp-today span {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #292d32;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-day:not(.rmdp-disabled):not(.rmdp-day-hidden) span:hover {
|
||||||
|
background-color: #f0edf5;
|
||||||
|
color: #292d32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-day.rmdp-selected span:not(.highlight) {
|
||||||
|
background-color: #292d32;
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-range {
|
||||||
|
background-color: #f0edf5;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-detail-date-calendar .rmdp-day.rmdp-range.start,
|
||||||
|
.task-detail-date-calendar .rmdp-day.rmdp-range.end {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
.dltr {
|
.dltr {
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-1
@@ -1006,7 +1006,9 @@
|
|||||||
"none": "هیچ کدام",
|
"none": "هیچ کدام",
|
||||||
"link": "لینک",
|
"link": "لینک",
|
||||||
"or": "یا",
|
"or": "یا",
|
||||||
"insert": "درج"
|
"insert": "درج",
|
||||||
|
"start_date": "تاریخ شروع",
|
||||||
|
"end_date": "تاریخ پایان"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Calendar, Paperclip2, Profile2User, Tag, TickSquare, type Icon } from "iconsax-react";
|
import { Calendar, Paperclip2, Profile2User, Tag, TickSquare, type Icon } from "iconsax-react";
|
||||||
import { type FC, type ReactNode } from "react";
|
import { type FC, type ReactNode, useRef } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import TaskDetailPopoverPanel from "./TaskDetailPopoverPanel";
|
||||||
import type { TaskDetailTab } from "./types";
|
import type { TaskDetailTab } from "./types";
|
||||||
|
|
||||||
type TabItem = {
|
type TabItem = {
|
||||||
@@ -24,49 +25,67 @@ type Props = {
|
|||||||
usersPopover?: ReactNode;
|
usersPopover?: ReactNode;
|
||||||
checklistPopover?: ReactNode;
|
checklistPopover?: ReactNode;
|
||||||
attachmentPopover?: ReactNode;
|
attachmentPopover?: ReactNode;
|
||||||
|
datePopover?: ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailActionBar: FC<Props> = ({ activeTab, onTabChange, labelsPopover, usersPopover, checklistPopover, attachmentPopover }) => {
|
const POPOVER_TABS = new Set<TaskDetailTab>(["labels", "users", "checklist", "attachment", "date"]);
|
||||||
|
|
||||||
|
type PopoverTab = "labels" | "users" | "checklist" | "attachment" | "date";
|
||||||
|
|
||||||
|
const TaskDetailActionBar: FC<Props> = ({ activeTab, onTabChange, labelsPopover, usersPopover, checklistPopover, attachmentPopover, datePopover }) => {
|
||||||
const { t } = useTranslation("global");
|
const { t } = useTranslation("global");
|
||||||
|
const anchorRefs = useRef<Record<PopoverTab, { current: HTMLElement | null }>>({
|
||||||
|
labels: { current: null },
|
||||||
|
users: { current: null },
|
||||||
|
checklist: { current: null },
|
||||||
|
attachment: { current: null },
|
||||||
|
date: { current: null },
|
||||||
|
});
|
||||||
|
|
||||||
|
const popoverByTab: Record<PopoverTab, ReactNode> = {
|
||||||
|
labels: labelsPopover,
|
||||||
|
users: usersPopover,
|
||||||
|
checklist: checklistPopover,
|
||||||
|
attachment: attachmentPopover,
|
||||||
|
date: datePopover,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<>
|
||||||
{TABS.map(({ id, icon: Icon, labelKey }) => {
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
const isActive = activeTab === id;
|
{TABS.map(({ id, icon: Icon, labelKey }) => {
|
||||||
|
const isActive = activeTab === id;
|
||||||
|
const hasPopover = POPOVER_TABS.has(id);
|
||||||
|
|
||||||
const button = (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => onTabChange(id)}
|
|
||||||
className={`flex items-center gap-1.5 border rounded-lg px-3 py-2 text-[11px] cursor-pointer transition-colors ${isActive ? "bg-[#4A4A4A] text-white border-transparent" : "bg-white/50 text-[#292D32] border-white"}`}
|
|
||||||
>
|
|
||||||
<Icon size={16} color={isActive ? "#FFFFFF" : "#292D32"} />
|
|
||||||
<span>{t(labelKey)}</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
|
|
||||||
if (id === "labels" || id === "users" || id === "checklist" || id === "attachment") {
|
|
||||||
return (
|
return (
|
||||||
<div key={id} className="relative">
|
<div
|
||||||
{button}
|
key={id}
|
||||||
{id === "labels"
|
ref={(element) => {
|
||||||
? labelsPopover
|
if (!hasPopover) return;
|
||||||
: id === "users"
|
anchorRefs.current[id as PopoverTab].current = element;
|
||||||
? usersPopover
|
}}
|
||||||
: id === "checklist"
|
>
|
||||||
? checklistPopover
|
<button
|
||||||
: attachmentPopover}
|
type="button"
|
||||||
|
onClick={() => onTabChange(id)}
|
||||||
|
className={`flex items-center gap-1.5 border rounded-lg px-3 py-2 text-[11px] cursor-pointer transition-colors ${isActive ? "bg-[#4A4A4A] text-white border-transparent" : "bg-white/50 text-[#292D32] border-white"}`}
|
||||||
|
>
|
||||||
|
<Icon size={16} color={isActive ? "#FFFFFF" : "#292D32"} />
|
||||||
|
<span>{t(labelKey)}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
return (
|
{(Object.keys(popoverByTab) as PopoverTab[]).map((tab) =>
|
||||||
<div key={id}>
|
popoverByTab[tab] ? (
|
||||||
{button}
|
<TaskDetailPopoverPanel key={tab} anchorRef={anchorRefs.current[tab]}>
|
||||||
</div>
|
{popoverByTab[tab]}
|
||||||
);
|
</TaskDetailPopoverPanel>
|
||||||
})}
|
) : null,
|
||||||
</div>
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import { type FC, type ReactNode, useLayoutEffect, useState } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import { clx } from "../../../../helpers/utils";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
anchorRef: { current: HTMLElement | null };
|
||||||
|
children: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
width?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TaskDetailPopoverPanel: FC<Props> = ({ anchorRef, children, className, width = 300 }) => {
|
||||||
|
const [position, setPosition] = useState<{ top: number; left: number } | null>(null);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
const updatePosition = () => {
|
||||||
|
const anchor = anchorRef.current;
|
||||||
|
if (!anchor) return;
|
||||||
|
|
||||||
|
const rect = anchor.getBoundingClientRect();
|
||||||
|
const padding = 8;
|
||||||
|
let left = rect.right - width;
|
||||||
|
left = Math.max(padding, Math.min(left, window.innerWidth - width - padding));
|
||||||
|
|
||||||
|
setPosition({
|
||||||
|
top: rect.bottom + 8,
|
||||||
|
left,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
updatePosition();
|
||||||
|
window.addEventListener("resize", updatePosition);
|
||||||
|
window.addEventListener("scroll", updatePosition, true);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("resize", updatePosition);
|
||||||
|
window.removeEventListener("scroll", updatePosition, true);
|
||||||
|
};
|
||||||
|
}, [anchorRef, width]);
|
||||||
|
|
||||||
|
if (!position) return null;
|
||||||
|
|
||||||
|
return createPortal(
|
||||||
|
<div
|
||||||
|
style={{ top: position.top, left: position.left, width }}
|
||||||
|
className={clx("fixed z-[80] bg-white/90 backdrop-blur-md rounded-2xl p-4 shadow-lg border border-white/80", className)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>,
|
||||||
|
document.body,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskDetailPopoverPanel;
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { type FC, useState } from "react";
|
import { type FC, useState } from "react";
|
||||||
import TaskDetailAttachmentPopover from "./attachment/TaskDetailAttachmentPopover";
|
import TaskDetailAttachmentPopover from "./attachment/TaskDetailAttachmentPopover";
|
||||||
|
import TaskDetailDatePopover from "./date/TaskDetailDatePopover";
|
||||||
|
import type DateObject from "react-date-object";
|
||||||
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";
|
||||||
@@ -30,6 +32,7 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
|||||||
const isUsersOpen = activeTab === "users";
|
const isUsersOpen = activeTab === "users";
|
||||||
const isChecklistOpen = activeTab === "checklist";
|
const isChecklistOpen = activeTab === "checklist";
|
||||||
const isAttachmentOpen = activeTab === "attachment";
|
const isAttachmentOpen = activeTab === "attachment";
|
||||||
|
const isDateOpen = activeTab === "date";
|
||||||
|
|
||||||
const handleTabChange = (tab: TaskDetailTab) => {
|
const handleTabChange = (tab: TaskDetailTab) => {
|
||||||
if (tab === "labels" && activeTab !== "labels") {
|
if (tab === "labels" && activeTab !== "labels") {
|
||||||
@@ -94,6 +97,14 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
|||||||
handleAttachmentClose();
|
handleAttachmentClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDateClose = () => {
|
||||||
|
if (isDateOpen) onTabChange("date");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDateSubmit = (_data: { startDate: DateObject | null; endDate: DateObject | null }) => {
|
||||||
|
handleDateClose();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<TaskDetailActionBar
|
<TaskDetailActionBar
|
||||||
@@ -130,6 +141,11 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange }) => {
|
|||||||
<TaskDetailAttachmentPopover onClose={handleAttachmentClose} onSubmit={handleAttachmentCreate} />
|
<TaskDetailAttachmentPopover onClose={handleAttachmentClose} onSubmit={handleAttachmentCreate} />
|
||||||
) : null
|
) : null
|
||||||
}
|
}
|
||||||
|
datePopover={
|
||||||
|
isDateOpen ? (
|
||||||
|
<TaskDetailDatePopover onClose={handleDateClose} onSubmit={handleDateSubmit} />
|
||||||
|
) : null
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TaskDetailMetadataPreview selectedLabels={selectedLabels} selectedUsers={selectedUsers} />
|
<TaskDetailMetadataPreview selectedLabels={selectedLabels} selectedUsers={selectedUsers} />
|
||||||
|
|||||||
+1
-5
@@ -7,11 +7,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailAttachmentPopover: FC<Props> = ({ onClose, onSubmit }) => {
|
const TaskDetailAttachmentPopover: FC<Props> = ({ onClose, onSubmit }) => {
|
||||||
return (
|
return <TaskDetailAttachmentForm onClose={onClose} onSubmit={onSubmit} />;
|
||||||
<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">
|
|
||||||
<TaskDetailAttachmentForm onClose={onClose} onSubmit={onSubmit} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TaskDetailAttachmentPopover;
|
export default TaskDetailAttachmentPopover;
|
||||||
|
|||||||
+1
-5
@@ -9,11 +9,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailChecklistPopover: FC<Props> = ({ checklists, onClose, onSubmit }) => {
|
const TaskDetailChecklistPopover: FC<Props> = ({ checklists, onClose, onSubmit }) => {
|
||||||
return (
|
return <TaskDetailChecklistForm checklists={checklists} onClose={onClose} onSubmit={onSubmit} />;
|
||||||
<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;
|
export default TaskDetailChecklistPopover;
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
import { type FC, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import DateObject from "react-date-object";
|
||||||
|
import persian from "react-date-object/calendars/persian";
|
||||||
|
import persian_fa from "react-date-object/locales/persian_fa";
|
||||||
|
import { Calendar } from "react-multi-date-picker";
|
||||||
|
import Button from "../../../../../components/Button";
|
||||||
|
import TaskDetailLabelCheckbox from "../labels/TaskDetailLabelCheckbox";
|
||||||
|
import type { TaskDateField } from "./types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClose: () => 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 { t } = useTranslation("global");
|
||||||
|
const [startDate, setStartDate] = useState<DateObject | null>(null);
|
||||||
|
const [endDate, setEndDate] = useState<DateObject | null>(null);
|
||||||
|
const [startEnabled, setStartEnabled] = useState(true);
|
||||||
|
const [endEnabled, setEndEnabled] = useState(false);
|
||||||
|
const [activeField, setActiveField] = useState<TaskDateField>("start");
|
||||||
|
|
||||||
|
const calendarValue = (() => {
|
||||||
|
if (startEnabled && endEnabled && startDate && endDate) {
|
||||||
|
return [startDate, endDate];
|
||||||
|
}
|
||||||
|
if (activeField === "end" && endDate) return endDate;
|
||||||
|
if (startDate) return startDate;
|
||||||
|
return undefined;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const handleCalendarChange = (value: DateObject | DateObject[] | null) => {
|
||||||
|
if (!value) return;
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const [start, end] = value;
|
||||||
|
if (startEnabled) setStartDate(start);
|
||||||
|
if (endEnabled) setEndDate(end ?? null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeField === "start" && startEnabled) {
|
||||||
|
setStartDate(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeField === "end" && endEnabled) {
|
||||||
|
setEndDate(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleStartToggle = () => {
|
||||||
|
const next = !startEnabled;
|
||||||
|
setStartEnabled(next);
|
||||||
|
if (next) {
|
||||||
|
setActiveField("start");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!endEnabled) {
|
||||||
|
setEndEnabled(true);
|
||||||
|
setActiveField("end");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEndToggle = () => {
|
||||||
|
const next = !endEnabled;
|
||||||
|
setEndEnabled(next);
|
||||||
|
if (next) {
|
||||||
|
setActiveField("end");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!startEnabled) {
|
||||||
|
setStartEnabled(true);
|
||||||
|
setActiveField("start");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const canSubmit = (startEnabled && Boolean(startDate)) || (endEnabled && Boolean(endDate));
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
if (!canSubmit) return;
|
||||||
|
onSubmit({
|
||||||
|
startDate: startEnabled ? startDate : null,
|
||||||
|
endDate: endEnabled ? endDate : null,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<div className="task-detail-date-calendar">
|
||||||
|
<Calendar
|
||||||
|
value={calendarValue}
|
||||||
|
onChange={handleCalendarChange}
|
||||||
|
calendar={persian}
|
||||||
|
locale={persian_fa}
|
||||||
|
range={startEnabled && endEnabled}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xs shrink-0 w-[72px]">{t("taskmanager.task_detail.start_date")}</span>
|
||||||
|
<input
|
||||||
|
readOnly
|
||||||
|
value={formatDateLabel(startDate)}
|
||||||
|
placeholder={t("taskmanager.select_date")}
|
||||||
|
onFocus={() => setActiveField("start")}
|
||||||
|
className="flex-1 h-8 text-xs rounded-xl border border-white bg-white/63 px-3 text-[#292D32] outline-none"
|
||||||
|
/>
|
||||||
|
<TaskDetailLabelCheckbox
|
||||||
|
checked={startEnabled}
|
||||||
|
onToggle={handleStartToggle}
|
||||||
|
ariaLabel={t("taskmanager.task_detail.start_date")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xs shrink-0 w-[72px]">{t("taskmanager.task_detail.end_date")}</span>
|
||||||
|
<input
|
||||||
|
readOnly
|
||||||
|
value={formatDateLabel(endDate)}
|
||||||
|
placeholder={t("taskmanager.select_date")}
|
||||||
|
onFocus={() => setActiveField("end")}
|
||||||
|
className="flex-1 h-8 text-xs rounded-xl border border-white bg-white/63 px-3 text-[#292D32] outline-none"
|
||||||
|
/>
|
||||||
|
<TaskDetailLabelCheckbox
|
||||||
|
checked={endEnabled}
|
||||||
|
onToggle={handleEndToggle}
|
||||||
|
ariaLabel={t("taskmanager.task_detail.end_date")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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={!canSubmit} className="h-8 bg-[#292D32] text-white rounded-xl text-xs disabled:opacity-50 w-[95px]">
|
||||||
|
{t("taskmanager.task_detail.insert")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskDetailDateForm;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { type FC } from "react";
|
||||||
|
import type DateObject from "react-date-object";
|
||||||
|
import TaskDetailDateForm from "./TaskDetailDateForm";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (data: { startDate: DateObject | null; endDate: DateObject | null }) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TaskDetailDatePopover: FC<Props> = ({ onClose, onSubmit }) => {
|
||||||
|
return <TaskDetailDateForm onClose={onClose} onSubmit={onSubmit} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TaskDetailDatePopover;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import type DateObject from "react-date-object";
|
||||||
|
|
||||||
|
export type TaskDateField = "start" | "end";
|
||||||
|
|
||||||
|
export type TaskDateRange = {
|
||||||
|
startDate: DateObject | null;
|
||||||
|
endDate: DateObject | null;
|
||||||
|
startEnabled: boolean;
|
||||||
|
endEnabled: boolean;
|
||||||
|
};
|
||||||
@@ -21,7 +21,7 @@ const TaskDetailLabelsPopover: FC<Props> = ({
|
|||||||
onCreate,
|
onCreate,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
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">
|
<>
|
||||||
{view === "list" ? (
|
{view === "list" ? (
|
||||||
<TaskDetailLabelsList
|
<TaskDetailLabelsList
|
||||||
labels={labels}
|
labels={labels}
|
||||||
@@ -36,7 +36,7 @@ const TaskDetailLabelsPopover: FC<Props> = ({
|
|||||||
onSubmit={onCreate}
|
onSubmit={onCreate}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -9,11 +9,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TaskDetailUsersPopover: FC<Props> = ({ users, selectedIds, onToggle }) => {
|
const TaskDetailUsersPopover: FC<Props> = ({ users, selectedIds, onToggle }) => {
|
||||||
return (
|
return <TaskDetailUsersList users={users} selectedIds={selectedIds} onToggle={onToggle} />;
|
||||||
<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">
|
|
||||||
<TaskDetailUsersList users={users} selectedIds={selectedIds} onToggle={onToggle} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TaskDetailUsersPopover;
|
export default TaskDetailUsersPopover;
|
||||||
|
|||||||
Reference in New Issue
Block a user