From 06ea2d30fbb49d330ee617abc6a36cc7509fad02 Mon Sep 17 00:00:00 2001 From: hamo-o Date: Fri, 2 May 2025 18:10:27 +0900 Subject: [PATCH 01/11] =?UTF-8?q?feat:=20=EA=B0=9C=EC=9D=B8=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20sync=20API=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/my-calendar/api/index.ts | 4 ++++ frontend/src/features/my-calendar/api/keys.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/frontend/src/features/my-calendar/api/index.ts b/frontend/src/features/my-calendar/api/index.ts index fb35f4c5..4615194f 100644 --- a/frontend/src/features/my-calendar/api/index.ts +++ b/frontend/src/features/my-calendar/api/index.ts @@ -26,4 +26,8 @@ export const personalEventApi = { params: { syncWithGoogleCalendar: syncWithGoogleCalendar.toString() }, }); }, + syncPersonalEvent: async (): Promise => { + const response = await request.get('/api/v1/personal-event/sync'); + return response; + }, }; \ No newline at end of file diff --git a/frontend/src/features/my-calendar/api/keys.ts b/frontend/src/features/my-calendar/api/keys.ts index da9d5af1..7bdd2a86 100644 --- a/frontend/src/features/my-calendar/api/keys.ts +++ b/frontend/src/features/my-calendar/api/keys.ts @@ -2,6 +2,7 @@ import type { DateRangeParams } from '../model'; export const personalEventKeys = { all: ['personalEvents'], + sync: ['syncPersonalEvents'], detail: (data: DateRangeParams) => [...personalEventKeys.all, data], }; \ No newline at end of file From 1d000ac9f9dedc4960f8c886f78b212329157c50 Mon Sep 17 00:00:00 2001 From: hamo-o Date: Fri, 2 May 2025 19:14:08 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20=EA=B0=9C=EC=9D=B8=EC=9D=BC?= =?UTF-8?q?=EC=A0=95=20=EB=A1=B1=ED=8F=B4=EB=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/my-calendar/api/mutations.ts | 30 +++++++++++++++++++ .../my-calendar/ui/MyCalendar/index.tsx | 3 ++ frontend/src/utils/error/HTTPError.ts | 1 + 3 files changed, 34 insertions(+) diff --git a/frontend/src/features/my-calendar/api/mutations.ts b/frontend/src/features/my-calendar/api/mutations.ts index f378d8ad..7d57336f 100644 --- a/frontend/src/features/my-calendar/api/mutations.ts +++ b/frontend/src/features/my-calendar/api/mutations.ts @@ -1,4 +1,8 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useEffect } from 'react'; + +import { addNoti } from '@/store/global/notification'; +import { HTTPError } from '@/utils/error'; import type { PersonalEventRequest } from '../model'; import { personalEventApi } from '.'; @@ -52,4 +56,30 @@ export const usePersonalEventDeleteMutation = () => { }); return { mutate }; +}; + +export const usePersonalEventSyncMutation = () => { + const queryClient = useQueryClient(); + + const { mutate, isPending } = useMutation({ + mutationFn: personalEventApi.syncPersonalEvent, + onSuccess: (data) => { + // TODO: 변경 사항 이용해서 쿼리 업데이트 + mutate(); + }, + onError: (error) => { + if (error instanceof HTTPError) { + if (error.isTimeoutError()) { + mutate(); + return; + } + addNoti({ type: 'error', title: error.message }); + } + setTimeout(mutate, 1000); + }, + }); + + useEffect(() => { + if (!isPending) mutate(); + }, []); }; \ No newline at end of file diff --git a/frontend/src/features/my-calendar/ui/MyCalendar/index.tsx b/frontend/src/features/my-calendar/ui/MyCalendar/index.tsx index 40f236a8..b51d9eee 100644 --- a/frontend/src/features/my-calendar/ui/MyCalendar/index.tsx +++ b/frontend/src/features/my-calendar/ui/MyCalendar/index.tsx @@ -4,6 +4,7 @@ import { formatDateToWeekRange, isAllday } from '@/utils/date'; import { formatDateToBarString } from '@/utils/date/format'; import { calcSizeByDate } from '@/utils/date/position'; +import { usePersonalEventSyncMutation } from '../../api/mutations'; import { usePersonalEventsQuery } from '../../api/queries'; import type { PersonalEventResponse } from '../../model'; import { CalendarCard } from '../CalendarCard'; @@ -51,6 +52,8 @@ export const MyCalendar = () => { endDate: formatDateToBarString(endDate), }); + usePersonalEventSyncMutation(); + return ( diff --git a/frontend/src/utils/error/HTTPError.ts b/frontend/src/utils/error/HTTPError.ts index 9d38887b..93ff2333 100644 --- a/frontend/src/utils/error/HTTPError.ts +++ b/frontend/src/utils/error/HTTPError.ts @@ -18,6 +18,7 @@ export class HTTPError extends Error { } isUnAuthorizedError = () => this.#status === 401; + isTimeoutError = () => this.#status === 408; isForbiddenError = () => this.#status === 403; isTooManyRequestsError = () => this.#status === 429; From 826daacd4604815298821ba71302266d6da5479d Mon Sep 17 00:00:00 2001 From: hamo-o Date: Fri, 2 May 2025 19:42:34 +0900 Subject: [PATCH 03/11] =?UTF-8?q?feat:=20sync=20API=20=EC=9D=91=EB=8B=B5?= =?UTF-8?q?=20DTO=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/my-calendar/api/index.ts | 9 +++++++-- frontend/src/features/my-calendar/model/index.ts | 15 +++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/frontend/src/features/my-calendar/api/index.ts b/frontend/src/features/my-calendar/api/index.ts index 4615194f..e9a2e3f1 100644 --- a/frontend/src/features/my-calendar/api/index.ts +++ b/frontend/src/features/my-calendar/api/index.ts @@ -1,6 +1,11 @@ import { request } from '@/utils/fetch'; -import type { DateRangeParams, PersonalEventRequest, PersonalEventResponse } from '../model'; +import type { + DateRangeParams, + PersonalEventRequest, + PersonalEventResponse, + PersonalEventSyncResponse, +} from '../model'; export const personalEventApi = { getPersonalEvent: async ( @@ -26,7 +31,7 @@ export const personalEventApi = { params: { syncWithGoogleCalendar: syncWithGoogleCalendar.toString() }, }); }, - syncPersonalEvent: async (): Promise => { + syncPersonalEvent: async (): Promise => { const response = await request.get('/api/v1/personal-event/sync'); return response; }, diff --git a/frontend/src/features/my-calendar/model/index.ts b/frontend/src/features/my-calendar/model/index.ts index 77ca47d8..6d4c2ccb 100644 --- a/frontend/src/features/my-calendar/model/index.ts +++ b/frontend/src/features/my-calendar/model/index.ts @@ -16,9 +16,24 @@ const PersonalEventRequest = PersonalEventDTO.omit( { id: true, googleEventId: true, calendarId: true }, ); +const GooglePersonalEventDTO = PersonalEventDTO.pick({ + googleEventId: true, + title: true, + startDateTime: true, + endDateTime: true, +}).extend({ + status: z.string(), +}); + +const PersonalEventSyncResponse = z.object({ + events: z.array(GooglePersonalEventDTO).nullable(), + type: z.enum(['timeout', 'replaced', 'sync']), +}); + export type PersonalEventDTO = z.infer; export type PersonalEventResponse = z.infer; export type PersonalEventRequest = z.infer; +export type PersonalEventSyncResponse = z.infer; export interface DateRangeParams { startDate: string; From 3eaecca1e7452c38d27056bdf0378146009dccda Mon Sep 17 00:00:00 2001 From: hamo-o Date: Fri, 2 May 2025 21:23:45 +0900 Subject: [PATCH 04/11] =?UTF-8?q?fix:=20EndolphinDate=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4=EC=9D=98=20=EC=83=9D=EC=84=B1=EC=9E=90=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/endolphin-date/date.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/frontend/src/utils/endolphin-date/date.ts b/frontend/src/utils/endolphin-date/date.ts index ad5dc988..69ef5404 100644 --- a/frontend/src/utils/endolphin-date/date.ts +++ b/frontend/src/utils/endolphin-date/date.ts @@ -13,7 +13,7 @@ export default class EndolphinDate { #formateToDate (input: InputDate): Date | null { if (!input) return null; - if (input instanceof Date) return this.#date; + if (input instanceof Date) return input; if (typeof input === 'number') { return new Date(input); } @@ -59,4 +59,17 @@ export default class EndolphinDate { getDate () { return this.#date; } + + valueOf () { + return this.#date?.getTime() ?? 0; + } + + toString () { + return format.formatDateToBarString(this.#date); + } + + [Symbol.toPrimitive] (hint: string) { + if (hint === 'number') return this.valueOf(); + return this.toString(); + } } \ No newline at end of file From 6cedb67fbc21036a12ae53d6994045aeb9ef71c7 Mon Sep 17 00:00:00 2001 From: hamo-o Date: Fri, 2 May 2025 21:45:47 +0900 Subject: [PATCH 05/11] =?UTF-8?q?fix:=20=EC=84=A0=ED=83=9D=EB=90=9C=20?= =?UTF-8?q?=EC=A3=BC=EC=9D=98=20=EC=B2=AB=EC=A7=B8=EB=82=A0=EA=B3=BC=20?= =?UTF-8?q?=EB=A7=88=EC=A7=80=EB=A7=89=EB=82=A0=EC=9D=98=20=EC=8B=9C?= =?UTF-8?q?=EA=B0=84=20=EC=84=B8=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/utils/date/date.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/src/utils/date/date.ts b/frontend/src/utils/date/date.ts index 0efa23b5..7e5a8a68 100644 --- a/frontend/src/utils/date/date.ts +++ b/frontend/src/utils/date/date.ts @@ -109,9 +109,11 @@ export const formatDateToWeekRange = (date: Date): { const selected = new Date(date); const firstDateOfWeek = new Date(selected); firstDateOfWeek.setDate(firstDateOfWeek.getDate() - selected.getDay()); + firstDateOfWeek.setHours(0, 0, 0, 0); const lastDateOfWeek = new Date(firstDateOfWeek); lastDateOfWeek.setDate(firstDateOfWeek.getDate() + 6); + lastDateOfWeek.setHours(23, 59, 59, 999); return { startDate: firstDateOfWeek, endDate: lastDateOfWeek }; }; From 2eb876d685d7f9e9564d2b268bde67532dc1492c Mon Sep 17 00:00:00 2001 From: hamo-o Date: Fri, 2 May 2025 21:49:54 +0900 Subject: [PATCH 06/11] =?UTF-8?q?fix:=20=EA=B5=AC=EA=B8=80=20=EC=BA=98?= =?UTF-8?q?=EB=A6=B0=EB=8D=94=20DTO=20status=20=EA=B5=AC=EC=B2=B4=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/features/my-calendar/model/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/features/my-calendar/model/index.ts b/frontend/src/features/my-calendar/model/index.ts index 6d4c2ccb..2ea6b076 100644 --- a/frontend/src/features/my-calendar/model/index.ts +++ b/frontend/src/features/my-calendar/model/index.ts @@ -22,7 +22,7 @@ const GooglePersonalEventDTO = PersonalEventDTO.pick({ startDateTime: true, endDateTime: true, }).extend({ - status: z.string(), + status: z.enum(['tentative', 'confirmed', 'cancelled']), }); const PersonalEventSyncResponse = z.object({ From 28184c2935af25fa5f2fe99ee907646ba7411fbf Mon Sep 17 00:00:00 2001 From: hamo-o Date: Fri, 2 May 2025 21:50:30 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20=ED=98=84=EC=9E=AC=20=EB=B3=B4?= =?UTF-8?q?=EA=B3=A0=20=EC=9E=88=EB=8A=94=20=EB=8B=AC=EB=A0=A5=EC=97=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=82=AC=ED=95=AD=EC=9D=B4=20=EC=9E=88?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20revalidate=20=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/features/my-calendar/api/mutations.ts | 19 +++++++++++++++---- .../my-calendar/ui/MyCalendar/index.tsx | 3 ++- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/frontend/src/features/my-calendar/api/mutations.ts b/frontend/src/features/my-calendar/api/mutations.ts index 7d57336f..672d7032 100644 --- a/frontend/src/features/my-calendar/api/mutations.ts +++ b/frontend/src/features/my-calendar/api/mutations.ts @@ -2,6 +2,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useEffect } from 'react'; import { addNoti } from '@/store/global/notification'; +import { EndolphinDate } from '@/utils/endolphin-date'; import { HTTPError } from '@/utils/error'; import type { PersonalEventRequest } from '../model'; @@ -58,14 +59,24 @@ export const usePersonalEventDeleteMutation = () => { return { mutate }; }; -export const usePersonalEventSyncMutation = () => { +export const usePersonalEventSyncMutation = (sunday: EndolphinDate, saturday: EndolphinDate) => { const queryClient = useQueryClient(); const { mutate, isPending } = useMutation({ mutationFn: personalEventApi.syncPersonalEvent, - onSuccess: (data) => { - // TODO: 변경 사항 이용해서 쿼리 업데이트 - mutate(); + onSuccess: ({ events, type }) => { + if (type === 'sync') { + // TODO: 직접 쿼리 조작하기 + const updated = events?.some(({ startDateTime, endDateTime, status } ) => { + const start = new EndolphinDate(startDateTime); + const end = new EndolphinDate(endDateTime); + if (status === 'cancelled') return true; + if (end < sunday || start > saturday) return false; + return true; + }); + if (updated) queryClient.invalidateQueries({ queryKey: personalEventKeys.all }); + mutate(); + } }, onError: (error) => { if (error instanceof HTTPError) { diff --git a/frontend/src/features/my-calendar/ui/MyCalendar/index.tsx b/frontend/src/features/my-calendar/ui/MyCalendar/index.tsx index b51d9eee..a0ab4902 100644 --- a/frontend/src/features/my-calendar/ui/MyCalendar/index.tsx +++ b/frontend/src/features/my-calendar/ui/MyCalendar/index.tsx @@ -3,6 +3,7 @@ import { useSharedCalendarContext } from '@/components/Calendar/context/SharedCa import { formatDateToWeekRange, isAllday } from '@/utils/date'; import { formatDateToBarString } from '@/utils/date/format'; import { calcSizeByDate } from '@/utils/date/position'; +import { EndolphinDate } from '@/utils/endolphin-date'; import { usePersonalEventSyncMutation } from '../../api/mutations'; import { usePersonalEventsQuery } from '../../api/queries'; @@ -52,7 +53,7 @@ export const MyCalendar = () => { endDate: formatDateToBarString(endDate), }); - usePersonalEventSyncMutation(); + usePersonalEventSyncMutation(new EndolphinDate(startDate), new EndolphinDate(endDate)); return ( From 4c4855d1e40f4184a99d575c6f8e559b7b6582f6 Mon Sep 17 00:00:00 2001 From: hamo-o Date: Thu, 15 May 2025 17:53:51 +0900 Subject: [PATCH 08/11] =?UTF-8?q?chore:=20tanstack=20query=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=EC=9E=90=EB=8F=84=EA=B5=AC=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/package.json | 1 + frontend/pnpm-lock.yaml | 20 ++++++++++++++++++++ frontend/src/routes/__root.tsx | 2 ++ 3 files changed, 23 insertions(+) diff --git a/frontend/package.json b/frontend/package.json index 562444c3..0114d28b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ }, "dependencies": { "@tanstack/react-query": "^5.66.0", + "@tanstack/react-query-devtools": "^5.76.1", "@tanstack/react-router": "^1.109.2", "@vanilla-extract/css": "^1.17.0", "@vanilla-extract/dynamic": "^2.1.2", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 4f040cd1..23c3c231 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@tanstack/react-query': specifier: ^5.66.0 version: 5.66.0(react@19.0.0) + '@tanstack/react-query-devtools': + specifier: ^5.76.1 + version: 5.76.1(@tanstack/react-query@5.66.0(react@19.0.0))(react@19.0.0) '@tanstack/react-router': specifier: ^1.109.2 version: 1.109.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -1079,6 +1082,15 @@ packages: '@tanstack/query-core@5.66.0': resolution: {integrity: sha512-J+JeBtthiKxrpzUu7rfIPDzhscXF2p5zE/hVdrqkACBP8Yu0M96mwJ5m/8cPPYQE9aRNvXztXHlNwIh4FEeMZw==} + '@tanstack/query-devtools@5.76.0': + resolution: {integrity: sha512-1p92nqOBPYVqVDU0Ua5nzHenC6EGZNrLnB2OZphYw8CNA1exuvI97FVgIKON7Uug3uQqvH/QY8suUKpQo8qHNQ==} + + '@tanstack/react-query-devtools@5.76.1': + resolution: {integrity: sha512-LFVWgk/VtXPkerNLfYIeuGHh0Aim/k9PFGA+JxLdRaUiroQ4j4eoEqBrUpQ1Pd/KXoG4AB9vVE/M6PUQ9vwxBQ==} + peerDependencies: + '@tanstack/react-query': ^5.76.1 + react: ^18 || ^19 + '@tanstack/react-query@5.66.0': resolution: {integrity: sha512-z3sYixFQJe8hndFnXgWu7C79ctL+pI0KAelYyW+khaNJ1m22lWrhJU2QrsTcRKMuVPtoZvfBYrTStIdKo+x0Xw==} peerDependencies: @@ -4085,6 +4097,14 @@ snapshots: '@tanstack/query-core@5.66.0': {} + '@tanstack/query-devtools@5.76.0': {} + + '@tanstack/react-query-devtools@5.76.1(@tanstack/react-query@5.66.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@tanstack/query-devtools': 5.76.0 + '@tanstack/react-query': 5.66.0(react@19.0.0) + react: 19.0.0 + '@tanstack/react-query@5.66.0(react@19.0.0)': dependencies: '@tanstack/query-core': 5.66.0 diff --git a/frontend/src/routes/__root.tsx b/frontend/src/routes/__root.tsx index e370af94..bbf2ba88 100644 --- a/frontend/src/routes/__root.tsx +++ b/frontend/src/routes/__root.tsx @@ -1,4 +1,5 @@ import type { QueryClient } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { createRootRouteWithContext, HeadContent, @@ -32,6 +33,7 @@ export const Route = createRootRouteWithContext()({