chore: handle calendar date range in frontend (#2277)

This commit is contained in:
Aaryan Khandelwal 2023-09-27 14:41:32 +05:30 committed by GitHub
parent 5298f1e53c
commit b3be363b00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 208 additions and 325 deletions

View File

@ -5,25 +5,12 @@ import { Popover, Transition } from "@headlessui/react";
// ui // ui
import { CustomMenu, ToggleSwitch } from "components/ui"; import { CustomMenu, ToggleSwitch } from "components/ui";
// icons // icons
import { import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
CheckIcon,
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "@heroicons/react/24/outline";
// helpers // helpers
import { import {
addMonths,
addSevenDaysToDate,
formatDate, formatDate,
getCurrentWeekEndDate,
getCurrentWeekStartDate,
isSameMonth, isSameMonth,
isSameYear, isSameYear,
lastDayOfWeek,
startOfWeek,
subtract7DaysToDate,
subtractMonths,
updateDateWithMonth, updateDateWithMonth,
updateDateWithYear, updateDateWithYear,
} from "helpers/calendar.helper"; } from "helpers/calendar.helper";
@ -31,31 +18,18 @@ import {
import { MONTHS_LIST, YEARS_LIST } from "constants/calendar"; import { MONTHS_LIST, YEARS_LIST } from "constants/calendar";
type Props = { type Props = {
isMonthlyView: boolean;
setIsMonthlyView: React.Dispatch<React.SetStateAction<boolean>>;
currentDate: Date; currentDate: Date;
setCurrentDate: React.Dispatch<React.SetStateAction<Date>>; setCurrentDate: React.Dispatch<React.SetStateAction<Date>>;
showWeekEnds: boolean; showWeekEnds: boolean;
setShowWeekEnds: React.Dispatch<React.SetStateAction<boolean>>; setShowWeekEnds: React.Dispatch<React.SetStateAction<boolean>>;
changeDateRange: (startDate: Date, endDate: Date) => void;
}; };
export const CalendarHeader: React.FC<Props> = ({ export const CalendarHeader: React.FC<Props> = ({
setIsMonthlyView,
isMonthlyView,
currentDate, currentDate,
setCurrentDate, setCurrentDate,
showWeekEnds, showWeekEnds,
setShowWeekEnds, setShowWeekEnds,
changeDateRange, }) => (
}) => {
const updateDate = (date: Date) => {
setCurrentDate(date);
changeDateRange(startOfWeek(date), lastDayOfWeek(date));
};
return (
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<div className="relative flex h-full w-full items-center justify-start gap-2 text-sm "> <div className="relative flex h-full w-full items-center justify-start gap-2 text-sm ">
<Popover className="flex h-full items-center justify-start rounded-lg"> <Popover className="flex h-full items-center justify-start rounded-lg">
@ -81,7 +55,7 @@ export const CalendarHeader: React.FC<Props> = ({
<div className="flex items-center justify-center gap-5 px-2 py-2 text-sm"> <div className="flex items-center justify-center gap-5 px-2 py-2 text-sm">
{YEARS_LIST.map((year) => ( {YEARS_LIST.map((year) => (
<button <button
onClick={() => updateDate(updateDateWithYear(year.label, currentDate))} onClick={() => setCurrentDate(updateDateWithYear(year.label, currentDate))}
className={` ${ className={` ${
isSameYear(year.value, currentDate) isSameYear(year.value, currentDate)
? "text-sm font-medium text-custom-text-100" ? "text-sm font-medium text-custom-text-100"
@ -96,7 +70,7 @@ export const CalendarHeader: React.FC<Props> = ({
{MONTHS_LIST.map((month) => ( {MONTHS_LIST.map((month) => (
<button <button
onClick={() => onClick={() =>
updateDate(updateDateWithMonth(`${month.value}`, currentDate)) setCurrentDate(updateDateWithMonth(`${month.value}`, currentDate))
} }
className={`px-2 py-2 text-xs text-custom-text-200 hover:font-medium hover:text-custom-text-100 ${ className={`px-2 py-2 text-xs text-custom-text-200 hover:font-medium hover:text-custom-text-100 ${
isSameMonth(`${month.value}`, currentDate) isSameMonth(`${month.value}`, currentDate)
@ -118,15 +92,16 @@ export const CalendarHeader: React.FC<Props> = ({
<button <button
className="cursor-pointer" className="cursor-pointer"
onClick={() => { onClick={() => {
if (isMonthlyView) { const previousMonthYear =
updateDate(subtractMonths(currentDate, 1)); currentDate.getMonth() === 0
} else { ? currentDate.getFullYear() - 1
setCurrentDate(subtract7DaysToDate(currentDate)); : currentDate.getFullYear();
changeDateRange( const previousMonthMonth =
getCurrentWeekStartDate(subtract7DaysToDate(currentDate)), currentDate.getMonth() === 0 ? 11 : currentDate.getMonth() - 1;
getCurrentWeekEndDate(subtract7DaysToDate(currentDate))
); const previousMonthFirstDate = new Date(previousMonthYear, previousMonthMonth, 1);
}
setCurrentDate(previousMonthFirstDate);
}} }}
> >
<ChevronLeftIcon className="h-4 w-4" /> <ChevronLeftIcon className="h-4 w-4" />
@ -134,15 +109,15 @@ export const CalendarHeader: React.FC<Props> = ({
<button <button
className="cursor-pointer" className="cursor-pointer"
onClick={() => { onClick={() => {
if (isMonthlyView) { const nextMonthYear =
updateDate(addMonths(currentDate, 1)); currentDate.getMonth() === 11
} else { ? currentDate.getFullYear() + 1
setCurrentDate(addSevenDaysToDate(currentDate)); : currentDate.getFullYear();
changeDateRange( const nextMonthMonth = (currentDate.getMonth() + 1) % 12;
getCurrentWeekStartDate(addSevenDaysToDate(currentDate)),
getCurrentWeekEndDate(addSevenDaysToDate(currentDate)) const nextMonthFirstDate = new Date(nextMonthYear, nextMonthMonth, 1);
);
} setCurrentDate(nextMonthFirstDate);
}} }}
> >
<ChevronRightIcon className="h-4 w-4" /> <ChevronRightIcon className="h-4 w-4" />
@ -153,17 +128,7 @@ export const CalendarHeader: React.FC<Props> = ({
<div className="flex w-full items-center justify-end gap-2"> <div className="flex w-full items-center justify-end gap-2">
<button <button
className="group flex cursor-pointer items-center gap-2 rounded-md border border-custom-border-200 px-3 py-1 text-sm hover:bg-custom-background-80 hover:text-custom-text-100 focus:outline-none" className="group flex cursor-pointer items-center gap-2 rounded-md border border-custom-border-200 px-3 py-1 text-sm hover:bg-custom-background-80 hover:text-custom-text-100 focus:outline-none"
onClick={() => { onClick={() => setCurrentDate(new Date())}
if (isMonthlyView) {
updateDate(new Date());
} else {
setCurrentDate(new Date());
changeDateRange(
getCurrentWeekStartDate(new Date()),
getCurrentWeekEndDate(new Date())
);
}
}}
> >
Today Today
</button> </button>
@ -171,43 +136,12 @@ export const CalendarHeader: React.FC<Props> = ({
<CustomMenu <CustomMenu
customButton={ customButton={
<div className="group flex cursor-pointer items-center gap-2 rounded-md border border-custom-border-200 px-3 py-1 text-sm hover:bg-custom-background-80 hover:text-custom-text-100 focus:outline-none"> <div className="group flex cursor-pointer items-center gap-2 rounded-md border border-custom-border-200 px-3 py-1 text-sm hover:bg-custom-background-80 hover:text-custom-text-100 focus:outline-none">
{isMonthlyView ? "Monthly" : "Weekly"} Options
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" /> <ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</div> </div>
} }
> >
<CustomMenu.MenuItem <div className="flex w-52 items-center justify-between px-1 text-sm text-custom-text-200">
onClick={() => {
setIsMonthlyView(true);
changeDateRange(startOfWeek(currentDate), lastDayOfWeek(currentDate));
}}
className="w-52 text-sm text-custom-text-200"
>
<div className="flex w-full max-w-[260px] items-center justify-between gap-2">
<span className="flex items-center gap-2">Monthly View</span>
<CheckIcon
className={`h-4 w-4 flex-shrink-0 ${isMonthlyView ? "opacity-100" : "opacity-0"}`}
/>
</div>
</CustomMenu.MenuItem>
<CustomMenu.MenuItem
onClick={() => {
setIsMonthlyView(false);
changeDateRange(
getCurrentWeekStartDate(currentDate),
getCurrentWeekEndDate(currentDate)
);
}}
className="w-52 text-sm text-custom-text-200"
>
<div className="flex w-full items-center justify-between gap-2">
<span className="flex items-center gap-2">Weekly View</span>
<CheckIcon
className={`h-4 w-4 flex-shrink-0 ${isMonthlyView ? "opacity-0" : "opacity-100"}`}
/>
</div>
</CustomMenu.MenuItem>
<div className="mt-1 flex w-52 items-center justify-between border-t border-custom-border-200 py-2 px-1 text-sm text-custom-text-200">
<h4>Show weekends</h4> <h4>Show weekends</h4>
<ToggleSwitch value={showWeekEnds} onChange={() => setShowWeekEnds(!showWeekEnds)} /> <ToggleSwitch value={showWeekEnds} onChange={() => setShowWeekEnds(!showWeekEnds)} />
</div> </div>
@ -215,6 +149,5 @@ export const CalendarHeader: React.FC<Props> = ({
</div> </div>
</div> </div>
); );
};
export default CalendarHeader; export default CalendarHeader;

View File

@ -1,10 +1,6 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { mutate } from "swr"; import { mutate } from "swr";
// react-beautiful-dnd
import { DragDropContext, DropResult } from "react-beautiful-dnd"; import { DragDropContext, DropResult } from "react-beautiful-dnd";
// services // services
import issuesService from "services/issues.service"; import issuesService from "services/issues.service";
@ -50,32 +46,28 @@ export const CalendarView: React.FC<Props> = ({
userAuth, userAuth,
}) => { }) => {
const [showWeekEnds, setShowWeekEnds] = useState(false); const [showWeekEnds, setShowWeekEnds] = useState(false);
const [currentDate, setCurrentDate] = useState(new Date());
const [isMonthlyView, setIsMonthlyView] = useState(true); const { calendarIssues, mutateIssues, params, activeMonthDate, setActiveMonthDate } =
useCalendarIssuesView();
const [calendarDates, setCalendarDates] = useState<ICalendarRange>({ const [calendarDates, setCalendarDates] = useState<ICalendarRange>({
startDate: startOfWeek(currentDate), startDate: startOfWeek(activeMonthDate),
endDate: lastDayOfWeek(currentDate), endDate: lastDayOfWeek(activeMonthDate),
}); });
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
const { calendarIssues, mutateIssues, params, displayFilters, setDisplayFilters } = const currentViewDays = showWeekEnds
useCalendarIssuesView(); ? eachDayOfInterval({
start: calendarDates.startDate,
const totalDate = eachDayOfInterval({ end: calendarDates.endDate,
})
: weekDayInterval({
start: calendarDates.startDate, start: calendarDates.startDate,
end: calendarDates.endDate, end: calendarDates.endDate,
}); });
const onlyWeekDays = weekDayInterval({
start: calendarDates.startDate,
end: calendarDates.endDate,
});
const currentViewDays = showWeekEnds ? totalDate : onlyWeekDays;
const currentViewDaysData = currentViewDays.map((date: Date) => { const currentViewDaysData = currentViewDays.map((date: Date) => {
const filterIssue = const filterIssue =
calendarIssues.length > 0 calendarIssues.length > 0
@ -148,27 +140,12 @@ export const CalendarView: React.FC<Props> = ({
.then(() => mutate(fetchKey)); .then(() => mutate(fetchKey));
}; };
const changeDateRange = (startDate: Date, endDate: Date) => {
setCalendarDates({
startDate,
endDate,
});
setDisplayFilters({
calendar_date_range: `${renderDateFormat(startDate)};after,${renderDateFormat(
endDate
)};before`,
});
};
useEffect(() => { useEffect(() => {
if (!displayFilters || displayFilters.calendar_date_range === "") setCalendarDates({
setDisplayFilters({ startDate: startOfWeek(activeMonthDate),
calendar_date_range: `${renderDateFormat( endDate: lastDayOfWeek(activeMonthDate),
startOfWeek(currentDate)
)};after,${renderDateFormat(lastDayOfWeek(currentDate))};before`,
}); });
}, [currentDate, displayFilters, setDisplayFilters]); }, [activeMonthDate]);
const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions; const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions;
@ -188,13 +165,10 @@ export const CalendarView: React.FC<Props> = ({
className="h-full rounded-lg p-8 text-custom-text-200" className="h-full rounded-lg p-8 text-custom-text-200"
> >
<CalendarHeader <CalendarHeader
isMonthlyView={isMonthlyView}
setIsMonthlyView={setIsMonthlyView}
showWeekEnds={showWeekEnds} showWeekEnds={showWeekEnds}
setShowWeekEnds={setShowWeekEnds} setShowWeekEnds={setShowWeekEnds}
currentDate={currentDate} currentDate={activeMonthDate}
setCurrentDate={setCurrentDate} setCurrentDate={setActiveMonthDate}
changeDateRange={changeDateRange}
/> />
<div <div
@ -205,30 +179,15 @@ export const CalendarView: React.FC<Props> = ({
{weeks.map((date, index) => ( {weeks.map((date, index) => (
<div <div
key={index} key={index}
className={`flex items-center justify-start gap-2 border-custom-border-200 bg-custom-background-90 p-1.5 text-base font-medium text-custom-text-200 ${ className={`flex items-center justify-start gap-2 border-custom-border-200 bg-custom-background-90 p-1.5 text-base font-medium text-custom-text-200`}
!isMonthlyView
? showWeekEnds
? (index + 1) % 7 === 0
? ""
: "border-r"
: (index + 1) % 5 === 0
? ""
: "border-r"
: ""
}`}
> >
<span> <span>{formatDate(date, "eee").substring(0, 3)}</span>
{isMonthlyView
? formatDate(date, "eee").substring(0, 3)
: formatDate(date, "eee")}
</span>
{!isMonthlyView && <span>{formatDate(date, "d")}</span>}
</div> </div>
))} ))}
</div> </div>
<div <div
className={`grid h-full ${isMonthlyView ? "auto-rows-min" : ""} ${ className={`grid h-full auto-rows-min ${
showWeekEnds ? "grid-cols-7" : "grid-cols-5" showWeekEnds ? "grid-cols-7" : "grid-cols-5"
} `} } `}
> >
@ -239,7 +198,6 @@ export const CalendarView: React.FC<Props> = ({
date={date} date={date}
handleIssueAction={handleIssueAction} handleIssueAction={handleIssueAction}
addIssueToDate={addIssueToDate} addIssueToDate={addIssueToDate}
isMonthlyView={isMonthlyView}
showWeekEnds={showWeekEnds} showWeekEnds={showWeekEnds}
user={user} user={user}
isNotAllowed={isNotAllowed} isNotAllowed={isNotAllowed}

View File

@ -24,14 +24,13 @@ type Props = {
issues: IIssue[]; issues: IIssue[];
}; };
addIssueToDate: (date: string) => void; addIssueToDate: (date: string) => void;
isMonthlyView: boolean;
showWeekEnds: boolean; showWeekEnds: boolean;
user: ICurrentUserResponse | undefined; user: ICurrentUserResponse | undefined;
isNotAllowed: boolean; isNotAllowed: boolean;
}; };
export const SingleCalendarDate: React.FC<Props> = (props) => { export const SingleCalendarDate: React.FC<Props> = (props) => {
const { handleIssueAction, date, index, isMonthlyView, showWeekEnds, user, isNotAllowed } = props; const { handleIssueAction, date, index, showWeekEnds, user, isNotAllowed } = props;
const router = useRouter(); const router = useRouter();
const { cycleId, moduleId } = router.query; const { cycleId, moduleId } = router.query;
@ -51,8 +50,6 @@ export const SingleCalendarDate: React.FC<Props> = (props) => {
ref={provided.innerRef} ref={provided.innerRef}
{...provided.droppableProps} {...provided.droppableProps}
className={`group relative flex min-h-[150px] flex-col gap-1.5 border-t border-custom-border-200 p-2.5 text-left text-sm font-medium hover:bg-custom-background-90 ${ className={`group relative flex min-h-[150px] flex-col gap-1.5 border-t border-custom-border-200 p-2.5 text-left text-sm font-medium hover:bg-custom-background-90 ${
isMonthlyView ? "" : "pt-9"
} ${
showWeekEnds showWeekEnds
? (index + 1) % 7 === 0 ? (index + 1) % 7 === 0
? "" ? ""
@ -62,7 +59,8 @@ export const SingleCalendarDate: React.FC<Props> = (props) => {
: "border-r" : "border-r"
}`} }`}
> >
{isMonthlyView && <span>{formatDate(new Date(date.date), "d")}</span>} <>
<span>{formatDate(new Date(date.date), "d")}</span>
{totalIssues > 0 && {totalIssues > 0 &&
date.issues.slice(0, showAllIssues ? totalIssues : 4).map((issue: IIssue, index) => ( date.issues.slice(0, showAllIssues ? totalIssues : 4).map((issue: IIssue, index) => (
<Draggable key={issue.id} draggableId={issue.id} index={index}> <Draggable key={issue.id} draggableId={issue.id} index={index}>
@ -82,7 +80,6 @@ export const SingleCalendarDate: React.FC<Props> = (props) => {
)} )}
</Draggable> </Draggable>
))} ))}
<div <div
className="fixed top-0 left-0 z-50" className="fixed top-0 left-0 z-50"
style={{ style={{
@ -127,6 +124,7 @@ export const SingleCalendarDate: React.FC<Props> = (props) => {
</div> </div>
{provided.placeholder} {provided.placeholder}
</>
</div> </div>
)} )}
</StrictModeDroppable> </StrictModeDroppable>

View File

@ -48,7 +48,6 @@ type ReducerFunctionType = (state: StateType, action: ReducerActionType) => Stat
export const initialState: StateType = { export const initialState: StateType = {
display_filters: { display_filters: {
calendar_date_range: "",
group_by: null, group_by: null,
layout: "list", layout: "list",
order_by: "-created_at", order_by: "-created_at",

View File

@ -112,18 +112,6 @@ export const formatDate = (date: Date, format: string): string => {
return formattedDate; return formattedDate;
}; };
export const subtractMonths = (date: Date, numMonths: number) => {
const result = new Date(date);
result.setMonth(result.getMonth() - numMonths);
return result;
};
export const addMonths = (date: Date, numMonths: number) => {
const result = new Date(date);
result.setMonth(result.getMonth() + numMonths);
return result;
};
export const updateDateWithYear = (yearString: string, date: Date) => { export const updateDateWithYear = (yearString: string, date: Date) => {
const year = parseInt(yearString); const year = parseInt(yearString);
const month = date.getMonth(); const month = date.getMonth();

View File

@ -1,4 +1,4 @@
import { useContext } from "react"; import { useContext, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
@ -10,6 +10,8 @@ import { issueViewContext } from "contexts/issue-view.context";
import issuesService from "services/issues.service"; import issuesService from "services/issues.service";
import cyclesService from "services/cycles.service"; import cyclesService from "services/cycles.service";
import modulesService from "services/modules.service"; import modulesService from "services/modules.service";
// helpers
import { renderDateFormat } from "helpers/date-time.helper";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
// fetch-keys // fetch-keys
@ -23,13 +25,17 @@ import {
const useCalendarIssuesView = () => { const useCalendarIssuesView = () => {
const { const {
display_filters: displayFilters, display_filters: displayFilters,
setDisplayFilters,
filters, filters,
setFilters, setFilters,
resetFilterToDefault, resetFilterToDefault,
setNewFilterDefaultView, setNewFilterDefaultView,
} = useContext(issueViewContext); } = useContext(issueViewContext);
const [activeMonthDate, setActiveMonthDate] = useState(new Date());
const firstDayOfMonth = new Date(activeMonthDate.getFullYear(), activeMonthDate.getMonth(), 1);
const lastDayOfMonth = new Date(activeMonthDate.getFullYear(), activeMonthDate.getMonth() + 1, 0);
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query;
@ -41,7 +47,9 @@ const useCalendarIssuesView = () => {
labels: filters?.labels ? filters?.labels.join(",") : undefined, labels: filters?.labels ? filters?.labels.join(",") : undefined,
created_by: filters?.created_by ? filters?.created_by.join(",") : undefined, created_by: filters?.created_by ? filters?.created_by.join(",") : undefined,
start_date: filters?.start_date ? filters?.start_date.join(",") : undefined, start_date: filters?.start_date ? filters?.start_date.join(",") : undefined,
target_date: displayFilters?.calendar_date_range, target_date: `${renderDateFormat(firstDayOfMonth)};after,${renderDateFormat(
lastDayOfMonth
)};before`,
}; };
const { data: projectCalendarIssues, mutate: mutateProjectCalendarIssues } = useSWR( const { data: projectCalendarIssues, mutate: mutateProjectCalendarIssues } = useSWR(
@ -101,8 +109,8 @@ const useCalendarIssuesView = () => {
: (projectCalendarIssues as IIssue[]); : (projectCalendarIssues as IIssue[]);
return { return {
displayFilters, activeMonthDate,
setDisplayFilters, setActiveMonthDate,
calendarIssues: calendarIssues ?? [], calendarIssues: calendarIssues ?? [],
mutateIssues: cycleId mutateIssues: cycleId
? mutateCycleCalendarIssues ? mutateCycleCalendarIssues

View File

@ -42,7 +42,6 @@ export interface IIssueFilterOptions {
} }
export interface IIssueDisplayFilterOptions { export interface IIssueDisplayFilterOptions {
calendar_date_range?: string;
group_by?: TIssueGroupByOptions; group_by?: TIssueGroupByOptions;
layout?: TIssueViewOptions; layout?: TIssueViewOptions;
order_by?: TIssueOrderByOptions; order_by?: TIssueOrderByOptions;