feat: calendar view (#561)

* feat:start and last day of month helper function

* feat: start and last day of week helper function

* feat: weekday and everyday interval helper function

* feat: calendar date formater helper function

* feat: monthly calendar view , feat: weekend date toggle, feat: calendar month and year picker

* feat: monthly , weekly view and weekend toggle

* feat: drag and drop added in calendar view

* fix: drag and drop mutation fix

* chore: refactoring , feat: calendar view option added

* fix: calendar view menu fix

* style: calendar view style improvement

* style: drag and drop styling

* fix:week day format fix

* chore: calendar constant added

* feat: calendar helper function added

* feat: month and year picker, month navigator, jump to today funtionality added

* feat: weekly navigation and jump to today fix, style: month year picker fix

---------

Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
This commit is contained in:
Anmol Singh Bhatia 2023-03-30 02:01:53 +05:30 committed by GitHub
parent 952c64d449
commit e6055da150
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 661 additions and 76 deletions

View File

@ -0,0 +1,419 @@
import React, { useState } from "react";
import useSWR, { mutate } from "swr";
import Link from "next/link";
import { useRouter } from "next/router";
// helper
import { renderDateFormat } from "helpers/date-time.helper";
import {
startOfWeek,
lastDayOfWeek,
eachDayOfInterval,
weekDayInterval,
formatDate,
getCurrentWeekStartDate,
getCurrentWeekEndDate,
subtractMonths,
addMonths,
updateDateWithYear,
updateDateWithMonth,
isSameMonth,
isSameYear,
subtract7DaysToDate,
addSevenDaysToDate,
} from "helpers/calendar.helper";
// ui
import { Popover, Transition } from "@headlessui/react";
import { DragDropContext, Draggable, Droppable, DropResult } from "react-beautiful-dnd";
import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { CustomMenu } from "components/ui";
// icon
import {
CheckIcon,
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "@heroicons/react/24/outline";
// services
import issuesService from "services/issues.service";
// fetch key
import { CALENDAR_ISSUES, ISSUE_DETAILS } from "constants/fetch-keys";
// type
import { IIssue } from "types";
// constant
import { monthOptions, yearOptions } from "constants/calendar";
interface ICalendarRange {
startDate: Date;
endDate: Date;
}
export const CalendarView = () => {
const [showWeekEnds, setShowWeekEnds] = useState<boolean>(false);
const [currentDate, setCurrentDate] = useState<Date>(new Date());
const [isMonthlyView, setIsMonthlyView] = useState<boolean>(true);
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const [calendarDateRange, setCalendarDateRange] = useState<ICalendarRange>({
startDate: startOfWeek(currentDate),
endDate: lastDayOfWeek(currentDate),
});
const { data: calendarIssues } = useSWR(
workspaceSlug && projectId ? CALENDAR_ISSUES(projectId as string) : null,
workspaceSlug && projectId
? () =>
issuesService.getIssuesWithParams(workspaceSlug as string, projectId as string, {
target_date: `${renderDateFormat(calendarDateRange.startDate)};after,${renderDateFormat(
calendarDateRange.endDate
)};before`,
})
: null
);
const totalDate = eachDayOfInterval({
start: calendarDateRange.startDate,
end: calendarDateRange.endDate,
});
const onlyWeekDays = weekDayInterval({
start: calendarDateRange.startDate,
end: calendarDateRange.endDate,
});
const currentViewDays = showWeekEnds ? totalDate : onlyWeekDays;
const currentViewDaysData = currentViewDays.map((date: Date) => {
const filterIssue =
calendarIssues && calendarIssues.length > 0
? (calendarIssues as IIssue[]).filter(
(issue) =>
issue.target_date && renderDateFormat(issue.target_date) === renderDateFormat(date)
)
: [];
return {
date: renderDateFormat(date),
issues: filterIssue,
};
});
const weeks = ((date: Date[]) => {
const weeks = [];
if (showWeekEnds) {
for (let day = 0; day <= 6; day++) {
weeks.push(date[day]);
}
} else {
for (let day = 0; day <= 4; day++) {
weeks.push(date[day]);
}
}
return weeks;
})(currentViewDays);
const onDragEnd = (result: DropResult) => {
const { source, destination, draggableId } = result;
if (!destination || !workspaceSlug || !projectId) return;
if (source.droppableId === destination.droppableId) return;
mutate<IIssue[]>(
CALENDAR_ISSUES(projectId as string),
(prevData) =>
(prevData ?? []).map((p) => {
if (p.id === draggableId)
return {
...p,
target_date: destination.droppableId,
};
return p;
}),
false
);
issuesService.patchIssue(workspaceSlug as string, projectId as string, draggableId, {
target_date: destination?.droppableId,
});
};
const updateDate = (date: Date) => {
setCurrentDate(date);
setCalendarDateRange({
startDate: startOfWeek(date),
endDate: lastDayOfWeek(date),
});
};
return (
<DragDropContext onDragEnd={onDragEnd}>
<div className="h-full overflow-y-auto rounded-lg text-gray-600 -m-2">
<div className="mb-4 flex items-center justify-between">
<div className="relative flex h-full w-full gap-2 items-center justify-start text-sm ">
<Popover className="flex h-full items-center justify-start rounded-lg">
{({ open }) => (
<>
<Popover.Button className={`group flex h-full items-start gap-1 text-gray-800`}>
<div className="flex items-center justify-center gap-2 text-2xl font-semibold">
<span className="text-black">{formatDate(currentDate, "Month")}</span>{" "}
<span>{formatDate(currentDate, "yyyy")}</span>
</div>
</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 top-10 left-0 z-20 w-full max-w-xs flex flex-col transform overflow-hidden bg-white shadow-lg rounded-[10px]">
<div className="flex justify-center items-center text-sm gap-5 px-2 py-2">
{yearOptions.map((year) => (
<button
onClick={() => updateDate(updateDateWithYear(year.label, currentDate))}
className={` ${
isSameYear(year.value, currentDate)
? "text-sm font-medium text-gray-800"
: "text-xs text-gray-400 "
} hover:text-sm hover:text-gray-800 hover:font-medium `}
>
{year.label}
</button>
))}
</div>
<div className="grid grid-cols-4 px-2 border-t border-gray-200">
{monthOptions.map((month) => (
<button
onClick={() =>
updateDate(updateDateWithMonth(month.value, currentDate))
}
className={`text-gray-400 text-xs px-2 py-2 hover:font-medium hover:text-gray-800 ${
isSameMonth(month.value, currentDate)
? "font-medium text-gray-800"
: ""
}`}
>
{month.label}
</button>
))}
</div>
</Popover.Panel>
</Transition>
</>
)}
</Popover>
<div className="flex items-center gap-2">
<button
className="cursor-pointer"
onClick={() => {
if (isMonthlyView) {
updateDate(subtractMonths(currentDate, 1));
} else {
setCurrentDate(subtract7DaysToDate(currentDate));
setCalendarDateRange({
startDate: getCurrentWeekStartDate(subtract7DaysToDate(currentDate)),
endDate: getCurrentWeekEndDate(subtract7DaysToDate(currentDate)),
});
}
}}
>
<ChevronLeftIcon className="h-4 w-4" />
</button>
<button
className="cursor-pointer"
onClick={() => {
if (isMonthlyView) {
updateDate(addMonths(currentDate, 1));
} else {
setCurrentDate(addSevenDaysToDate(currentDate));
setCalendarDateRange({
startDate: getCurrentWeekStartDate(addSevenDaysToDate(currentDate)),
endDate: getCurrentWeekEndDate(addSevenDaysToDate(currentDate)),
});
}
}}
>
<ChevronRightIcon className="h-4 w-4" />
</button>
</div>
</div>
<div className="flex w-full gap-2 items-center justify-end">
<button
className="group flex cursor-pointer items-center gap-2 rounded-md border bg-white px-4 py-1.5 text-sm hover:bg-gray-100 hover:text-gray-900 focus:outline-none"
onClick={() => {
if(isMonthlyView){
updateDate(new Date())
}else{
setCurrentDate(new Date());
setCalendarDateRange({
startDate: getCurrentWeekStartDate(new Date()),
endDate: getCurrentWeekEndDate(new Date()),
});
}
}}
>
Today{" "}
</button>
<CustomMenu
customButton={
<div
className={`group flex cursor-pointer items-center gap-2 rounded-md border bg-white px-3 py-1.5 text-sm hover:bg-gray-100 hover:text-gray-900 focus:outline-none `}
>
{isMonthlyView ? "Monthly" : "Weekly"}
<ChevronDownIcon className="h-3 w-3" aria-hidden="true" />
</div>
}
>
<CustomMenu.MenuItem
onClick={() => {
setIsMonthlyView(true);
setCalendarDateRange({
startDate: startOfWeek(currentDate),
endDate: lastDayOfWeek(currentDate),
});
}}
className="w-52 text-sm text-gray-600"
>
<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);
setCalendarDateRange({
startDate: getCurrentWeekStartDate(currentDate),
endDate: getCurrentWeekEndDate(currentDate),
});
}}
className="w-52 text-sm text-gray-600"
>
<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-gray-200 py-2 px-1 text-sm text-gray-600">
<h4>Show weekends</h4>
<button
type="button"
className={`relative inline-flex h-3.5 w-6 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none ${
showWeekEnds ? "bg-green-500" : "bg-gray-200"
}`}
role="switch"
aria-checked={showWeekEnds}
onClick={() => setShowWeekEnds(!showWeekEnds)}
>
<span className="sr-only">Show weekends</span>
<span
aria-hidden="true"
className={`inline-block h-2.5 w-2.5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out ${
showWeekEnds ? "translate-x-2.5" : "translate-x-0"
}`}
/>
</button>
</div>
</CustomMenu>
</div>
</div>
<div
className={`grid auto-rows-[minmax(36px,1fr)] rounded-lg ${
showWeekEnds ? "grid-cols-7" : "grid-cols-5"
}`}
>
{weeks.map((date, index) => (
<div
key={index}
className={`flex items-center justify-start p-1.5 gap-2 border-gray-300 bg-gray-100 text-base font-medium text-gray-600 ${
!isMonthlyView
? showWeekEnds
? (index + 1) % 7 === 0
? ""
: "border-r"
: (index + 1) % 5 === 0
? ""
: "border-r"
: ""
}`}
>
<span>
{isMonthlyView ? formatDate(date, "eee").substring(0, 3) : formatDate(date, "eee")}
</span>
{!isMonthlyView && <span>{formatDate(date, "d")}</span>}
</div>
))}
</div>
<div
className={`grid h-full auto-rows-[minmax(170px,1fr)] ${
showWeekEnds ? "grid-cols-7" : "grid-cols-5"
} `}
>
{currentViewDaysData.map((date, index) => (
<StrictModeDroppable droppableId={date.date}>
{(provided, snapshot) => (
<div
key={index}
ref={provided.innerRef}
{...provided.droppableProps}
className={`flex flex-col gap-1.5 border-t border-gray-300 p-2.5 text-left text-sm font-medium hover:bg-gray-100 ${
showWeekEnds
? (index + 1) % 7 === 0
? ""
: "border-r"
: (index + 1) % 5 === 0
? ""
: "border-r"
}`}
>
{isMonthlyView && <span>{formatDate(new Date(date.date), "d")}</span>}
{date.issues.length > 0 &&
date.issues.map((issue: IIssue, index) => (
<Draggable draggableId={issue.id} index={index}>
{(provided, snapshot) => (
<div
key={index}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
className={`w-full cursor-pointer rounded bg-white p-1.5 hover:scale-105 ${
snapshot.isDragging ? "shadow-lg" : ""
}`}
>
<Link
href={`/${workspaceSlug}/projects/${issue?.project_detail?.id}/issues/${issue.id}`}
className="w-full"
>
{issue.name}
</Link>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</StrictModeDroppable>
))}
</div>
</div>
</DragDropContext>
);
};

View File

@ -0,0 +1 @@
export * from "./calendar"

View File

@ -12,7 +12,7 @@ import { SelectFilters } from "components/views";
// ui
import { CustomMenu } from "components/ui";
// icons
import { ChevronDownIcon, ListBulletIcon } from "@heroicons/react/24/outline";
import { ChevronDownIcon, ListBulletIcon, CalendarDaysIcon } from "@heroicons/react/24/outline";
import { Squares2X2Icon } from "@heroicons/react/20/solid";
// helpers
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
@ -27,8 +27,7 @@ export const IssuesFilterView: React.FC = () => {
const {
issueView,
setIssueViewToList,
setIssueViewToKanban,
setIssueView,
groupByProperty,
setGroupByProperty,
orderBy,
@ -54,7 +53,7 @@ export const IssuesFilterView: React.FC = () => {
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-gray-200 ${
issueView === "list" ? "bg-gray-200" : ""
}`}
onClick={() => setIssueViewToList()}
onClick={() => setIssueView("list")}
>
<ListBulletIcon className="h-4 w-4" />
</button>
@ -63,10 +62,19 @@ export const IssuesFilterView: React.FC = () => {
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-gray-200 ${
issueView === "kanban" ? "bg-gray-200" : ""
}`}
onClick={() => setIssueViewToKanban()}
onClick={() => setIssueView("kanban")}
>
<Squares2X2Icon className="h-4 w-4" />
</button>
<button
type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none duration-300 hover:bg-gray-200 ${
issueView === "calendar" ? "bg-gray-200" : ""
}`}
onClick={() => setIssueView("calendar")}
>
<CalendarDaysIcon className="h-4 w-4" />
</button>
</div>
<SelectFilters
filters={filters}

View File

@ -21,6 +21,7 @@ import StrictModeDroppable from "components/dnd/StrictModeDroppable";
import { CreateUpdateViewModal } from "components/views";
// ui
import { Avatar, EmptySpace, EmptySpaceItem, PrimaryButton, Spinner } from "components/ui";
import { CalendarView } from "./calendar-view";
// icons
import {
ListBulletIcon,
@ -751,7 +752,7 @@ export const IssuesView: React.FC<Props> = ({
isCompleted={isCompleted}
userAuth={userAuth}
/>
) : (
) : issueView === "kanban" ? (
<AllBoards
type={type}
states={states}
@ -771,6 +772,8 @@ export const IssuesView: React.FC<Props> = ({
isCompleted={isCompleted}
userAuth={userAuth}
/>
) : (
<CalendarView />
)}
</>
) : (

View File

@ -0,0 +1,22 @@
export const monthOptions = [
{ value: "1", label: "January" },
{ value: "2", label: "February" },
{ value: "3", label: "March" },
{ value: "4", label: "April" },
{ value: "5", label: "May" },
{ value: "6", label: "June" },
{ value: "7", label: "July" },
{ value: "8", label: "August" },
{ value: "9", label: "September" },
{ value: "10", label: "October" },
{ value: "11", label: "November" },
{ value: "12", label: "December" },
];
export const yearOptions = [
{ value: "2021", label: "2021" },
{ value: "2022", label: "2022" },
{ value: "2023", label: "2023" },
{ value: "2024", label: "2024" },
{ value: "2025", label: "2025" },
];

View File

@ -121,6 +121,9 @@ export const SUB_ISSUES = (issueId: string) => `SUB_ISSUES_${issueId.toUpperCase
// integrations
// Calendar
export const CALENDAR_ISSUES = (projectId: string) => `CALENDAR_ISSUES_${projectId}`;
// Pages
export const RECENT_PAGES_LIST = (projectId: string) =>
`RECENT_PAGES_LIST_${projectId.toUpperCase()}`;

View File

@ -12,6 +12,7 @@ import viewsService from "services/views.service";
// types
import {
IIssueFilterOptions,
TIssueViewOptions,
IProjectMember,
TIssueGroupByOptions,
TIssueOrderByOptions,
@ -22,7 +23,7 @@ import { USER_PROJECT_VIEW, VIEW_DETAILS } from "constants/fetch-keys";
export const issueViewContext = createContext<ContextType>({} as ContextType);
type IssueViewProps = {
issueView: "list" | "kanban";
issueView: TIssueViewOptions;
groupByProperty: TIssueGroupByOptions;
orderBy: TIssueOrderByOptions;
showEmptyGroups: boolean;
@ -48,12 +49,11 @@ type ContextType = IssueViewProps & {
setFilters: (filters: Partial<IIssueFilterOptions>, saveToServer?: boolean) => void;
resetFilterToDefault: () => void;
setNewFilterDefaultView: () => void;
setIssueViewToKanban: () => void;
setIssueViewToList: () => void;
setIssueView: (property: TIssueViewOptions) => void;
};
type StateType = {
issueView: "list" | "kanban";
issueView: TIssueViewOptions;
groupByProperty: TIssueGroupByOptions;
orderBy: TIssueOrderByOptions;
showEmptyGroups: boolean;
@ -227,66 +227,34 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> =
: null
);
const setIssueViewToKanban = useCallback(() => {
dispatch({
type: "SET_ISSUE_VIEW",
payload: {
issueView: "kanban",
},
});
dispatch({
type: "SET_GROUP_BY_PROPERTY",
payload: {
groupByProperty: "state",
},
});
if (!workspaceSlug || !projectId) return;
saveDataToServer(workspaceSlug as string, projectId as string, {
...state,
issueView: "kanban",
groupByProperty: "state",
});
}, [workspaceSlug, projectId, state]);
const setIssueViewToList = useCallback(() => {
dispatch({
type: "SET_ISSUE_VIEW",
payload: {
issueView: "list",
},
});
dispatch({
type: "SET_GROUP_BY_PROPERTY",
payload: {
groupByProperty: null,
},
});
if (!workspaceSlug || !projectId) return;
mutateMyViewProps((prevData) => {
if (!prevData) return prevData;
return {
...prevData,
view_props: {
...state,
issueView: "list",
groupByProperty: null,
const setIssueView = useCallback(
(property: TIssueViewOptions) => {
dispatch({
type: "SET_ISSUE_VIEW",
payload: {
issueView: property,
},
};
}, false);
});
saveDataToServer(workspaceSlug as string, projectId as string, {
...state,
issueView: "list",
groupByProperty: null,
});
}, [workspaceSlug, projectId, state, mutateMyViewProps]);
if (property === "kanban") {
dispatch({
type: "SET_GROUP_BY_PROPERTY",
payload: {
groupByProperty: "state",
},
});
}
if (!workspaceSlug || !projectId) return;
saveDataToServer(workspaceSlug as string, projectId as string, {
...state,
issueView: property,
groupByProperty: "state",
});
},
[workspaceSlug, projectId, state]
);
const setGroupByProperty = useCallback(
(property: TIssueGroupByOptions) => {
@ -492,8 +460,7 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> =
setFilters,
resetFilterToDefault: resetToDefault,
setNewFilterDefaultView: setNewDefaultView,
setIssueViewToKanban,
setIssueViewToList,
setIssueView,
}}
>
<ToastAlert />

View File

@ -0,0 +1,161 @@
export const startOfWeek = (date: Date) => {
const startOfMonthDate = new Date(date.getFullYear(), date.getMonth(), 1);
const dayOfWeek = startOfMonthDate.getDay() % 7;
const startOfWeekDate = new Date(
startOfMonthDate.getFullYear(),
startOfMonthDate.getMonth(),
startOfMonthDate.getDate() - dayOfWeek
);
const timezoneOffset = startOfMonthDate.getTimezoneOffset();
const timezoneOffsetMilliseconds = timezoneOffset * 60 * 1000;
const startOfWeekAdjusted = new Date(startOfWeekDate.getTime() - timezoneOffsetMilliseconds);
return startOfWeekAdjusted;
};
export const lastDayOfWeek = (date: Date) => {
const lastDayOfPreviousMonth = new Date(date.getFullYear(), date.getMonth() + 1, 0);
const dayOfWeek = lastDayOfPreviousMonth.getDay() % 7;
const daysUntilEndOfWeek = 6 - dayOfWeek;
const lastDayOfWeekDate = new Date(
lastDayOfPreviousMonth.getFullYear(),
lastDayOfPreviousMonth.getMonth(),
lastDayOfPreviousMonth.getDate() + daysUntilEndOfWeek
);
const timezoneOffset = lastDayOfPreviousMonth.getTimezoneOffset();
const timezoneOffsetMilliseconds = timezoneOffset * 60 * 1000;
const lastDayOfWeekAdjusted = new Date(lastDayOfWeekDate.getTime() - timezoneOffsetMilliseconds);
return lastDayOfWeekAdjusted;
};
export const getCurrentWeekStartDate = (date: Date) => {
const today = new Date(date);
const dayOfWeek = today.getDay();
const startOfWeek = new Date(today.getFullYear(), today.getMonth(), today.getDate() - dayOfWeek);
const timezoneOffset = startOfWeek.getTimezoneOffset();
const timezoneOffsetMilliseconds = timezoneOffset * 60 * 1000;
const startOfWeekAdjusted = new Date(startOfWeek.getTime() - timezoneOffsetMilliseconds);
return startOfWeekAdjusted;
};
export const getCurrentWeekEndDate = (date: Date) => {
const today = new Date(date);
const dayOfWeek = today.getDay();
const endOfWeek = new Date(
today.getFullYear(),
today.getMonth(),
today.getDate() + (6 - dayOfWeek)
);
const timezoneOffset = endOfWeek.getTimezoneOffset();
const timezoneOffsetMilliseconds = timezoneOffset * 60 * 1000;
const endOfWeekAdjusted = new Date(endOfWeek.getTime() - timezoneOffsetMilliseconds);
return endOfWeekAdjusted;
};
export const eachDayOfInterval = ({ start, end }: { start: Date; end: Date }) => {
const days = [];
const current = new Date(start);
while (current <= end) {
days.push(new Date(current));
current.setDate(current.getDate() + 1);
}
return days;
};
export const weekDayInterval = ({ start, end }: { start: Date; end: Date }) => {
const dates = [];
const currentDate = new Date(start);
const endDate = new Date(end);
while (currentDate <= endDate) {
const dayOfWeek = currentDate.getDay();
if (dayOfWeek !== 0 && dayOfWeek !== 6) {
dates.push(new Date(currentDate));
}
currentDate.setDate(currentDate.getDate() + 1);
}
return dates;
};
export const formatDate = (date: Date, format: string): string => {
const day = date.getDate();
const month = date.getMonth() + 1;
const year = date.getFullYear();
const hours = date.getHours();
const minutes = date.getMinutes();
const seconds = date.getSeconds();
const daysOfWeek = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
const monthsOfYear = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const formattedDate = format
.replace("dd", day.toString().padStart(2, "0"))
.replace("d", day.toString())
.replace("eee", daysOfWeek[date.getDay()])
.replace("Month", monthsOfYear[month - 1])
.replace("yyyy", year.toString())
.replace("yyy", year.toString().slice(-3))
.replace("hh", hours.toString().padStart(2, "0"))
.replace("mm", minutes.toString().padStart(2, "0"))
.replace("ss", seconds.toString().padStart(2, "0"));
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) => {
const year = parseInt(yearString);
const month = date.getMonth();
const day = date.getDate();
return new Date(year, month, day);
};
export const updateDateWithMonth = (monthString: string, date: Date) => {
const month = parseInt(monthString) - 1;
const year = date.getFullYear();
const day = date.getDate();
return new Date(year, month, day);
};
export const isSameMonth = (monthString: string, date: Date) => {
const month = parseInt(monthString) - 1;
return month === date.getMonth();
};
export const isSameYear = (yearString: string, date: Date) => {
const year = parseInt(yearString);
return year === date.getFullYear();
};
export const addSevenDaysToDate = (date: Date) => {
const currentDate = date;
const newDate = new Date(currentDate.setDate(currentDate.getDate() + 7));
return newDate;
};
export const subtract7DaysToDate = (date: Date) => {
const currentDate = date;
const newDate = new Date(currentDate.getTime() - 7 * 24 * 60 * 60 * 1000);
return newDate;
};

View File

@ -36,8 +36,7 @@ const useIssuesView = () => {
setFilters,
resetFilterToDefault,
setNewFilterDefaultView,
setIssueViewToKanban,
setIssueViewToList,
setIssueView
} = useContext(issueViewContext);
const router = useRouter();
@ -147,8 +146,7 @@ const useIssuesView = () => {
isNotEmpty: !isEmpty,
resetFilterToDefault,
setNewFilterDefaultView,
setIssueViewToKanban,
setIssueViewToList,
setIssueView,
} as const;
};

View File

@ -240,6 +240,8 @@ export interface IIssueFilterOptions {
created_by: string[] | null;
}
export type TIssueViewOptions = "list" | "kanban" | "calendar";
export type TIssueGroupByOptions = "state" | "priority" | "labels" | "created_by" | null;
export type TIssueOrderByOptions = "-created_at" | "updated_at" | "priority" | "sort_order";

View File

@ -5,7 +5,8 @@ import type {
IWorkspaceLite,
TIssueGroupByOptions,
TIssueOrderByOptions,
} from "types";
TIssueViewOptions,
} from "./";
export interface IProject {
cover_image: string | null;
@ -50,7 +51,7 @@ export interface IFavoriteProject {
}
type ProjectViewTheme = {
issueView: "list" | "kanban";
issueView: TIssueViewOptions;
groupByProperty: TIssueGroupByOptions;
orderBy: TIssueOrderByOptions;
filters: IIssueFilterOptions;