fix: date range picker on cycles and modules list (#6676)
* fix: Handled workspace switcher closing on click * fix: replaced date range picker with date picker at some places
This commit is contained in:
parent
fbbf58481d
commit
59a0925d34
5 changed files with 299 additions and 179 deletions
|
|
@ -3,7 +3,16 @@
|
||||||
import React, { FC, useEffect, useState } from "react";
|
import React, { FC, useEffect, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { ArchiveIcon, ArchiveRestoreIcon, ChevronRight, EllipsisIcon, LinkIcon, Trash2 } from "lucide-react";
|
import {
|
||||||
|
ArchiveIcon,
|
||||||
|
ArchiveRestoreIcon,
|
||||||
|
CalendarCheck2,
|
||||||
|
CalendarClock,
|
||||||
|
ChevronRight,
|
||||||
|
EllipsisIcon,
|
||||||
|
LinkIcon,
|
||||||
|
Trash2,
|
||||||
|
} from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { CYCLE_STATUS, CYCLE_UPDATED, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { CYCLE_STATUS, CYCLE_UPDATED, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
|
@ -11,7 +20,7 @@ import { ICycle } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { CustomMenu, setToast, TOAST_TYPE } from "@plane/ui";
|
import { CustomMenu, setToast, TOAST_TYPE } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
import { DateDropdown } from "@/components/dropdowns";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper";
|
import { renderFormattedPayloadDate, getDate } from "@/helpers/date-time.helper";
|
||||||
import { copyUrlToClipboard } from "@/helpers/string.helper";
|
import { copyUrlToClipboard } from "@/helpers/string.helper";
|
||||||
|
|
@ -54,7 +63,7 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// form info
|
// form info
|
||||||
const { control, reset } = useForm({
|
const { control, reset, getValues } = useForm({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -145,20 +154,13 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateChange = async (startDate: Date | undefined, endDate: Date | undefined) => {
|
const handleDateChange = async (payload: { start_date?: string | null; end_date?: string | null }) => {
|
||||||
if (!startDate || !endDate) return;
|
|
||||||
|
|
||||||
let isDateValid = false;
|
let isDateValid = false;
|
||||||
|
|
||||||
const payload = {
|
if (cycleDetails?.start_date && cycleDetails?.end_date)
|
||||||
start_date: renderFormattedPayloadDate(startDate),
|
|
||||||
end_date: renderFormattedPayloadDate(endDate),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (cycleDetails?.start_date && cycleDetails.end_date)
|
|
||||||
isDateValid = await dateChecker({
|
isDateValid = await dateChecker({
|
||||||
...payload,
|
...payload,
|
||||||
cycle_id: cycleDetails.id,
|
cycle_id: cycleDetails?.id,
|
||||||
});
|
});
|
||||||
else isDateValid = await dateChecker(payload);
|
else isDateValid = await dateChecker(payload);
|
||||||
|
|
||||||
|
|
@ -177,6 +179,7 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||||
});
|
});
|
||||||
reset({ ...cycleDetails });
|
reset({ ...cycleDetails });
|
||||||
}
|
}
|
||||||
|
return isDateValid;
|
||||||
};
|
};
|
||||||
|
|
||||||
const isEditingAllowed = allowPermissions(
|
const isEditingAllowed = allowPermissions(
|
||||||
|
|
@ -285,39 +288,79 @@ export const CycleSidebarHeader: FC<Props> = observer((props) => {
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Controller
|
||||||
|
name="start_date"
|
||||||
|
control={control}
|
||||||
|
rules={{ required: "Please select a date" }}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<DateDropdown
|
||||||
|
value={value ?? null}
|
||||||
|
onChange={async (val) => {
|
||||||
|
let isDateValid;
|
||||||
|
const valDate = val ? renderFormattedPayloadDate(val) : null;
|
||||||
|
if (getValues("end_date")) {
|
||||||
|
isDateValid = await handleDateChange({
|
||||||
|
start_date: valDate,
|
||||||
|
end_date: renderFormattedPayloadDate(getValues("end_date")),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
isDateValid = await handleDateChange({
|
||||||
|
start_date: valDate,
|
||||||
|
end_date: valDate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
isDateValid && onChange(renderFormattedPayloadDate(val));
|
||||||
|
}}
|
||||||
|
placeholder={t("common.order_by.start_date")}
|
||||||
|
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
|
||||||
|
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||||
|
buttonContainerClassName={`h-6 w-full flex ${!isEditingAllowed || isArchived || isCompleted ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||||
|
optionsClassName="z-10"
|
||||||
|
disabled={!isEditingAllowed || isArchived || isCompleted}
|
||||||
|
showTooltip
|
||||||
|
maxDate={getDate(getValues("end_date"))}
|
||||||
|
isClearable={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
name="end_date"
|
||||||
name="start_date"
|
control={control}
|
||||||
render={({ field: { value: startDateValue, onChange: onChangeStartDate } }) => (
|
rules={{ required: "Please select a date" }}
|
||||||
<Controller
|
render={({ field: { value, onChange } }) => (
|
||||||
control={control}
|
<DateDropdown
|
||||||
name="end_date"
|
value={getDate(value) ?? null}
|
||||||
render={({ field: { value: endDateValue, onChange: onChangeEndDate } }) => (
|
onChange={async (val) => {
|
||||||
<DateRangeDropdown
|
let isDateValid;
|
||||||
className="h-7"
|
const valDate = val ? renderFormattedPayloadDate(val) : null;
|
||||||
buttonVariant="transparent-with-text"
|
if (getValues("start_date")) {
|
||||||
minDate={new Date()}
|
isDateValid = await handleDateChange({
|
||||||
value={{
|
end_date: valDate,
|
||||||
from: getDate(startDateValue),
|
start_date: renderFormattedPayloadDate(getValues("start_date")),
|
||||||
to: getDate(endDateValue),
|
});
|
||||||
}}
|
} else {
|
||||||
onSelect={(val) => {
|
isDateValid = await handleDateChange({
|
||||||
onChangeStartDate(val?.from ? renderFormattedPayloadDate(val.from) : null);
|
end_date: valDate,
|
||||||
onChangeEndDate(val?.to ? renderFormattedPayloadDate(val.to) : null);
|
start_date: valDate,
|
||||||
handleDateChange(val?.from, val?.to);
|
});
|
||||||
}}
|
}
|
||||||
placeholder={{
|
isDateValid && onChange(renderFormattedPayloadDate(val));
|
||||||
from: "Start date",
|
}}
|
||||||
to: "End date",
|
placeholder={t("common.order_by.due_date")}
|
||||||
}}
|
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
||||||
required={cycleDetails.status !== "draft"}
|
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||||
disabled={!isEditingAllowed || isArchived || isCompleted}
|
buttonContainerClassName={`h-6 w-full flex ${!isEditingAllowed || isArchived || isCompleted ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||||
/>
|
optionsClassName="z-10"
|
||||||
)}
|
disabled={!isEditingAllowed || isArchived || isCompleted}
|
||||||
/>
|
showTooltip
|
||||||
)}
|
minDate={getDate(getValues("start_date"))}
|
||||||
/>
|
isClearable={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import React, { FC, MouseEvent, useEffect, useMemo, useState } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { Eye, Users } from "lucide-react";
|
import { CalendarCheck2, CalendarClock, Eye, Users } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { CYCLE_FAVORITED, CYCLE_UNFAVORITED, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import { CYCLE_FAVORITED, CYCLE_UNFAVORITED, EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
|
|
@ -23,7 +23,7 @@ import {
|
||||||
} from "@plane/ui";
|
} from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { CycleQuickActions, TransferIssuesModal } from "@/components/cycles";
|
import { CycleQuickActions, TransferIssuesModal } from "@/components/cycles";
|
||||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
import { DateDropdown } from "@/components/dropdowns";
|
||||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||||
// constants
|
// constants
|
||||||
// helpers
|
// helpers
|
||||||
|
|
@ -41,7 +41,6 @@ import { CycleAdditionalActions } from "@/plane-web/components/cycles";
|
||||||
import { CycleService } from "@/services/cycle.service";
|
import { CycleService } from "@/services/cycle.service";
|
||||||
|
|
||||||
const cycleService = new CycleService();
|
const cycleService = new CycleService();
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|
@ -77,7 +76,7 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||||
const { getUserDetails } = useMember();
|
const { getUserDetails } = useMember();
|
||||||
|
|
||||||
// form
|
// form
|
||||||
const { control, reset } = useForm({
|
const { control, reset, getValues } = useForm({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -98,7 +97,6 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||||
workspaceSlug,
|
workspaceSlug,
|
||||||
projectId
|
projectId
|
||||||
);
|
);
|
||||||
const renderIcon = Boolean(cycleDetails.start_date) || Boolean(cycleDetails.end_date);
|
|
||||||
|
|
||||||
// handlers
|
// handlers
|
||||||
const handleAddToFavorites = (e: MouseEvent<HTMLButtonElement>) => {
|
const handleAddToFavorites = (e: MouseEvent<HTMLButtonElement>) => {
|
||||||
|
|
@ -171,20 +169,13 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateChange = async (startDate: Date | undefined, endDate: Date | undefined) => {
|
const handleDateChange = async (payload: { start_date?: string | null; end_date?: string | null }) => {
|
||||||
if (!startDate || !endDate) return;
|
|
||||||
|
|
||||||
let isDateValid = false;
|
let isDateValid = false;
|
||||||
|
|
||||||
const payload = {
|
if (cycleDetails?.start_date && cycleDetails?.end_date)
|
||||||
start_date: renderFormattedPayloadDate(startDate),
|
|
||||||
end_date: renderFormattedPayloadDate(endDate),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (cycleDetails && cycleDetails.start_date && cycleDetails.end_date)
|
|
||||||
isDateValid = await dateChecker({
|
isDateValid = await dateChecker({
|
||||||
...payload,
|
...payload,
|
||||||
cycle_id: cycleDetails.id,
|
cycle_id: cycleDetails?.id,
|
||||||
});
|
});
|
||||||
else isDateValid = await dateChecker(payload);
|
else isDateValid = await dateChecker(payload);
|
||||||
|
|
||||||
|
|
@ -203,6 +194,7 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||||
});
|
});
|
||||||
reset({ ...cycleDetails });
|
reset({ ...cycleDetails });
|
||||||
}
|
}
|
||||||
|
return isDateValid;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createdByDetails = cycleDetails.created_by ? getUserDetails(cycleDetails.created_by) : undefined;
|
const createdByDetails = cycleDetails.created_by ? getUserDetails(cycleDetails.created_by) : undefined;
|
||||||
|
|
@ -268,35 +260,77 @@ export const CycleListItemAction: FC<Props> = observer((props) => {
|
||||||
|
|
||||||
{!isActive && (
|
{!isActive && (
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
|
||||||
name="start_date"
|
name="start_date"
|
||||||
render={({ field: { value: startDateValue, onChange: onChangeStartDate } }) => (
|
control={control}
|
||||||
<Controller
|
rules={{ required: "Please select a date" }}
|
||||||
control={control}
|
render={({ field: { value, onChange } }) => (
|
||||||
name="end_date"
|
<DateDropdown
|
||||||
render={({ field: { value: endDateValue, onChange: onChangeEndDate } }) => (
|
value={value ?? null}
|
||||||
<DateRangeDropdown
|
onChange={async (val) => {
|
||||||
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 border-[0.5px] border-custom-border-300 rounded text-xs`}
|
let isDateValid;
|
||||||
buttonVariant="transparent-with-text"
|
const valDate = val ? renderFormattedPayloadDate(val) : null;
|
||||||
minDate={new Date()}
|
if (getValues("end_date")) {
|
||||||
value={{
|
isDateValid = await handleDateChange({
|
||||||
from: getDate(startDateValue),
|
start_date: valDate,
|
||||||
to: getDate(endDateValue),
|
end_date: renderFormattedPayloadDate(getValues("end_date")),
|
||||||
}}
|
});
|
||||||
onSelect={(val) => {
|
} else {
|
||||||
onChangeStartDate(val?.from ? renderFormattedPayloadDate(val.from) : null);
|
isDateValid = await handleDateChange({
|
||||||
onChangeEndDate(val?.to ? renderFormattedPayloadDate(val.to) : null);
|
start_date: valDate,
|
||||||
handleDateChange(val?.from, val?.to);
|
end_date: valDate,
|
||||||
}}
|
});
|
||||||
placeholder={{
|
}
|
||||||
from: "Start date",
|
isDateValid && onChange(renderFormattedPayloadDate(val));
|
||||||
to: "End date",
|
}}
|
||||||
}}
|
placeholder={t("common.order_by.start_date")}
|
||||||
required={cycleDetails.status !== "draft"}
|
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
|
||||||
disabled={isDisabled}
|
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||||
hideIcon={{ from: renderIcon ?? true, to: renderIcon }}
|
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||||
/>
|
optionsClassName="z-10"
|
||||||
)}
|
disabled={isDisabled}
|
||||||
|
renderByDefault={isMobile}
|
||||||
|
showTooltip
|
||||||
|
maxDate={getDate(getValues("end_date"))}
|
||||||
|
isClearable={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isActive && (
|
||||||
|
<Controller
|
||||||
|
name="end_date"
|
||||||
|
control={control}
|
||||||
|
rules={{ required: "Please select a date" }}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<DateDropdown
|
||||||
|
value={getDate(value) ?? null}
|
||||||
|
onChange={async (val) => {
|
||||||
|
let isDateValid;
|
||||||
|
const valDate = val ? renderFormattedPayloadDate(val) : null;
|
||||||
|
if (getValues("start_date")) {
|
||||||
|
isDateValid = await handleDateChange({
|
||||||
|
end_date: valDate,
|
||||||
|
start_date: renderFormattedPayloadDate(getValues("start_date")),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
isDateValid = await handleDateChange({
|
||||||
|
end_date: valDate,
|
||||||
|
start_date: valDate,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
isDateValid && onChange(renderFormattedPayloadDate(val));
|
||||||
|
}}
|
||||||
|
placeholder={t("common.order_by.due_date")}
|
||||||
|
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
||||||
|
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||||
|
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||||
|
optionsClassName="z-10"
|
||||||
|
disabled={isDisabled}
|
||||||
|
renderByDefault={isMobile}
|
||||||
|
showTooltip
|
||||||
|
minDate={getDate(getValues("start_date"))}
|
||||||
|
isClearable={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import { useParams } from "next/navigation";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import {
|
import {
|
||||||
ArchiveRestoreIcon,
|
ArchiveRestoreIcon,
|
||||||
|
CalendarCheck2,
|
||||||
CalendarClock,
|
CalendarClock,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
|
|
@ -42,7 +43,7 @@ import {
|
||||||
TextArea,
|
TextArea,
|
||||||
} from "@plane/ui";
|
} from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { DateRangeDropdown, MemberDropdown } from "@/components/dropdowns";
|
import { DateDropdown, MemberDropdown } from "@/components/dropdowns";
|
||||||
import {
|
import {
|
||||||
ArchiveModuleModal,
|
ArchiveModuleModal,
|
||||||
DeleteModuleModal,
|
DeleteModuleModal,
|
||||||
|
|
@ -104,7 +105,7 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||||
const estimateType = areEstimateEnabled && currentActiveEstimateId && estimateById(currentActiveEstimateId);
|
const estimateType = areEstimateEnabled && currentActiveEstimateId && estimateById(currentActiveEstimateId);
|
||||||
const isEstimatePointValid = estimateType && estimateType?.type == EEstimateSystem.POINTS ? true : false;
|
const isEstimatePointValid = estimateType && estimateType?.type == EEstimateSystem.POINTS ? true : false;
|
||||||
|
|
||||||
const { reset, control } = useForm({
|
const { reset, control, getValues } = useForm({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -194,11 +195,8 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDateChange = async (startDate: Date | undefined, targetDate: Date | undefined) => {
|
const handleDateChange = async (data: Partial<IModule>) => {
|
||||||
submitChanges({
|
submitChanges(data);
|
||||||
start_date: startDate ? renderFormattedPayloadDate(startDate) : null,
|
|
||||||
target_date: targetDate ? renderFormattedPayloadDate(targetDate) : null,
|
|
||||||
});
|
|
||||||
setToast({
|
setToast({
|
||||||
type: TOAST_TYPE.SUCCESS,
|
type: TOAST_TYPE.SUCCESS,
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
|
|
@ -411,50 +409,68 @@ export const ModuleAnalyticsSidebar: React.FC<Props> = observer((props) => {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center justify-start gap-1">
|
|
||||||
<div className="flex w-2/5 items-center justify-start gap-2 text-custom-text-300">
|
|
||||||
<CalendarClock className="h-4 w-4" />
|
|
||||||
<span className="text-base">{t("date_range")}</span>
|
|
||||||
</div>
|
|
||||||
<div className="h-7 w-3/5">
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="start_date"
|
|
||||||
render={({ field: { value: startDateValue, onChange: onChangeStartDate } }) => (
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="target_date"
|
|
||||||
render={({ field: { value: endDateValue, onChange: onChangeEndDate } }) => {
|
|
||||||
const startDate = getDate(startDateValue);
|
|
||||||
const endDate = getDate(endDateValue);
|
|
||||||
return (
|
|
||||||
<DateRangeDropdown
|
|
||||||
buttonContainerClassName="w-full"
|
|
||||||
buttonVariant="background-with-text"
|
|
||||||
value={{
|
|
||||||
from: startDate,
|
|
||||||
to: endDate,
|
|
||||||
}}
|
|
||||||
onSelect={(val) => {
|
|
||||||
onChangeStartDate(val?.from ? renderFormattedPayloadDate(val.from) : null);
|
|
||||||
onChangeEndDate(val?.to ? renderFormattedPayloadDate(val.to) : null);
|
|
||||||
handleDateChange(val?.from, val?.to);
|
|
||||||
}}
|
|
||||||
placeholder={{
|
|
||||||
from: t("start_date"),
|
|
||||||
to: t("target_date"),
|
|
||||||
}}
|
|
||||||
disabled={!isEditingAllowed || isArchived}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-col gap-5 pb-6 pt-2.5">
|
<div className="flex flex-col gap-5 pb-6 pt-2.5">
|
||||||
|
<div className="flex items-center justify-start gap-1">
|
||||||
|
<div className="flex w-2/5 items-center justify-start gap-2 text-custom-text-300">
|
||||||
|
<CalendarClock className="h-4 w-4" />
|
||||||
|
<span className="text-base">Start date</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
<Controller
|
||||||
|
name="start_date"
|
||||||
|
control={control}
|
||||||
|
rules={{ required: "Please select a date" }}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<DateDropdown
|
||||||
|
value={value ?? null}
|
||||||
|
onChange={(val) => {
|
||||||
|
console.log(val);
|
||||||
|
onChange(renderFormattedPayloadDate(val));
|
||||||
|
handleDateChange({ start_date: val ? renderFormattedPayloadDate(val) : null });
|
||||||
|
}}
|
||||||
|
placeholder={t("common.order_by.start_date")}
|
||||||
|
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
|
||||||
|
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||||
|
buttonContainerClassName={`h-6 w-full flex ${!isEditingAllowed || isArchived ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||||
|
optionsClassName="z-30"
|
||||||
|
disabled={!isEditingAllowed || isArchived}
|
||||||
|
showTooltip
|
||||||
|
maxDate={getDate(getValues("target_date"))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-start gap-1">
|
||||||
|
<div className="flex w-2/5 items-center justify-start gap-2 text-custom-text-300">
|
||||||
|
<CalendarClock className="h-4 w-4" />
|
||||||
|
<span className="text-base">Due date</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
|
<Controller
|
||||||
|
name="target_date"
|
||||||
|
control={control}
|
||||||
|
rules={{ required: "Please select a date" }}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<DateDropdown
|
||||||
|
value={getDate(value) ?? null}
|
||||||
|
onChange={(val) => {
|
||||||
|
onChange(renderFormattedPayloadDate(val));
|
||||||
|
handleDateChange({ target_date: val ? renderFormattedPayloadDate(val) : null });
|
||||||
|
}}
|
||||||
|
placeholder={t("common.order_by.due_date")}
|
||||||
|
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
||||||
|
buttonVariant={value ? "border-with-text" : "border-without-text"}
|
||||||
|
buttonContainerClassName={`h-6 w-full flex ${!isEditingAllowed || isArchived ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||||
|
optionsClassName="z-30"
|
||||||
|
disabled={!isEditingAllowed || isArchived}
|
||||||
|
showTooltip
|
||||||
|
minDate={getDate(getValues("start_date"))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="flex items-center justify-start gap-1">
|
<div className="flex items-center justify-start gap-1">
|
||||||
<div className="flex w-2/5 items-center justify-start gap-2 text-custom-text-300">
|
<div className="flex w-2/5 items-center justify-start gap-2 text-custom-text-300">
|
||||||
<SquareUser className="h-4 w-4" />
|
<SquareUser className="h-4 w-4" />
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import React, { SyntheticEvent, useRef } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
import { useParams, usePathname, useSearchParams } from "next/navigation";
|
||||||
import { Info, SquareUser } from "lucide-react";
|
import { CalendarCheck2, CalendarClock, Info, SquareUser } from "lucide-react";
|
||||||
// plane package imports
|
// plane package imports
|
||||||
import {
|
import {
|
||||||
MODULE_STATUS,
|
MODULE_STATUS,
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
EUserPermissions,
|
EUserPermissions,
|
||||||
EUserPermissionsLevel,
|
EUserPermissionsLevel,
|
||||||
} from "@plane/constants";
|
} from "@plane/constants";
|
||||||
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { IModule } from "@plane/types";
|
import { IModule } from "@plane/types";
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
|
|
@ -26,7 +27,7 @@ import {
|
||||||
setToast,
|
setToast,
|
||||||
} from "@plane/ui";
|
} from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
import { DateDropdown } from "@/components/dropdowns";
|
||||||
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
import { ButtonAvatars } from "@/components/dropdowns/member/avatar";
|
||||||
import { ModuleQuickActions } from "@/components/modules";
|
import { ModuleQuickActions } from "@/components/modules";
|
||||||
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
||||||
|
|
@ -58,7 +59,7 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||||
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
const { getModuleById, addModuleToFavorites, removeModuleFromFavorites, updateModuleDetails } = useModule();
|
||||||
const { getUserDetails } = useMember();
|
const { getUserDetails } = useMember();
|
||||||
const { captureEvent } = useEventTracker();
|
const { captureEvent } = useEventTracker();
|
||||||
|
const { t } = useTranslation();
|
||||||
// derived values
|
// derived values
|
||||||
const moduleDetails = getModuleById(moduleId);
|
const moduleDetails = getModuleById(moduleId);
|
||||||
const isEditingAllowed = allowPermissions(
|
const isEditingAllowed = allowPermissions(
|
||||||
|
|
@ -66,7 +67,6 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||||
EUserPermissionsLevel.PROJECT
|
EUserPermissionsLevel.PROJECT
|
||||||
);
|
);
|
||||||
const isDisabled = !isEditingAllowed || !!moduleDetails?.archived_at;
|
const isDisabled = !isEditingAllowed || !!moduleDetails?.archived_at;
|
||||||
const renderIcon = Boolean(moduleDetails?.start_date) || Boolean(moduleDetails?.target_date);
|
|
||||||
|
|
||||||
const { isMobile } = usePlatformOS();
|
const { isMobile } = usePlatformOS();
|
||||||
const handleAddToFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
|
const handleAddToFavorites = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
|
@ -236,27 +236,38 @@ export const ModuleCardItem: React.FC<Props> = observer((props) => {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<LinearProgressIndicator size="lg" data={progressIndicatorData} />
|
<LinearProgressIndicator size="lg" data={progressIndicatorData} />
|
||||||
<div className="flex items-center justify-between py-0.5" onClick={handleEventPropagation}>
|
<div className="flex items-center gap-2 py-0.5" onClick={handleEventPropagation}>
|
||||||
<DateRangeDropdown
|
<DateDropdown
|
||||||
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 border-[0.5px] border-custom-border-300 rounded text-xs`}
|
value={moduleDetails.start_date}
|
||||||
buttonVariant="transparent-with-text"
|
onChange={(val) => {
|
||||||
className="h-7"
|
|
||||||
value={{
|
|
||||||
from: getDate(moduleDetails.start_date),
|
|
||||||
to: getDate(moduleDetails.target_date),
|
|
||||||
}}
|
|
||||||
onSelect={(val) => {
|
|
||||||
handleModuleDetailsChange({
|
handleModuleDetailsChange({
|
||||||
start_date: val?.from ? renderFormattedPayloadDate(val.from) : null,
|
start_date: val ? renderFormattedPayloadDate(val) : null,
|
||||||
target_date: val?.to ? renderFormattedPayloadDate(val.to) : null,
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
placeholder={{
|
placeholder={t("common.order_by.start_date")}
|
||||||
from: "Start date",
|
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
|
||||||
to: "End date",
|
buttonVariant={moduleDetails.start_date ? "border-with-text" : "border-without-text"}
|
||||||
}}
|
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||||
|
optionsClassName="z-10"
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
hideIcon={{ from: renderIcon ?? true, to: renderIcon }}
|
showTooltip
|
||||||
|
maxDate={getDate(moduleDetails.target_date)}
|
||||||
|
/>
|
||||||
|
<DateDropdown
|
||||||
|
value={moduleDetails.target_date}
|
||||||
|
onChange={(val) => {
|
||||||
|
handleModuleDetailsChange({
|
||||||
|
target_date: val ? renderFormattedPayloadDate(val) : null,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder={t("common.order_by.due_date")}
|
||||||
|
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
||||||
|
buttonVariant={moduleDetails.target_date ? "border-with-text" : "border-without-text"}
|
||||||
|
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||||
|
optionsClassName="z-10"
|
||||||
|
disabled={isDisabled}
|
||||||
|
showTooltip
|
||||||
|
minDate={getDate(moduleDetails.start_date)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,21 @@ import React, { FC } from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams } from "next/navigation";
|
||||||
// icons
|
// icons
|
||||||
import { SquareUser } from "lucide-react";
|
import { CalendarCheck2, CalendarClock, SquareUser } from "lucide-react";
|
||||||
// types
|
// types
|
||||||
import { MODULE_STATUS, MODULE_FAVORITED, MODULE_UNFAVORITED , EUserPermissions, EUserPermissionsLevel } from "@plane/constants";
|
import {
|
||||||
|
MODULE_STATUS,
|
||||||
|
MODULE_FAVORITED,
|
||||||
|
MODULE_UNFAVORITED,
|
||||||
|
EUserPermissions,
|
||||||
|
EUserPermissionsLevel,
|
||||||
|
} from "@plane/constants";
|
||||||
import { useTranslation } from "@plane/i18n";
|
import { useTranslation } from "@plane/i18n";
|
||||||
import { IModule } from "@plane/types";
|
import { IModule } from "@plane/types";
|
||||||
// ui
|
// ui
|
||||||
import { FavoriteStar, TOAST_TYPE, Tooltip, setPromiseToast, setToast } from "@plane/ui";
|
import { FavoriteStar, TOAST_TYPE, Tooltip, setPromiseToast, setToast } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { DateRangeDropdown } from "@/components/dropdowns";
|
import { DateDropdown } from "@/components/dropdowns";
|
||||||
import { ModuleQuickActions } from "@/components/modules";
|
import { ModuleQuickActions } from "@/components/modules";
|
||||||
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
import { ModuleStatusDropdown } from "@/components/modules/module-status-dropdown";
|
||||||
// constants
|
// constants
|
||||||
|
|
@ -132,28 +138,38 @@ export const ModuleListItemAction: FC<Props> = observer((props) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DateRangeDropdown
|
<DateDropdown
|
||||||
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 border-[0.5px] border-custom-border-300 rounded text-xs`}
|
value={moduleDetails.start_date}
|
||||||
buttonVariant="transparent-with-text"
|
onChange={(val) => {
|
||||||
className="h-7"
|
|
||||||
value={{
|
|
||||||
from: getDate(moduleDetails.start_date),
|
|
||||||
to: getDate(moduleDetails.target_date),
|
|
||||||
}}
|
|
||||||
onSelect={(val) => {
|
|
||||||
handleModuleDetailsChange({
|
handleModuleDetailsChange({
|
||||||
start_date: val?.from ? renderFormattedPayloadDate(val.from) : null,
|
start_date: val ? renderFormattedPayloadDate(val) : null,
|
||||||
target_date: val?.to ? renderFormattedPayloadDate(val.to) : null,
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
placeholder={{
|
placeholder={t("common.order_by.start_date")}
|
||||||
from: t("start_date"),
|
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
|
||||||
to: t("end_date"),
|
buttonVariant={moduleDetails.start_date ? "border-with-text" : "border-without-text"}
|
||||||
}}
|
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||||
|
optionsClassName="z-10"
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
hideIcon={{ from: renderIcon ?? true, to: renderIcon }}
|
showTooltip
|
||||||
|
maxDate={getDate(moduleDetails.target_date)}
|
||||||
|
/>
|
||||||
|
<DateDropdown
|
||||||
|
value={moduleDetails.target_date}
|
||||||
|
onChange={(val) => {
|
||||||
|
handleModuleDetailsChange({
|
||||||
|
target_date: val ? renderFormattedPayloadDate(val) : null,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
placeholder={t("common.order_by.due_date")}
|
||||||
|
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
||||||
|
buttonVariant={moduleDetails.target_date ? "border-with-text" : "border-without-text"}
|
||||||
|
buttonContainerClassName={`h-6 w-full flex ${isDisabled ? "cursor-not-allowed" : "cursor-pointer"} items-center gap-1.5 text-custom-text-300 rounded text-xs`}
|
||||||
|
optionsClassName="z-10"
|
||||||
|
disabled={isDisabled}
|
||||||
|
showTooltip
|
||||||
|
minDate={getDate(moduleDetails.start_date)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{moduleStatus && (
|
{moduleStatus && (
|
||||||
<ModuleStatusDropdown
|
<ModuleStatusDropdown
|
||||||
isDisabled={isDisabled}
|
isDisabled={isDisabled}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue