diff --git a/biome.jsonc b/biome.jsonc index 973a95b..0efd1ca 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -33,7 +33,15 @@ "noUnusedTemplateLiteral": "error", "useNumberNamespace": "error", "noInferrableTypes": "error", - "noUselessElse": "error" + "noUselessElse": "error", + "noRestrictedImports": { + "level": "error", + "options": { + "paths": { + "dayjs": "dayjs は lib/dayjs を使用する" + } + } + } }, "nursery": { "useSortedClasses": "warn" diff --git a/client/package.json b/client/package.json index f75ad3e..8bfb0be 100644 --- a/client/package.json +++ b/client/package.json @@ -15,11 +15,13 @@ "@cloudflare/pages-plugin-vercel-og": "^0.1.2", "@fullcalendar/core": "^6.1.15", "@fullcalendar/interaction": "^6.1.15", + "@fullcalendar/moment-timezone": "^6.1.20", "@fullcalendar/react": "^6.1.15", "@fullcalendar/timegrid": "^6.1.15", "@hookform/resolvers": "^4.1.3", "@tailwindcss/vite": "^4.0.13", "dayjs": "^1.11.13", + "moment-timezone": "^0.5.48", "react": "^19.0.0", "react-dom": "^19.0.0", "react-hook-form": "^7.54.2", diff --git a/client/src/components/Calendar.tsx b/client/src/components/Calendar.tsx index 721f245..ba02cd9 100644 --- a/client/src/components/Calendar.tsx +++ b/client/src/components/Calendar.tsx @@ -1,8 +1,3 @@ -import interactionPlugin from "@fullcalendar/interaction"; -import FullCalendar from "@fullcalendar/react"; -import timeGridPlugin from "@fullcalendar/timegrid"; -import dayjs from "dayjs"; -import "dayjs/locale/ja"; import type { DateSelectArg, DateSpanApi, @@ -12,23 +7,26 @@ import type { EventMountArg, SlotLabelContentArg, } from "@fullcalendar/core/index.js"; +import interactionPlugin from "@fullcalendar/interaction"; +import momentTimezonePlugin from "@fullcalendar/moment-timezone"; +import FullCalendar from "@fullcalendar/react"; +import timeGridPlugin from "@fullcalendar/timegrid"; import type React from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Tooltip } from "react-tooltip"; import useCalendarScrollBlock from "../hooks/useCalendarScrollBlock"; import { EditingMatrix, ViewingMatrix } from "../lib/CalendarMatrix"; +import dayjs, { type Dayjs } from "../lib/dayjs"; import type { EditingSlot } from "../pages/eventId/Submission"; -dayjs.locale("ja"); - type AllowedRange = { - startTime: Date; - endTime: Date; + startTime: Dayjs; + endTime: Dayjs; }; type ViewingSlot = { - from: Date; - to: Date; + from: Dayjs; + to: Dayjs; guestId: string; optionId: string; }; @@ -55,8 +53,8 @@ type CalendarEvent = Pick { - const countDays = dayjs(endDate).startOf("day").diff(dayjs(startDate).startOf("day"), "day") + 1; + const countDays = endDate.startOf("day").diff(startDate.startOf("day"), "day") + 1; // TODO: +1 は不要かも const editingMatrixRef = useRef(new EditingMatrix(countDays + 1, startDate)); const viewingMatrixRef = useRef(new ViewingMatrix(countDays + 1, startDate)); // TODO: 現在は最初の選択範囲のみ。FullCalendar の制約により、複数の allowedRanges には対応できないため、のちに selectAllow などで独自実装が必要 const tmpAllowedRange = allowedRanges[0] ?? { - startTime: dayjs(new Date()).set("hour", 0).set("minute", 0).toDate(), - endTime: dayjs(new Date()).set("hour", 23).set("minute", 59).toDate(), + startTime: dayjs.utc().tz().set("hour", 0).set("minute", 0).toDate(), + endTime: dayjs.utc().tz().set("hour", 23).set("minute", 59).toDate(), }; const calendarRef = useRef(null); @@ -126,14 +124,14 @@ export const Calendar = ({ // editingSlots → editingMatrix editingMatrixRef.current.clear(); editingSlots.forEach((slot) => { - const { from, to } = getVertexes(slot.from, slot.to); + const { from, to } = normalizeVertexes(slot.from, slot.to); editingMatrixRef.current.setRange(from, to, slot.participationOptionId); }); viewingMatrixRef.current.clear(); viewingSlots.forEach((slot) => { - const { from, to } = getVertexes(slot.from, slot.to); + const { from, to } = normalizeVertexes(slot.from, slot.to); viewingMatrixRef.current.setGuestRange(from, to, slot.guestId, slot.optionId); }); @@ -147,8 +145,8 @@ export const Calendar = ({ return { id: `${EDITING_EVENT}-${index}`, className: EDITING_EVENT, - start: slot.from, - end: slot.to, + start: slot.from.format(), + end: slot.to.format(), textColor: "white", backgroundColor, borderColor: baseColor, @@ -218,8 +216,8 @@ export const Calendar = ({ viewingEvents.push({ id: `${VIEWING_EVENT}-${index}`, className: `${VIEWING_EVENT} ${VIEWING_EVENT}-${index}`, - start: slot.from, - end: slot.to, + start: slot.from.format(), + end: slot.to.format(), color: defaultColor, display: "background" as const, extendedProps: { @@ -316,18 +314,18 @@ export const Calendar = ({ dayHeaderContent: (args: DayHeaderContentArg) => { return (
-
{dayjs(args.date).format("M/D")}
-
{dayjs(args.date).format("(ddd)")}
+
{dayjs.utc(args.date).tz().format("M/D")}
+
{dayjs.utc(args.date).tz().format("(ddd)")}
); }, slotLabelContent: (args: SlotLabelContentArg) => { - return
{dayjs(args.date).format("HH:mm")}
; + return
{dayjs.utc(args.date).tz().format("HH:mm")}
; }, slotLabelInterval: "00:30:00", validRange: { - start: startDate, - end: endDate, + start: startDate.format(), + end: endDate.format(), }, expandRows: true, }, @@ -356,7 +354,7 @@ export const Calendar = ({ (info: DateSelectArg) => { if (!editMode) return; - const { from, to } = getVertexes(info.start, info.end); + const { from, to } = normalizeVertexes(dayjs.utc(info.start).tz(), dayjs.utc(info.end).tz()); if (isSelectionDeleting.current === null) return; const isDeletion = isSelectionDeleting.current; @@ -456,7 +454,7 @@ export const Calendar = ({ } if (info.event.classNames.includes(EDITING_EVENT)) { return ( -
{`${dayjs(info.event.start).format("HH:mm")} - ${dayjs(info.event.end).format("HH:mm")}`}
+
{`${dayjs.utc(info.event.start).tz().format("HH:mm")} - ${dayjs.utc(info.event.end).tz().format("HH:mm")}`}
); } }, []); @@ -472,14 +470,14 @@ export const Calendar = ({
info.end.getHours() || - (info.start.getHours() === info.end.getHours() && info.start.getMinutes() > info.end.getMinutes()) - ) { - [startTime, endTime] = [endTime, startTime]; - } - // 現在選択されている参加形態の色を取得 const currentOption = participationOptions.find((o) => o.id === currentParticipationOptionId); const baseColor = currentOption ? currentOption.color : `rgb(${PRIMARY_RGB.join(",")})`; @@ -561,10 +545,10 @@ function displaySelection( calendarApi.addEvent({ id: SELECT_EVENT, className: isSelectionDeleting.current ? DELETE_SELECT_EVENT : CREATE_SELECT_EVENT, - startTime: startTime, - endTime: endTime, - startRecur: info.start, - endRecur: info.end, + startTime: from.format("HH:mm"), + endTime: to.format("HH:mm"), + startRecur: from.startOf("day").format("YYYY-MM-DD"), + endRecur: to.startOf("day").add(1, "day").format("YYYY-MM-DD"), display: "background", backgroundColor: backgroundColor, borderColor: borderColor, @@ -573,22 +557,24 @@ function displaySelection( } /** - * 矩形選択した際の左上と右下の頂点を返す。from < to が前提 + * 矩形選択の始点・終点を、左上(=日付も時刻も早い)・右下(=日付も時刻も遅い)に正規化して返す。 + * FullCalendar の返す selection を矩形選択に利用するために使用。 + * なお FullCalendar は逆向きに選択した場合、時間順に入れ替えて from, to を渡してくるので from < to は常に満たされる */ -function getVertexes(from: Date, to: Date) { - if (from > to) { +function normalizeVertexes(from: Dayjs, to: Dayjs) { + if (!from.isBefore(to)) { throw new Error("from < to is required"); } - const needSwap = dayjs(from).format("HH:mm") > dayjs(to).format("HH:mm"); - if (!needSwap) { + const fromTime = from.hour() * 60 + from.minute(); + const toTime = to.hour() * 60 + to.minute(); + + if (fromTime < toTime) { + // from の時刻 < to の時刻なら、そのまま返す return { from, to }; } - - const fromMinute = dayjs(from).hour() * 60 + dayjs(from).minute(); - const toMinute = dayjs(to).hour() * 60 + dayjs(to).minute(); - - return { - from: dayjs(from).startOf("day").add(toMinute, "minute").toDate(), - to: dayjs(to).startOf("day").add(fromMinute, "minute").toDate(), - }; + // from の時刻 >= to の時刻の場合、矩形選択の左上と右上の点を算出しそれを新たな from, to として返す。 + // fullcalendar は [from, to) で返してくるので、swap 時はそれぞれ 1 セル (=15分) ずらすことが必要。 + const newFrom = from.startOf("day").add(toTime, "minute").subtract(15, "minute"); + const newTo = to.startOf("day").add(fromTime, "minute").add(15, "minute"); + return { from: newFrom, to: newTo }; } diff --git a/client/src/lib/CalendarMatrix.ts b/client/src/lib/CalendarMatrix.ts index 040e4ad..cb24741 100644 --- a/client/src/lib/CalendarMatrix.ts +++ b/client/src/lib/CalendarMatrix.ts @@ -1,17 +1,14 @@ -import dayjs, { type Dayjs } from "dayjs"; -import "dayjs/locale/ja"; - -dayjs.locale("ja"); +import type { Dayjs } from "../lib/dayjs"; export type EditingMatrixSlot = { - from: Date; - to: Date; + from: Dayjs; + to: Dayjs; optionId: string; }; export type ViewingMatrixSlot = { - from: Date; - to: Date; + from: Dayjs; + to: Dayjs; guestIdToOptionId: Record; }; @@ -24,27 +21,27 @@ abstract class CalendarMatrixBase { * 15 分を 1 セルとしたセルの数 (96 = 24 * 4) */ protected readonly quarterCount = 96; - protected initialDate: Dayjs; + protected initialDatetime: Dayjs; - constructor(dayCount: number, initialDate: Date) { + constructor(dayCount: number, initialDate: Dayjs) { this.matrix = Array.from({ length: dayCount }, () => Array.from({ length: this.quarterCount }, () => null)); - this.initialDate = dayjs(initialDate).startOf("day"); + this.initialDatetime = initialDate.startOf("day"); } - protected getIndex(date: Date): [number, number] { - const totalMinutes = date.getHours() * 60 + date.getMinutes(); - const dayDiff = dayjs(date).startOf("day").diff(this.initialDate, "day"); + protected getIndex(dt: Dayjs): [number, number] { + const totalMinutes = dt.hour() * 60 + dt.minute(); + const dayDiff = dt.startOf("day").diff(this.initialDatetime, "day"); return [dayDiff, Math.floor(totalMinutes / 15)]; } - getIsSlotExist(date: Date): boolean { - const [row, col] = this.getIndex(date); + getIsSlotExist(dt: Dayjs): boolean { + const [row, col] = this.getIndex(dt); return this.matrix[row][col] !== null; } - setRange(from: Date, to: Date, newValue: T | null): void { + setRange(from: Dayjs, to: Dayjs, newValue: T | null): void { const [startRow, startCol] = this.getIndex(from); - const [endRow, endCol] = this.getIndex(dayjs(to).subtract(1, "minute").toDate()); + const [endRow, endCol] = this.getIndex(to.subtract(1, "minute")); for (let r = startRow; r <= endRow; r++) { for (let c = startCol; c <= endCol; c++) { this.matrix[r][c] = newValue; @@ -75,14 +72,8 @@ export class EditingMatrix extends CalendarMatrixBase { private convertRunsToSlots(runs: { start: number; end: number; value: string }[], day: number): EditingMatrixSlot[] { return runs.map((run) => { - const from = this.initialDate - .add(day, "day") - .add(run.start * 15, "minute") - .toDate(); - const to = this.initialDate - .add(day, "day") - .add(run.end * 15, "minute") - .toDate(); + const from = this.initialDatetime.add(day, "day").add(run.start * 15, "minute"); + const to = this.initialDatetime.add(day, "day").add(run.end * 15, "minute"); const optionId = run.value; return { from, to, optionId }; }); @@ -93,9 +84,9 @@ export class EditingMatrix extends CalendarMatrixBase { * 閲覧中イベントの {@link CalendarMatrixBase} */ export class ViewingMatrix extends CalendarMatrixBase> { - setGuestRange(from: Date, to: Date, guestId: string, optionId: string): void { + setGuestRange(from: Dayjs, to: Dayjs, guestId: string, optionId: string): void { const [startRow, startCol] = this.getIndex(from); - const [endRow, endCol] = this.getIndex(dayjs(to).subtract(1, "minute").toDate()); + const [endRow, endCol] = this.getIndex(to.subtract(1, "minute")); for (let r = startRow; r <= endRow; r++) { for (let c = startCol; c <= endCol; c++) { if (this.matrix[r][c] === null) { @@ -120,14 +111,8 @@ export class ViewingMatrix extends CalendarMatrixBase> { day: number, ): ViewingMatrixSlot[] { return runs.map((run) => { - const from = this.initialDate - .add(day, "day") - .add(run.start * 15, "minute") - .toDate(); - const to = this.initialDate - .add(day, "day") - .add(run.end * 15, "minute") - .toDate(); + const from = this.initialDatetime.add(day, "day").add(run.start * 15, "minute"); + const to = this.initialDatetime.add(day, "day").add(run.end * 15, "minute"); const guestIdToOptionId = run.value; return { from, to, guestIdToOptionId }; }); diff --git a/client/src/lib/dayjs.ts b/client/src/lib/dayjs.ts new file mode 100644 index 0000000..b66eaeb --- /dev/null +++ b/client/src/lib/dayjs.ts @@ -0,0 +1,14 @@ +// biome-ignore lint/style/noRestrictedImports: このファイルのものを使うための制約 +import dayjs from "dayjs"; +import timezone from "dayjs/plugin/timezone"; +import utc from "dayjs/plugin/utc"; +import "dayjs/locale/ja"; + +dayjs.extend(utc); +dayjs.extend(timezone); +dayjs.tz.setDefault("Asia/Tokyo"); // itsuhima では時刻x日付のマトリックスを矩形選択するため、座標計算等がタイムゾーンに依存する +dayjs.locale("ja"); + +export default dayjs; +// biome-ignore lint/style/noRestrictedImports: このファイルのものを使うための制約 +export type { Dayjs } from "dayjs"; diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index a5acdc4..45552c8 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -103,11 +103,11 @@ export default function HomePage() { function ProjectDashboard({ involvedProjects }: { involvedProjects: BriefProject[] }) { const hostedProjects = involvedProjects .filter((p) => p.isHost) - .sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()); + .sort((a, b) => b.startDate.valueOf() - a.startDate.valueOf()); const participatingProjects = involvedProjects .filter((p) => !p.isHost) - .sort((a, b) => new Date(b.startDate).getTime() - new Date(a.startDate).getTime()); + .sort((a, b) => b.startDate.valueOf() - a.startDate.valueOf()); return (
@@ -146,10 +146,6 @@ function ProjectSection({ title, icon, projects }: { title: string; icon: React. } function ProjectRow({ project, isLast }: { project: BriefProject; isLast: boolean }) { - const formatDate = (date: Date) => { - return date.toLocaleDateString("ja-JP", { year: "numeric", month: "short", day: "numeric" }); - }; - return ( - {formatDate(project.startDate)} 〜 {formatDate(project.endDate)} + {project.startDate.format("YYYY年M月D日")} 〜 {project.endDate.format("YYYY年M月D日")}
diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index bc1e21a..bbcc6b2 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -1,5 +1,4 @@ import { zodResolver } from "@hookform/resolvers/zod"; -import dayjs from "dayjs"; import { hc } from "hono/client"; import { useCallback, useEffect, useState } from "react"; import { useFieldArray, useForm } from "react-hook-form"; @@ -22,6 +21,7 @@ import { editReqSchema, projectReqSchema } from "../../../common/validators"; import type { AppType } from "../../../server/src/main"; import Header from "../components/Header"; import { EXTERNAL_LINKS } from "../constants/links"; +import dayjs from "../lib/dayjs"; import { projectReviver } from "../revivers"; import type { Project } from "../types"; import { API_ENDPOINT, FRONTEND_ORIGIN } from "../utils"; @@ -121,8 +121,8 @@ export default function ProjectPage() { defaultValues: { name: "", description: "", - startDate: eventId ? "" : dayjs().format("YYYY-MM-DD"), - endDate: eventId ? "" : dayjs().add(6, "day").format("YYYY-MM-DD"), + startDate: eventId ? "" : dayjs.tz().format("YYYY-MM-DD"), + endDate: eventId ? "" : dayjs.tz().add(6, "day").format("YYYY-MM-DD"), allowedRanges: [{ startTime: "08:00", endTime: "23:00" }], participationOptions: eventId ? [] @@ -160,12 +160,12 @@ export default function ProjectPage() { reset({ name: project.name, description: project.description, - startDate: dayjs(project.startDate).format("YYYY-MM-DD"), - endDate: dayjs(project.endDate).format("YYYY-MM-DD"), + startDate: project.startDate.tz().format("YYYY-MM-DD"), + endDate: project.endDate.tz().format("YYYY-MM-DD"), allowedRanges: [ { - startTime: dayjs(project.allowedRanges[0].startTime).format("HH:mm"), - endTime: dayjs(project.allowedRanges[0].endTime).format("HH:mm"), + startTime: project.allowedRanges[0].startTime.tz().format("HH:mm"), + endTime: project.allowedRanges[0].endTime.tz().format("HH:mm"), }, ], participationOptions: project.participationOptions.map((opt) => ({ @@ -180,21 +180,32 @@ export default function ProjectPage() { const onSubmit = async (data: FormSchemaType) => { setSubmitLoading(true); - // 日付をISO形式に変換 - const startDateTime = new Date(`${data.startDate}T00:00:00.000`).toISOString(); - const endDateTime = new Date(`${data.endDate}T23:59:59.999`).toISOString(); - - // range もISO形式に変換 - const rangeWithDateTime = data.allowedRanges?.map((range) => ({ - startTime: new Date(`${data.startDate}T${range.startTime}:00`).toISOString(), - endTime: new Date(`${data.startDate}T${range.endTime}:00`).toISOString(), - })); + const startDtISO = dayjs.tz(data.startDate).startOf("day").toISOString(); + const endDtISO = dayjs.tz(data.endDate).endOf("day").toISOString(); + + const rangeWithDateTime = data.allowedRanges?.map((range) => { + // 注: 現在のところ日付部分は使用していない。 + const startTime = dayjs + .tz(data.startDate) + .hour(Number(range.startTime.split(":")[0])) + .minute(Number(range.startTime.split(":")[1])) + .second(0); + const endTime = dayjs + .tz(data.endDate) + .hour(Number(range.endTime.split(":")[0])) + .minute(Number(range.endTime.split(":")[1])) + .second(0); + return { + startTime: startTime.toISOString(), + endTime: endTime.toISOString(), + }; + }); const eventData = { name: data.name ?? "", description: data.description ?? "", - startDate: startDateTime, - endDate: endDateTime, + startDate: startDtISO, + endDate: endDtISO, allowedRanges: rangeWithDateTime ?? [], participationOptions: (data.participationOptions ?? []).map((opt) => ({ id: opt.id, @@ -382,7 +393,17 @@ export default function ProjectPage() { {/* 時間帯 */}
-

時間帯

+
+

時間帯

+
+
+ 日本標準時 (JST) +
+
+
0 ? "tooltip tooltip-top w-full" : "w-full"} data-tip={ diff --git a/client/src/pages/eventId/Submission.tsx b/client/src/pages/eventId/Submission.tsx index 59df823..c52c2cd 100644 --- a/client/src/pages/eventId/Submission.tsx +++ b/client/src/pages/eventId/Submission.tsx @@ -294,7 +294,17 @@ export default function SubmissionPage() { {/* プロジェクト情報 */}
-

{project.name}

+
+

{project.name}

+
+
+ 日本標準時 (JST) +
+
+
{isHost && ( @@ -414,8 +424,8 @@ export default function SubmissionPage() { if (!guestName) return; postSubmissions( editingSlots.map((slot) => ({ - start: slot.from, - end: slot.to, + start: slot.from.toDate(), + end: slot.to.toDate(), participationOptionId: slot.participationOptionId, })), myGuestId ?? "", diff --git a/client/src/revivers.ts b/client/src/revivers.ts index a852863..6924949 100644 --- a/client/src/revivers.ts +++ b/client/src/revivers.ts @@ -1,4 +1,6 @@ // レスポンスの ISO 8601 文字列を Date に変換 + +import dayjs from "./lib/dayjs"; import type { BriefProject, ISOStringBriefProject, ISOStringProject, Project } from "./types"; /** @@ -9,12 +11,12 @@ import type { BriefProject, ISOStringBriefProject, ISOStringProject, Project } f export function projectReviver(project: ISOStringProject): Project { return { ...project, - startDate: new Date(project.startDate), - endDate: new Date(project.endDate), + startDate: dayjs.utc(project.startDate).tz(), + endDate: dayjs.utc(project.endDate).tz(), allowedRanges: project.allowedRanges.map((range) => ({ ...range, - startTime: new Date(range.startTime), - endTime: new Date(range.endTime), + startTime: dayjs.utc(range.startTime).tz(), + endTime: dayjs.utc(range.endTime).tz(), })), participationOptions: project.participationOptions.map((opt) => ({ ...opt })), hosts: project.hosts.map((host) => ({ ...host })), @@ -22,8 +24,8 @@ export function projectReviver(project: ISOStringProject): Project { ...guest, slots: guest.slots.map((slot) => ({ ...slot, - from: new Date(slot.from), - to: new Date(slot.to), + from: dayjs.utc(slot.from).tz(), + to: dayjs.utc(slot.to).tz(), })), })), meAsGuest: project.meAsGuest @@ -31,8 +33,8 @@ export function projectReviver(project: ISOStringProject): Project { ...project.meAsGuest, slots: project.meAsGuest.slots.map((slot) => ({ ...slot, - from: new Date(slot.from), - to: new Date(slot.to), + from: dayjs.utc(slot.from).tz(), + to: dayjs.utc(slot.to).tz(), })), } : null, @@ -47,7 +49,7 @@ export function projectReviver(project: ISOStringProject): Project { export function briefProjectReviver(project: ISOStringBriefProject): BriefProject { return { ...project, - startDate: new Date(project.startDate), - endDate: new Date(project.endDate), + startDate: dayjs.utc(project.startDate).tz(), + endDate: dayjs.utc(project.endDate).tz(), }; } diff --git a/client/src/types.ts b/client/src/types.ts index 132771e..d309300 100644 --- a/client/src/types.ts +++ b/client/src/types.ts @@ -1,3 +1,5 @@ +import type { Dayjs } from "./lib/dayjs"; + type ParticipationOption = { id: string; label: string; @@ -9,16 +11,16 @@ export type Slot = { id: string; projectId: string; guestId: string; - from: Date; - to: Date; + from: Dayjs; + to: Dayjs; participationOptionId: string; }; type AllowedRange = { id: string; projectId: string; - startTime: Date; - endTime: Date; + startTime: Dayjs; + endTime: Dayjs; }; type Host = { @@ -37,8 +39,8 @@ export type Project = { id: string; name: string; description: string; - startDate: Date; - endDate: Date; + startDate: Dayjs; + endDate: Dayjs; allowedRanges: AllowedRange[]; participationOptions: ParticipationOption[]; hosts: Host[]; diff --git a/package-lock.json b/package-lock.json index 74e6130..1035e58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,11 +22,13 @@ "@cloudflare/pages-plugin-vercel-og": "^0.1.2", "@fullcalendar/core": "^6.1.15", "@fullcalendar/interaction": "^6.1.15", + "@fullcalendar/moment-timezone": "^6.1.20", "@fullcalendar/react": "^6.1.15", "@fullcalendar/timegrid": "^6.1.15", "@hookform/resolvers": "^4.1.3", "@tailwindcss/vite": "^4.0.13", "dayjs": "^1.11.13", + "moment-timezone": "^0.5.48", "react": "^19.0.0", "react-dom": "^19.0.0", "react-hook-form": "^7.54.2", @@ -966,9 +968,9 @@ "license": "MIT" }, "node_modules/@fullcalendar/core": { - "version": "6.1.19", - "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.19.tgz", - "integrity": "sha512-z0aVlO5e4Wah6p6mouM0UEqtRf1MZZPt4mwzEyU6kusaNL+dlWQgAasF2cK23hwT4cmxkEmr4inULXgpyeExdQ==", + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.20.tgz", + "integrity": "sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==", "license": "MIT", "dependencies": { "preact": "~10.12.1" @@ -992,6 +994,16 @@ "@fullcalendar/core": "~6.1.19" } }, + "node_modules/@fullcalendar/moment-timezone": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/@fullcalendar/moment-timezone/-/moment-timezone-6.1.20.tgz", + "integrity": "sha512-fGk3bQU4hf0rgw3Zd/PH6Ok0Db+s9/nsuALj3IG8GYFqInwLsHZI0Qc+ljN8jv9LrLS5sOBBOZHWDg2ncx1inw==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.20", + "moment-timezone": "^0.5.40" + } + }, "node_modules/@fullcalendar/react": { "version": "6.1.19", "resolved": "https://registry.npmjs.org/@fullcalendar/react/-/react-6.1.19.tgz", @@ -2351,6 +2363,27 @@ "yallist": "^3.0.2" } }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.48", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", + "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", + "license": "MIT", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",