mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[WEB-371]: Implemented react-day-picker
for date selections (#3679)
* dev: initialize new date picker * style: selected date focus state * chore: replace custom date filter modal components * chore: replaced inbox snooze popover datepicker * chore: replaced the custom date picker * style: date range picker designed * chore: date range picker implemented throughout the platform * chore: updated tab indices * chore: range-picker in the issue layouts * chore: passed due date color * chore: removed range picker from issue dates
This commit is contained in:
parent
e86d2ba743
commit
b1592adc66
@ -1,11 +1,10 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { add } from "date-fns";
|
import { add } from "date-fns";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { DateDropdown } from "components/dropdowns";
|
||||||
import { Calendar } from "lucide-react";
|
import { Calendar } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// components
|
|
||||||
import { CustomDatePicker } from "components/ui";
|
|
||||||
// ui
|
// ui
|
||||||
import { Button, CustomSelect, Input, TextArea, ToggleSwitch } from "@plane/ui";
|
import { Button, CustomSelect, Input, TextArea, ToggleSwitch } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
@ -167,7 +166,7 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
|
|||||||
<CustomSelect
|
<CustomSelect
|
||||||
customButton={
|
customButton={
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-2 rounded border-[0.5px] border-custom-border-200 px-2 py-1 ${
|
className={`flex items-center gap-2 rounded border-[0.5px] border-custom-border-300 px-2 py-0.5 ${
|
||||||
neverExpires ? "text-custom-text-400" : ""
|
neverExpires ? "text-custom-text-400" : ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@ -194,20 +193,13 @@ export const CreateApiTokenForm: React.FC<Props> = (props) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{watch("expired_at") === "custom" && (
|
{watch("expired_at") === "custom" && (
|
||||||
<CustomDatePicker
|
<DateDropdown
|
||||||
value={customDate}
|
value={customDate}
|
||||||
onChange={(date) => setCustomDate(date ? new Date(date) : null)}
|
onChange={(date) => setCustomDate(date)}
|
||||||
minDate={tomorrow}
|
minDate={tomorrow}
|
||||||
customInput={
|
icon={<Calendar className="h-3 w-3" />}
|
||||||
<div
|
buttonVariant="border-with-text"
|
||||||
className={`flex cursor-pointer items-center gap-2 !rounded border-[0.5px] border-custom-border-200 px-2 py-1 text-xs !shadow-none !duration-0 ${
|
placeholder="Set date"
|
||||||
customDate ? "w-[7.5rem]" : ""
|
|
||||||
} ${neverExpires ? "!cursor-not-allowed text-custom-text-400" : "hover:bg-custom-background-80"}`}
|
|
||||||
>
|
|
||||||
<Calendar className="h-3 w-3" />
|
|
||||||
{customDate ? renderFormattedDate(customDate) : "Set date"}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
disabled={neverExpires}
|
disabled={neverExpires}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import DatePicker from "react-datepicker";
|
import { DayPicker } from "react-day-picker";
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
import { Dialog, Transition } from "@headlessui/react";
|
||||||
|
import { X } from "lucide-react";
|
||||||
// components
|
// components
|
||||||
import { DateFilterSelect } from "./date-filter-select";
|
import { DateFilterSelect } from "./date-filter-select";
|
||||||
// ui
|
// ui
|
||||||
import { Button } from "@plane/ui";
|
import { Button } from "@plane/ui";
|
||||||
// icons
|
|
||||||
import { X } from "lucide-react";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { renderFormattedPayloadDate, renderFormattedDate } from "helpers/date-time.helper";
|
import { renderFormattedPayloadDate, renderFormattedDate } from "helpers/date-time.helper";
|
||||||
|
|
||||||
@ -46,9 +45,6 @@ export const DateFilterModal: React.FC<Props> = ({ title, handleClose, isOpen, o
|
|||||||
|
|
||||||
const isInvalid = watch("filterType") === "range" ? new Date(watch("date1")) > new Date(watch("date2")) : false;
|
const isInvalid = watch("filterType") === "range" ? new Date(watch("date1")) > new Date(watch("date2")) : false;
|
||||||
|
|
||||||
const nextDay = new Date(watch("date1"));
|
|
||||||
nextDay.setDate(nextDay.getDate() + 1);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Transition.Root show={isOpen} as={Fragment}>
|
<Transition.Root show={isOpen} as={Fragment}>
|
||||||
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
<Dialog as="div" className="relative z-20" onClose={handleClose}>
|
||||||
@ -91,12 +87,15 @@ export const DateFilterModal: React.FC<Props> = ({ title, handleClose, isOpen, o
|
|||||||
control={control}
|
control={control}
|
||||||
name="date1"
|
name="date1"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<DatePicker
|
<DayPicker
|
||||||
selected={value}
|
selected={value ? new Date(value) : undefined}
|
||||||
onChange={(val) => onChange(val)}
|
defaultMonth={value ? new Date(value) : undefined}
|
||||||
dateFormat="dd-MM-yyyy"
|
onSelect={(date) => onChange(date)}
|
||||||
calendarClassName="h-full"
|
mode="single"
|
||||||
inline
|
disabled={[
|
||||||
|
{ after: new Date(watch("date2")) }
|
||||||
|
]}
|
||||||
|
className="border border-custom-border-200 p-3 rounded-md"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -105,13 +104,15 @@ export const DateFilterModal: React.FC<Props> = ({ title, handleClose, isOpen, o
|
|||||||
control={control}
|
control={control}
|
||||||
name="date2"
|
name="date2"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<DatePicker
|
<DayPicker
|
||||||
selected={value}
|
selected={value ? new Date(value) : undefined}
|
||||||
onChange={onChange}
|
defaultMonth={value ? new Date(value) : undefined}
|
||||||
dateFormat="dd-MM-yyyy"
|
onSelect={(date) => onChange(date)}
|
||||||
calendarClassName="h-full"
|
mode="single"
|
||||||
minDate={nextDay}
|
disabled={[
|
||||||
inline
|
{ before: new Date(watch("date1")) }
|
||||||
|
]}
|
||||||
|
className="border border-custom-border-200 p-3 rounded-md"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// components
|
// components
|
||||||
import { DateDropdown, ProjectDropdown } from "components/dropdowns";
|
import { DateRangeDropdown, ProjectDropdown } from "components/dropdowns";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input, TextArea } from "@plane/ui";
|
import { Button, Input, TextArea } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
@ -32,7 +32,6 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||||||
formState: { errors, isSubmitting, dirtyFields },
|
formState: { errors, isSubmitting, dirtyFields },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
control,
|
control,
|
||||||
watch,
|
|
||||||
reset,
|
reset,
|
||||||
} = useForm<ICycle>({
|
} = useForm<ICycle>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@ -51,15 +50,6 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||||||
});
|
});
|
||||||
}, [data, reset]);
|
}, [data, reset]);
|
||||||
|
|
||||||
const startDate = watch("start_date");
|
|
||||||
const endDate = watch("end_date");
|
|
||||||
|
|
||||||
const minDate = startDate ? new Date(startDate) : new Date();
|
|
||||||
minDate.setDate(minDate.getDate() + 1);
|
|
||||||
|
|
||||||
const maxDate = endDate ? new Date(endDate) : null;
|
|
||||||
maxDate?.setDate(maxDate.getDate() - 1);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit((formData) => handleFormSubmit(formData, dirtyFields))}>
|
<form onSubmit={handleSubmit((formData) => handleFormSubmit(formData, dirtyFields))}>
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
@ -132,39 +122,37 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<div>
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="start_date"
|
name="start_date"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value: startDateValue, onChange: onChangeStartDate } }) => (
|
||||||
<div className="h-7">
|
|
||||||
<DateDropdown
|
|
||||||
value={value}
|
|
||||||
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
|
|
||||||
buttonVariant="border-with-text"
|
|
||||||
placeholder="Start date"
|
|
||||||
minDate={new Date()}
|
|
||||||
maxDate={maxDate ?? undefined}
|
|
||||||
tabIndex={3}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="end_date"
|
name="end_date"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value: endDateValue, onChange: onChangeEndDate } }) => (
|
||||||
<div className="h-7">
|
<DateRangeDropdown
|
||||||
<DateDropdown
|
|
||||||
value={value}
|
|
||||||
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
|
|
||||||
buttonVariant="border-with-text"
|
buttonVariant="border-with-text"
|
||||||
placeholder="End date"
|
className="h-7"
|
||||||
minDate={minDate}
|
minDate={new Date()}
|
||||||
tabIndex={4}
|
value={{
|
||||||
|
from: startDateValue ? new Date(startDateValue) : undefined,
|
||||||
|
to: endDateValue ? new Date(endDateValue) : undefined,
|
||||||
|
}}
|
||||||
|
onSelect={(val) => {
|
||||||
|
onChangeStartDate(val?.from ? renderFormattedPayloadDate(val.from) : null);
|
||||||
|
onChangeEndDate(val?.to ? renderFormattedPayloadDate(val.to) : null);
|
||||||
|
}}
|
||||||
|
placeholder={{
|
||||||
|
from: "Start date",
|
||||||
|
to: "End date",
|
||||||
|
}}
|
||||||
|
hideIcon={{
|
||||||
|
to: true,
|
||||||
|
}}
|
||||||
|
tabIndex={3}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -172,10 +160,10 @@ export const CycleForm: React.FC<Props> = (props) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-100 pt-5 ">
|
<div className="flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-100 pt-5 ">
|
||||||
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={5}>
|
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={4}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={6}>
|
<Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={5}>
|
||||||
{data ? (isSubmitting ? "Updating" : "Update cycle") : isSubmitting ? "Creating" : "Create cycle"}
|
{data ? (isSubmitting ? "Updating" : "Update cycle") : isSubmitting ? "Creating" : "Create cycle"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { Disclosure, Popover, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
import isEmpty from "lodash/isEmpty";
|
import isEmpty from "lodash/isEmpty";
|
||||||
// services
|
// services
|
||||||
import { CycleService } from "services/cycle.service";
|
import { CycleService } from "services/cycle.service";
|
||||||
@ -14,27 +14,12 @@ import { SidebarProgressStats } from "components/core";
|
|||||||
import ProgressChart from "components/core/sidebar/progress-chart";
|
import ProgressChart from "components/core/sidebar/progress-chart";
|
||||||
import { CycleDeleteModal } from "components/cycles/delete-modal";
|
import { CycleDeleteModal } from "components/cycles/delete-modal";
|
||||||
// ui
|
// ui
|
||||||
import { CustomRangeDatePicker } from "components/ui";
|
|
||||||
import { Avatar, CustomMenu, Loader, LayersIcon } from "@plane/ui";
|
import { Avatar, CustomMenu, Loader, LayersIcon } from "@plane/ui";
|
||||||
// icons
|
// icons
|
||||||
import {
|
import { ChevronDown, LinkIcon, Trash2, UserCircle2, AlertCircle, ChevronRight, CalendarClock } from "lucide-react";
|
||||||
ChevronDown,
|
|
||||||
LinkIcon,
|
|
||||||
Trash2,
|
|
||||||
UserCircle2,
|
|
||||||
AlertCircle,
|
|
||||||
ChevronRight,
|
|
||||||
CalendarCheck2,
|
|
||||||
CalendarClock,
|
|
||||||
} from "lucide-react";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { copyUrlToClipboard } from "helpers/string.helper";
|
import { copyUrlToClipboard } from "helpers/string.helper";
|
||||||
import {
|
import { findHowManyDaysLeft, renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||||
findHowManyDaysLeft,
|
|
||||||
isDateGreaterThanToday,
|
|
||||||
renderFormattedPayloadDate,
|
|
||||||
renderFormattedDate,
|
|
||||||
} from "helpers/date-time.helper";
|
|
||||||
// types
|
// types
|
||||||
import { ICycle } from "@plane/types";
|
import { ICycle } from "@plane/types";
|
||||||
// constants
|
// constants
|
||||||
@ -42,6 +27,7 @@ import { EUserWorkspaceRoles } from "constants/workspace";
|
|||||||
import { CYCLE_UPDATED } from "constants/event-tracker";
|
import { CYCLE_UPDATED } from "constants/event-tracker";
|
||||||
// fetch-keys
|
// fetch-keys
|
||||||
import { CYCLE_STATUS } from "constants/cycle";
|
import { CYCLE_STATUS } from "constants/cycle";
|
||||||
|
import { DateRangeDropdown } from "components/dropdowns";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
cycleId: string;
|
cycleId: string;
|
||||||
@ -61,9 +47,6 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const { cycleId, handleClose } = props;
|
const { cycleId, handleClose } = props;
|
||||||
// states
|
// states
|
||||||
const [cycleDeleteModal, setCycleDeleteModal] = useState(false);
|
const [cycleDeleteModal, setCycleDeleteModal] = useState(false);
|
||||||
// refs
|
|
||||||
const startDateButtonRef = useRef<HTMLButtonElement | null>(null);
|
|
||||||
const endDateButtonRef = useRef<HTMLButtonElement | null>(null);
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, peekCycle } = router.query;
|
const { workspaceSlug, projectId, peekCycle } = router.query;
|
||||||
@ -74,13 +57,13 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
} = useUser();
|
} = useUser();
|
||||||
const { getCycleById, updateCycleDetails } = useCycle();
|
const { getCycleById, updateCycleDetails } = useCycle();
|
||||||
const { getUserDetails } = useMember();
|
const { getUserDetails } = useMember();
|
||||||
|
// derived values
|
||||||
const cycleDetails = getCycleById(cycleId);
|
const cycleDetails = getCycleById(cycleId);
|
||||||
const cycleOwnerDetails = cycleDetails ? getUserDetails(cycleDetails.owned_by) : undefined;
|
const cycleOwnerDetails = cycleDetails ? getUserDetails(cycleDetails.owned_by) : undefined;
|
||||||
|
// toast alert
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
// form info
|
||||||
const { setValue, reset, watch } = useForm({
|
const { control, reset } = useForm({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -145,146 +128,25 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartDateChange = async (date: string) => {
|
const handleDateChange = async (startDate: Date | undefined, endDate: Date | undefined) => {
|
||||||
setValue("start_date", date);
|
if (!startDate || !endDate) return;
|
||||||
|
|
||||||
if (!watch("end_date") || watch("end_date") === "") endDateButtonRef.current?.click();
|
let isDateValid = false;
|
||||||
|
|
||||||
if (watch("start_date") && watch("end_date") && watch("start_date") !== "" && watch("start_date") !== "") {
|
const payload = {
|
||||||
if (!isDateGreaterThanToday(`${watch("end_date")}`)) {
|
start_date: renderFormattedPayloadDate(startDate),
|
||||||
setToastAlert({
|
end_date: renderFormattedPayloadDate(endDate),
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: "Unable to create cycle in past date. Please enter a valid date.",
|
|
||||||
});
|
|
||||||
reset({ ...cycleDetails });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cycleDetails?.start_date && cycleDetails?.end_date) {
|
|
||||||
const isDateValidForExistingCycle = await dateChecker({
|
|
||||||
start_date: `${watch("start_date")}`,
|
|
||||||
end_date: `${watch("end_date")}`,
|
|
||||||
cycle_id: cycleDetails.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDateValidForExistingCycle) {
|
|
||||||
submitChanges(
|
|
||||||
{
|
|
||||||
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
|
||||||
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
|
||||||
},
|
|
||||||
"start_date"
|
|
||||||
);
|
|
||||||
setToastAlert({
|
|
||||||
type: "success",
|
|
||||||
title: "Success!",
|
|
||||||
message: "Cycle updated successfully.",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message:
|
|
||||||
"You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
reset({ ...cycleDetails });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDateValid = await dateChecker({
|
|
||||||
start_date: `${watch("start_date")}`,
|
|
||||||
end_date: `${watch("end_date")}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDateValid) {
|
|
||||||
submitChanges(
|
|
||||||
{
|
|
||||||
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
|
||||||
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
|
||||||
},
|
|
||||||
"start_date"
|
|
||||||
);
|
|
||||||
setToastAlert({
|
|
||||||
type: "success",
|
|
||||||
title: "Success!",
|
|
||||||
message: "Cycle updated successfully.",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message:
|
|
||||||
"You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates",
|
|
||||||
});
|
|
||||||
reset({ ...cycleDetails });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleEndDateChange = async (date: string) => {
|
if (cycleDetails && cycleDetails.start_date && cycleDetails.end_date)
|
||||||
setValue("end_date", date);
|
isDateValid = await dateChecker({
|
||||||
|
...payload,
|
||||||
if (!watch("start_date") || watch("start_date") === "") startDateButtonRef.current?.click();
|
|
||||||
|
|
||||||
if (watch("start_date") && watch("end_date") && watch("start_date") !== "" && watch("start_date") !== "") {
|
|
||||||
if (!isDateGreaterThanToday(`${watch("end_date")}`)) {
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message: "Unable to create cycle in past date. Please enter a valid date.",
|
|
||||||
});
|
|
||||||
reset({ ...cycleDetails });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cycleDetails?.start_date && cycleDetails?.end_date) {
|
|
||||||
const isDateValidForExistingCycle = await dateChecker({
|
|
||||||
start_date: `${watch("start_date")}`,
|
|
||||||
end_date: `${watch("end_date")}`,
|
|
||||||
cycle_id: cycleDetails.id,
|
cycle_id: cycleDetails.id,
|
||||||
});
|
});
|
||||||
|
else isDateValid = await dateChecker(payload);
|
||||||
if (isDateValidForExistingCycle) {
|
|
||||||
submitChanges(
|
|
||||||
{
|
|
||||||
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
|
||||||
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
|
||||||
},
|
|
||||||
"end_date"
|
|
||||||
);
|
|
||||||
setToastAlert({
|
|
||||||
type: "success",
|
|
||||||
title: "Success!",
|
|
||||||
message: "Cycle updated successfully.",
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
setToastAlert({
|
|
||||||
type: "error",
|
|
||||||
title: "Error!",
|
|
||||||
message:
|
|
||||||
"You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
reset({ ...cycleDetails });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDateValid = await dateChecker({
|
|
||||||
start_date: `${watch("start_date")}`,
|
|
||||||
end_date: `${watch("end_date")}`,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (isDateValid) {
|
if (isDateValid) {
|
||||||
submitChanges(
|
submitChanges(payload, "date_range");
|
||||||
{
|
|
||||||
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
|
||||||
end_date: renderFormattedPayloadDate(`${watch("end_date")}`),
|
|
||||||
},
|
|
||||||
"end_date"
|
|
||||||
);
|
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
@ -295,11 +157,10 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
type: "error",
|
type: "error",
|
||||||
title: "Error!",
|
title: "Error!",
|
||||||
message:
|
message:
|
||||||
"You have a cycle already on the given dates, if you want to create your draft cycle you can do that by removing dates",
|
"You already have a cycle on the given dates, if you want to create a draft cycle, you can do that by removing both the dates.",
|
||||||
});
|
});
|
||||||
reset({ ...cycleDetails });
|
reset({ ...cycleDetails });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: refactor this
|
// TODO: refactor this
|
||||||
@ -351,9 +212,6 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
</Loader>
|
</Loader>
|
||||||
);
|
);
|
||||||
|
|
||||||
const endDate = new Date(watch("end_date") ?? cycleDetails.end_date ?? "");
|
|
||||||
const startDate = new Date(watch("start_date") ?? cycleDetails.start_date ?? "");
|
|
||||||
|
|
||||||
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
|
const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus);
|
||||||
|
|
||||||
const issueCount =
|
const issueCount =
|
||||||
@ -440,125 +298,52 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
<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 items-center justify-start gap-1">
|
||||||
<div className="flex w-1/2 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">
|
||||||
<CalendarClock className="h-4 w-4" />
|
<CalendarClock className="h-4 w-4" />
|
||||||
<span className="text-base">Start date</span>
|
<span className="text-base">Date range</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex w-1/2 items-center rounded-sm">
|
<div className="w-3/5 h-7">
|
||||||
<Popover className="flex h-full w-full items-center justify-center rounded-lg">
|
<Controller
|
||||||
{({ close }) => (
|
control={control}
|
||||||
<>
|
name="start_date"
|
||||||
<Popover.Button
|
render={({ field: { value: startDateValue, onChange: onChangeStartDate } }) => (
|
||||||
ref={startDateButtonRef}
|
<Controller
|
||||||
className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${
|
control={control}
|
||||||
isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
|
name="end_date"
|
||||||
}`}
|
render={({ field: { value: endDateValue, onChange: onChangeEndDate } }) => (
|
||||||
disabled={isCompleted || !isEditingAllowed}
|
<DateRangeDropdown
|
||||||
>
|
className="h-7"
|
||||||
<span
|
buttonContainerClassName="w-full"
|
||||||
className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${
|
buttonVariant="background-with-text"
|
||||||
watch("start_date") ? "" : "text-custom-text-400"
|
minDate={new Date()}
|
||||||
}`}
|
value={{
|
||||||
>
|
from: startDateValue ? new Date(startDateValue) : undefined,
|
||||||
{renderFormattedDate(startDate) ?? "No date selected"}
|
to: endDateValue ? new Date(endDateValue) : undefined,
|
||||||
</span>
|
|
||||||
</Popover.Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
as={React.Fragment}
|
|
||||||
enter="transition ease-out duration-200"
|
|
||||||
enterFrom="opacity-0 translate-y-1"
|
|
||||||
enterTo="opacity-100 translate-y-0"
|
|
||||||
leave="transition ease-in duration-150"
|
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
|
||||||
leaveTo="opacity-0 translate-y-1"
|
|
||||||
>
|
|
||||||
<Popover.Panel className="absolute right-0 top-10 z-20 transform overflow-hidden">
|
|
||||||
<CustomRangeDatePicker
|
|
||||||
value={watch("start_date") ? watch("start_date") : cycleDetails?.start_date}
|
|
||||||
onChange={(val) => {
|
|
||||||
if (val) {
|
|
||||||
setTrackElement("CYCLE_PAGE_SIDEBAR_START_DATE_BUTTON");
|
|
||||||
handleStartDateChange(val);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
startDate={watch("start_date") ?? watch("end_date") ?? null}
|
onSelect={(val) => {
|
||||||
endDate={watch("end_date") ?? watch("start_date") ?? null}
|
onChangeStartDate(val?.from ? renderFormattedPayloadDate(val.from) : null);
|
||||||
maxDate={new Date(`${watch("end_date")}`)}
|
onChangeEndDate(val?.to ? renderFormattedPayloadDate(val.to) : null);
|
||||||
selectsStart={watch("end_date") ? true : false}
|
handleDateChange(val?.from, val?.to);
|
||||||
|
}}
|
||||||
|
placeholder={{
|
||||||
|
from: "Start date",
|
||||||
|
to: "End date",
|
||||||
|
}}
|
||||||
|
required={cycleDetails.status !== "draft"}
|
||||||
/>
|
/>
|
||||||
</Popover.Panel>
|
|
||||||
</Transition>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</Popover>
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-start gap-1">
|
<div className="flex items-center justify-start gap-1">
|
||||||
<div className="flex w-1/2 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">
|
||||||
<CalendarCheck2 className="h-4 w-4" />
|
|
||||||
<span className="text-base">Target date</span>
|
|
||||||
</div>
|
|
||||||
<div className="relative flex w-1/2 items-center rounded-sm">
|
|
||||||
<Popover className="flex h-full w-full items-center justify-center rounded-lg">
|
|
||||||
{({ close }) => (
|
|
||||||
<>
|
|
||||||
<Popover.Button
|
|
||||||
ref={endDateButtonRef}
|
|
||||||
className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${
|
|
||||||
isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
|
|
||||||
}`}
|
|
||||||
disabled={isCompleted || !isEditingAllowed}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${
|
|
||||||
watch("end_date") ? "" : "text-custom-text-400"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{renderFormattedDate(endDate) ?? "No date selected"}
|
|
||||||
</span>
|
|
||||||
</Popover.Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
as={React.Fragment}
|
|
||||||
enter="transition ease-out duration-200"
|
|
||||||
enterFrom="opacity-0 translate-y-1"
|
|
||||||
enterTo="opacity-100 translate-y-0"
|
|
||||||
leave="transition ease-in duration-150"
|
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
|
||||||
leaveTo="opacity-0 translate-y-1"
|
|
||||||
>
|
|
||||||
<Popover.Panel className="absolute right-0 top-10 z-20 transform overflow-hidden">
|
|
||||||
<CustomRangeDatePicker
|
|
||||||
value={watch("end_date") ? watch("end_date") : cycleDetails?.end_date}
|
|
||||||
onChange={(val) => {
|
|
||||||
if (val) {
|
|
||||||
setTrackElement("CYCLE_PAGE_SIDEBAR_END_DATE_BUTTON");
|
|
||||||
handleEndDateChange(val);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
startDate={watch("start_date") ?? watch("end_date") ?? null}
|
|
||||||
endDate={watch("end_date") ?? watch("start_date") ?? null}
|
|
||||||
minDate={new Date(`${watch("start_date")}`)}
|
|
||||||
selectsEnd={watch("start_date") ? true : false}
|
|
||||||
/>
|
|
||||||
</Popover.Panel>
|
|
||||||
</Transition>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-start gap-1">
|
|
||||||
<div className="flex w-1/2 items-center justify-start gap-2 text-custom-text-300">
|
|
||||||
<UserCircle2 className="h-4 w-4" />
|
<UserCircle2 className="h-4 w-4" />
|
||||||
<span className="text-base">Lead</span>
|
<span className="text-base">Lead</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-1/2 items-center rounded-sm">
|
<div className="flex w-3/5 items-center rounded-sm">
|
||||||
<div className="flex items-center gap-2.5">
|
<div className="flex items-center gap-2.5">
|
||||||
<Avatar name={cycleOwnerDetails?.display_name} src={cycleOwnerDetails?.avatar} />
|
<Avatar name={cycleOwnerDetails?.display_name} src={cycleOwnerDetails?.avatar} />
|
||||||
<span className="text-sm text-custom-text-200">{cycleOwnerDetails?.display_name}</span>
|
<span className="text-sm text-custom-text-200">{cycleOwnerDetails?.display_name}</span>
|
||||||
@ -567,11 +352,11 @@ export const CycleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-start gap-1">
|
<div className="flex items-center justify-start gap-1">
|
||||||
<div className="flex w-1/2 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">
|
||||||
<LayersIcon className="h-4 w-4" />
|
<LayersIcon className="h-4 w-4" />
|
||||||
<span className="text-base">Issues</span>
|
<span className="text-base">Issues</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-1/2 items-center">
|
<div className="flex w-3/5 items-center">
|
||||||
<span className="px-1.5 text-sm text-custom-text-300">{issueCount}</span>
|
<span className="px-1.5 text-sm text-custom-text-300">{issueCount}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
261
web/components/dropdowns/date-range.tsx
Normal file
261
web/components/dropdowns/date-range.tsx
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { Combobox } from "@headlessui/react";
|
||||||
|
import { usePopper } from "react-popper";
|
||||||
|
import { Placement } from "@popperjs/core";
|
||||||
|
import { DateRange, DayPicker, Matcher } from "react-day-picker";
|
||||||
|
import { ArrowRight, CalendarDays } from "lucide-react";
|
||||||
|
// hooks
|
||||||
|
import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||||
|
import { useDropdownKeyDown } from "hooks/use-dropdown-key-down";
|
||||||
|
// components
|
||||||
|
import { DropdownButton } from "./buttons";
|
||||||
|
// ui
|
||||||
|
import { Button } from "@plane/ui";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "helpers/common.helper";
|
||||||
|
import { renderFormattedDate } from "helpers/date-time.helper";
|
||||||
|
// types
|
||||||
|
import { TButtonVariants } from "./types";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
applyButtonText?: string;
|
||||||
|
bothRequired?: boolean;
|
||||||
|
buttonClassName?: string;
|
||||||
|
buttonContainerClassName?: string;
|
||||||
|
buttonFromDateClassName?: string;
|
||||||
|
buttonToDateClassName?: string;
|
||||||
|
buttonVariant: TButtonVariants;
|
||||||
|
cancelButtonText?: string;
|
||||||
|
className?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
hideIcon?: {
|
||||||
|
from?: boolean;
|
||||||
|
to?: boolean;
|
||||||
|
};
|
||||||
|
icon?: React.ReactNode;
|
||||||
|
minDate?: Date;
|
||||||
|
maxDate?: Date;
|
||||||
|
onSelect: (range: DateRange | undefined) => void;
|
||||||
|
placeholder?: {
|
||||||
|
from?: string;
|
||||||
|
to?: string;
|
||||||
|
};
|
||||||
|
placement?: Placement;
|
||||||
|
required?: boolean;
|
||||||
|
showTooltip?: boolean;
|
||||||
|
tabIndex?: number;
|
||||||
|
value: {
|
||||||
|
from: Date | undefined;
|
||||||
|
to: Date | undefined;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DateRangeDropdown: React.FC<Props> = (props) => {
|
||||||
|
const {
|
||||||
|
applyButtonText = "Apply changes",
|
||||||
|
bothRequired = true,
|
||||||
|
buttonClassName,
|
||||||
|
buttonContainerClassName,
|
||||||
|
buttonFromDateClassName,
|
||||||
|
buttonToDateClassName,
|
||||||
|
buttonVariant,
|
||||||
|
cancelButtonText = "Cancel",
|
||||||
|
className,
|
||||||
|
disabled = false,
|
||||||
|
hideIcon = {
|
||||||
|
from: true,
|
||||||
|
to: true,
|
||||||
|
},
|
||||||
|
icon = <CalendarDays className="h-3 w-3 flex-shrink-0" />,
|
||||||
|
minDate,
|
||||||
|
maxDate,
|
||||||
|
onSelect,
|
||||||
|
placeholder = {
|
||||||
|
from: "Add date",
|
||||||
|
to: "Add date",
|
||||||
|
},
|
||||||
|
placement,
|
||||||
|
required = false,
|
||||||
|
showTooltip = false,
|
||||||
|
tabIndex,
|
||||||
|
value,
|
||||||
|
} = props;
|
||||||
|
// states
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [dateRange, setDateRange] = useState<DateRange>(value);
|
||||||
|
// refs
|
||||||
|
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
// popper-js refs
|
||||||
|
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
|
||||||
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
||||||
|
// popper-js init
|
||||||
|
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||||
|
placement: placement ?? "bottom-start",
|
||||||
|
modifiers: [
|
||||||
|
{
|
||||||
|
name: "preventOverflow",
|
||||||
|
options: {
|
||||||
|
padding: 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
const onOpen = () => {
|
||||||
|
if (referenceElement) referenceElement.focus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
if (!isOpen) return;
|
||||||
|
setIsOpen(false);
|
||||||
|
setDateRange({
|
||||||
|
from: value.from,
|
||||||
|
to: value.to,
|
||||||
|
});
|
||||||
|
if (referenceElement) referenceElement.blur();
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleDropdown = () => {
|
||||||
|
if (!isOpen) onOpen();
|
||||||
|
setIsOpen((prevIsOpen) => !prevIsOpen);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = useDropdownKeyDown(toggleDropdown, handleClose);
|
||||||
|
|
||||||
|
const handleOnClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
e.preventDefault();
|
||||||
|
toggleDropdown();
|
||||||
|
};
|
||||||
|
|
||||||
|
useOutsideClickDetector(dropdownRef, handleClose);
|
||||||
|
|
||||||
|
const disabledDays: Matcher[] = [];
|
||||||
|
if (minDate) disabledDays.push({ before: minDate });
|
||||||
|
if (maxDate) disabledDays.push({ after: maxDate });
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setDateRange(value);
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Combobox
|
||||||
|
as="div"
|
||||||
|
ref={dropdownRef}
|
||||||
|
tabIndex={tabIndex}
|
||||||
|
className={cn("h-full", className)}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
if (!isOpen) handleKeyDown(e);
|
||||||
|
} else handleKeyDown(e);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Combobox.Button as={React.Fragment}>
|
||||||
|
<button
|
||||||
|
ref={setReferenceElement}
|
||||||
|
type="button"
|
||||||
|
className={cn(
|
||||||
|
"clickable block h-full max-w-full outline-none",
|
||||||
|
{
|
||||||
|
"cursor-not-allowed text-custom-text-200": disabled,
|
||||||
|
"cursor-pointer": !disabled,
|
||||||
|
},
|
||||||
|
buttonContainerClassName
|
||||||
|
)}
|
||||||
|
onClick={handleOnClick}
|
||||||
|
>
|
||||||
|
<DropdownButton
|
||||||
|
className={buttonClassName}
|
||||||
|
isActive={isOpen}
|
||||||
|
tooltipHeading="Date range"
|
||||||
|
tooltipContent={
|
||||||
|
<>
|
||||||
|
{dateRange.from ? renderFormattedDate(dateRange.from) : "N/A"}
|
||||||
|
{" - "}
|
||||||
|
{dateRange.to ? renderFormattedDate(dateRange.to) : "N/A"}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
showTooltip={showTooltip}
|
||||||
|
variant={buttonVariant}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"h-full flex items-center justify-center gap-1 rounded-sm flex-grow",
|
||||||
|
buttonFromDateClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!hideIcon.from && icon}
|
||||||
|
{dateRange.from ? renderFormattedDate(dateRange.from) : placeholder.from}
|
||||||
|
</span>
|
||||||
|
<ArrowRight className="h-3 w-3 flex-shrink-0" />
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"h-full flex items-center justify-center gap-1 rounded-sm flex-grow",
|
||||||
|
buttonToDateClassName
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!hideIcon.to && icon}
|
||||||
|
{dateRange.to ? renderFormattedDate(dateRange.to) : placeholder.to}
|
||||||
|
</span>
|
||||||
|
</DropdownButton>
|
||||||
|
</button>
|
||||||
|
</Combobox.Button>
|
||||||
|
{isOpen && (
|
||||||
|
<Combobox.Options className="fixed z-10" static>
|
||||||
|
<div
|
||||||
|
className="my-1 bg-custom-background-100 shadow-custom-shadow-rg rounded-md overflow-hidden p-3"
|
||||||
|
ref={setPopperElement}
|
||||||
|
style={styles.popper}
|
||||||
|
{...attributes.popper}
|
||||||
|
>
|
||||||
|
<DayPicker
|
||||||
|
selected={dateRange}
|
||||||
|
onSelect={(val) => {
|
||||||
|
// if both the dates are not required, immediately call onSelect
|
||||||
|
if (!bothRequired) onSelect(val);
|
||||||
|
setDateRange({
|
||||||
|
from: val?.from ?? undefined,
|
||||||
|
to: val?.to ?? undefined,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
mode="range"
|
||||||
|
disabled={disabledDays}
|
||||||
|
showOutsideDays
|
||||||
|
initialFocus
|
||||||
|
footer={
|
||||||
|
bothRequired && (
|
||||||
|
<div className="grid grid-cols-2 items-center gap-3.5 pt-6 relative">
|
||||||
|
<div className="absolute left-0 top-1 h-[0.5px] w-full border-t-[0.5px] border-custom-border-300" />
|
||||||
|
<Button
|
||||||
|
variant="neutral-primary"
|
||||||
|
onClick={() => {
|
||||||
|
setDateRange({
|
||||||
|
from: undefined,
|
||||||
|
to: undefined,
|
||||||
|
});
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{cancelButtonText}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
onSelect(dateRange);
|
||||||
|
handleClose();
|
||||||
|
}}
|
||||||
|
// if required, both the dates should be selected
|
||||||
|
// if not required, either both or none of the dates should be selected
|
||||||
|
disabled={required ? !(dateRange.from && dateRange.to) : !!dateRange.from !== !!dateRange.to}
|
||||||
|
>
|
||||||
|
{applyButtonText}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Combobox.Options>
|
||||||
|
)}
|
||||||
|
</Combobox>
|
||||||
|
);
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useRef, useState } from "react";
|
import React, { useRef, useState } from "react";
|
||||||
import { Combobox } from "@headlessui/react";
|
import { Combobox } from "@headlessui/react";
|
||||||
import DatePicker from "react-datepicker";
|
import { DayPicker, Matcher } from "react-day-picker";
|
||||||
import { usePopper } from "react-popper";
|
import { usePopper } from "react-popper";
|
||||||
import { CalendarDays, X } from "lucide-react";
|
import { CalendarDays, X } from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
@ -50,6 +50,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
|||||||
tabIndex,
|
tabIndex,
|
||||||
value,
|
value,
|
||||||
} = props;
|
} = props;
|
||||||
|
// states
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
// refs
|
// refs
|
||||||
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
const dropdownRef = useRef<HTMLDivElement | null>(null);
|
||||||
@ -102,18 +103,25 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
|||||||
|
|
||||||
useOutsideClickDetector(dropdownRef, handleClose);
|
useOutsideClickDetector(dropdownRef, handleClose);
|
||||||
|
|
||||||
|
const disabledDays: Matcher[] = [];
|
||||||
|
if (minDate) disabledDays.push({ before: minDate });
|
||||||
|
if (maxDate) disabledDays.push({ after: maxDate });
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Combobox
|
<Combobox
|
||||||
as="div"
|
as="div"
|
||||||
ref={dropdownRef}
|
ref={dropdownRef}
|
||||||
tabIndex={tabIndex}
|
tabIndex={tabIndex}
|
||||||
className={cn("h-full", className)}
|
className={cn("h-full", className)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
if (!isOpen) handleKeyDown(e);
|
||||||
|
} else handleKeyDown(e);
|
||||||
|
}}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<Combobox.Button as={React.Fragment}>
|
<Combobox.Button as={React.Fragment}>
|
||||||
<button
|
<button
|
||||||
ref={setReferenceElement}
|
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(
|
className={cn(
|
||||||
"clickable block h-full max-w-full outline-none",
|
"clickable block h-full max-w-full outline-none",
|
||||||
@ -123,6 +131,7 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
|||||||
},
|
},
|
||||||
buttonContainerClassName
|
buttonContainerClassName
|
||||||
)}
|
)}
|
||||||
|
ref={setReferenceElement}
|
||||||
onClick={handleOnClick}
|
onClick={handleOnClick}
|
||||||
>
|
>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
@ -151,15 +160,22 @@ export const DateDropdown: React.FC<Props> = (props) => {
|
|||||||
</Combobox.Button>
|
</Combobox.Button>
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<Combobox.Options className="fixed z-10" static>
|
<Combobox.Options className="fixed z-10" static>
|
||||||
<div className="my-1" ref={setPopperElement} style={styles.popper} {...attributes.popper}>
|
<div
|
||||||
<DatePicker
|
className="my-1 bg-custom-background-100 shadow-custom-shadow-rg rounded-md overflow-hidden p-3"
|
||||||
selected={value ? new Date(value) : null}
|
ref={setPopperElement}
|
||||||
onChange={dropdownOnChange}
|
style={styles.popper}
|
||||||
dateFormat="dd-MM-yyyy"
|
{...attributes.popper}
|
||||||
minDate={minDate}
|
>
|
||||||
maxDate={maxDate}
|
<DayPicker
|
||||||
calendarClassName="shadow-custom-shadow-rg rounded"
|
selected={value ? new Date(value) : undefined}
|
||||||
inline
|
defaultMonth={value ? new Date(value) : undefined}
|
||||||
|
onSelect={(date) => {
|
||||||
|
dropdownOnChange(date ?? null);
|
||||||
|
}}
|
||||||
|
showOutsideDays
|
||||||
|
initialFocus
|
||||||
|
disabled={disabledDays}
|
||||||
|
mode="single"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Combobox.Options>
|
</Combobox.Options>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
export * from "./member";
|
export * from "./member";
|
||||||
export * from "./cycle";
|
export * from "./cycle";
|
||||||
|
export * from "./date-range";
|
||||||
export * from "./date";
|
export * from "./date";
|
||||||
export * from "./estimate";
|
export * from "./estimate";
|
||||||
export * from "./module";
|
export * from "./module";
|
||||||
|
@ -182,7 +182,7 @@ export const ProjectMemberDropdown: React.FC<Props> = observer((props) => {
|
|||||||
>
|
>
|
||||||
{!hideIcon && <ButtonAvatars showTooltip={showTooltip} userIds={value} />}
|
{!hideIcon && <ButtonAvatars showTooltip={showTooltip} userIds={value} />}
|
||||||
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
||||||
<span className="flex-grow truncate text-sm leading-5">
|
<span className="flex-grow truncate text-xs leading-5">
|
||||||
{Array.isArray(value) && value.length > 0
|
{Array.isArray(value) && value.length > 0
|
||||||
? value.length === 1
|
? value.length === 1
|
||||||
? getUserDetails(value[0])?.display_name
|
? getUserDetails(value[0])?.display_name
|
||||||
|
@ -167,7 +167,7 @@ export const WorkspaceMemberDropdown: React.FC<MemberDropdownProps> = observer((
|
|||||||
>
|
>
|
||||||
{!hideIcon && <ButtonAvatars showTooltip={showTooltip} userIds={value} />}
|
{!hideIcon && <ButtonAvatars showTooltip={showTooltip} userIds={value} />}
|
||||||
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
{BUTTON_VARIANTS_WITH_TEXT.includes(buttonVariant) && (
|
||||||
<span className="flex-grow truncate text-sm leading-5">
|
<span className="flex-grow truncate text-xs leading-5">
|
||||||
{Array.isArray(value) && value.length > 0
|
{Array.isArray(value) && value.length > 0
|
||||||
? value.length === 1
|
? value.length === 1
|
||||||
? getUserDetails(value[0])?.display_name
|
? getUserDetails(value[0])?.display_name
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { FC, useCallback, useEffect, useMemo, useState } from "react";
|
import { FC, useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import DatePicker from "react-datepicker";
|
import { DayPicker } from "react-day-picker";
|
||||||
import { Popover } from "@headlessui/react";
|
import { Popover } from "@headlessui/react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useUser, useInboxIssues, useIssueDetail, useWorkspace, useEventTracker } from "hooks/store";
|
import { useUser, useInboxIssues, useIssueDetail, useWorkspace, useEventTracker } from "hooks/store";
|
||||||
@ -266,15 +266,15 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
<Popover.Panel className="absolute right-0 z-10 mt-2 w-80 rounded-md bg-custom-background-100 p-2 shadow-lg">
|
<Popover.Panel className="absolute right-0 z-10 mt-2 w-80 rounded-md bg-custom-background-100 p-2 shadow-lg">
|
||||||
{({ close }) => (
|
{({ close }) => (
|
||||||
<div className="flex h-full w-full flex-col gap-y-1">
|
<div className="flex h-full w-full flex-col gap-y-1">
|
||||||
<DatePicker
|
<DayPicker
|
||||||
selected={date ? new Date(date) : null}
|
selected={date ? new Date(date) : undefined}
|
||||||
onChange={(val) => {
|
defaultMonth={date ? new Date(date) : undefined}
|
||||||
if (!val) return;
|
onSelect={(date) => { if (!date) return; setDate(date) }}
|
||||||
setDate(val);
|
mode="single"
|
||||||
}}
|
className="border border-custom-border-200 rounded-md p-3"
|
||||||
dateFormat="dd-MM-yyyy"
|
disabled={[{
|
||||||
minDate={tomorrow}
|
before: tomorrow,
|
||||||
inline
|
}]}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="primary"
|
variant="primary"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import { differenceInCalendarDays } from "date-fns";
|
||||||
import {
|
import {
|
||||||
LinkIcon,
|
LinkIcon,
|
||||||
Signal,
|
Signal,
|
||||||
@ -11,8 +12,7 @@ import {
|
|||||||
XCircle,
|
XCircle,
|
||||||
CircleDot,
|
CircleDot,
|
||||||
CopyPlus,
|
CopyPlus,
|
||||||
CalendarClock,
|
CalendarDays,
|
||||||
CalendarCheck2,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEstimate, useIssueDetail, useProject, useUser } from "hooks/store";
|
import { useEstimate, useIssueDetail, useProject, useUser } from "hooks/store";
|
||||||
@ -36,10 +36,11 @@ import {
|
|||||||
StateDropdown,
|
StateDropdown,
|
||||||
} from "components/dropdowns";
|
} from "components/dropdowns";
|
||||||
// icons
|
// icons
|
||||||
import { ContrastIcon, DiceIcon, DoubleCircleIcon, RelatedIcon, StateGroupIcon, UserGroupIcon } from "@plane/ui";
|
import { ContrastIcon, DiceIcon, DoubleCircleIcon, RelatedIcon, UserGroupIcon } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||||
import { copyTextToClipboard } from "helpers/string.helper";
|
import { copyTextToClipboard } from "helpers/string.helper";
|
||||||
|
import { cn } from "helpers/common.helper";
|
||||||
// types
|
// types
|
||||||
import type { TIssueOperations } from "./root";
|
import type { TIssueOperations } from "./root";
|
||||||
|
|
||||||
@ -89,6 +90,8 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
||||||
maxDate?.setDate(maxDate.getDate());
|
maxDate?.setDate(maxDate.getDate());
|
||||||
|
|
||||||
|
const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{workspaceSlug && projectId && issue && (
|
{workspaceSlug && projectId && issue && (
|
||||||
@ -195,7 +198,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
<div className="flex items-center gap-2 h-8">
|
<div className="flex items-center gap-2 h-8">
|
||||||
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
||||||
<CalendarClock className="h-4 w-4 flex-shrink-0" />
|
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
||||||
<span>Start date</span>
|
<span>Start date</span>
|
||||||
</div>
|
</div>
|
||||||
<DateDropdown
|
<DateDropdown
|
||||||
@ -221,7 +224,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
<div className="flex items-center gap-2 h-8">
|
<div className="flex items-center gap-2 h-8">
|
||||||
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
<div className="flex items-center gap-1 w-2/5 flex-shrink-0 text-sm text-custom-text-300">
|
||||||
<CalendarCheck2 className="h-4 w-4 flex-shrink-0" />
|
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
||||||
<span>Due date</span>
|
<span>Due date</span>
|
||||||
</div>
|
</div>
|
||||||
<DateDropdown
|
<DateDropdown
|
||||||
@ -237,9 +240,12 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
buttonVariant="transparent-with-text"
|
buttonVariant="transparent-with-text"
|
||||||
className="w-3/5 flex-grow group"
|
className="w-3/5 flex-grow group"
|
||||||
buttonContainerClassName="w-full text-left"
|
buttonContainerClassName="w-full text-left"
|
||||||
buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`}
|
buttonClassName={cn("text-sm", {
|
||||||
|
"text-custom-text-400": !issue.target_date,
|
||||||
|
"text-red-500": targetDateDistance <= 0,
|
||||||
|
})}
|
||||||
hideIcon
|
hideIcon
|
||||||
clearIconClassName="h-3 w-3 hidden group-hover:inline"
|
clearIconClassName="h-3 w-3 hidden group-hover:inline !text-custom-text-100"
|
||||||
// TODO: add this logic
|
// TODO: add this logic
|
||||||
// showPlaceholderIcon
|
// showPlaceholderIcon
|
||||||
/>
|
/>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { CalendarCheck2, CalendarClock, Layers, Link, Paperclip } from "lucide-react";
|
import { differenceInCalendarDays } from "date-fns";
|
||||||
|
import { Layers, Link, Paperclip } from "lucide-react";
|
||||||
import xor from "lodash/xor";
|
import xor from "lodash/xor";
|
||||||
// hooks
|
// hooks
|
||||||
import { useEventTracker, useEstimate, useLabel, useIssues } from "hooks/store";
|
import { useEventTracker, useEstimate, useLabel, useIssues } from "hooks/store";
|
||||||
@ -230,6 +231,8 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
||||||
maxDate?.setDate(maxDate.getDate());
|
maxDate?.setDate(maxDate.getDate());
|
||||||
|
|
||||||
|
const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
{/* basic properties */}
|
{/* basic properties */}
|
||||||
@ -279,7 +282,6 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
<DateDropdown
|
<DateDropdown
|
||||||
value={issue.start_date ?? null}
|
value={issue.start_date ?? null}
|
||||||
onChange={handleStartDate}
|
onChange={handleStartDate}
|
||||||
icon={<CalendarClock className="h-3 w-3 flex-shrink-0" />}
|
|
||||||
maxDate={maxDate ?? undefined}
|
maxDate={maxDate ?? undefined}
|
||||||
placeholder="Start date"
|
placeholder="Start date"
|
||||||
buttonVariant={issue.start_date ? "border-with-text" : "border-without-text"}
|
buttonVariant={issue.start_date ? "border-with-text" : "border-without-text"}
|
||||||
@ -295,10 +297,11 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
<DateDropdown
|
<DateDropdown
|
||||||
value={issue?.target_date ?? null}
|
value={issue?.target_date ?? null}
|
||||||
onChange={handleTargetDate}
|
onChange={handleTargetDate}
|
||||||
icon={<CalendarCheck2 className="h-3 w-3 flex-shrink-0" />}
|
|
||||||
minDate={minDate ?? undefined}
|
minDate={minDate ?? undefined}
|
||||||
placeholder="Due date"
|
placeholder="Due date"
|
||||||
buttonVariant={issue.target_date ? "border-with-text" : "border-without-text"}
|
buttonVariant={issue.target_date ? "border-with-text" : "border-without-text"}
|
||||||
|
buttonClassName={targetDateDistance <= 0 ? "text-red-500" : ""}
|
||||||
|
clearIconClassName="!text-custom-text-100"
|
||||||
disabled={isReadOnly}
|
disabled={isReadOnly}
|
||||||
showTooltip
|
showTooltip
|
||||||
/>
|
/>
|
||||||
@ -375,7 +378,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
<WithDisplayPropertiesHOC
|
<WithDisplayPropertiesHOC
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
displayPropertyKey="sub_issue_count"
|
displayPropertyKey="sub_issue_count"
|
||||||
shouldRenderProperty={!!issue?.sub_issues_count}
|
shouldRenderProperty={(properties) => !!properties.sub_issue_count}
|
||||||
>
|
>
|
||||||
<Tooltip tooltipHeading="Sub-issues" tooltipContent={`${issue.sub_issues_count}`}>
|
<Tooltip tooltipHeading="Sub-issues" tooltipContent={`${issue.sub_issues_count}`}>
|
||||||
<div
|
<div
|
||||||
@ -392,7 +395,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
<WithDisplayPropertiesHOC
|
<WithDisplayPropertiesHOC
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
displayPropertyKey="attachment_count"
|
displayPropertyKey="attachment_count"
|
||||||
shouldRenderProperty={!!issue?.attachment_count}
|
shouldRenderProperty={(properties) => !!properties.attachment_count}
|
||||||
>
|
>
|
||||||
<Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}>
|
<Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}>
|
||||||
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1">
|
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1">
|
||||||
@ -406,7 +409,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
|
|||||||
<WithDisplayPropertiesHOC
|
<WithDisplayPropertiesHOC
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
displayPropertyKey="link"
|
displayPropertyKey="link"
|
||||||
shouldRenderProperty={!!issue?.link_count}
|
shouldRenderProperty={(properties) => !!properties.link}
|
||||||
>
|
>
|
||||||
<Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}>
|
<Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}>
|
||||||
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1">
|
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1">
|
||||||
|
@ -4,16 +4,20 @@ import { IIssueDisplayProperties } from "@plane/types";
|
|||||||
|
|
||||||
interface IWithDisplayPropertiesHOC {
|
interface IWithDisplayPropertiesHOC {
|
||||||
displayProperties: IIssueDisplayProperties;
|
displayProperties: IIssueDisplayProperties;
|
||||||
shouldRenderProperty?: boolean;
|
shouldRenderProperty?: (displayProperties: IIssueDisplayProperties) => boolean;
|
||||||
displayPropertyKey: keyof IIssueDisplayProperties;
|
displayPropertyKey: keyof IIssueDisplayProperties | (keyof IIssueDisplayProperties)[];
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WithDisplayPropertiesHOC = observer(
|
export const WithDisplayPropertiesHOC = observer(
|
||||||
({ displayProperties, shouldRenderProperty = true, displayPropertyKey, children }: IWithDisplayPropertiesHOC) => {
|
({ displayProperties, shouldRenderProperty, displayPropertyKey, children }: IWithDisplayPropertiesHOC) => {
|
||||||
const shouldDisplayPropertyFromFilters = displayProperties[displayPropertyKey];
|
let shouldDisplayPropertyFromFilters = false;
|
||||||
|
if (Array.isArray(displayPropertyKey))
|
||||||
|
shouldDisplayPropertyFromFilters = displayPropertyKey.every((key) => !!displayProperties[key]);
|
||||||
|
else shouldDisplayPropertyFromFilters = !!displayProperties[displayPropertyKey];
|
||||||
|
|
||||||
const renderProperty = shouldDisplayPropertyFromFilters && shouldRenderProperty;
|
const renderProperty =
|
||||||
|
shouldDisplayPropertyFromFilters && (shouldRenderProperty ? shouldRenderProperty(displayProperties) : true);
|
||||||
|
|
||||||
if (!renderProperty) return null;
|
if (!renderProperty) return null;
|
||||||
|
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
|
import differenceInCalendarDays from "date-fns/differenceInCalendarDays";
|
||||||
// components
|
// components
|
||||||
import { DateDropdown } from "components/dropdowns";
|
import { DateDropdown } from "components/dropdowns";
|
||||||
// helpers
|
// helpers
|
||||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||||
// types
|
// types
|
||||||
import { TIssue } from "@plane/types";
|
import { TIssue } from "@plane/types";
|
||||||
|
import { cn } from "helpers/common.helper";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
issue: TIssue;
|
issue: TIssue;
|
||||||
@ -17,6 +19,8 @@ type Props = {
|
|||||||
export const SpreadsheetDueDateColumn: React.FC<Props> = observer((props: Props) => {
|
export const SpreadsheetDueDateColumn: React.FC<Props> = observer((props: Props) => {
|
||||||
const { issue, onChange, disabled, onClose } = props;
|
const { issue, onChange, disabled, onClose } = props;
|
||||||
|
|
||||||
|
const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-11 border-b-[0.5px] border-custom-border-200">
|
<div className="h-11 border-b-[0.5px] border-custom-border-200">
|
||||||
<DateDropdown
|
<DateDropdown
|
||||||
@ -36,8 +40,11 @@ export const SpreadsheetDueDateColumn: React.FC<Props> = observer((props: Props)
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder="Due date"
|
placeholder="Due date"
|
||||||
buttonVariant="transparent-with-text"
|
buttonVariant="transparent-with-text"
|
||||||
buttonClassName="rounded-none text-left"
|
|
||||||
buttonContainerClassName="w-full"
|
buttonContainerClassName="w-full"
|
||||||
|
buttonClassName={cn("rounded-none text-left", {
|
||||||
|
"text-red-500": targetDateDistance <= 0,
|
||||||
|
})}
|
||||||
|
clearIconClassName="!text-custom-text-100"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -34,7 +34,7 @@ export const IssueColumn = observer((props: Props) => {
|
|||||||
<WithDisplayPropertiesHOC
|
<WithDisplayPropertiesHOC
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
displayPropertyKey={property}
|
displayPropertyKey={property}
|
||||||
shouldRenderProperty={shouldRenderProperty}
|
shouldRenderProperty={() => shouldRenderProperty}
|
||||||
>
|
>
|
||||||
<td
|
<td
|
||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
|
@ -25,7 +25,7 @@ export const SpreadsheetHeaderColumn = observer((props: Props) => {
|
|||||||
<WithDisplayPropertiesHOC
|
<WithDisplayPropertiesHOC
|
||||||
displayProperties={displayProperties}
|
displayProperties={displayProperties}
|
||||||
displayPropertyKey={property}
|
displayPropertyKey={property}
|
||||||
shouldRenderProperty={shouldRenderProperty}
|
shouldRenderProperty={() => shouldRenderProperty}
|
||||||
>
|
>
|
||||||
<th
|
<th
|
||||||
className="h-11 w-full min-w-[8rem] items-center bg-custom-background-90 text-sm font-medium px-4 py-1 border border-b-0 border-t-0 border-custom-border-100"
|
className="h-11 w-full min-w-[8rem] items-center bg-custom-background-90 text-sm font-medium px-4 py-1 border border-b-0 border-t-0 border-custom-border-100"
|
||||||
|
@ -494,14 +494,10 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
<div className="h-7">
|
<div className="h-7">
|
||||||
<DateDropdown
|
<DateDropdown
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(date) => {
|
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
|
||||||
onChange(date ? renderFormattedPayloadDate(date) : null);
|
|
||||||
handleFormChange();
|
|
||||||
}}
|
|
||||||
buttonVariant="border-with-text"
|
buttonVariant="border-with-text"
|
||||||
placeholder="Start date"
|
|
||||||
maxDate={maxDate ?? undefined}
|
maxDate={maxDate ?? undefined}
|
||||||
tabIndex={10}
|
placeholder="Start date"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -513,14 +509,10 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
<div className="h-7">
|
<div className="h-7">
|
||||||
<DateDropdown
|
<DateDropdown
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(date) => {
|
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
|
||||||
onChange(date ? renderFormattedPayloadDate(date) : null);
|
|
||||||
handleFormChange();
|
|
||||||
}}
|
|
||||||
buttonVariant="border-with-text"
|
buttonVariant="border-with-text"
|
||||||
placeholder="Due date"
|
|
||||||
minDate={minDate ?? undefined}
|
minDate={minDate ?? undefined}
|
||||||
tabIndex={11}
|
placeholder="Due date"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -539,7 +531,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
}}
|
}}
|
||||||
value={value}
|
value={value}
|
||||||
buttonVariant="border-with-text"
|
buttonVariant="border-with-text"
|
||||||
tabIndex={12}
|
tabIndex={11}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -559,7 +551,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
handleFormChange();
|
handleFormChange();
|
||||||
}}
|
}}
|
||||||
buttonVariant="border-with-text"
|
buttonVariant="border-with-text"
|
||||||
tabIndex={13}
|
tabIndex={12}
|
||||||
multiple
|
multiple
|
||||||
showCount
|
showCount
|
||||||
/>
|
/>
|
||||||
@ -581,7 +573,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
}}
|
}}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
buttonVariant="border-with-text"
|
buttonVariant="border-with-text"
|
||||||
tabIndex={14}
|
tabIndex={13}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -611,7 +603,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
tabIndex={15}
|
tabIndex={14}
|
||||||
>
|
>
|
||||||
{watch("parent_id") ? (
|
{watch("parent_id") ? (
|
||||||
<>
|
<>
|
||||||
@ -661,7 +653,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") onCreateMoreToggleChange(!isCreateMoreToggleEnabled);
|
if (e.key === "Enter") onCreateMoreToggleChange(!isCreateMoreToggleEnabled);
|
||||||
}}
|
}}
|
||||||
tabIndex={16}
|
tabIndex={15}
|
||||||
>
|
>
|
||||||
<div className="flex cursor-pointer items-center justify-center">
|
<div className="flex cursor-pointer items-center justify-center">
|
||||||
<ToggleSwitch value={isCreateMoreToggleEnabled} onChange={() => {}} size="sm" />
|
<ToggleSwitch value={isCreateMoreToggleEnabled} onChange={() => {}} size="sm" />
|
||||||
@ -669,7 +661,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
<span className="text-xs">Create more</span>
|
<span className="text-xs">Create more</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Button variant="neutral-primary" size="sm" onClick={onClose} tabIndex={17}>
|
<Button variant="neutral-primary" size="sm" onClick={onClose} tabIndex={16}>
|
||||||
Discard
|
Discard
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@ -681,7 +673,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
onClick={handleSubmit((data) => handleFormSubmit({ ...data, is_draft: false }))}
|
onClick={handleSubmit((data) => handleFormSubmit({ ...data, is_draft: false }))}
|
||||||
tabIndex={18}
|
tabIndex={17}
|
||||||
>
|
>
|
||||||
{isSubmitting ? "Moving" : "Move from draft"}
|
{isSubmitting ? "Moving" : "Move from draft"}
|
||||||
</Button>
|
</Button>
|
||||||
@ -691,7 +683,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
size="sm"
|
size="sm"
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
onClick={handleSubmit((data) => handleFormSubmit(data, true))}
|
onClick={handleSubmit((data) => handleFormSubmit(data, true))}
|
||||||
tabIndex={18}
|
tabIndex={17}
|
||||||
>
|
>
|
||||||
{isSubmitting ? "Saving" : "Save as draft"}
|
{isSubmitting ? "Saving" : "Save as draft"}
|
||||||
</Button>
|
</Button>
|
||||||
@ -699,7 +691,7 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button variant="primary" type="submit" size="sm" loading={isSubmitting} tabIndex={isDraft ? 19 : 18}>
|
<Button variant="primary" type="submit" size="sm" loading={isSubmitting} tabIndex={isDraft ? 18 : 17}>
|
||||||
{data?.id ? (isSubmitting ? "Updating" : "Update issue") : isSubmitting ? "Creating" : "Create issue"}
|
{data?.id ? (isSubmitting ? "Updating" : "Update issue") : isSubmitting ? "Creating" : "Create issue"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,16 +1,7 @@
|
|||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import {
|
import { differenceInCalendarDays } from "date-fns";
|
||||||
Signal,
|
import { Signal, Tag, Triangle, LayoutPanelTop, CircleDot, CopyPlus, XCircle, CalendarDays } from "lucide-react";
|
||||||
Tag,
|
|
||||||
Triangle,
|
|
||||||
LayoutPanelTop,
|
|
||||||
CircleDot,
|
|
||||||
CopyPlus,
|
|
||||||
XCircle,
|
|
||||||
CalendarClock,
|
|
||||||
CalendarCheck2,
|
|
||||||
} from "lucide-react";
|
|
||||||
// hooks
|
// hooks
|
||||||
import { useIssueDetail, useProject } from "hooks/store";
|
import { useIssueDetail, useProject } from "hooks/store";
|
||||||
// ui icons
|
// ui icons
|
||||||
@ -33,6 +24,8 @@ import {
|
|||||||
} from "components/dropdowns";
|
} from "components/dropdowns";
|
||||||
// components
|
// components
|
||||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||||
|
// helpers
|
||||||
|
import { cn } from "helpers/common.helper";
|
||||||
|
|
||||||
interface IPeekOverviewProperties {
|
interface IPeekOverviewProperties {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@ -61,6 +54,8 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
const maxDate = issue.target_date ? new Date(issue.target_date) : null;
|
||||||
maxDate?.setDate(maxDate.getDate());
|
maxDate?.setDate(maxDate.getDate());
|
||||||
|
|
||||||
|
const targetDateDistance = issue.target_date ? differenceInCalendarDays(new Date(issue.target_date), new Date()) : 1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<h6 className="text-sm font-medium">Properties</h6>
|
<h6 className="text-sm font-medium">Properties</h6>
|
||||||
@ -129,7 +124,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
{/* start date */}
|
{/* start date */}
|
||||||
<div className="flex w-full items-center gap-3 h-8">
|
<div className="flex w-full items-center gap-3 h-8">
|
||||||
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||||
<CalendarClock className="h-4 w-4 flex-shrink-0" />
|
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
||||||
<span>Start date</span>
|
<span>Start date</span>
|
||||||
</div>
|
</div>
|
||||||
<DateDropdown
|
<DateDropdown
|
||||||
@ -156,7 +151,7 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
{/* due date */}
|
{/* due date */}
|
||||||
<div className="flex w-full items-center gap-3 h-8">
|
<div className="flex w-full items-center gap-3 h-8">
|
||||||
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
<div className="flex items-center gap-1 w-1/4 flex-shrink-0 text-sm text-custom-text-300">
|
||||||
<CalendarCheck2 className="h-4 w-4 flex-shrink-0" />
|
<CalendarDays className="h-4 w-4 flex-shrink-0" />
|
||||||
<span>Due date</span>
|
<span>Due date</span>
|
||||||
</div>
|
</div>
|
||||||
<DateDropdown
|
<DateDropdown
|
||||||
@ -172,9 +167,12 @@ export const PeekOverviewProperties: FC<IPeekOverviewProperties> = observer((pro
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className="w-3/4 flex-grow group"
|
className="w-3/4 flex-grow group"
|
||||||
buttonContainerClassName="w-full text-left"
|
buttonContainerClassName="w-full text-left"
|
||||||
buttonClassName={`text-sm ${issue?.target_date ? "" : "text-custom-text-400"}`}
|
buttonClassName={cn("text-sm", {
|
||||||
|
"text-custom-text-400": !issue.target_date,
|
||||||
|
"text-red-500": targetDateDistance <= 0,
|
||||||
|
})}
|
||||||
hideIcon
|
hideIcon
|
||||||
clearIconClassName="h-3 w-3 hidden group-hover:inline"
|
clearIconClassName="h-3 w-3 hidden group-hover:inline !text-custom-text-100"
|
||||||
// TODO: add this logic
|
// TODO: add this logic
|
||||||
// showPlaceholderIcon
|
// showPlaceholderIcon
|
||||||
/>
|
/>
|
||||||
|
@ -2,7 +2,7 @@ import { useEffect } from "react";
|
|||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
// components
|
// components
|
||||||
import { ModuleStatusSelect } from "components/modules";
|
import { ModuleStatusSelect } from "components/modules";
|
||||||
import { DateDropdown, ProjectDropdown, ProjectMemberDropdown } from "components/dropdowns";
|
import { DateRangeDropdown, ProjectDropdown, ProjectMemberDropdown } from "components/dropdowns";
|
||||||
// ui
|
// ui
|
||||||
import { Button, Input, TextArea } from "@plane/ui";
|
import { Button, Input, TextArea } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
@ -27,18 +27,12 @@ const defaultValues: Partial<IModule> = {
|
|||||||
member_ids: [],
|
member_ids: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ModuleForm: React.FC<Props> = ({
|
export const ModuleForm: React.FC<Props> = (props) => {
|
||||||
handleFormSubmit,
|
const { handleFormSubmit, handleClose, status, projectId, setActiveProject, data } = props;
|
||||||
handleClose,
|
// form info
|
||||||
status,
|
|
||||||
projectId,
|
|
||||||
setActiveProject,
|
|
||||||
data,
|
|
||||||
}) => {
|
|
||||||
const {
|
const {
|
||||||
formState: { errors, isSubmitting, dirtyFields },
|
formState: { errors, isSubmitting, dirtyFields },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
watch,
|
|
||||||
control,
|
control,
|
||||||
reset,
|
reset,
|
||||||
} = useForm<IModule>({
|
} = useForm<IModule>({
|
||||||
@ -67,15 +61,6 @@ export const ModuleForm: React.FC<Props> = ({
|
|||||||
});
|
});
|
||||||
}, [data, reset]);
|
}, [data, reset]);
|
||||||
|
|
||||||
const startDate = watch("start_date");
|
|
||||||
const targetDate = watch("target_date");
|
|
||||||
|
|
||||||
const minDate = startDate ? new Date(startDate) : null;
|
|
||||||
minDate?.setDate(minDate.getDate());
|
|
||||||
|
|
||||||
const maxDate = targetDate ? new Date(targetDate) : null;
|
|
||||||
maxDate?.setDate(maxDate.getDate());
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit(handleCreateUpdateModule)}>
|
<form onSubmit={handleSubmit(handleCreateUpdateModule)}>
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
@ -152,36 +137,39 @@ export const ModuleForm: React.FC<Props> = ({
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="start_date"
|
name="start_date"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value: startDateValue, onChange: onChangeStartDate } }) => (
|
||||||
<div className="h-7">
|
|
||||||
<DateDropdown
|
|
||||||
value={value}
|
|
||||||
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
|
|
||||||
buttonVariant="border-with-text"
|
|
||||||
placeholder="Start date"
|
|
||||||
maxDate={maxDate ?? undefined}
|
|
||||||
tabIndex={3}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="target_date"
|
name="target_date"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value: endDateValue, onChange: onChangeEndDate } }) => (
|
||||||
<div className="h-7">
|
<DateRangeDropdown
|
||||||
<DateDropdown
|
|
||||||
value={value}
|
|
||||||
onChange={(date) => onChange(date ? renderFormattedPayloadDate(date) : null)}
|
|
||||||
buttonVariant="border-with-text"
|
buttonVariant="border-with-text"
|
||||||
placeholder="Target date"
|
className="h-7"
|
||||||
minDate={minDate ?? undefined}
|
minDate={new Date()}
|
||||||
tabIndex={4}
|
value={{
|
||||||
|
from: startDateValue ? new Date(startDateValue) : undefined,
|
||||||
|
to: endDateValue ? new Date(endDateValue) : undefined,
|
||||||
|
}}
|
||||||
|
onSelect={(val) => {
|
||||||
|
onChangeStartDate(val?.from ? renderFormattedPayloadDate(val.from) : null);
|
||||||
|
onChangeEndDate(val?.to ? renderFormattedPayloadDate(val.to) : null);
|
||||||
|
}}
|
||||||
|
placeholder={{
|
||||||
|
from: "Start date",
|
||||||
|
to: "End date",
|
||||||
|
}}
|
||||||
|
hideIcon={{
|
||||||
|
to: true,
|
||||||
|
}}
|
||||||
|
tabIndex={3}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<ModuleStatusSelect control={control} error={errors.status} tabIndex={5} />
|
)}
|
||||||
|
/>
|
||||||
|
<div className="h-7">
|
||||||
|
<ModuleStatusSelect control={control} error={errors.status} tabIndex={4} />
|
||||||
|
</div>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="lead_id"
|
name="lead_id"
|
||||||
@ -194,7 +182,7 @@ export const ModuleForm: React.FC<Props> = ({
|
|||||||
multiple={false}
|
multiple={false}
|
||||||
buttonVariant="border-with-text"
|
buttonVariant="border-with-text"
|
||||||
placeholder="Lead"
|
placeholder="Lead"
|
||||||
tabIndex={6}
|
tabIndex={5}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -212,7 +200,7 @@ export const ModuleForm: React.FC<Props> = ({
|
|||||||
buttonVariant={value && value.length > 0 ? "transparent-without-text" : "border-with-text"}
|
buttonVariant={value && value.length > 0 ? "transparent-without-text" : "border-with-text"}
|
||||||
buttonClassName={value && value.length > 0 ? "hover:bg-transparent px-0" : ""}
|
buttonClassName={value && value.length > 0 ? "hover:bg-transparent px-0" : ""}
|
||||||
placeholder="Members"
|
placeholder="Members"
|
||||||
tabIndex={7}
|
tabIndex={6}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -221,10 +209,10 @@ export const ModuleForm: React.FC<Props> = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200 pt-5">
|
<div className="mt-5 flex items-center justify-end gap-2 border-t-[0.5px] border-custom-border-200 pt-5">
|
||||||
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={8}>
|
<Button variant="neutral-primary" size="sm" onClick={handleClose} tabIndex={7}>
|
||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={9}>
|
<Button variant="primary" size="sm" type="submit" loading={isSubmitting} tabIndex={8}>
|
||||||
{status ? (isSubmitting ? "Updating" : "Update module") : isSubmitting ? "Creating" : "Create module"}
|
{status ? (isSubmitting ? "Updating" : "Update module") : isSubmitting ? "Creating" : "Create module"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { observer } from "mobx-react-lite";
|
import { observer } from "mobx-react-lite";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm } from "react-hook-form";
|
||||||
import { Disclosure, Popover, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
CalendarCheck2,
|
|
||||||
CalendarClock,
|
CalendarClock,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
ChevronRight,
|
ChevronRight,
|
||||||
@ -22,12 +21,11 @@ import useToast from "hooks/use-toast";
|
|||||||
import { LinkModal, LinksList, SidebarProgressStats } from "components/core";
|
import { LinkModal, LinksList, SidebarProgressStats } from "components/core";
|
||||||
import { DeleteModuleModal } from "components/modules";
|
import { DeleteModuleModal } from "components/modules";
|
||||||
import ProgressChart from "components/core/sidebar/progress-chart";
|
import ProgressChart from "components/core/sidebar/progress-chart";
|
||||||
import { ProjectMemberDropdown } from "components/dropdowns";
|
import { DateRangeDropdown, ProjectMemberDropdown } from "components/dropdowns";
|
||||||
// ui
|
// ui
|
||||||
import { CustomRangeDatePicker } from "components/ui";
|
|
||||||
import { CustomMenu, Loader, LayersIcon, CustomSelect, ModuleStatusIcon, UserGroupIcon } from "@plane/ui";
|
import { CustomMenu, Loader, LayersIcon, CustomSelect, ModuleStatusIcon, UserGroupIcon } from "@plane/ui";
|
||||||
// helpers
|
// helpers
|
||||||
import { isDateGreaterThanToday, renderFormattedPayloadDate, renderFormattedDate } from "helpers/date-time.helper";
|
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
||||||
import { copyUrlToClipboard } from "helpers/string.helper";
|
import { copyUrlToClipboard } from "helpers/string.helper";
|
||||||
// types
|
// types
|
||||||
import { ILinkDetails, IModule, ModuleLink } from "@plane/types";
|
import { ILinkDetails, IModule, ModuleLink } from "@plane/types";
|
||||||
@ -56,9 +54,6 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
|
const [moduleDeleteModal, setModuleDeleteModal] = useState(false);
|
||||||
const [moduleLinkModal, setModuleLinkModal] = useState(false);
|
const [moduleLinkModal, setModuleLinkModal] = useState(false);
|
||||||
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
|
const [selectedLinkToUpdate, setSelectedLinkToUpdate] = useState<ILinkDetails | null>(null);
|
||||||
// refs
|
|
||||||
const startDateButtonRef = useRef<HTMLButtonElement | null>(null);
|
|
||||||
const endDateButtonRef = useRef<HTMLButtonElement | null>(null);
|
|
||||||
// router
|
// router
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { workspaceSlug, projectId, peekModule } = router.query;
|
const { workspaceSlug, projectId, peekModule } = router.query;
|
||||||
@ -72,7 +67,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const { setToastAlert } = useToast();
|
const { setToastAlert } = useToast();
|
||||||
|
|
||||||
const { setValue, watch, reset, control } = useForm({
|
const { reset, control } = useForm({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -85,7 +80,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
payload: { ...res, changed_properties: Object.keys(data)[0], element: "Right side-peek", state: "SUCCESS" },
|
payload: { ...res, changed_properties: Object.keys(data)[0], element: "Right side-peek", state: "SUCCESS" },
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch((_) => {
|
.catch(() => {
|
||||||
captureModuleEvent({
|
captureModuleEvent({
|
||||||
eventName: MODULE_UPDATED,
|
eventName: MODULE_UPDATED,
|
||||||
payload: { ...data, state: "FAILED" },
|
payload: { ...data, state: "FAILED" },
|
||||||
@ -187,40 +182,16 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStartDateChange = async (date: string) => {
|
const handleDateChange = async (startDate: Date | undefined, targetDate: Date | undefined) => {
|
||||||
setValue("start_date", date);
|
|
||||||
|
|
||||||
if (!watch("target_date") || watch("target_date") === "") endDateButtonRef.current?.click();
|
|
||||||
|
|
||||||
if (watch("start_date") && watch("target_date") && watch("start_date") !== "" && watch("start_date") !== "") {
|
|
||||||
submitChanges({
|
submitChanges({
|
||||||
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
start_date: startDate ? renderFormattedPayloadDate(startDate) : null,
|
||||||
target_date: renderFormattedPayloadDate(`${watch("target_date")}`),
|
target_date: targetDate ? renderFormattedPayloadDate(targetDate) : null,
|
||||||
});
|
});
|
||||||
setToastAlert({
|
setToastAlert({
|
||||||
type: "success",
|
type: "success",
|
||||||
title: "Success!",
|
title: "Success!",
|
||||||
message: "Module updated successfully.",
|
message: "Module updated successfully.",
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleEndDateChange = async (date: string) => {
|
|
||||||
setValue("target_date", date);
|
|
||||||
|
|
||||||
if (!watch("start_date") || watch("start_date") === "") endDateButtonRef.current?.click();
|
|
||||||
|
|
||||||
if (watch("start_date") && watch("target_date") && watch("start_date") !== "" && watch("start_date") !== "") {
|
|
||||||
submitChanges({
|
|
||||||
target_date: renderFormattedPayloadDate(`${watch("target_date")}`),
|
|
||||||
start_date: renderFormattedPayloadDate(`${watch("start_date")}`),
|
|
||||||
});
|
|
||||||
setToastAlert({
|
|
||||||
type: "success",
|
|
||||||
title: "Success!",
|
|
||||||
message: "Module updated successfully.",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -257,9 +228,6 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
</Loader>
|
</Loader>
|
||||||
);
|
);
|
||||||
|
|
||||||
const startDate = new Date(watch("start_date") ?? moduleDetails.start_date ?? "");
|
|
||||||
const endDate = new Date(watch("target_date") ?? moduleDetails.target_date ?? "");
|
|
||||||
|
|
||||||
const moduleStatus = MODULE_STATUS.find((status) => status.value === moduleDetails.status);
|
const moduleStatus = MODULE_STATUS.find((status) => status.value === moduleDetails.status);
|
||||||
|
|
||||||
const issueCount =
|
const issueCount =
|
||||||
@ -319,7 +287,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="status"
|
name="status"
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value } }) => (
|
||||||
<CustomSelect
|
<CustomSelect
|
||||||
customButton={
|
customButton={
|
||||||
<span
|
<span
|
||||||
@ -361,118 +329,42 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col gap-5 pb-6 pt-2.5">
|
|
||||||
<div className="flex items-center justify-start gap-1">
|
<div className="flex items-center justify-start gap-1">
|
||||||
<div className="flex w-1/2 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">
|
||||||
<CalendarClock className="h-4 w-4" />
|
<CalendarClock className="h-4 w-4" />
|
||||||
|
<span className="text-base">Date range</span>
|
||||||
<span className="text-base">Start date</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex w-1/2 items-center rounded-sm">
|
<div className="w-3/5 h-7">
|
||||||
<Popover className="flex h-full w-full items-center justify-center rounded-lg">
|
<Controller
|
||||||
{({ close }) => (
|
control={control}
|
||||||
<>
|
name="start_date"
|
||||||
<Popover.Button
|
render={({ field: { value: startDateValue, onChange: onChangeStartDate } }) => (
|
||||||
ref={startDateButtonRef}
|
<Controller
|
||||||
className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${
|
control={control}
|
||||||
isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
|
name="target_date"
|
||||||
}`}
|
render={({ field: { value: endDateValue, onChange: onChangeEndDate } }) => (
|
||||||
disabled={!isEditingAllowed}
|
<DateRangeDropdown
|
||||||
>
|
buttonContainerClassName="w-full"
|
||||||
<span
|
buttonVariant="background-with-text"
|
||||||
className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${
|
minDate={new Date()}
|
||||||
watch("start_date") ? "" : "text-custom-text-400"
|
value={{
|
||||||
}`}
|
from: startDateValue ? new Date(startDateValue) : undefined,
|
||||||
>
|
to: endDateValue ? new Date(endDateValue) : undefined,
|
||||||
{renderFormattedDate(startDate) ?? "No date selected"}
|
|
||||||
</span>
|
|
||||||
</Popover.Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
as={React.Fragment}
|
|
||||||
enter="transition ease-out duration-200"
|
|
||||||
enterFrom="opacity-0 translate-y-1"
|
|
||||||
enterTo="opacity-100 translate-y-0"
|
|
||||||
leave="transition ease-in duration-150"
|
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
|
||||||
leaveTo="opacity-0 translate-y-1"
|
|
||||||
>
|
|
||||||
<Popover.Panel className="absolute right-0 top-10 z-20 transform overflow-hidden">
|
|
||||||
<CustomRangeDatePicker
|
|
||||||
value={watch("start_date") ? watch("start_date") : moduleDetails?.start_date}
|
|
||||||
onChange={(val) => {
|
|
||||||
if (val) {
|
|
||||||
handleStartDateChange(val);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
startDate={watch("start_date") ?? watch("target_date") ?? null}
|
onSelect={(val) => {
|
||||||
endDate={watch("target_date") ?? watch("start_date") ?? null}
|
onChangeStartDate(val?.from ? renderFormattedPayloadDate(val.from) : null);
|
||||||
maxDate={new Date(`${watch("target_date")}`)}
|
onChangeEndDate(val?.to ? renderFormattedPayloadDate(val.to) : null);
|
||||||
selectsStart={watch("target_date") ? true : false}
|
handleDateChange(val?.from, val?.to);
|
||||||
/>
|
}}
|
||||||
</Popover.Panel>
|
placeholder={{
|
||||||
</Transition>
|
from: "Start date",
|
||||||
</>
|
to: "Target date",
|
||||||
)}
|
|
||||||
</Popover>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-start gap-1">
|
|
||||||
<div className="flex w-1/2 items-center justify-start gap-2 text-custom-text-300">
|
|
||||||
<CalendarCheck2 className="h-4 w-4" />
|
|
||||||
<span className="text-base">Target date</span>
|
|
||||||
</div>
|
|
||||||
<div className="relative flex w-1/2 items-center rounded-sm">
|
|
||||||
<Popover className="flex h-full w-full items-center justify-center rounded-lg">
|
|
||||||
{({ close }) => (
|
|
||||||
<>
|
|
||||||
<Popover.Button
|
|
||||||
ref={endDateButtonRef}
|
|
||||||
className={`w-full cursor-pointer rounded-sm text-sm font-medium text-custom-text-300 hover:bg-custom-background-80 ${
|
|
||||||
isEditingAllowed ? "cursor-pointer" : "cursor-not-allowed"
|
|
||||||
}`}
|
|
||||||
disabled={!isEditingAllowed}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={`group flex w-full items-center justify-between gap-2 px-1.5 py-1 text-sm ${
|
|
||||||
watch("target_date") ? "" : "text-custom-text-400"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{renderFormattedDate(endDate) ?? "No date selected"}
|
|
||||||
</span>
|
|
||||||
</Popover.Button>
|
|
||||||
|
|
||||||
<Transition
|
|
||||||
as={React.Fragment}
|
|
||||||
enter="transition ease-out duration-200"
|
|
||||||
enterFrom="opacity-0 translate-y-1"
|
|
||||||
enterTo="opacity-100 translate-y-0"
|
|
||||||
leave="transition ease-in duration-150"
|
|
||||||
leaveFrom="opacity-100 translate-y-0"
|
|
||||||
leaveTo="opacity-0 translate-y-1"
|
|
||||||
>
|
|
||||||
<Popover.Panel className="absolute right-0 top-10 z-20 transform overflow-hidden">
|
|
||||||
<CustomRangeDatePicker
|
|
||||||
value={watch("target_date") ? watch("target_date") : moduleDetails?.target_date}
|
|
||||||
onChange={(val) => {
|
|
||||||
if (val) {
|
|
||||||
handleEndDateChange(val);
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
startDate={watch("start_date") ?? watch("target_date") ?? null}
|
|
||||||
endDate={watch("target_date") ?? watch("start_date") ?? null}
|
|
||||||
minDate={new Date(`${watch("start_date")}`)}
|
|
||||||
selectsEnd={watch("start_date") ? true : false}
|
|
||||||
/>
|
/>
|
||||||
</Popover.Panel>
|
|
||||||
</Transition>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</Popover>
|
/>
|
||||||
</div>
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -484,7 +376,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
<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 items-center justify-start gap-1">
|
||||||
<div className="flex w-1/2 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">
|
||||||
<UserCircle2 className="h-4 w-4" />
|
<UserCircle2 className="h-4 w-4" />
|
||||||
<span className="text-base">Lead</span>
|
<span className="text-base">Lead</span>
|
||||||
</div>
|
</div>
|
||||||
@ -492,7 +384,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
name="lead_id"
|
name="lead_id"
|
||||||
render={({ field: { value } }) => (
|
render={({ field: { value } }) => (
|
||||||
<div className="w-1/2">
|
<div className="w-3/5 h-7">
|
||||||
<ProjectMemberDropdown
|
<ProjectMemberDropdown
|
||||||
value={value ?? null}
|
value={value ?? null}
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
@ -508,7 +400,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-start gap-1">
|
<div className="flex items-center justify-start gap-1">
|
||||||
<div className="flex w-1/2 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">
|
||||||
<UserGroupIcon className="h-4 w-4" />
|
<UserGroupIcon className="h-4 w-4" />
|
||||||
<span className="text-base">Members</span>
|
<span className="text-base">Members</span>
|
||||||
</div>
|
</div>
|
||||||
@ -516,7 +408,7 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
name="member_ids"
|
name="member_ids"
|
||||||
render={({ field: { value } }) => (
|
render={({ field: { value } }) => (
|
||||||
<div className="w-1/2">
|
<div className="w-3/5 h-7">
|
||||||
<ProjectMemberDropdown
|
<ProjectMemberDropdown
|
||||||
value={value ?? []}
|
value={value ?? []}
|
||||||
onChange={(val: string[]) => {
|
onChange={(val: string[]) => {
|
||||||
@ -532,13 +424,12 @@ export const ModuleDetailsSidebar: React.FC<Props> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center justify-start gap-1">
|
<div className="flex items-center justify-start gap-1">
|
||||||
<div className="flex w-1/2 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">
|
||||||
<LayersIcon className="h-4 w-4" />
|
<LayersIcon className="h-4 w-4" />
|
||||||
<span className="text-base">Issues</span>
|
<span className="text-base">Issues</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex w-1/2 items-center">
|
<div className="h-7 w-3/5 flex items-center">
|
||||||
<span className="px-1.5 text-sm text-custom-text-300">{issueCount}</span>
|
<span className="px-1.5 text-sm text-custom-text-300">{issueCount}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Fragment, FC } from "react";
|
import { Fragment, FC } from "react";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useForm, Controller } from "react-hook-form";
|
import { useForm, Controller } from "react-hook-form";
|
||||||
|
import { DateDropdown } from "components/dropdowns";
|
||||||
import { Transition, Dialog } from "@headlessui/react";
|
import { Transition, Dialog } from "@headlessui/react";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
// constants
|
// constants
|
||||||
@ -9,7 +10,6 @@ import { allTimeIn30MinutesInterval12HoursFormat } from "constants/notification"
|
|||||||
import useToast from "hooks/use-toast";
|
import useToast from "hooks/use-toast";
|
||||||
// ui
|
// ui
|
||||||
import { Button, CustomSelect } from "@plane/ui";
|
import { Button, CustomSelect } from "@plane/ui";
|
||||||
import { CustomDatePicker } from "components/ui";
|
|
||||||
// types
|
// types
|
||||||
import type { IUserNotification } from "@plane/types";
|
import type { IUserNotification } from "@plane/types";
|
||||||
|
|
||||||
@ -169,17 +169,18 @@ export const SnoozeNotificationModal: FC<SnoozeModalProps> = (props) => {
|
|||||||
control={control}
|
control={control}
|
||||||
rules={{ required: "Please select a date" }}
|
rules={{ required: "Please select a date" }}
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<CustomDatePicker
|
<DateDropdown
|
||||||
placeholder="Select date"
|
|
||||||
value={value}
|
value={value}
|
||||||
|
placeholder="Select date"
|
||||||
onChange={(val) => {
|
onChange={(val) => {
|
||||||
setValue("time", null);
|
setValue("time", null);
|
||||||
onChange(val);
|
onChange(val);
|
||||||
}}
|
}}
|
||||||
className="w-full rounded-md border border-custom-border-300 bg-custom-background-100 px-3 py-2 !text-sm text-custom-text-100 placeholder:!text-custom-text-400 focus:outline-none"
|
|
||||||
wrapperClassName="w-full"
|
|
||||||
noBorder
|
|
||||||
minDate={new Date()}
|
minDate={new Date()}
|
||||||
|
buttonVariant="border-with-text"
|
||||||
|
buttonContainerClassName="w-full text-left"
|
||||||
|
buttonClassName="border-custom-border-300 px-3 py-2.5"
|
||||||
|
hideIcon
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@ -213,8 +214,7 @@ export const SnoozeNotificationModal: FC<SnoozeModalProps> = (props) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setValue("period", "AM");
|
setValue("period", "AM");
|
||||||
}}
|
}}
|
||||||
className={`flex h-full w-1/2 cursor-pointer items-center justify-center text-center ${
|
className={`flex h-full w-1/2 cursor-pointer items-center justify-center text-center ${watch("period") === "AM"
|
||||||
watch("period") === "AM"
|
|
||||||
? "bg-custom-primary-100/90 text-custom-primary-0"
|
? "bg-custom-primary-100/90 text-custom-primary-0"
|
||||||
: "bg-custom-background-80"
|
: "bg-custom-background-80"
|
||||||
}`}
|
}`}
|
||||||
@ -225,8 +225,7 @@ export const SnoozeNotificationModal: FC<SnoozeModalProps> = (props) => {
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
setValue("period", "PM");
|
setValue("period", "PM");
|
||||||
}}
|
}}
|
||||||
className={`flex h-full w-1/2 cursor-pointer items-center justify-center text-center ${
|
className={`flex h-full w-1/2 cursor-pointer items-center justify-center text-center ${watch("period") === "PM"
|
||||||
watch("period") === "PM"
|
|
||||||
? "bg-custom-primary-100/90 text-custom-primary-0"
|
? "bg-custom-primary-100/90 text-custom-primary-0"
|
||||||
: "bg-custom-background-80"
|
: "bg-custom-background-80"
|
||||||
}`}
|
}`}
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
// react-datepicker
|
|
||||||
import DatePicker from "react-datepicker";
|
|
||||||
import "react-datepicker/dist/react-datepicker.css";
|
|
||||||
// helpers
|
|
||||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
renderAs?: "input" | "button";
|
|
||||||
value: Date | string | null | undefined;
|
|
||||||
onChange: (val: string | null) => void;
|
|
||||||
handleOnOpen?: () => void;
|
|
||||||
handleOnClose?: () => void;
|
|
||||||
customInput?: React.ReactNode;
|
|
||||||
placeholder?: string;
|
|
||||||
displayShortForm?: boolean;
|
|
||||||
error?: boolean;
|
|
||||||
noBorder?: boolean;
|
|
||||||
wrapperClassName?: string;
|
|
||||||
className?: string;
|
|
||||||
isClearable?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
maxDate?: Date;
|
|
||||||
minDate?: Date;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CustomDatePicker: React.FC<Props> = ({
|
|
||||||
renderAs = "button",
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
handleOnOpen,
|
|
||||||
handleOnClose,
|
|
||||||
placeholder = "Select date",
|
|
||||||
error = false,
|
|
||||||
noBorder = false,
|
|
||||||
wrapperClassName = "",
|
|
||||||
className = "",
|
|
||||||
isClearable = true,
|
|
||||||
disabled = false,
|
|
||||||
customInput,
|
|
||||||
maxDate,
|
|
||||||
minDate,
|
|
||||||
}) => (
|
|
||||||
<DatePicker
|
|
||||||
placeholderText={placeholder}
|
|
||||||
selected={value ? new Date(value) : null}
|
|
||||||
onChange={(val) => {
|
|
||||||
if (!val) onChange(null);
|
|
||||||
else onChange(renderFormattedPayloadDate(val));
|
|
||||||
}}
|
|
||||||
onCalendarOpen={handleOnOpen}
|
|
||||||
onCalendarClose={handleOnClose}
|
|
||||||
wrapperClassName={wrapperClassName}
|
|
||||||
customInput={customInput}
|
|
||||||
className={`${
|
|
||||||
renderAs === "input"
|
|
||||||
? "block px-2 py-2 text-sm focus:outline-none"
|
|
||||||
: renderAs === "button"
|
|
||||||
? `px-2 py-1 text-xs shadow-sm ${disabled ? "" : "hover:bg-custom-background-80"} duration-300`
|
|
||||||
: ""
|
|
||||||
} ${error ? "border-red-500 bg-red-100" : ""} ${disabled ? "cursor-not-allowed" : "cursor-pointer"} ${
|
|
||||||
noBorder ? "" : "border border-custom-border-200"
|
|
||||||
} w-full rounded-md caret-transparent outline-none ${className}`}
|
|
||||||
dateFormat="MMM dd, yyyy"
|
|
||||||
isClearable={Boolean(isClearable && !disabled)}
|
|
||||||
disabled={disabled}
|
|
||||||
maxDate={maxDate}
|
|
||||||
minDate={minDate}
|
|
||||||
/>
|
|
||||||
);
|
|
@ -1,10 +1,8 @@
|
|||||||
export * from "./graphs";
|
export * from "./graphs";
|
||||||
export * from "./datepicker";
|
|
||||||
export * from "./empty-space";
|
export * from "./empty-space";
|
||||||
export * from "./labels-list";
|
export * from "./labels-list";
|
||||||
export * from "./multi-level-dropdown";
|
export * from "./multi-level-dropdown";
|
||||||
export * from "./markdown-to-component";
|
export * from "./markdown-to-component";
|
||||||
export * from "./integration-and-import-export-banner";
|
export * from "./integration-and-import-export-banner";
|
||||||
export * from "./range-datepicker";
|
|
||||||
export * from "./profile-empty-state";
|
export * from "./profile-empty-state";
|
||||||
export * from "./loader";
|
export * from "./loader";
|
||||||
|
@ -1,65 +0,0 @@
|
|||||||
// react-datepicker
|
|
||||||
import DatePicker from "react-datepicker";
|
|
||||||
import "react-datepicker/dist/react-datepicker.css";
|
|
||||||
// helpers
|
|
||||||
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
renderAs?: "input" | "button";
|
|
||||||
value: Date | string | null | undefined;
|
|
||||||
onChange: (val: string | null) => void;
|
|
||||||
error?: boolean;
|
|
||||||
className?: string;
|
|
||||||
isClearable?: boolean;
|
|
||||||
disabled?: boolean;
|
|
||||||
startDate: string | null;
|
|
||||||
endDate: string | null;
|
|
||||||
selectsStart?: boolean;
|
|
||||||
selectsEnd?: boolean;
|
|
||||||
minDate?: Date | null | undefined;
|
|
||||||
maxDate?: Date | null | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CustomRangeDatePicker: React.FC<Props> = ({
|
|
||||||
renderAs = "button",
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
error = false,
|
|
||||||
className = "",
|
|
||||||
disabled = false,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
selectsStart = false,
|
|
||||||
selectsEnd = false,
|
|
||||||
minDate = null,
|
|
||||||
maxDate = null,
|
|
||||||
}) => (
|
|
||||||
<DatePicker
|
|
||||||
selected={value ? new Date(value) : null}
|
|
||||||
onChange={(val) => {
|
|
||||||
if (!val) onChange(null);
|
|
||||||
else onChange(renderFormattedPayloadDate(val));
|
|
||||||
}}
|
|
||||||
className={`${
|
|
||||||
renderAs === "input"
|
|
||||||
? "block px-3 py-2 text-sm focus:outline-none"
|
|
||||||
: renderAs === "button"
|
|
||||||
? `px-3 py-1 text-xs shadow-sm ${
|
|
||||||
disabled ? "" : "hover:bg-custom-background-80"
|
|
||||||
} duration-300 focus:border-custom-primary focus:outline-none focus:ring-1 focus:ring-custom-primary`
|
|
||||||
: ""
|
|
||||||
} ${error ? "border-red-500 bg-red-100" : ""} ${
|
|
||||||
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
|
||||||
} w-full rounded-md border border-custom-border-200 bg-transparent caret-transparent ${className}`}
|
|
||||||
dateFormat="dd-MM-yyyy"
|
|
||||||
disabled={disabled}
|
|
||||||
selectsStart={selectsStart}
|
|
||||||
selectsEnd={selectsEnd}
|
|
||||||
startDate={startDate ? new Date(startDate) : new Date()}
|
|
||||||
endDate={endDate ? new Date(endDate) : new Date()}
|
|
||||||
minDate={minDate}
|
|
||||||
maxDate={maxDate}
|
|
||||||
shouldCloseOnSelect
|
|
||||||
inline
|
|
||||||
/>
|
|
||||||
);
|
|
@ -106,14 +106,14 @@ export const ISSUE_DISPLAY_PROPERTIES: {
|
|||||||
title: string;
|
title: string;
|
||||||
}[] = [
|
}[] = [
|
||||||
{ key: "assignee", title: "Assignee" },
|
{ key: "assignee", title: "Assignee" },
|
||||||
{ key: "start_date", title: "Start Date" },
|
{ key: "start_date", title: "Start date" },
|
||||||
{ key: "due_date", title: "Due Date" },
|
{ key: "due_date", title: "Due date" },
|
||||||
{ key: "key", title: "ID" },
|
{ key: "key", title: "ID" },
|
||||||
{ key: "labels", title: "Labels" },
|
{ key: "labels", title: "Labels" },
|
||||||
{ key: "priority", title: "Priority" },
|
{ key: "priority", title: "Priority" },
|
||||||
{ key: "state", title: "State" },
|
{ key: "state", title: "State" },
|
||||||
{ key: "sub_issue_count", title: "Sub Issue Count" },
|
{ key: "sub_issue_count", title: "Sub issue count" },
|
||||||
{ key: "attachment_count", title: "Attachment Count" },
|
{ key: "attachment_count", title: "Attachment count" },
|
||||||
{ key: "link", title: "Link" },
|
{ key: "link", title: "Link" },
|
||||||
{ key: "estimate", title: "Estimate" },
|
{ key: "estimate", title: "Estimate" },
|
||||||
{ key: "modules", title: "Modules" },
|
{ key: "modules", title: "Modules" },
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { IIssueDisplayProperties, TIssue, TIssueOrderByOptions } from "@plane/types";
|
import { IIssueDisplayProperties, TIssue, TIssueOrderByOptions } from "@plane/types";
|
||||||
import { LayersIcon, DoubleCircleIcon, UserGroupIcon, DiceIcon, ContrastIcon } from "@plane/ui";
|
import { LayersIcon, DoubleCircleIcon, UserGroupIcon, DiceIcon, ContrastIcon } from "@plane/ui";
|
||||||
import { CalendarDays, Link2, Signal, Tag, Triangle, Paperclip, CalendarClock, CalendarCheck } from "lucide-react";
|
import { CalendarDays, Link2, Signal, Tag, Triangle, Paperclip } from "lucide-react";
|
||||||
import { FC } from "react";
|
import { FC } from "react";
|
||||||
import { ISvgIcons } from "@plane/ui/src/icons/type";
|
import { ISvgIcons } from "@plane/ui/src/icons/type";
|
||||||
import {
|
import {
|
||||||
@ -60,7 +60,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: {
|
|||||||
ascendingOrderTitle: "New",
|
ascendingOrderTitle: "New",
|
||||||
descendingOrderKey: "target_date",
|
descendingOrderKey: "target_date",
|
||||||
descendingOrderTitle: "Old",
|
descendingOrderTitle: "Old",
|
||||||
icon: CalendarCheck,
|
icon: CalendarDays,
|
||||||
Column: SpreadsheetDueDateColumn,
|
Column: SpreadsheetDueDateColumn,
|
||||||
},
|
},
|
||||||
estimate: {
|
estimate: {
|
||||||
@ -114,7 +114,7 @@ export const SPREADSHEET_PROPERTY_DETAILS: {
|
|||||||
ascendingOrderTitle: "New",
|
ascendingOrderTitle: "New",
|
||||||
descendingOrderKey: "start_date",
|
descendingOrderKey: "start_date",
|
||||||
descendingOrderTitle: "Old",
|
descendingOrderTitle: "Old",
|
||||||
icon: CalendarClock,
|
icon: CalendarDays,
|
||||||
Column: SpreadsheetStartDateColumn,
|
Column: SpreadsheetStartDateColumn,
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { differenceInDays, format, formatDistanceToNow, isAfter, isValid, parseISO } from "date-fns";
|
import { differenceInDays, format, formatDistanceToNow, isAfter, isEqual, isValid, parseISO } from "date-fns";
|
||||||
|
import { isNil } from "lodash";
|
||||||
|
|
||||||
// Format Date Helpers
|
// Format Date Helpers
|
||||||
/**
|
/**
|
||||||
@ -172,3 +173,23 @@ export const getWeekNumberOfDate = (date: Date): number => {
|
|||||||
const weekNumber = Math.ceil((days + 1) / 7);
|
const weekNumber = Math.ceil((days + 1) / 7);
|
||||||
return weekNumber;
|
return weekNumber;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {boolean} boolean value depending on whether the dates are equal
|
||||||
|
* @description Returns boolean value depending on whether the dates are equal
|
||||||
|
* @param date1
|
||||||
|
* @param date2
|
||||||
|
* @example checkIfDatesAreEqual("2024-01-01", "2024-01-01") // true
|
||||||
|
* @example checkIfDatesAreEqual("2024-01-01", "2024-01-02") // false
|
||||||
|
*/
|
||||||
|
export const checkIfDatesAreEqual = (
|
||||||
|
date1: Date | string | null | undefined,
|
||||||
|
date2: Date | string | null | undefined
|
||||||
|
): boolean => {
|
||||||
|
if (isNil(date1) && isNil(date2)) return true;
|
||||||
|
if (isNil(date1) || isNil(date2)) return false;
|
||||||
|
|
||||||
|
const parsedDate1 = new Date(date1);
|
||||||
|
const parsedDate2 = new Date(date2);
|
||||||
|
return isEqual(parsedDate1, parsedDate2);
|
||||||
|
};
|
||||||
|
@ -48,7 +48,7 @@
|
|||||||
"posthog-js": "^1.105.0",
|
"posthog-js": "^1.105.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-datepicker": "^4.8.0",
|
"react-day-picker": "^8.10.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
"react-hook-form": "^7.38.0",
|
"react-hook-form": "^7.38.0",
|
||||||
@ -67,7 +67,6 @@
|
|||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/react": "^18.2.42",
|
"@types/react": "^18.2.42",
|
||||||
"@types/react-color": "^3.0.6",
|
"@types/react-color": "^3.0.6",
|
||||||
"@types/react-datepicker": "^4.8.0",
|
|
||||||
"@types/react-dom": "^18.2.17",
|
"@types/react-dom": "^18.2.17",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||||
|
@ -5,7 +5,7 @@ import { AppProps } from "next/app";
|
|||||||
import "styles/globals.css";
|
import "styles/globals.css";
|
||||||
import "styles/command-pallette.css";
|
import "styles/command-pallette.css";
|
||||||
import "styles/nprogress.css";
|
import "styles/nprogress.css";
|
||||||
import "styles/react-datepicker.css";
|
import "styles/react-day-picker.css";
|
||||||
// constants
|
// constants
|
||||||
import { SITE_TITLE } from "constants/seo-variables";
|
import { SITE_TITLE } from "constants/seo-variables";
|
||||||
// mobx store provider
|
// mobx store provider
|
||||||
|
@ -1,142 +0,0 @@
|
|||||||
.react-datepicker-wrapper input::placeholder {
|
|
||||||
color: rgba(var(--color-text-200));
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker-wrapper input:-ms-input-placeholder {
|
|
||||||
color: rgba(var(--color-text-200));
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker-wrapper .react-datepicker__close-icon::after {
|
|
||||||
background: transparent;
|
|
||||||
color: rgba(var(--color-text-200));
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker-popper {
|
|
||||||
z-index: 30 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker-wrapper {
|
|
||||||
position: relative;
|
|
||||||
background-color: rgba(var(--color-background-100)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker-wrapper,
|
|
||||||
.react-datepicker__input-container {
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker {
|
|
||||||
font-family: "Inter" !important;
|
|
||||||
background-color: rgba(var(--color-background-100)) !important;
|
|
||||||
border: 0.5px solid rgba(var(--color-border-300)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__month-container {
|
|
||||||
width: 300px;
|
|
||||||
background-color: rgba(var(--color-background-100)) !important;
|
|
||||||
color: rgba(var(--color-text-100)) !important;
|
|
||||||
border-radius: 10px !important;
|
|
||||||
/* border: 1px solid rgba(var(--color-background-80)) !important; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__header {
|
|
||||||
border-radius: 10px !important;
|
|
||||||
background-color: rgba(var(--color-background-100)) !important;
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__navigation {
|
|
||||||
line-height: 0.78;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__triangle {
|
|
||||||
border-color: rgba(var(--color-background-100)) transparent transparent transparent !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__triangle:before {
|
|
||||||
border-bottom-color: rgba(var(--color-background-80)) !important;
|
|
||||||
}
|
|
||||||
.react-datepicker__triangle:after {
|
|
||||||
border-bottom-color: rgba(var(--color-background-100)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__current-month {
|
|
||||||
font-weight: 500 !important;
|
|
||||||
color: rgba(var(--color-text-100)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__month {
|
|
||||||
border-collapse: collapse;
|
|
||||||
color: rgba(var(--color-text-100)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day-names {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-left: 14px;
|
|
||||||
width: 280px;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(7, 1fr);
|
|
||||||
gap: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day-name {
|
|
||||||
color: rgba(var(--color-text-200)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__week {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(7, 1fr);
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day {
|
|
||||||
color: rgba(var(--color-text-100)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day {
|
|
||||||
border-radius: 50% !important;
|
|
||||||
transition: all 0.15s ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day:hover {
|
|
||||||
background-color: rgba(var(--color-background-80)) !important;
|
|
||||||
color: rgba(var(--color-text-100)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day--selected,
|
|
||||||
.react-datepicker__day--selected:hover {
|
|
||||||
background-color: #216ba5 !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day--disabled,
|
|
||||||
.react-datepicker__day--disabled:hover {
|
|
||||||
background: transparent !important;
|
|
||||||
color: rgba(var(--color-text-400)) !important;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day--today {
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day--highlighted {
|
|
||||||
background-color: rgba(var(--color-background-80)) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day--keyboard-selected {
|
|
||||||
background-color: #216ba5 !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day--in-range-start,
|
|
||||||
.react-datepicker__day--in-range-end {
|
|
||||||
background-color: rgba(var(--color-primary-100)) !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.react-datepicker__day--in-range:not(.react-datepicker__day--in-range-start):not(.react-datepicker__day--in-range-end) {
|
|
||||||
background-color: rgba(var(--color-primary-100)) !important;
|
|
||||||
color: white !important;
|
|
||||||
}
|
|
368
web/styles/react-day-picker.css
Normal file
368
web/styles/react-day-picker.css
Normal file
@ -0,0 +1,368 @@
|
|||||||
|
.rdp {
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
--rdp-cell-size: 40px;
|
||||||
|
/* Size of the day cells. */
|
||||||
|
--rdp-caption-font-size: 1.15rem;
|
||||||
|
/* Font size for the caption labels. */
|
||||||
|
--rdp-caption-navigation-size: 1.25rem;
|
||||||
|
/* Font size for the caption labels. */
|
||||||
|
--rdp-accent-color: rgba(var(--color-primary-100));
|
||||||
|
/* Accent color for the background of selected days. */
|
||||||
|
--rdp-background-color: rgba(var(--color-primary-100), 0.2);
|
||||||
|
/* Background color for the hovered/focused elements. */
|
||||||
|
--rdp-dark-background-color: rgba(var(--color-primary-300));
|
||||||
|
/* Background color for the hovered/focused, already selected elements. */
|
||||||
|
--rdp-outline: 2px solid var(--rdp-accent-color);
|
||||||
|
/* Outline border for focused elements */
|
||||||
|
--rdp-selected-color: #ffffff;
|
||||||
|
/* Color of selected day text */
|
||||||
|
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide elements for devices that are not screen readers */
|
||||||
|
.rdp-vhidden {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: 0;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
position: absolute !important;
|
||||||
|
top: 0;
|
||||||
|
width: 1px !important;
|
||||||
|
height: 1px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
clip: rect(1px, 1px, 1px, 1px) !important;
|
||||||
|
border: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Buttons */
|
||||||
|
.rdp-button_reset {
|
||||||
|
appearance: none;
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
cursor: default;
|
||||||
|
color: inherit;
|
||||||
|
background: none;
|
||||||
|
font: inherit;
|
||||||
|
|
||||||
|
-moz-appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-button_reset:focus-visible {
|
||||||
|
/* Make sure to reset outline only when :focus-visible is supported */
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-button {
|
||||||
|
border: 2px solid transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-button[disabled]:not(.rdp-day_selected) {
|
||||||
|
opacity: 0.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-button:not([disabled]) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-button:focus-visible:not([disabled]):not(.rdp-day_selected) {
|
||||||
|
color: inherit;
|
||||||
|
background-color: var(--rdp-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-button:focus-visible:not([disabled]).rdp-day_selected:not(.rdp-day_range_middle) {
|
||||||
|
outline: var(--rdp-outline);
|
||||||
|
outline-offset: 2px;
|
||||||
|
background-color: var(--rdp-dark-background-color);
|
||||||
|
outline-width: thin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-button:hover:not([disabled]).rdp-day_selected {
|
||||||
|
background-color: var(--rdp-dark-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-button:hover:not([disabled]):not(.rdp-day_selected) {
|
||||||
|
background-color: var(--rdp-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-months {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-month {
|
||||||
|
margin: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-month:first-child {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-month:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-table {
|
||||||
|
margin: 0;
|
||||||
|
max-width: calc(var(--rdp-cell-size) * 7);
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-with_weeknumber .rdp-table {
|
||||||
|
max-width: calc(var(--rdp-cell-size) * 8);
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-caption {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-multiple_months .rdp-caption {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-caption_dropdowns {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-caption_label {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 0.25em;
|
||||||
|
white-space: nowrap;
|
||||||
|
color: currentColor;
|
||||||
|
border: 0;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: var(--rdp-caption-font-size);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-nav {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-multiple_months .rdp-caption_start .rdp-nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-multiple_months .rdp-caption_end .rdp-nav {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 0;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-nav_button {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: var(--rdp-caption-navigation-size);
|
||||||
|
height: var(--rdp-caption-navigation-size);
|
||||||
|
padding: 0.25em;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-nav_button:hover,
|
||||||
|
.rdp-nav_button:focus-visible {
|
||||||
|
background-color: rgba(var(--color-background-80)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- */
|
||||||
|
/* Dropdowns */
|
||||||
|
/* ---------- */
|
||||||
|
|
||||||
|
.rdp-dropdown_year,
|
||||||
|
.rdp-dropdown_month {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-dropdown {
|
||||||
|
appearance: none;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
cursor: inherit;
|
||||||
|
opacity: 0;
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-dropdown[disabled] {
|
||||||
|
opacity: unset;
|
||||||
|
color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-dropdown:focus-visible:not([disabled])+.rdp-caption_label {
|
||||||
|
background-color: var(--rdp-background-color);
|
||||||
|
border: var(--rdp-outline);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-dropdown_icon {
|
||||||
|
margin: 0 0 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-head {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-head_row,
|
||||||
|
.rdp-row {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-head_cell {
|
||||||
|
vertical-align: middle;
|
||||||
|
font-size: 0.75em;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
height: 100%;
|
||||||
|
height: var(--rdp-cell-size);
|
||||||
|
padding: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-tbody {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-tfoot {
|
||||||
|
margin: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-cell {
|
||||||
|
width: var(--rdp-cell-size);
|
||||||
|
height: 100%;
|
||||||
|
height: var(--rdp-cell-size);
|
||||||
|
padding: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-weeknumber {
|
||||||
|
font-size: 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-weeknumber,
|
||||||
|
.rdp-day {
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: var(--rdp-cell-size);
|
||||||
|
max-width: var(--rdp-cell-size);
|
||||||
|
height: var(--rdp-cell-size);
|
||||||
|
margin: 0;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-radius: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-day_today:not(.rdp-day_outside) {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-day_today:not(.rdp-day_outside)::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
bottom: 2px;
|
||||||
|
width: 0.5em;
|
||||||
|
height: 0.5em;
|
||||||
|
background-color: var(--rdp-background-color);
|
||||||
|
border-radius: 100%;
|
||||||
|
transform: translate(-50%, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-day_selected,
|
||||||
|
.rdp-day_selected:focus-visible,
|
||||||
|
.rdp-day_selected:hover {
|
||||||
|
color: var(--rdp-selected-color);
|
||||||
|
opacity: 1;
|
||||||
|
background-color: var(--rdp-accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-day_outside:not(.rdp-day_selected) {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-day_selected:focus-visible {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:has(.rdp-day_range_start),
|
||||||
|
td:has(.rdp-day_range_middle),
|
||||||
|
td:has(.rdp-day_range_end) {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:has(.rdp-day_range_start)::before,
|
||||||
|
td:has(.rdp-day_range_middle)::before,
|
||||||
|
td:has(.rdp-day_range_end)::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--rdp-background-color);
|
||||||
|
top: 50%;
|
||||||
|
height: 100%;
|
||||||
|
width: 50%;
|
||||||
|
transform: translate(0, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
td:has(.rdp-day_range_start)::before {
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:has(.rdp-day_range_middle)::before {
|
||||||
|
left: 50%;
|
||||||
|
width: 100%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
td:has(.rdp-day_range_end)::before {
|
||||||
|
right: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:has(.rdp-day_range_start.rdp-day_range_end)::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-day_range_middle {
|
||||||
|
background-color: transparent;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rdp-day_range_middle:hover,
|
||||||
|
.rdp-day_range_middle:focus-visible {
|
||||||
|
background-color: var(--rdp-background-color) !important;
|
||||||
|
color: inherit !important;
|
||||||
|
}
|
40
yarn.lock
40
yarn.lock
@ -1865,7 +1865,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||||
|
|
||||||
"@popperjs/core@^2.11.7", "@popperjs/core@^2.11.8", "@popperjs/core@^2.9.0", "@popperjs/core@^2.9.2":
|
"@popperjs/core@^2.11.7", "@popperjs/core@^2.11.8", "@popperjs/core@^2.9.0":
|
||||||
version "2.11.8"
|
version "2.11.8"
|
||||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
|
||||||
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==
|
||||||
@ -2791,16 +2791,6 @@
|
|||||||
"@types/react" "*"
|
"@types/react" "*"
|
||||||
"@types/reactcss" "*"
|
"@types/reactcss" "*"
|
||||||
|
|
||||||
"@types/react-datepicker@^4.8.0":
|
|
||||||
version "4.19.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-datepicker/-/react-datepicker-4.19.4.tgz#07807187ead4c7025bc0039fe05eb5713298f3e4"
|
|
||||||
integrity sha512-HRD0LHTxBVe61LRJgTdPscbapLQl7+jI/7bxnPGpvzdJ/iXN9q7ucYv8HKULeIAN84O5LzFhwTMOkO4QnIUJaQ==
|
|
||||||
dependencies:
|
|
||||||
"@popperjs/core" "^2.9.2"
|
|
||||||
"@types/react" "*"
|
|
||||||
date-fns "^2.0.1"
|
|
||||||
react-popper "^2.2.5"
|
|
||||||
|
|
||||||
"@types/react-dom@^18.2.17":
|
"@types/react-dom@^18.2.17":
|
||||||
version "18.2.18"
|
version "18.2.18"
|
||||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd"
|
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.18.tgz#16946e6cd43971256d874bc3d0a72074bb8571dd"
|
||||||
@ -3617,7 +3607,7 @@ class-variance-authority@^0.7.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
clsx "2.0.0"
|
clsx "2.0.0"
|
||||||
|
|
||||||
classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2:
|
classnames@^2.3.1, classnames@^2.3.2:
|
||||||
version "2.5.0"
|
version "2.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.0.tgz#9304d63d8d7135989e33e1ffb0bcc65178f92c2a"
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.0.tgz#9304d63d8d7135989e33e1ffb0bcc65178f92c2a"
|
||||||
integrity sha512-FQuRlyKinxrb5gwJlfVASbSrDlikDJ07426TrfPsdGLvtochowmkbnSFdQGJ2aoXrSetq5KqGV9emvWpy+91xA==
|
integrity sha512-FQuRlyKinxrb5gwJlfVASbSrDlikDJ07426TrfPsdGLvtochowmkbnSFdQGJ2aoXrSetq5KqGV9emvWpy+91xA==
|
||||||
@ -3918,7 +3908,7 @@ dash-get@^1.0.2:
|
|||||||
resolved "https://registry.yarnpkg.com/dash-get/-/dash-get-1.0.2.tgz#4c9e9ad5ef04c4bf9d3c9a451f6f7997298dcc7c"
|
resolved "https://registry.yarnpkg.com/dash-get/-/dash-get-1.0.2.tgz#4c9e9ad5ef04c4bf9d3c9a451f6f7997298dcc7c"
|
||||||
integrity sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==
|
integrity sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==
|
||||||
|
|
||||||
date-fns@^2.0.1, date-fns@^2.30.0:
|
date-fns@^2.30.0:
|
||||||
version "2.30.0"
|
version "2.30.0"
|
||||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
|
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
|
||||||
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
|
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
|
||||||
@ -7237,7 +7227,7 @@ progress@^2.0.0, progress@^2.0.3:
|
|||||||
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
|
||||||
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
|
||||||
|
|
||||||
prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
|
prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.8.1:
|
||||||
version "15.8.1"
|
version "15.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||||
@ -7481,17 +7471,10 @@ react-css-styled@^1.1.9:
|
|||||||
css-styled "~1.0.8"
|
css-styled "~1.0.8"
|
||||||
framework-utils "^1.1.0"
|
framework-utils "^1.1.0"
|
||||||
|
|
||||||
react-datepicker@^4.8.0:
|
react-day-picker@^8.10.0:
|
||||||
version "4.25.0"
|
version "8.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.25.0.tgz#86b3ee8ac764bad1650046d0cf9280837bf6d845"
|
resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.10.0.tgz#729c5b9564967a924213978fb9c0751884a60595"
|
||||||
integrity sha512-zB7CSi44SJ0sqo8hUQ3BF1saE/knn7u25qEMTO1CQGofY1VAKahO8k9drZtp0cfW1DMfoYLR3uSY1/uMvbEzbg==
|
integrity sha512-mz+qeyrOM7++1NCb1ARXmkjMkzWVh2GL9YiPbRjKe0zHccvekk4HE+0MPOZOrosn8r8zTHIIeOUXTmXRqmkRmg==
|
||||||
dependencies:
|
|
||||||
"@popperjs/core" "^2.11.8"
|
|
||||||
classnames "^2.2.6"
|
|
||||||
date-fns "^2.30.0"
|
|
||||||
prop-types "^15.7.2"
|
|
||||||
react-onclickoutside "^6.13.0"
|
|
||||||
react-popper "^2.3.0"
|
|
||||||
|
|
||||||
react-dom@18.2.0, react-dom@^18.2.0:
|
react-dom@18.2.0, react-dom@^18.2.0:
|
||||||
version "18.2.0"
|
version "18.2.0"
|
||||||
@ -7575,11 +7558,6 @@ react-moveable@^0.54.2:
|
|||||||
react-css-styled "^1.1.9"
|
react-css-styled "^1.1.9"
|
||||||
react-selecto "^1.25.0"
|
react-selecto "^1.25.0"
|
||||||
|
|
||||||
react-onclickoutside@^6.13.0:
|
|
||||||
version "6.13.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.13.0.tgz#e165ea4e5157f3da94f4376a3ab3e22a565f4ffc"
|
|
||||||
integrity sha512-ty8So6tcUpIb+ZE+1HAhbLROvAIJYyJe/1vRrrcmW+jLsaM+/powDRqxzo6hSh9CuRZGSL1Q8mvcF5WRD93a0A==
|
|
||||||
|
|
||||||
react-popper@^1.3.11:
|
react-popper@^1.3.11:
|
||||||
version "1.3.11"
|
version "1.3.11"
|
||||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.11.tgz#a2cc3f0a67b75b66cfa62d2c409f9dd1fcc71ffd"
|
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.11.tgz#a2cc3f0a67b75b66cfa62d2c409f9dd1fcc71ffd"
|
||||||
@ -7593,7 +7571,7 @@ react-popper@^1.3.11:
|
|||||||
typed-styles "^0.0.7"
|
typed-styles "^0.0.7"
|
||||||
warning "^4.0.2"
|
warning "^4.0.2"
|
||||||
|
|
||||||
react-popper@^2.2.5, react-popper@^2.3.0:
|
react-popper@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
|
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.3.0.tgz#17891c620e1320dce318bad9fede46a5f71c70ba"
|
||||||
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
|
integrity sha512-e1hj8lL3uM+sgSR4Lxzn5h1GxBlpa4CQz0XLF8kx4MDrDRWY0Ena4c97PUeSX9i5W3UAfDP0z0FXCTQkoXUl3Q==
|
||||||
|
Loading…
Reference in New Issue
Block a user