Merge pull request #36 from linagora/30-temporal-navigation
temporal navigation
This commit is contained in:
@@ -19,12 +19,3 @@
|
|||||||
.App-link {
|
.App-link {
|
||||||
color: #61dafb;
|
color: #61dafb;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes App-logo-spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ main {
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar{
|
.sidebar {
|
||||||
border-right: 2px gray;
|
border-right: 2px gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,3 +182,13 @@ main {
|
|||||||
.calendar-list li.personal::before {
|
.calendar-list li.personal::before {
|
||||||
background-color: #d93025;
|
background-color: #d93025;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.event-dot {
|
||||||
|
margin-top: 2px;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background-color: #d93025; /* adjust to your theme */
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ import { CalendarEvent } from "../../features/Events/EventsTypes";
|
|||||||
import CalendarSelection from "./CalendarSelection";
|
import CalendarSelection from "./CalendarSelection";
|
||||||
import { getCalendarDetailAsync } from "../../features/Calendars/CalendarSlice";
|
import { getCalendarDetailAsync } from "../../features/Calendars/CalendarSlice";
|
||||||
import ImportAlert from "../../features/Events/ImportAlert";
|
import ImportAlert from "../../features/Events/ImportAlert";
|
||||||
|
import {
|
||||||
|
formatDateToYYYYMMDDTHHMMSS,
|
||||||
|
getCalendarRange,
|
||||||
|
} from "../../utils/dateUtils";
|
||||||
|
import { Calendars } from "../../features/Calendars/CalendarTypes";
|
||||||
|
|
||||||
export default function CalendarApp() {
|
export default function CalendarApp() {
|
||||||
const calendarRef = useRef<CalendarApi | null>(null);
|
const calendarRef = useRef<CalendarApi | null>(null);
|
||||||
@@ -24,51 +29,63 @@ export default function CalendarApp() {
|
|||||||
const pending = useAppSelector((state) => state.calendars.pending);
|
const pending = useAppSelector((state) => state.calendars.pending);
|
||||||
const userId = useAppSelector((state) => state.user.userData.openpaasId);
|
const userId = useAppSelector((state) => state.user.userData.openpaasId);
|
||||||
|
|
||||||
|
const userPersonnalCalendars: Calendars[] = useAppSelector((state) =>
|
||||||
|
Object.keys(state.calendars.list).map((id) => {
|
||||||
|
if (id.split("/")[0] === userId) {
|
||||||
|
return state.calendars.list[id];
|
||||||
|
}
|
||||||
|
return {} as Calendars;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
let personnalEvents: CalendarEvent[] = [];
|
||||||
|
Object.keys(userPersonnalCalendars).forEach((value, id) => {
|
||||||
|
if (userPersonnalCalendars[id].events) {
|
||||||
|
personnalEvents = personnalEvents.concat(
|
||||||
|
Object.keys(userPersonnalCalendars[id].events).map(
|
||||||
|
(eventid) => userPersonnalCalendars[id].events[eventid]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
const [selectedCalendars, setSelectedCalendars] = useState<string[]>(
|
const [selectedCalendars, setSelectedCalendars] = useState<string[]>(
|
||||||
Object.keys(calendars).filter((id) => id.split("/")[0] === userId)
|
Object.keys(calendars).filter((id) => id.split("/")[0] === userId)
|
||||||
);
|
);
|
||||||
const fetchedIdsRef = useRef<Set<string>>(new Set());
|
const fetchedIdsRef = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
|
const calendarRange = getCalendarRange(selectedDate);
|
||||||
|
|
||||||
|
// Create a stable string key for the range
|
||||||
|
const rangeKey = `${formatDateToYYYYMMDDTHHMMSS(
|
||||||
|
calendarRange.start
|
||||||
|
)}_${formatDateToYYYYMMDDTHHMMSS(calendarRange.end)}`;
|
||||||
|
|
||||||
let filteredEvents: CalendarEvent[] = [];
|
let filteredEvents: CalendarEvent[] = [];
|
||||||
selectedCalendars.forEach((id) => {
|
selectedCalendars.forEach((id) => {
|
||||||
filteredEvents = filteredEvents.concat(calendars[id].events);
|
filteredEvents = filteredEvents
|
||||||
|
.concat(
|
||||||
|
Object.keys(calendars[id].events).map(
|
||||||
|
(eventid) => calendars[id].events[eventid]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.filter((event) => !(event.status === "CANCELLED"));
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
selectedCalendars.forEach((id) => {
|
selectedCalendars.forEach((id) => {
|
||||||
const events = calendars[id]?.events ?? [];
|
if (!pending && rangeKey) {
|
||||||
|
|
||||||
const isEmpty = events.length === 0;
|
|
||||||
const notFetched = !fetchedIdsRef.current.has(id);
|
|
||||||
|
|
||||||
if (isEmpty && notFetched && !pending) {
|
|
||||||
dispatch(
|
dispatch(
|
||||||
getCalendarDetailAsync({
|
getCalendarDetailAsync({
|
||||||
access_token: tokens.access_token,
|
access_token: tokens.access_token,
|
||||||
calId: id,
|
calId: id,
|
||||||
|
match: {
|
||||||
|
start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start),
|
||||||
|
end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
fetchedIdsRef.current.add(id);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [selectedCalendars, calendars, pending, tokens.access_token, dispatch]);
|
}, [rangeKey, selectedCalendars]);
|
||||||
|
|
||||||
const [date, setDate] = useState(new Date());
|
|
||||||
|
|
||||||
const months = [
|
|
||||||
"January",
|
|
||||||
"February",
|
|
||||||
"March",
|
|
||||||
"April",
|
|
||||||
"May",
|
|
||||||
"June",
|
|
||||||
"July",
|
|
||||||
"August",
|
|
||||||
"September",
|
|
||||||
"October",
|
|
||||||
"November",
|
|
||||||
"December",
|
|
||||||
];
|
|
||||||
|
|
||||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
||||||
const [anchorElCal, setAnchorElCal] = useState<HTMLElement | null>(null);
|
const [anchorElCal, setAnchorElCal] = useState<HTMLElement | null>(null);
|
||||||
@@ -92,25 +109,39 @@ export default function CalendarApp() {
|
|||||||
<div className="calendar-label">
|
<div className="calendar-label">
|
||||||
<div className="calendar-label">
|
<div className="calendar-label">
|
||||||
<span>
|
<span>
|
||||||
{months[date.getMonth()]} {date.getFullYear()}
|
{selectedDate.toLocaleDateString("us-us", {
|
||||||
|
month: "long",
|
||||||
|
year: "numeric",
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setDate(new Date(date.getFullYear(), date.getMonth() - 1))
|
setSelectedDate(
|
||||||
|
new Date(
|
||||||
|
selectedDate.getFullYear(),
|
||||||
|
selectedDate.getMonth() - 1
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<
|
<
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
setDate(new Date(date.getFullYear(), date.getMonth() + 1))
|
setSelectedDate(
|
||||||
|
new Date(
|
||||||
|
selectedDate.getFullYear(),
|
||||||
|
selectedDate.getMonth() + 1
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ReactCalendar
|
<ReactCalendar
|
||||||
|
key={selectedDate.toDateString()}
|
||||||
showNeighboringMonth={false}
|
showNeighboringMonth={false}
|
||||||
calendarType="hebrew"
|
calendarType="hebrew"
|
||||||
formatShortWeekday={(locale, date) =>
|
formatShortWeekday={(locale, date) =>
|
||||||
@@ -124,6 +155,17 @@ export default function CalendarApp() {
|
|||||||
prevLabel={null}
|
prevLabel={null}
|
||||||
nextLabel={null}
|
nextLabel={null}
|
||||||
showNavigation={false}
|
showNavigation={false}
|
||||||
|
tileContent={({ date }) => {
|
||||||
|
const hasEvents = personnalEvents.some((event) => {
|
||||||
|
const eventDate = new Date(event.start);
|
||||||
|
return (
|
||||||
|
eventDate.getFullYear() === date.getFullYear() &&
|
||||||
|
eventDate.getMonth() === date.getMonth() &&
|
||||||
|
eventDate.getDate() === date.getDate()
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return hasEvents ? <div className="event-dot" /> : null;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<CalendarSelection
|
<CalendarSelection
|
||||||
selectedCalendars={selectedCalendars}
|
selectedCalendars={selectedCalendars}
|
||||||
@@ -156,13 +198,19 @@ export default function CalendarApp() {
|
|||||||
weekNumberFormat={{ week: "long" }}
|
weekNumberFormat={{ week: "long" }}
|
||||||
slotDuration={"00:30:00"}
|
slotDuration={"00:30:00"}
|
||||||
slotLabelInterval={"00:30:00"}
|
slotLabelInterval={"00:30:00"}
|
||||||
scrollTime={"07:00:00"}
|
scrollTime={"09:00:00"}
|
||||||
allDayText=""
|
allDayText=""
|
||||||
slotLabelFormat={{
|
slotLabelFormat={{
|
||||||
hour: "2-digit",
|
hour: "2-digit",
|
||||||
minute: "2-digit",
|
minute: "2-digit",
|
||||||
hour12: false,
|
hour12: false,
|
||||||
}}
|
}}
|
||||||
|
datesSet={(arg) => {
|
||||||
|
const today = new Date();
|
||||||
|
setSelectedDate(
|
||||||
|
today > arg.start && today < arg.end ? today : arg.start
|
||||||
|
);
|
||||||
|
}}
|
||||||
dayHeaderContent={(arg) => {
|
dayHeaderContent={(arg) => {
|
||||||
const date = arg.date.getDate();
|
const date = arg.date.getDate();
|
||||||
const weekDay = arg.date
|
const weekDay = arg.date
|
||||||
|
|||||||
@@ -79,7 +79,3 @@
|
|||||||
left: 1511px;
|
left: 1511px;
|
||||||
gap: 24px;
|
gap: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive for mobile */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ export async function getCalendars(userId: string, opaque_token: string) {
|
|||||||
return calendars;
|
return calendars;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCalendar(id: string, opaque_token: string) {
|
export async function getCalendar(
|
||||||
|
id: string,
|
||||||
|
opaque_token: string,
|
||||||
|
match: { start: string; end: string }
|
||||||
|
) {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${(window as any).CALENDAR_BASE_URL}/dav/calendars/${id}.json`,
|
`${(window as any).CALENDAR_BASE_URL}/dav/calendars/${id}.json`,
|
||||||
{
|
{
|
||||||
@@ -24,7 +28,7 @@ export async function getCalendar(id: string, opaque_token: string) {
|
|||||||
Authorization: `Bearer ${opaque_token}`,
|
Authorization: `Bearer ${opaque_token}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
match: { start: "20250525T000000", end: "20250708T000000" },
|
match,
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const getCalendarsListAsync = createAsyncThunk<
|
|||||||
: cal._links.self.href.replace("/calendars/", "").replace(".json", "");
|
: cal._links.self.href.replace("/calendars/", "").replace(".json", "");
|
||||||
|
|
||||||
const color = cal["apple:color"];
|
const color = cal["apple:color"];
|
||||||
importedCalendars[id] = { id, name, description, color, events: [] };
|
importedCalendars[id] = { id, name, description, color, events: {} };
|
||||||
}
|
}
|
||||||
|
|
||||||
return importedCalendars;
|
return importedCalendars;
|
||||||
@@ -32,14 +32,16 @@ export const getCalendarsListAsync = createAsyncThunk<
|
|||||||
|
|
||||||
export const getCalendarDetailAsync = createAsyncThunk<
|
export const getCalendarDetailAsync = createAsyncThunk<
|
||||||
{ calId: string; events: CalendarEvent[] }, // Return type
|
{ calId: string; events: CalendarEvent[] }, // Return type
|
||||||
{ access_token: string; calId: string } // Arg type
|
{ access_token: string; calId: string; match: { start: string; end: string } } // Arg type
|
||||||
>("calendars/getCalendarDetails", async ({ access_token, calId }) => {
|
>("calendars/getCalendarDetails", async ({ access_token, calId, match }) => {
|
||||||
const calendar = await getCalendar(calId, access_token);
|
const calendar = await getCalendar(calId, access_token, match);
|
||||||
const color = calendar["apple:color"];
|
const color = calendar["apple:color"];
|
||||||
const events: CalendarEvent[] = calendar._embedded["dav:item"].map(
|
const events: CalendarEvent[] = calendar._embedded["dav:item"].flatMap(
|
||||||
(eventdata: any) => {
|
(eventdata: any) => {
|
||||||
const datas = eventdata.data[2][0][1];
|
const vevents = eventdata.data[2] as any[][]; // array of ['vevent', RawEntry[], []]
|
||||||
return parseCalendarEvent(datas, color, calId);
|
return vevents.map((vevent: any[]) => {
|
||||||
|
return parseCalendarEvent(vevent[1], color, calId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -56,28 +58,25 @@ const CalendarSlice = createSlice({
|
|||||||
state.list[id].name = action.payload.name;
|
state.list[id].name = action.payload.name;
|
||||||
state.list[id].color = action.payload.color;
|
state.list[id].color = action.payload.color;
|
||||||
state.list[id].description = action.payload.description;
|
state.list[id].description = action.payload.description;
|
||||||
state.list[id].events = [];
|
state.list[id].events = {};
|
||||||
},
|
},
|
||||||
addEvent: (
|
addEvent: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ calendarUid: string; event: CalendarEvent }>
|
action: PayloadAction<{ calendarUid: string; event: CalendarEvent }>
|
||||||
) => {
|
) => {
|
||||||
if (!state.list[action.payload.calendarUid].events) {
|
if (!state.list[action.payload.calendarUid].events) {
|
||||||
state.list[action.payload.calendarUid].events = [];
|
state.list[action.payload.calendarUid].events = {};
|
||||||
}
|
}
|
||||||
state.list[action.payload.calendarUid].events.push(action.payload.event);
|
state.list[action.payload.calendarUid].events[action.payload.event.uid] =
|
||||||
|
action.payload.event;
|
||||||
},
|
},
|
||||||
removeEvent: (
|
removeEvent: (
|
||||||
state,
|
state,
|
||||||
action: PayloadAction<{ calendarUid: string; eventUid: string }>
|
action: PayloadAction<{ calendarUid: string; eventUid: string }>
|
||||||
) => {
|
) => {
|
||||||
state.list[action.payload.calendarUid as string].events = state.list[
|
delete state.list[action.payload.calendarUid].events[
|
||||||
action.payload.calendarUid
|
action.payload.eventUid
|
||||||
].events.filter((event) => {
|
];
|
||||||
if (event.uid !== action.payload.calendarUid) {
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
@@ -101,12 +100,15 @@ const CalendarSlice = createSlice({
|
|||||||
if (!state.list[action.payload.calId]) {
|
if (!state.list[action.payload.calId]) {
|
||||||
state.list[action.payload.calId] = {
|
state.list[action.payload.calId] = {
|
||||||
id: action.payload.calId,
|
id: action.payload.calId,
|
||||||
events: [] as CalendarEvent[],
|
events: {},
|
||||||
} as Calendars;
|
} as Calendars;
|
||||||
}
|
}
|
||||||
state.list[action.payload.calId].events = action.payload.events;
|
action.payload.events.forEach((event) => {
|
||||||
state.list[action.payload.calId].events.forEach((event) => {
|
state.list[action.payload.calId].events[event.uid] = event;
|
||||||
event.color = state.list[action.payload.calId].color;
|
});
|
||||||
|
Object.keys(state.list[action.payload.calId].events).forEach((id) => {
|
||||||
|
state.list[action.payload.calId].events[id].color =
|
||||||
|
state.list[action.payload.calId].color;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,5 +8,5 @@ export interface Calendars {
|
|||||||
description?: string;
|
description?: string;
|
||||||
calscale?: string;
|
calscale?: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
events: CalendarEvent[];
|
events: Record<string, CalendarEvent>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,4 +17,5 @@ export interface CalendarEvent {
|
|||||||
color?: string;
|
color?: string;
|
||||||
allday?: Boolean;
|
allday?: Boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
status?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,18 +18,24 @@ export default function ImportAlert() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Object.keys(calendars).map((calendarId) =>
|
{Object.keys(calendars).map((calendarId) =>
|
||||||
calendars[calendarId].events
|
Object.keys(calendars[calendarId].events)
|
||||||
.filter((event) => event.error)
|
.filter((id) => calendars[calendarId].events[id].error)
|
||||||
.map((event) => {
|
.map((id) => {
|
||||||
const isVisible = visibleAlerts[event.uid] ?? true; // default to visible
|
const isVisible =
|
||||||
|
visibleAlerts[calendars[calendarId].events[id].uid] ?? true; // default to visible
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Collapse in={isVisible} key={event.uid}>
|
<Collapse
|
||||||
|
in={isVisible}
|
||||||
|
key={calendars[calendarId].events[id].uid}
|
||||||
|
>
|
||||||
<Alert
|
<Alert
|
||||||
severity="error"
|
severity="error"
|
||||||
onClose={() => toggleEventAlert(event.uid)}
|
onClose={() =>
|
||||||
|
toggleEventAlert(calendars[calendarId].events[id].uid)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{event.error}
|
{calendars[calendarId].events[id].error}
|
||||||
</Alert>
|
</Alert>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export function parseCalendarEvent(
|
|||||||
calendarid: string
|
calendarid: string
|
||||||
): CalendarEvent {
|
): CalendarEvent {
|
||||||
const event: Partial<CalendarEvent> = { color, attendee: [] };
|
const event: Partial<CalendarEvent> = { color, attendee: [] };
|
||||||
|
let recurrenceId;
|
||||||
|
|
||||||
for (const [key, params, type, value] of data) {
|
for (const [key, params, type, value] of data) {
|
||||||
switch (key.toLowerCase()) {
|
switch (key.toLowerCase()) {
|
||||||
@@ -61,8 +62,16 @@ export function parseCalendarEvent(
|
|||||||
case "sequence":
|
case "sequence":
|
||||||
event.sequence = Number(value);
|
event.sequence = Number(value);
|
||||||
break;
|
break;
|
||||||
|
case "recurrence-id":
|
||||||
|
recurrenceId = value;
|
||||||
|
break;
|
||||||
|
case "status":
|
||||||
|
event.status = String(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (recurrenceId && event.uid) {
|
||||||
|
event.uid = `${event.uid}/${recurrenceId}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (!event.uid || !event.start) {
|
if (!event.uid || !event.start) {
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Provider } from "react-redux";
|
|||||||
import App from "./App";
|
import App from "./App";
|
||||||
import { store } from "./app/store";
|
import { store } from "./app/store";
|
||||||
|
|
||||||
import reportWebVitals from "./reportWebVitals";
|
|
||||||
const container = document.getElementById("root")!;
|
const container = document.getElementById("root")!;
|
||||||
|
|
||||||
const root = createRoot(container);
|
const root = createRoot(container);
|
||||||
@@ -15,5 +14,3 @@ root.render(
|
|||||||
</Provider>
|
</Provider>
|
||||||
</React.StrictMode>
|
</React.StrictMode>
|
||||||
);
|
);
|
||||||
|
|
||||||
reportWebVitals();
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
export function formatDateToYYYYMMDDTHHMMSS(date: Date) {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(date.getDate()).padStart(2, "0");
|
||||||
|
return `${year}${month}${day}T000000`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCalendarRange(date = new Date()) {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = date.getMonth();
|
||||||
|
|
||||||
|
const firstOfMonth = new Date(year, month, 1);
|
||||||
|
const dayOfWeekStart = firstOfMonth.getDay();
|
||||||
|
const diffToMonday = (dayOfWeekStart + 6) % 7;
|
||||||
|
const startDate = new Date(firstOfMonth);
|
||||||
|
startDate.setDate(firstOfMonth.getDate() - diffToMonday);
|
||||||
|
|
||||||
|
const lastOfMonth = new Date(year, month + 1, 0);
|
||||||
|
const dayOfWeekEnd = lastOfMonth.getDay();
|
||||||
|
|
||||||
|
const diffToSunday = (7 - dayOfWeekEnd) % 7;
|
||||||
|
const endDate = new Date(lastOfMonth);
|
||||||
|
endDate.setDate(lastOfMonth.getDate() + diffToSunday);
|
||||||
|
|
||||||
|
return {
|
||||||
|
start: startDate,
|
||||||
|
end: endDate,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user