From 353c7190077af334df6cc0a1060a2d21c0aa2c4e Mon Sep 17 00:00:00 2001
From: kimtaewoo <70637743+kim3360@users.noreply.github.com>
Date: Wed, 24 Jun 2026 19:47:28 +0900
Subject: [PATCH 1/6] =?UTF-8?q?feat=20:=20tanstackquery=20devtools=20?=
=?UTF-8?q?=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=EC=B6=94?=
=?UTF-8?q?=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
package-lock.json | 44 +++++++++++++++++++++++++++++++------
package.json | 1 +
src/app/providers/index.tsx | 11 +++++++++-
3 files changed, 48 insertions(+), 8 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index 516104af..69f22928 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -28,6 +28,7 @@
"@storybook/addon-docs": "10.3.6",
"@storybook/addon-vitest": "10.3.6",
"@storybook/react-vite": "10.3.6",
+ "@tanstack/react-query-devtools": "^5.101.1",
"@types/node": "^24.10.1",
"@types/react": "^18.3.27",
"@types/react-dom": "^18.3.7",
@@ -3036,9 +3037,20 @@
}
},
"node_modules/@tanstack/query-core": {
- "version": "5.90.20",
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz",
- "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==",
+ "version": "5.101.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.1.tgz",
+ "integrity": "sha512-Y6Y92dkXtNqx67m2pMSxUsA3zOCwv862JexZRP8/EPwvKXMPu9m8rv43spiXWzOUIggQ3SQApttALStzhA8B4g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/query-devtools": {
+ "version": "5.101.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.101.1.tgz",
+ "integrity": "sha512-37RQ9U2PxlXQiv1era2t+uHgVhmiyvxqTMu30+KoVf0rufiucu6rpGRKFJk61Wh5OAZFKqCQd6lxTzFWfLZiuQ==",
+ "dev": true,
"license": "MIT",
"funding": {
"type": "github",
@@ -3046,18 +3058,36 @@
}
},
"node_modules/@tanstack/react-query": {
- "version": "5.90.21",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz",
- "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==",
+ "version": "5.101.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.1.tgz",
+ "integrity": "sha512-ZnONUuQKJe1bJMStXUL1s5uKN9FcfC28j5cK+iDZcdSHtUv1wtin1cGc/Oewhf2Oc4eKY7lggtpvT/AbMmhHew==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.101.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
+ "node_modules/@tanstack/react-query-devtools": {
+ "version": "5.101.1",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.101.1.tgz",
+ "integrity": "sha512-OXFR9XKdEslraq3cpl3kCUeNvTIq/xGWEZiFZdn2bLB/q4WxSALMEDKYZ5yYjMQytsfnQxwQYqV4qtVEf0nuog==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "@tanstack/query-core": "5.90.20"
+ "@tanstack/query-devtools": "5.101.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
+ "@tanstack/react-query": "^5.101.1",
"react": "^18 || ^19"
}
},
diff --git a/package.json b/package.json
index 52341968..5b1f3fe3 100644
--- a/package.json
+++ b/package.json
@@ -45,6 +45,7 @@
"@storybook/addon-docs": "10.3.6",
"@storybook/addon-vitest": "10.3.6",
"@storybook/react-vite": "10.3.6",
+ "@tanstack/react-query-devtools": "^5.101.1",
"@types/node": "^24.10.1",
"@types/react": "^18.3.27",
"@types/react-dom": "^18.3.7",
diff --git a/src/app/providers/index.tsx b/src/app/providers/index.tsx
index cfe90e2d..cda14c6a 100644
--- a/src/app/providers/index.tsx
+++ b/src/app/providers/index.tsx
@@ -1,5 +1,6 @@
import { useEffect, type ReactNode } from 'react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
+import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { initKakaoSDK, initAppleSDK } from '@/shared/lib/socialLogin'
const queryClient = new QueryClient()
@@ -55,6 +56,14 @@ export function AppProviders({ children }: AppProvidersProps) {
}, [])
return (
- {children}
+
+ {children}
+ {import.meta.env.DEV ? (
+
+ ) : null}
+
)
}
From 20431ab536570c202859ca6782460b44a7806f8f Mon Sep 17 00:00:00 2001
From: kimtaewoo <70637743+kim3360@users.noreply.github.com>
Date: Wed, 24 Jun 2026 19:48:46 +0900
Subject: [PATCH 2/6] =?UTF-8?q?fix=20:=20G3=20=EA=B7=BC=EB=AC=B4=EC=8B=9C?=
=?UTF-8?q?=EA=B0=84=20=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20=EB=B6=80?=
=?UTF-8?q?=EB=B6=84=20=EC=8A=A4=EC=BC=80=EC=A4=84=20=EC=8B=9C=EA=B0=84=20?=
=?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=EB=A1=9C=20=EB=B3=80=EA=B2=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../hooks/useSubstituteRequestFlow.ts | 72 +++++----
.../components/SubstituteRequestModalFlow.tsx | 146 ++++++------------
2 files changed, 88 insertions(+), 130 deletions(-)
diff --git a/src/features/user/home/workspace/hooks/useSubstituteRequestFlow.ts b/src/features/user/home/workspace/hooks/useSubstituteRequestFlow.ts
index d439effe..e2404276 100644
--- a/src/features/user/home/workspace/hooks/useSubstituteRequestFlow.ts
+++ b/src/features/user/home/workspace/hooks/useSubstituteRequestFlow.ts
@@ -2,7 +2,10 @@ import { useMemo, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import { format, parse } from 'date-fns'
-import type { CalendarViewData } from '@/features/home/common/schedule/types/calendarView'
+import type {
+ CalendarEvent,
+ CalendarViewData,
+} from '@/features/home/common/schedule/types/calendarView'
import { DATE_KEY_FORMAT } from '@/features/home/common/schedule/constants/calendar'
import {
adaptExchangeableSchedulesToCalendar,
@@ -11,6 +14,7 @@ import {
import { getExchangeableWorkers } from '@/features/user/home/workspace/api/exchangeableWorkers'
import { createSubstituteRequest } from '@/features/user/home/workspace/api/substituteRequests'
import { WEEKDAY_LABELS } from '@/shared/constants/calendar'
+import { splitClockToParts } from '@/shared/lib/clock'
import { getAxiosErrorMessage } from '@/shared/lib/getAxiosErrorMessage'
import { queryKeys } from '@/shared/lib/queryKeys'
@@ -44,10 +48,10 @@ function workerIdFromCandidateKey(key: string): number | undefined {
return Number.isFinite(id) ? id : undefined
}
-function pickScheduleIdForSelectedDate(
+function pickSelectedScheduleEvent(
calendarData: CalendarViewData | null | undefined,
selected: Date | null
-): number | null {
+): CalendarEvent | null {
if (selected == null || !calendarData?.events?.length) return null
const key = format(selected, DATE_KEY_FORMAT)
const sameDay = calendarData.events.filter(e => e.dateKey === key)
@@ -56,12 +60,21 @@ function pickScheduleIdForSelectedDate(
(a, b) =>
new Date(a.startDateTime).getTime() - new Date(b.startDateTime).getTime()
)
- const first = sameDay[0]
- return first != null &&
- typeof first.shiftId === 'number' &&
- Number.isFinite(first.shiftId)
- ? first.shiftId
- : null
+ return sameDay[0] ?? null
+}
+
+function scheduleTimeParts(event: CalendarEvent | null) {
+ if (event == null) {
+ return { startHour: '--', startMin: '--', endHour: '--', endMin: '--' }
+ }
+ const start = splitClockToParts(event.startTimeLabel)
+ const end = splitClockToParts(event.endTimeLabel)
+ return {
+ startHour: start.hour,
+ startMin: start.minute,
+ endHour: end.hour,
+ endMin: end.minute,
+ }
}
interface UseSubstituteRequestFlowParams {
@@ -95,10 +108,6 @@ export function useSubstituteRequestFlow({
const [selectedCalendarDate, setSelectedCalendarDate] = useState(
null
)
- const [startHour, setStartHour] = useState('18')
- const [startMin, setStartMin] = useState('00')
- const [endHour, setEndHour] = useState('20')
- const [endMin, setEndMin] = useState('00')
const [selectedCandidateKeys, setSelectedCandidateKeys] = useState<
Set
>(new Set())
@@ -134,24 +143,32 @@ export function useSubstituteRequestFlow({
return calendarData ?? null
}, [workspaceId, exchangeableSchedulesResponse, calendarData])
+ const selectedScheduleEvent = useMemo(
+ () => pickSelectedScheduleEvent(resolvedCalendarData, selectedCalendarDate),
+ [resolvedCalendarData, selectedCalendarDate]
+ )
+
+ const { startHour, startMin, endHour, endMin } = useMemo(
+ () => scheduleTimeParts(selectedScheduleEvent),
+ [selectedScheduleEvent]
+ )
+
const selectedWeekdayLabel = useMemo(() => {
if (selectedCalendarDate == null) return null
return WEEKDAY_LABELS[selectedCalendarDate.getDay()]
}, [selectedCalendarDate])
const summarySelectedTimeLabel = useMemo(() => {
- const sh = normalizeHourInput(startHour)
- const sm = normalizeMinuteInput(startMin)
- const eh = normalizeHourInput(endHour)
- const em = normalizeMinuteInput(endMin)
- return `${sh}:${sm} ~ ${eh}:${em}`
- }, [startHour, startMin, endHour, endMin])
-
- const substituteScheduleId = useMemo(
- () =>
- pickScheduleIdForSelectedDate(resolvedCalendarData, selectedCalendarDate),
- [resolvedCalendarData, selectedCalendarDate]
- )
+ if (selectedScheduleEvent == null) return '—'
+ return `${selectedScheduleEvent.startTimeLabel} ~ ${selectedScheduleEvent.endTimeLabel}`
+ }, [selectedScheduleEvent])
+
+ const substituteScheduleId = useMemo(() => {
+ const shiftId = selectedScheduleEvent?.shiftId
+ return shiftId != null && Number.isFinite(shiftId) ? shiftId : null
+ }, [selectedScheduleEvent])
+
+ const hasSelectedSchedule = selectedScheduleEvent != null
const {
data: exchangeableResponse,
@@ -316,13 +333,10 @@ export function useSubstituteRequestFlow({
selectedDateKey,
onSubstituteCalendarDaySelect,
startHour,
- setStartHour,
startMin,
- setStartMin,
endHour,
- setEndHour,
endMin,
- setEndMin,
+ hasSelectedSchedule,
selectedWeekdayLabel,
summarySelectedTimeLabel,
substituteScheduleId,
diff --git a/src/pages/user/workspace-detail/components/SubstituteRequestModalFlow.tsx b/src/pages/user/workspace-detail/components/SubstituteRequestModalFlow.tsx
index fb4ba3ac..2afd2050 100644
--- a/src/pages/user/workspace-detail/components/SubstituteRequestModalFlow.tsx
+++ b/src/pages/user/workspace-detail/components/SubstituteRequestModalFlow.tsx
@@ -4,22 +4,17 @@ import ChevronLeftIcon from '@/assets/icons/nav/chevron-left.svg'
import { WEEKDAY_LABELS } from '@/shared/constants/calendar'
import { SubstituteCalendarPickerPanel } from './SubstituteCalendarPickerPanel'
-import {
- normalizeHourInput,
- normalizeMinuteInput,
- timeDigits,
- useSubstituteRequestFlow,
-} from '@/features/user'
+import { useSubstituteRequestFlow } from '@/features/user'
import { WorkerRoleBadge } from '@/shared/ui/home/WorkerRoleBadge'
import { cn } from '@/shared/lib/utils'
import type { CalendarViewData } from '@/features/home/common/schedule/types/calendarView'
-const timeFieldInputClass =
- 'min-w-0 flex-1 bg-transparent text-center tabular-nums typography-body01-semibold text-text-90 outline-none placeholder:text-text-50'
+const timeFieldReadOnlyClass =
+ 'min-w-0 flex-1 text-center tabular-nums typography-body01-semibold text-text-90'
-const timeSegmentLabelClass =
- 'flex h-[50px] min-w-0 flex-1 cursor-text items-center justify-center gap-1.5 rounded-2xl bg-bg-dark px-3 outline-none transition focus-within:ring-2 focus-within:ring-main'
+const timeSegmentReadOnlyClass =
+ 'flex h-[50px] min-w-0 flex-1 items-center justify-center gap-1.5 rounded-2xl bg-bg-dark px-3'
interface SubstituteRequestModalFlowProps {
onClose: () => void
@@ -136,64 +131,34 @@ export function SubstituteRequestModalFlow({
출근 시간
-
+
:
-
+
@@ -202,70 +167,49 @@ export function SubstituteRequestModalFlow({
퇴근 시간
-
+
:
-
+
+
+ {!flow.hasSelectedSchedule ? (
+
+ 선택한 날짜에 등록된 근무 스케줄이 없습니다.
+
+ ) : null}