setting date

This commit is contained in:
hamid zarghami
2026-07-12 12:15:55 +03:30
parent c11c972c7c
commit e1fe030316
4 changed files with 95 additions and 27 deletions
@@ -3,12 +3,9 @@ import { useTranslation } from "react-i18next";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { useGetUsers } from "../../../users/hooks/useUserData"; import { useGetUsers } from "../../../users/hooks/useUserData";
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 { parseApiDate, toApiDate } from "./date/utils";
import type DateObject from "react-date-object"; import type DateObject from "react-date-object";
import DateObjectClass from "react-date-object";
import persian from "react-date-object/calendars/persian";
import persian_fa from "react-date-object/locales/persian_fa";
import TaskDetailChecklistPopover from "./checklist/TaskDetailChecklistPopover"; import TaskDetailChecklistPopover from "./checklist/TaskDetailChecklistPopover";
import TaskDetailLabelsPopover from "./labels/TaskDetailLabelsPopover"; import TaskDetailLabelsPopover from "./labels/TaskDetailLabelsPopover";
import type { LabelsView, TaskLabel } from "./labels/types"; import type { LabelsView, TaskLabel } from "./labels/types";
@@ -28,12 +25,6 @@ type Props = {
taskId: string; taskId: string;
}; };
const parseDate = (value?: string): DateObject | null => {
if (!value) return null;
const date = new DateObjectClass({ date: value, calendar: persian, locale: persian_fa });
return date.isValid ? date : null;
};
const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail, taskId }) => { const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail, taskId }) => {
const { t } = useTranslation("global"); const { t } = useTranslation("global");
const createTaskRemark = useCreateTaskRemark(); const createTaskRemark = useCreateTaskRemark();
@@ -46,8 +37,8 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail, task
const [labels, setLabels] = useState<TaskLabel[]>([]); const [labels, setLabels] = useState<TaskLabel[]>([]);
const [selectedLabelIds, setSelectedLabelIds] = useState<Set<string>>(new Set()); const [selectedLabelIds, setSelectedLabelIds] = useState<Set<string>>(new Set());
const [selectedUserIds, setSelectedUserIds] = useState<Set<string>>(new Set()); const [selectedUserIds, setSelectedUserIds] = useState<Set<string>>(new Set());
const [startDate, setStartDate] = useState<DateObject | null>(DEFAULT_DATE_RANGE.startDate); const [startDate, setStartDate] = useState<DateObject | null>(null);
const [endDate, setEndDate] = useState<DateObject | null>(DEFAULT_DATE_RANGE.endDate); const [endDate, setEndDate] = useState<DateObject | null>(null);
useEffect(() => { useEffect(() => {
if (!taskDetail) return; if (!taskDetail) return;
@@ -68,10 +59,8 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail, task
setSelectedUserIds(new Set()); setSelectedUserIds(new Set());
} }
const parsedStartDate = parseDate(taskDetail.startDate); setStartDate(parseApiDate(taskDetail.startDate));
const parsedEndDate = parseDate(taskDetail.endDate); setEndDate(parseApiDate(taskDetail.endDate));
if (parsedStartDate) setStartDate(parsedStartDate);
if (parsedEndDate) setEndDate(parsedEndDate);
}, [taskDetail]); }, [taskDetail]);
const selectedLabels = labels.filter((label) => selectedLabelIds.has(label.id)); const selectedLabels = labels.filter((label) => selectedLabelIds.has(label.id));
@@ -252,9 +241,33 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail, task
}; };
const handleDateSubmit = (data: { startDate: DateObject | null; endDate: DateObject | null }) => { const handleDateSubmit = (data: { startDate: DateObject | null; endDate: DateObject | null }) => {
const effectiveTaskId = taskId || taskDetail?.id;
if (!effectiveTaskId) return;
const previousStartDate = startDate;
const previousEndDate = endDate;
const params = {
startDate: data.startDate ? toApiDate(data.startDate) : null,
endDate: data.endDate ? toApiDate(data.endDate) : null,
};
setStartDate(data.startDate); setStartDate(data.startDate);
setEndDate(data.endDate); setEndDate(data.endDate);
updateTask.mutate(
{ id: effectiveTaskId, params },
{
onSuccess: () => {
handleDateClose(); handleDateClose();
toast.success(t("success"));
},
onError: (error: ErrorType) => {
toast.error(error.response?.data?.error.message[0]);
setStartDate(previousStartDate);
setEndDate(previousEndDate);
},
},
);
}; };
return ( return (
@@ -306,7 +319,13 @@ const TaskDetailToolbar: FC<Props> = ({ activeTab, onTabChange, taskDetail, task
} }
datePopover={ datePopover={
isDateOpen ? ( isDateOpen ? (
<TaskDetailDatePopover onClose={handleDateClose} onSubmit={handleDateSubmit} /> <TaskDetailDatePopover
onClose={handleDateClose}
onSubmit={handleDateSubmit}
initialStartDate={startDate}
initialEndDate={endDate}
isSubmitting={updateTask.isPending}
/>
) : null ) : null
} }
/> />
@@ -12,14 +12,23 @@ 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;
initialStartDate?: DateObject | null;
initialEndDate?: DateObject | null;
isSubmitting?: boolean;
}; };
const TaskDetailDateForm: FC<Props> = ({ onClose, onSubmit }) => { const TaskDetailDateForm: FC<Props> = ({
onClose,
onSubmit,
initialStartDate = null,
initialEndDate = null,
isSubmitting = false,
}) => {
const { t } = useTranslation("global"); const { t } = useTranslation("global");
const [startDate, setStartDate] = useState<DateObject | null>(null); const [startDate, setStartDate] = useState<DateObject | null>(initialStartDate);
const [endDate, setEndDate] = useState<DateObject | null>(null); const [endDate, setEndDate] = useState<DateObject | null>(initialEndDate);
const [startEnabled, setStartEnabled] = useState(true); const [startEnabled, setStartEnabled] = useState(Boolean(initialStartDate) || !initialEndDate);
const [endEnabled, setEndEnabled] = useState(false); const [endEnabled, setEndEnabled] = useState(Boolean(initialEndDate));
const [activeField, setActiveField] = useState<TaskDateField>("start"); const [activeField, setActiveField] = useState<TaskDateField>("start");
const calendarValue = (() => { const calendarValue = (() => {
@@ -135,10 +144,21 @@ const TaskDetailDateForm: FC<Props> = ({ onClose, onSubmit }) => {
</div> </div>
<div className="flex justify-end gap-2"> <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]"> <Button
type="button"
onClick={onClose}
disabled={isSubmitting}
className="h-8 bg-[#E8E4F0] text-[#292D32] rounded-xl text-xs w-[95px]"
>
{t("cancel")} {t("cancel")}
</Button> </Button>
<Button type="button" onClick={handleSubmit} disabled={!canSubmit} className="h-8 bg-[#292D32] text-white rounded-xl text-xs disabled:opacity-50 w-[95px]"> <Button
type="button"
onClick={handleSubmit}
disabled={!canSubmit || isSubmitting}
isLoading={isSubmitting}
className="h-8 bg-[#292D32] text-white rounded-xl text-xs disabled:opacity-50 w-[95px]"
>
{t("taskmanager.task_detail.insert")} {t("taskmanager.task_detail.insert")}
</Button> </Button>
</div> </div>
@@ -5,10 +5,27 @@ import TaskDetailDateForm from "./TaskDetailDateForm";
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;
initialStartDate?: DateObject | null;
initialEndDate?: DateObject | null;
isSubmitting?: boolean;
}; };
const TaskDetailDatePopover: FC<Props> = ({ onClose, onSubmit }) => { const TaskDetailDatePopover: FC<Props> = ({
return <TaskDetailDateForm onClose={onClose} onSubmit={onSubmit} />; onClose,
onSubmit,
initialStartDate,
initialEndDate,
isSubmitting,
}) => {
return (
<TaskDetailDateForm
onClose={onClose}
onSubmit={onSubmit}
initialStartDate={initialStartDate}
initialEndDate={initialEndDate}
isSubmitting={isSubmitting}
/>
);
}; };
export default TaskDetailDatePopover; export default TaskDetailDatePopover;
@@ -1,7 +1,19 @@
import DateObject from "react-date-object"; import DateObject from "react-date-object";
import gregorian from "react-date-object/calendars/gregorian";
import persian from "react-date-object/calendars/persian"; import persian from "react-date-object/calendars/persian";
import gregorian_en from "react-date-object/locales/gregorian_en";
import persian_fa from "react-date-object/locales/persian_fa"; import persian_fa from "react-date-object/locales/persian_fa";
export const parseApiDate = (value?: string | null): DateObject | null => {
if (!value) return null;
const date = new DateObject({ date: value, calendar: gregorian });
return date.isValid ? date : null;
};
export const toApiDate = (date: DateObject): string => {
return date.convert(gregorian, gregorian_en).format("YYYY-MM-DD");
};
export const formatDateLabel = (date: DateObject | null) => { export const formatDateLabel = (date: DateObject | null) => {
if (!date) return ""; if (!date) return "";
return date.convert(persian, persian_fa).format("D MMMM"); return date.convert(persian, persian_fa).format("D MMMM");