From 10579fac77f95b5ec036a7e306f037c6a0797420 Mon Sep 17 00:00:00 2001 From: SeongHwan Date: Sun, 10 May 2026 19:17:38 +0900 Subject: [PATCH 01/20] =?UTF-8?q?refactor:=20=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EA=B2=BD=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/useWorkerScheduleManageViewModel.ts | 0 src/pages/manager/worker-schedule/index.tsx | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/features/manager/{home => worker-schedule}/hooks/useWorkerScheduleManageViewModel.ts (100%) diff --git a/src/features/manager/home/hooks/useWorkerScheduleManageViewModel.ts b/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts similarity index 100% rename from src/features/manager/home/hooks/useWorkerScheduleManageViewModel.ts rename to src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts diff --git a/src/pages/manager/worker-schedule/index.tsx b/src/pages/manager/worker-schedule/index.tsx index 82ca5e7..189a3b5 100644 --- a/src/pages/manager/worker-schedule/index.tsx +++ b/src/pages/manager/worker-schedule/index.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { WorkerRoleBadge } from '@/shared/ui/home/WorkerRoleBadge' -import { useWorkerScheduleManageViewModel } from '@/features/manager/home/hooks/useWorkerScheduleManageViewModel' +import { useWorkerScheduleManageViewModel } from '@/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel' import chevronDownIcon from '@/assets/icons/home/chevron-down.svg' import calendarIcon from '@/assets/icons/schedule/schedule_calendar.svg' import { Navbar } from '@/shared/ui/common/Navbar' From 5f9df08ea3c7fc12033f486f8efbf87e881b6943 Mon Sep 17 00:00:00 2001 From: SeongHwan Date: Sun, 10 May 2026 19:19:08 +0900 Subject: [PATCH 02/20] =?UTF-8?q?refactor:=20=EB=A7=A4=EB=8B=88=EC=A0=80?= =?UTF-8?q?=20api=20=ED=8F=B4=EB=8D=94=20=EC=9C=84=EC=B9=98=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/manager/{home => }/api/posting.ts | 0 src/features/manager/{home => }/api/schedule.ts | 0 src/features/manager/{home => }/api/substitute.ts | 0 src/features/manager/{home => }/api/worker.ts | 0 src/features/manager/{home => }/api/workspace.ts | 0 src/features/manager/home/hooks/useManagedPostingsViewModel.ts | 2 +- src/features/manager/home/hooks/useManagedWorkspacesQuery.ts | 2 +- src/features/manager/home/hooks/useMonthlySchedulesViewModel.ts | 2 +- .../manager/home/hooks/useSubstituteRequestsViewModel.ts | 2 +- src/features/manager/home/hooks/useTodaySchedulesViewModel.ts | 2 +- src/features/manager/home/hooks/useWorkspaceDetailQuery.ts | 2 +- src/features/manager/home/hooks/useWorkspaceWorkersViewModel.ts | 2 +- 12 files changed, 7 insertions(+), 7 deletions(-) rename src/features/manager/{home => }/api/posting.ts (100%) rename src/features/manager/{home => }/api/schedule.ts (100%) rename src/features/manager/{home => }/api/substitute.ts (100%) rename src/features/manager/{home => }/api/worker.ts (100%) rename src/features/manager/{home => }/api/workspace.ts (100%) diff --git a/src/features/manager/home/api/posting.ts b/src/features/manager/api/posting.ts similarity index 100% rename from src/features/manager/home/api/posting.ts rename to src/features/manager/api/posting.ts diff --git a/src/features/manager/home/api/schedule.ts b/src/features/manager/api/schedule.ts similarity index 100% rename from src/features/manager/home/api/schedule.ts rename to src/features/manager/api/schedule.ts diff --git a/src/features/manager/home/api/substitute.ts b/src/features/manager/api/substitute.ts similarity index 100% rename from src/features/manager/home/api/substitute.ts rename to src/features/manager/api/substitute.ts diff --git a/src/features/manager/home/api/worker.ts b/src/features/manager/api/worker.ts similarity index 100% rename from src/features/manager/home/api/worker.ts rename to src/features/manager/api/worker.ts diff --git a/src/features/manager/home/api/workspace.ts b/src/features/manager/api/workspace.ts similarity index 100% rename from src/features/manager/home/api/workspace.ts rename to src/features/manager/api/workspace.ts diff --git a/src/features/manager/home/hooks/useManagedPostingsViewModel.ts b/src/features/manager/home/hooks/useManagedPostingsViewModel.ts index f21f4da..f9514ef 100644 --- a/src/features/manager/home/hooks/useManagedPostingsViewModel.ts +++ b/src/features/manager/home/hooks/useManagedPostingsViewModel.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react' import { useInfiniteQuery } from '@tanstack/react-query' -import { fetchManagedPostings } from '@/features/manager/home/api/posting' +import { fetchManagedPostings } from '@/features/manager/api/posting' import { adaptPostingDto } from '@/features/manager/home/types/posting' import { queryKeys } from '@/shared/lib/queryKeys' diff --git a/src/features/manager/home/hooks/useManagedWorkspacesQuery.ts b/src/features/manager/home/hooks/useManagedWorkspacesQuery.ts index 2d57944..971d716 100644 --- a/src/features/manager/home/hooks/useManagedWorkspacesQuery.ts +++ b/src/features/manager/home/hooks/useManagedWorkspacesQuery.ts @@ -1,6 +1,6 @@ import { useEffect, useMemo } from 'react' import { useQuery } from '@tanstack/react-query' -import { fetchManagedWorkspaces } from '@/features/manager/home/api/workspace' +import { fetchManagedWorkspaces } from '@/features/manager/api/workspace' import { useWorkspaceStore } from '@/shared/stores/useWorkspaceStore' import { queryKeys } from '@/shared/lib/queryKeys' diff --git a/src/features/manager/home/hooks/useMonthlySchedulesViewModel.ts b/src/features/manager/home/hooks/useMonthlySchedulesViewModel.ts index 487600d..bf12916 100644 --- a/src/features/manager/home/hooks/useMonthlySchedulesViewModel.ts +++ b/src/features/manager/home/hooks/useMonthlySchedulesViewModel.ts @@ -1,7 +1,7 @@ import { useCallback, useMemo, useState } from 'react' import { addMonths, format } from 'date-fns' import { useQuery } from '@tanstack/react-query' -import { fetchMonthlySchedules } from '@/features/manager/home/api/schedule' +import { fetchMonthlySchedules } from '@/features/manager/api/schedule' import type { ManagerScheduleApiResponse, ManagerScheduleShiftDto, diff --git a/src/features/manager/home/hooks/useSubstituteRequestsViewModel.ts b/src/features/manager/home/hooks/useSubstituteRequestsViewModel.ts index 5488c50..196ca3f 100644 --- a/src/features/manager/home/hooks/useSubstituteRequestsViewModel.ts +++ b/src/features/manager/home/hooks/useSubstituteRequestsViewModel.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react' import { useInfiniteQuery } from '@tanstack/react-query' -import { fetchSubstituteRequests } from '@/features/manager/home/api/substitute' +import { fetchSubstituteRequests } from '@/features/manager/api/substitute' import { adaptSubstituteRequestDto } from '@/features/manager/home/types/substitute' import { queryKeys } from '@/shared/lib/queryKeys' diff --git a/src/features/manager/home/hooks/useTodaySchedulesViewModel.ts b/src/features/manager/home/hooks/useTodaySchedulesViewModel.ts index 72098ee..d01f78d 100644 --- a/src/features/manager/home/hooks/useTodaySchedulesViewModel.ts +++ b/src/features/manager/home/hooks/useTodaySchedulesViewModel.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react' import { useQuery } from '@tanstack/react-query' -import { fetchTodaySchedules } from '@/features/manager/home/api/schedule' +import { fetchTodaySchedules } from '@/features/manager/api/schedule' import { queryKeys } from '@/shared/lib/queryKeys' import { toTimeLabel } from '@/features/home/common/schedule/lib/date' import type { TodayWorkerItem } from '@/features/manager/home/ui/TodayWorkerList' diff --git a/src/features/manager/home/hooks/useWorkspaceDetailQuery.ts b/src/features/manager/home/hooks/useWorkspaceDetailQuery.ts index c9e5eb3..f53fe6c 100644 --- a/src/features/manager/home/hooks/useWorkspaceDetailQuery.ts +++ b/src/features/manager/home/hooks/useWorkspaceDetailQuery.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react' import { useQuery } from '@tanstack/react-query' -import { fetchWorkspaceDetail } from '@/features/manager/home/api/workspace' +import { fetchWorkspaceDetail } from '@/features/manager/api/workspace' import { queryKeys } from '@/shared/lib/queryKeys' export function useWorkspaceDetailQuery(workspaceId: number | null) { diff --git a/src/features/manager/home/hooks/useWorkspaceWorkersViewModel.ts b/src/features/manager/home/hooks/useWorkspaceWorkersViewModel.ts index 78b6ce3..e779188 100644 --- a/src/features/manager/home/hooks/useWorkspaceWorkersViewModel.ts +++ b/src/features/manager/home/hooks/useWorkspaceWorkersViewModel.ts @@ -1,6 +1,6 @@ import { useMemo } from 'react' import { useInfiniteQuery } from '@tanstack/react-query' -import { fetchWorkspaceWorkers } from '@/features/manager/home/api/worker' +import { fetchWorkspaceWorkers } from '@/features/manager/api/worker' import { adaptWorkerDto } from '@/features/manager/home/lib/worker' import { queryKeys } from '@/shared/lib/queryKeys' From f1e6037e05dd17f684400858998f8aa3a0faffd8 Mon Sep 17 00:00:00 2001 From: SeongHwan Date: Sun, 10 May 2026 19:53:56 +0900 Subject: [PATCH 03/20] =?UTF-8?q?feat:=20=EB=A7=A4=EC=9E=A5=20=EA=B7=BC?= =?UTF-8?q?=EB=AC=B4=EC=9E=90=20=ED=83=80=EC=9E=85=20colorCode=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/manager/home/types/worker.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/features/manager/home/types/worker.ts b/src/features/manager/home/types/worker.ts index bff93a2..51c35c8 100644 --- a/src/features/manager/home/types/worker.ts +++ b/src/features/manager/home/types/worker.ts @@ -25,6 +25,7 @@ export interface WorkerDto { user: WorkerUserDto status: WorkerStatusDto position: WorkerPositionDto + colorCode: string employedAt: string resignedAt: string | null nextShiftDateTime: string | null From d4c606657b427d7c9a205a75acc4670ff1b720c8 Mon Sep 17 00:00:00 2001 From: SeongHwan Date: Sun, 10 May 2026 20:04:57 +0900 Subject: [PATCH 04/20] =?UTF-8?q?feat:=20=EA=B7=BC=EB=AC=B4=EC=9E=90=20?= =?UTF-8?q?=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0=ED=9A=8C=20API=20?= =?UTF-8?q?=EC=97=B0=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../worker-schedule/hooks/query/index.ts | 1 + .../hooks/query/useWorkspaceWorkers.ts | 34 ++++++++ .../hooks/useWorkerScheduleManageViewModel.ts | 77 +++++++++++++----- src/pages/manager/worker-schedule/index.tsx | 79 ++++++++++--------- 4 files changed, 134 insertions(+), 57 deletions(-) create mode 100644 src/features/manager/worker-schedule/hooks/query/index.ts create mode 100644 src/features/manager/worker-schedule/hooks/query/useWorkspaceWorkers.ts diff --git a/src/features/manager/worker-schedule/hooks/query/index.ts b/src/features/manager/worker-schedule/hooks/query/index.ts new file mode 100644 index 0000000..cc9cc7c --- /dev/null +++ b/src/features/manager/worker-schedule/hooks/query/index.ts @@ -0,0 +1 @@ +export { useWorkspaceWorkers } from './useWorkspaceWorkers' diff --git a/src/features/manager/worker-schedule/hooks/query/useWorkspaceWorkers.ts b/src/features/manager/worker-schedule/hooks/query/useWorkspaceWorkers.ts new file mode 100644 index 0000000..c7a8a09 --- /dev/null +++ b/src/features/manager/worker-schedule/hooks/query/useWorkspaceWorkers.ts @@ -0,0 +1,34 @@ +import { useQuery } from '@tanstack/react-query' +import { fetchWorkspaceWorkers } from '@/features/manager/api/worker' +import { queryKeys } from '@/shared/lib/queryKeys' +import type { WorkersApiResponse } from '@/features/manager/home/types/worker' + +const PAGE_SIZE = 50 + +interface UseWorkspaceWorkersParams { + workspaceId?: number + status?: string + name?: string +} + +export function useWorkspaceWorkers({ + workspaceId, + status, + name, +}: UseWorkspaceWorkersParams) { + return useQuery({ + queryKey: queryKeys.managerWorkspace.workers(workspaceId ?? 0, { + status, + name, + pageSize: PAGE_SIZE, + }), + queryFn: () => + fetchWorkspaceWorkers({ + workspaceId: workspaceId ?? 0, + pageSize: PAGE_SIZE, + status, + name, + }), + enabled: !!workspaceId, + }) +} diff --git a/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts b/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts index 5aa2de7..c8d63df 100644 --- a/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts +++ b/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts @@ -1,18 +1,32 @@ -import { useMemo, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' +import { useWorkspaceWorkers } from './query' +import { useWorkspaceStore } from '@/shared/stores/useWorkspaceStore' +import type { StoreWorkerRole } from '@/features/manager/home/types/storeWorkerRole' +import { ScheduleColor } from '@/features/manager/worker-schedule/types/scheduleColor' +import type { ScheduleColor as ScheduleColorType } from '@/features/manager/worker-schedule/types/scheduleColor' const WORKDAY_OPTIONS = ['월', '화', '수', '목', '금', '토', '일'] as const const DEFAULT_SELECTED_DAYS = ['수', '금'] -const MOCK_WORKERS = [ - { name: '이름임', role: 'manager' as const }, - { name: '김민준', role: 'staff' as const }, - { name: '박지은', role: 'staff' as const }, - { name: '이수호', role: 'staff' as const }, - { name: '정하나', role: 'manager' as const }, -] - export function useWorkerScheduleManageViewModel() { + const activeWorkspaceId = useWorkspaceStore(state => state.activeWorkspaceId) + const { data: workersResponse, isLoading } = useWorkspaceWorkers({ + workspaceId: activeWorkspaceId ?? undefined, + }) + + const workers = useMemo(() => { + if (!workersResponse?.data.data) return [] + return workersResponse.data.data.map(worker => ({ + id: worker.id, + name: worker.user.name, + role: (worker.position.type.toLowerCase() === 'manager' + ? 'manager' + : 'staff') as StoreWorkerRole, + colorCode: worker.colorCode, + })) + }, [workersResponse]) + const [selectedDays, setSelectedDays] = useState( DEFAULT_SELECTED_DAYS ) @@ -21,6 +35,24 @@ export function useWorkerScheduleManageViewModel() { const [endHour, setEndHour] = useState('') const [endMinute, setEndMinute] = useState('') const [selectedWorkerIndex, setSelectedWorkerIndex] = useState(0) + const [selectedColor, setSelectedColor] = useState( + ScheduleColor.Pink + ) + + const validIndex = Math.min(selectedWorkerIndex, Math.max(0, workers.length - 1)) + const selectedWorker = workers[validIndex] || { id: 0, name: '', role: 'staff' as const } + const selectedWorkerColorCode = selectedWorker.colorCode || undefined + useEffect(() => { + if (selectedWorkerColorCode) { + const color = Object.entries(ScheduleColor).find( + ([, value]) => value === selectedWorkerColorCode + ) + if (color) { + // eslint-disable-next-line react-hooks/set-state-in-effect + setSelectedColor(color[1] as ScheduleColorType) + } + } + }, [selectedWorkerColorCode]) const workTimeRangeLabel = useMemo(() => { const sh = startHour || '00' @@ -37,21 +69,26 @@ export function useWorkerScheduleManageViewModel() { } return { - worker: MOCK_WORKERS[selectedWorkerIndex], - workers: MOCK_WORKERS, + isLoading, + worker: selectedWorker, + workers, selectedWorkerIndex, setSelectedWorkerIndex, + selectedColor, + setSelectedColor, workdayOptions: WORKDAY_OPTIONS, selectedDays, - workTimeRangeLabel, - startHour, - startMinute, - endHour, - endMinute, - setStartHour, - setStartMinute, - setEndHour, - setEndMinute, toggleDay, + workTime: { + rangeLabel: workTimeRangeLabel, + startHour, + startMinute, + endHour, + endMinute, + setStartHour, + setStartMinute, + setEndHour, + setEndMinute, + }, } } diff --git a/src/pages/manager/worker-schedule/index.tsx b/src/pages/manager/worker-schedule/index.tsx index 189a3b5..eaf6649 100644 --- a/src/pages/manager/worker-schedule/index.tsx +++ b/src/pages/manager/worker-schedule/index.tsx @@ -6,9 +6,10 @@ import calendarIcon from '@/assets/icons/schedule/schedule_calendar.svg' import { Navbar } from '@/shared/ui/common/Navbar' import { ScheduleCalendar } from '@/shared/ui/common/ScheduleCalendar' import { ColorPickerDropdown } from '@/shared/ui/schedule/ColorPickerDropdown' +import { Spinner } from '@/shared/ui/Spinner' +import { cn } from '@/shared/lib/utils' import type { ScheduleTab } from '@/features/manager' import { SCHEDULE_TABS } from '@/features/manager' -import { ScheduleColor } from '@/features/manager/worker-schedule/types/scheduleColor' interface TimeSelectBoxProps { value: string @@ -56,26 +57,18 @@ export function ManagerWorkerSchedulePage() { const [selectedDate, setSelectedDate] = useState(null) const [isWorkerDropdownOpen, setIsWorkerDropdownOpen] = useState(false) const [isColorPickerOpen, setIsColorPickerOpen] = useState(false) - const [selectedColor, setSelectedColor] = useState( - ScheduleColor.Pink - ) const { + isLoading, worker, workers, selectedWorkerIndex, setSelectedWorkerIndex, workdayOptions, selectedDays, - workTimeRangeLabel, - startHour, - startMinute, - endHour, - endMinute, - setStartHour, - setStartMinute, - setEndHour, - setEndMinute, toggleDay, + workTime, + selectedColor, + setSelectedColor, } = useWorkerScheduleManageViewModel() return ( @@ -89,11 +82,10 @@ export function ManagerWorkerSchedulePage() { key={tab} type="button" onClick={() => setActiveTab(tab)} - className={`flex h-[46px] flex-1 items-center justify-center typography-body01-semibold ${ - isActive - ? 'border-b-2 border-line-3 text-text-100' - : 'text-text-50' - }`} + className={cn( + 'flex h-[46px] flex-1 items-center justify-center typography-body01-semibold', + isActive ? 'border-b-2 border-line-3 text-text-100' : 'text-text-50' + )} > {tab} @@ -102,11 +94,22 @@ export function ManagerWorkerSchedulePage() {
+ {isLoading && ( +
+ +
+ )} + {!isLoading && ( + <>

근무자 선택

@@ -142,11 +145,10 @@ export function ManagerWorkerSchedulePage() { setSelectedWorkerIndex(index) setIsWorkerDropdownOpen(false) }} - className={`flex h-[70px] w-full items-center gap-4 rounded-2xl px-3 py-4 transition-colors ${ - selectedWorkerIndex === index - ? 'bg-main/10' - : 'hover:bg-bg-light' - }`} + className={cn( + 'flex h-[70px] w-full items-center gap-4 rounded-2xl px-3 py-4 transition-colors', + selectedWorkerIndex === index ? 'bg-main/10' : 'hover:bg-bg-light' + )} >
toggleDay(day)} > {day} @@ -212,7 +215,7 @@ export function ManagerWorkerSchedulePage() { src={chevronDownIcon} alt="" aria-hidden="true" - className={`h-5 w-5 transition-transform ${showCalendar ? 'rotate-180' : ''}`} + className={cn('h-5 w-5 transition-transform', showCalendar && 'rotate-180')} />
@@ -245,7 +248,7 @@ export function ManagerWorkerSchedulePage() { 근무 시간 선택

- {workTimeRangeLabel} + {workTime.rangeLabel}

@@ -254,9 +257,9 @@ export function ManagerWorkerSchedulePage() {
@@ -282,9 +285,9 @@ export function ManagerWorkerSchedulePage() {
)} + + )}
From 44576d85e0313f0337c32fc70ff94785187c1380 Mon Sep 17 00:00:00 2001 From: SeongHwan Date: Sun, 10 May 2026 20:07:29 +0900 Subject: [PATCH 05/20] =?UTF-8?q?feat:=20=ED=98=84=EC=9E=AC=20=EA=B7=BC?= =?UTF-8?q?=EB=AC=B4=EC=A4=91=EC=9D=B8=20=EC=9D=B8=EC=9B=90=EB=A7=8C=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C=EB=90=98=EB=8F=84=EB=A1=9D=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/manager/home/hooks/useWorkspaceWorkersViewModel.ts | 2 +- .../worker-schedule/hooks/useWorkerScheduleManageViewModel.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/features/manager/home/hooks/useWorkspaceWorkersViewModel.ts b/src/features/manager/home/hooks/useWorkspaceWorkersViewModel.ts index e779188..3ff1b0b 100644 --- a/src/features/manager/home/hooks/useWorkspaceWorkersViewModel.ts +++ b/src/features/manager/home/hooks/useWorkspaceWorkersViewModel.ts @@ -4,7 +4,7 @@ import { fetchWorkspaceWorkers } from '@/features/manager/api/worker' import { adaptWorkerDto } from '@/features/manager/home/lib/worker' import { queryKeys } from '@/shared/lib/queryKeys' -const PAGE_SIZE = 20 +const PAGE_SIZE = 50 export function useWorkspaceWorkersViewModel( workspaceId: number | null, diff --git a/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts b/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts index c8d63df..4b461f7 100644 --- a/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts +++ b/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts @@ -13,6 +13,7 @@ export function useWorkerScheduleManageViewModel() { const activeWorkspaceId = useWorkspaceStore(state => state.activeWorkspaceId) const { data: workersResponse, isLoading } = useWorkspaceWorkers({ workspaceId: activeWorkspaceId ?? undefined, + status: 'ACTIVATED', }) const workers = useMemo(() => { From 3cf141d0a933dbd56ac9e13c04ee79f7b402a70d Mon Sep 17 00:00:00 2001 From: SeongHwan Date: Tue, 12 May 2026 16:13:34 +0900 Subject: [PATCH 06/20] =?UTF-8?q?feat:=20=EA=B3=A0=EC=A0=95=20=EC=8A=A4?= =?UTF-8?q?=EC=BC=80=EC=A4=84=20API=20=EB=B0=8F=20Query,=20Mutation=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/manager/index.ts | 13 + .../worker-schedule/api/fixedWorkerSchdule.ts | 50 +++ .../worker-schedule/hooks/mutation/index.ts | 3 + .../mutation/useCreateFixedWorkerSchedule.ts | 18 + .../mutation/useDeleteFixedWorkerSchedule.ts | 17 + .../mutation/useUpdateFixedWorkerSchedule.ts | 23 ++ .../worker-schedule/hooks/query/index.ts | 1 + .../hooks/query/useFixedWorkerSchedules.ts | 11 + .../hooks/useWorkerScheduleManageViewModel.ts | 22 +- .../types/fixedWorkerSchdules.ts | 40 ++ src/pages/manager/worker-schedule/index.tsx | 380 +++++++++--------- src/shared/lib/queryKeys.ts | 4 + 12 files changed, 390 insertions(+), 192 deletions(-) create mode 100644 src/features/manager/worker-schedule/api/fixedWorkerSchdule.ts create mode 100644 src/features/manager/worker-schedule/hooks/mutation/index.ts create mode 100644 src/features/manager/worker-schedule/hooks/mutation/useCreateFixedWorkerSchedule.ts create mode 100644 src/features/manager/worker-schedule/hooks/mutation/useDeleteFixedWorkerSchedule.ts create mode 100644 src/features/manager/worker-schedule/hooks/mutation/useUpdateFixedWorkerSchedule.ts create mode 100644 src/features/manager/worker-schedule/hooks/query/useFixedWorkerSchedules.ts create mode 100644 src/features/manager/worker-schedule/types/fixedWorkerSchdules.ts diff --git a/src/features/manager/index.ts b/src/features/manager/index.ts index 3312e30..47b2218 100644 --- a/src/features/manager/index.ts +++ b/src/features/manager/index.ts @@ -1,3 +1,16 @@ +export { + getFixedWorkerSchdules, + postFixedWorkerSchdules, + deleteFixedWorkerSchdule, + patchFixedWorkerSchdule, +} from '@/features/manager/worker-schedule/api/fixedWorkerSchdule' +export type { + ResponseGetFixedWorkerSchdules, + RequestPostFixedWorkerSchdules, + ResponsePostFixedWorkerSchdules, + ResponseDeleteFixedWorkerSchdules, + RequestPatchFixedWorkerSchdules, +} from '@/features/manager/worker-schedule/types/fixedWorkerSchdules' export { StoreWorkerListItem } from '@/features/manager/home/ui/StoreWorkerListItem' export { WorkspaceChangeList } from '@/features/manager/home/ui/WorkspaceChangeList' export { WorkspaceChangeCard } from '@/features/manager/home/ui/WorkspaceChangeCard' diff --git a/src/features/manager/worker-schedule/api/fixedWorkerSchdule.ts b/src/features/manager/worker-schedule/api/fixedWorkerSchdule.ts new file mode 100644 index 0000000..37b99f5 --- /dev/null +++ b/src/features/manager/worker-schedule/api/fixedWorkerSchdule.ts @@ -0,0 +1,50 @@ +import axiosInstance from '@/shared/lib/axiosInstance' +import type { + ResponseGetFixedWorkerSchdules, + RequestPostFixedWorkerSchdules, + ResponsePostFixedWorkerSchdules, + ResponseDeleteFixedWorkerSchdules, + RequestPatchFixedWorkerSchdules, +} from '@/features/manager' + +export async function getFixedWorkerSchdules( + workspaceId: number +): Promise { + const response = await axiosInstance.get( + `/manager/workspaces/${workspaceId}/fixed-worker-schedules` + ) + return response.data +} + +export async function postFixedWorkerSchdules( + workspaceId: number, + body: RequestPostFixedWorkerSchdules +): Promise { + const response = await axiosInstance.post( + `/manager/workspaces/${workspaceId}/fixed-worker-schedules`, + body + ) + return response.data +} + +export async function deleteFixedWorkerSchdule( + workspaceId: number, + workerScheduleId: number +): Promise { + const response = + await axiosInstance.delete( + `/manager/workspaces/${workspaceId}/fixed-worker-schedules/${workerScheduleId}` + ) + return response.data +} + +export async function patchFixedWorkerSchdule( + workspaceId: number, + workerScheduleId: number, + body: RequestPatchFixedWorkerSchdules +): Promise { + await axiosInstance.patch( + `/manager/workspaces/${workspaceId}/fixed-worker-schedules/${workerScheduleId}`, + body + ) +} diff --git a/src/features/manager/worker-schedule/hooks/mutation/index.ts b/src/features/manager/worker-schedule/hooks/mutation/index.ts new file mode 100644 index 0000000..0c46798 --- /dev/null +++ b/src/features/manager/worker-schedule/hooks/mutation/index.ts @@ -0,0 +1,3 @@ +export { useCreateFixedWorkerSchedule } from './useCreateFixedWorkerSchedule' +export { useUpdateFixedWorkerSchedule } from './useUpdateFixedWorkerSchedule' +export { useDeleteFixedWorkerSchedule } from './useDeleteFixedWorkerSchedule' diff --git a/src/features/manager/worker-schedule/hooks/mutation/useCreateFixedWorkerSchedule.ts b/src/features/manager/worker-schedule/hooks/mutation/useCreateFixedWorkerSchedule.ts new file mode 100644 index 0000000..a9bdeb1 --- /dev/null +++ b/src/features/manager/worker-schedule/hooks/mutation/useCreateFixedWorkerSchedule.ts @@ -0,0 +1,18 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { postFixedWorkerSchdules } from '@/features/manager/worker-schedule/api/fixedWorkerSchdule' +import { queryKeys } from '@/shared/lib/queryKeys' +import type { RequestPostFixedWorkerSchdules } from '@/features/manager/worker-schedule/types/fixedWorkerSchdules' + +export function useCreateFixedWorkerSchedule(workspaceId: number) { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (body: RequestPostFixedWorkerSchdules) => + postFixedWorkerSchdules(workspaceId, body), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.fixedWorkerSchedule.list(workspaceId), + }) + }, + }) +} diff --git a/src/features/manager/worker-schedule/hooks/mutation/useDeleteFixedWorkerSchedule.ts b/src/features/manager/worker-schedule/hooks/mutation/useDeleteFixedWorkerSchedule.ts new file mode 100644 index 0000000..b6f9b6f --- /dev/null +++ b/src/features/manager/worker-schedule/hooks/mutation/useDeleteFixedWorkerSchedule.ts @@ -0,0 +1,17 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { deleteFixedWorkerSchdule } from '@/features/manager/worker-schedule/api/fixedWorkerSchdule' +import { queryKeys } from '@/shared/lib/queryKeys' + +export function useDeleteFixedWorkerSchedule(workspaceId: number) { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (workerScheduleId: number) => + deleteFixedWorkerSchdule(workspaceId, workerScheduleId), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.fixedWorkerSchedule.list(workspaceId), + }) + }, + }) +} diff --git a/src/features/manager/worker-schedule/hooks/mutation/useUpdateFixedWorkerSchedule.ts b/src/features/manager/worker-schedule/hooks/mutation/useUpdateFixedWorkerSchedule.ts new file mode 100644 index 0000000..40188d2 --- /dev/null +++ b/src/features/manager/worker-schedule/hooks/mutation/useUpdateFixedWorkerSchedule.ts @@ -0,0 +1,23 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { patchFixedWorkerSchdule } from '@/features/manager/worker-schedule/api/fixedWorkerSchdule' +import { queryKeys } from '@/shared/lib/queryKeys' +import type { RequestPatchFixedWorkerSchdules } from '@/features/manager/worker-schedule/types/fixedWorkerSchdules' + +interface UpdateFixedWorkerScheduleParams { + workerScheduleId: number + body: RequestPatchFixedWorkerSchdules +} + +export function useUpdateFixedWorkerSchedule(workspaceId: number) { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: ({ workerScheduleId, body }: UpdateFixedWorkerScheduleParams) => + patchFixedWorkerSchdule(workspaceId, workerScheduleId, body), + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: queryKeys.fixedWorkerSchedule.list(workspaceId), + }) + }, + }) +} diff --git a/src/features/manager/worker-schedule/hooks/query/index.ts b/src/features/manager/worker-schedule/hooks/query/index.ts index cc9cc7c..bd3f4c5 100644 --- a/src/features/manager/worker-schedule/hooks/query/index.ts +++ b/src/features/manager/worker-schedule/hooks/query/index.ts @@ -1 +1,2 @@ export { useWorkspaceWorkers } from './useWorkspaceWorkers' +export { useFixedWorkerSchedules } from './useFixedWorkerSchedules' diff --git a/src/features/manager/worker-schedule/hooks/query/useFixedWorkerSchedules.ts b/src/features/manager/worker-schedule/hooks/query/useFixedWorkerSchedules.ts new file mode 100644 index 0000000..17438eb --- /dev/null +++ b/src/features/manager/worker-schedule/hooks/query/useFixedWorkerSchedules.ts @@ -0,0 +1,11 @@ +import { useQuery } from '@tanstack/react-query' +import { getFixedWorkerSchdules } from '@/features/manager/worker-schedule/api/fixedWorkerSchdule' +import { queryKeys } from '@/shared/lib/queryKeys' + +export function useFixedWorkerSchedules(workspaceId: number) { + return useQuery({ + queryKey: queryKeys.fixedWorkerSchedule.list(workspaceId), + queryFn: () => getFixedWorkerSchdules(workspaceId), + enabled: !!workspaceId, + }) +} diff --git a/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts b/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts index 4b461f7..a8c811d 100644 --- a/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts +++ b/src/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel.ts @@ -4,10 +4,7 @@ import { useWorkspaceStore } from '@/shared/stores/useWorkspaceStore' import type { StoreWorkerRole } from '@/features/manager/home/types/storeWorkerRole' import { ScheduleColor } from '@/features/manager/worker-schedule/types/scheduleColor' import type { ScheduleColor as ScheduleColorType } from '@/features/manager/worker-schedule/types/scheduleColor' - -const WORKDAY_OPTIONS = ['월', '화', '수', '목', '금', '토', '일'] as const - -const DEFAULT_SELECTED_DAYS = ['수', '금'] +import { WEEKDAY_LABELS } from '@/shared/constants/calendar' export function useWorkerScheduleManageViewModel() { const activeWorkspaceId = useWorkspaceStore(state => state.activeWorkspaceId) @@ -28,9 +25,7 @@ export function useWorkerScheduleManageViewModel() { })) }, [workersResponse]) - const [selectedDays, setSelectedDays] = useState( - DEFAULT_SELECTED_DAYS - ) + const [selectedDays, setSelectedDays] = useState([]) const [startHour, setStartHour] = useState('') const [startMinute, setStartMinute] = useState('') const [endHour, setEndHour] = useState('') @@ -40,8 +35,15 @@ export function useWorkerScheduleManageViewModel() { ScheduleColor.Pink ) - const validIndex = Math.min(selectedWorkerIndex, Math.max(0, workers.length - 1)) - const selectedWorker = workers[validIndex] || { id: 0, name: '', role: 'staff' as const } + const validIndex = Math.min( + selectedWorkerIndex, + Math.max(0, workers.length - 1) + ) + const selectedWorker = workers[validIndex] || { + id: 0, + name: '', + role: 'staff' as const, + } const selectedWorkerColorCode = selectedWorker.colorCode || undefined useEffect(() => { if (selectedWorkerColorCode) { @@ -77,7 +79,7 @@ export function useWorkerScheduleManageViewModel() { setSelectedWorkerIndex, selectedColor, setSelectedColor, - workdayOptions: WORKDAY_OPTIONS, + workdayOptions: WEEKDAY_LABELS, selectedDays, toggleDay, workTime: { diff --git a/src/features/manager/worker-schedule/types/fixedWorkerSchdules.ts b/src/features/manager/worker-schedule/types/fixedWorkerSchdules.ts new file mode 100644 index 0000000..da3cf87 --- /dev/null +++ b/src/features/manager/worker-schedule/types/fixedWorkerSchdules.ts @@ -0,0 +1,40 @@ +import type { CommonApiResponse } from '@/shared/types/common' + +export type ResponseGetFixedWorkerSchdules = CommonApiResponse<{ + id: number + workspaceWorkerId: number + startDayofWeek: string + startTime: string + endDayOfWeek: string + endTime: string + status: string +}> + +export type RequestPostFixedWorkerSchdules = { + workspaceId: number + schedules: { + startDayOfWeek: string + startTime: string + endDayOfWeek: string + endTime: string + } +} + +export type ResponsePostFixedWorkerSchdules = { + timestamp?: string + data?: object + code?: string +} + +export type ResponseDeleteFixedWorkerSchdules = { + timestamp?: string + data?: object + code?: string +} + +export type RequestPatchFixedWorkerSchdules = { + startDayOfWeek: string + startTime: string + endDayOfWeek: string + endTime: string +} diff --git a/src/pages/manager/worker-schedule/index.tsx b/src/pages/manager/worker-schedule/index.tsx index eaf6649..4308155 100644 --- a/src/pages/manager/worker-schedule/index.tsx +++ b/src/pages/manager/worker-schedule/index.tsx @@ -84,7 +84,9 @@ export function ManagerWorkerSchedulePage() { onClick={() => setActiveTab(tab)} className={cn( 'flex h-[46px] flex-1 items-center justify-center typography-body01-semibold', - isActive ? 'border-b-2 border-line-3 text-text-100' : 'text-text-50' + isActive + ? 'border-b-2 border-line-3 text-text-100' + : 'text-text-50' )} > {tab} @@ -101,213 +103,227 @@ export function ManagerWorkerSchedulePage() { )} {!isLoading && ( <> -
-

근무자 선택

- - - {isWorkerDropdownOpen && ( -
- {workers.map((w, index) => ( - - ))} -
- )} -
- - {activeTab === '고정' ? ( -
-

근무일 선택

-
- {workdayOptions.map(day => { - const selected = selectedDays.includes(day) - return ( - - ) - })} -
-
- ) : ( - <> -
-

날짜 선택

- - {showCalendar && ( -
- { - setSelectedDate(date) - setShowCalendar(false) - }} - /> + {isWorkerDropdownOpen && ( +
+ {workers.map((w, index) => ( + + ))}
)}
-
- setIsColorPickerOpen(!isColorPickerOpen)} - /> -
- - )} - {!isColorPickerOpen && ( -
-

- 근무 시간 선택 -

-

- {workTime.rangeLabel} -

-
- - 출근 시간 - -
- -
-
-
+
+ ) : ( + <> +
+

+ 날짜 선택 +

+ -
- - 퇴근 시간 - -
- -
-
+ )} +
+
+ setIsColorPickerOpen(!isColorPickerOpen)} /> +
+ + )} + {!isColorPickerOpen && ( +
+

+ 근무 시간 선택 +

+

+ {workTime.rangeLabel} +

+ +
+ + 출근 시간 + +
+ +
+
+ +
- -
- - - )} + +
+ + 퇴근 시간 + +
+ +
+
+ +
+
+ + )} )} diff --git a/src/shared/lib/queryKeys.ts b/src/shared/lib/queryKeys.ts index 8865298..4488353 100644 --- a/src/shared/lib/queryKeys.ts +++ b/src/shared/lib/queryKeys.ts @@ -54,4 +54,8 @@ export const queryKeys = { user: { me: () => ['user', 'me'] as const, }, + fixedWorkerSchedule: { + list: (workspaceId: number) => + ['fixedWorkerSchedule', 'list', workspaceId] as const, + }, } as const From c2e420865f42d549932700bcdac7a7e3f0f20f8f Mon Sep 17 00:00:00 2001 From: Dohyeon Date: Sun, 17 May 2026 15:49:31 +0900 Subject: [PATCH 07/20] =?UTF-8?q?refactor:=20worker-schedule=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20import=EB=A5=BC=20manager=20public=20API?= =?UTF-8?q?=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/manager/index.ts | 2 ++ src/pages/manager/worker-schedule/index.tsx | 8 +++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/features/manager/index.ts b/src/features/manager/index.ts index 47b2218..726365a 100644 --- a/src/features/manager/index.ts +++ b/src/features/manager/index.ts @@ -16,5 +16,7 @@ export { WorkspaceChangeList } from '@/features/manager/home/ui/WorkspaceChangeL export { WorkspaceChangeCard } from '@/features/manager/home/ui/WorkspaceChangeCard' export { TodayWorkerList } from '@/features/manager/home/ui/TodayWorkerList' export { useManagerHomeViewModel } from '@/features/manager/home/hooks/useManagerHomeViewModel' +export { useWorkerScheduleManageViewModel } from '@/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel' +export { ScheduleColor } from '@/features/manager/worker-schedule/types/scheduleColor' export type { ScheduleTab } from '@/features/manager/schedule/types/workerSchedule' export { SCHEDULE_TABS } from '@/features/manager/schedule/constants/workerSchedule' diff --git a/src/pages/manager/worker-schedule/index.tsx b/src/pages/manager/worker-schedule/index.tsx index d56c8bf..9bf8785 100644 --- a/src/pages/manager/worker-schedule/index.tsx +++ b/src/pages/manager/worker-schedule/index.tsx @@ -1,7 +1,6 @@ import { useState } from 'react' import { Navigate, useParams } from 'react-router-dom' import { WorkerRoleBadge } from '@/shared/ui/home/WorkerRoleBadge' -import { useWorkerScheduleManageViewModel } from '@/features/manager/worker-schedule/hooks/useWorkerScheduleManageViewModel' import chevronDownIcon from '@/assets/icons/home/chevron-down.svg' import calendarIcon from '@/assets/icons/schedule/schedule_calendar.svg' import { Navbar } from '@/shared/ui/common/Navbar' @@ -10,8 +9,11 @@ import { ColorPickerDropdown } from '@/shared/ui/schedule/ColorPickerDropdown' import { Spinner } from '@/shared/ui/Spinner' import { cn } from '@/shared/lib/utils' import type { ScheduleTab } from '@/features/manager' -import { SCHEDULE_TABS } from '@/features/manager' -import { ScheduleColor } from '@/features/manager/worker-schedule/types/scheduleColor' +import { + SCHEDULE_TABS, + ScheduleColor, + useWorkerScheduleManageViewModel, +} from '@/features/manager' import { ROUTES } from '@/shared/constants/routes' function parsePositiveRouteInt(raw: string | undefined): number | null { From 02e65249a95a51f7362322df644af57cad0a01f9 Mon Sep 17 00:00:00 2001 From: Dohyeon Date: Sun, 17 May 2026 20:32:41 +0900 Subject: [PATCH 08/20] =?UTF-8?q?fix:=20=EB=94=94=EC=9E=90=EC=9D=B8=20ui?= =?UTF-8?q?=201=EC=B0=A8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/CollapsibleScheduleSection.tsx | 44 +++ .../components/ColorSelectSection.tsx | 76 ++++ .../components/FixedScheduleDateSection.tsx | 95 +++++ .../components/GeneralScheduleDateSection.tsx | 47 +++ .../components/RecurrenceSelect.tsx | 42 ++ .../components/ScheduleDateRow.tsx | 111 ++++++ .../components/ScheduleSaveButton.tsx | 20 + .../components/ScheduleTabBar.tsx | 36 ++ .../components/WeekdayPicker.tsx | 37 ++ .../components/WorkTimeRangeField.tsx | 211 ++++++++++ .../components/WorkerSelectSection.tsx | 121 ++++++ src/pages/manager/worker-schedule/index.tsx | 363 +++--------------- .../lib/formatKoreanWorkTime.ts | 29 ++ .../worker-schedule/lib/scheduleDateParts.ts | 29 ++ .../manager/worker-schedule/types/workTime.ts | 10 + .../ui/schedule/ManagerMonthCalendar.tsx | 159 ++++++++ 16 files changed, 1127 insertions(+), 303 deletions(-) create mode 100644 src/pages/manager/worker-schedule/components/CollapsibleScheduleSection.tsx create mode 100644 src/pages/manager/worker-schedule/components/ColorSelectSection.tsx create mode 100644 src/pages/manager/worker-schedule/components/FixedScheduleDateSection.tsx create mode 100644 src/pages/manager/worker-schedule/components/GeneralScheduleDateSection.tsx create mode 100644 src/pages/manager/worker-schedule/components/RecurrenceSelect.tsx create mode 100644 src/pages/manager/worker-schedule/components/ScheduleDateRow.tsx create mode 100644 src/pages/manager/worker-schedule/components/ScheduleSaveButton.tsx create mode 100644 src/pages/manager/worker-schedule/components/ScheduleTabBar.tsx create mode 100644 src/pages/manager/worker-schedule/components/WeekdayPicker.tsx create mode 100644 src/pages/manager/worker-schedule/components/WorkTimeRangeField.tsx create mode 100644 src/pages/manager/worker-schedule/components/WorkerSelectSection.tsx create mode 100644 src/pages/manager/worker-schedule/lib/formatKoreanWorkTime.ts create mode 100644 src/pages/manager/worker-schedule/lib/scheduleDateParts.ts create mode 100644 src/pages/manager/worker-schedule/types/workTime.ts create mode 100644 src/shared/ui/schedule/ManagerMonthCalendar.tsx diff --git a/src/pages/manager/worker-schedule/components/CollapsibleScheduleSection.tsx b/src/pages/manager/worker-schedule/components/CollapsibleScheduleSection.tsx new file mode 100644 index 0000000..422d286 --- /dev/null +++ b/src/pages/manager/worker-schedule/components/CollapsibleScheduleSection.tsx @@ -0,0 +1,44 @@ +import type { ReactNode } from 'react' +import chevronDownIcon from '@/assets/icons/home/chevron-down.svg' +import { cn } from '@/shared/lib/utils' + +interface CollapsibleScheduleSectionProps { + title: string + icon?: ReactNode + isOpen: boolean + onToggle: () => void + children: ReactNode + className?: string +} + +export function CollapsibleScheduleSection({ + title, + icon, + isOpen, + onToggle, + children, + className, +}: CollapsibleScheduleSectionProps) { + return ( +
+ + {isOpen ? children : null} +
+ ) +} diff --git a/src/pages/manager/worker-schedule/components/ColorSelectSection.tsx b/src/pages/manager/worker-schedule/components/ColorSelectSection.tsx new file mode 100644 index 0000000..b9d456e --- /dev/null +++ b/src/pages/manager/worker-schedule/components/ColorSelectSection.tsx @@ -0,0 +1,76 @@ +import chevronDownIcon from '@/assets/icons/home/chevron-down.svg' +import { ScheduleColor } from '@/features/manager' +import { cn } from '@/shared/lib/utils' + +interface ColorSelectSectionProps { + selectedColor: ScheduleColor + onColorChange: (color: ScheduleColor) => void + isOpen: boolean + onToggle: () => void +} + +const COLOR_GRID_PADDING = 'px-[30px] pt-[29px] pb-[29px]' + +export function ColorSelectSection({ + selectedColor, + onColorChange, + isOpen, + onToggle, +}: ColorSelectSectionProps) { + const colors = Object.values(ScheduleColor) + + return ( +
+ + {isOpen ? ( +
+ {colors.map(color => ( + + ))} +
+ ) : null} +
+ ) +} diff --git a/src/pages/manager/worker-schedule/components/FixedScheduleDateSection.tsx b/src/pages/manager/worker-schedule/components/FixedScheduleDateSection.tsx new file mode 100644 index 0000000..95aef48 --- /dev/null +++ b/src/pages/manager/worker-schedule/components/FixedScheduleDateSection.tsx @@ -0,0 +1,95 @@ +import calendarIcon from '@/assets/icons/schedule/schedule_calendar.svg' +import { CollapsibleScheduleSection } from '@/pages/manager/worker-schedule/components/CollapsibleScheduleSection' +import { RecurrenceSelect } from '@/pages/manager/worker-schedule/components/RecurrenceSelect' +import type { ScheduleRecurrence } from '@/pages/manager/worker-schedule/components/RecurrenceSelect' +import { ScheduleDateRow } from '@/pages/manager/worker-schedule/components/ScheduleDateRow' +import { WeekdayPicker } from '@/pages/manager/worker-schedule/components/WeekdayPicker' +import { WorkTimeRangeField } from '@/pages/manager/worker-schedule/components/WorkTimeRangeField' +import type { WorkTimeEditorState } from '@/pages/manager/worker-schedule/types/workTime' + +interface FixedScheduleDateSectionProps { + isOpen: boolean + onToggle: () => void + workdayOptions: readonly string[] + selectedDays: string[] + onToggleDay: (day: string) => void + recurrence: ScheduleRecurrence + onRecurrenceChange: (value: ScheduleRecurrence) => void + startDate: Date + endDate: Date + onStartDateChange: (date: Date) => void + onEndDateChange: (date: Date) => void + workTime: WorkTimeEditorState + fixedScheduleLoading?: boolean +} + +export function FixedScheduleDateSection({ + isOpen, + onToggle, + workdayOptions, + selectedDays, + onToggleDay, + recurrence, + onRecurrenceChange, + startDate, + endDate, + onStartDateChange, + onEndDateChange, + workTime, + fixedScheduleLoading, +}: FixedScheduleDateSectionProps) { + return ( + + ) +} diff --git a/src/pages/manager/worker-schedule/components/GeneralScheduleDateSection.tsx b/src/pages/manager/worker-schedule/components/GeneralScheduleDateSection.tsx new file mode 100644 index 0000000..fff95e3 --- /dev/null +++ b/src/pages/manager/worker-schedule/components/GeneralScheduleDateSection.tsx @@ -0,0 +1,47 @@ +import calendarIcon from '@/assets/icons/schedule/schedule_calendar.svg' +import { CollapsibleScheduleSection } from '@/pages/manager/worker-schedule/components/CollapsibleScheduleSection' +import { WorkTimeRangeField } from '@/pages/manager/worker-schedule/components/WorkTimeRangeField' +import type { WorkTimeEditorState } from '@/pages/manager/worker-schedule/types/workTime' +import { ManagerMonthCalendar } from '@/shared/ui/schedule/ManagerMonthCalendar' + +interface GeneralScheduleDateSectionProps { + isOpen: boolean + onToggle: () => void + selectedDate: Date | null + onDateChange: (date: Date) => void + workTime: WorkTimeEditorState +} + +export function GeneralScheduleDateSection({ + isOpen, + onToggle, + selectedDate, + onDateChange, + workTime, +}: GeneralScheduleDateSectionProps) { + return ( + + ) +} diff --git a/src/pages/manager/worker-schedule/components/RecurrenceSelect.tsx b/src/pages/manager/worker-schedule/components/RecurrenceSelect.tsx new file mode 100644 index 0000000..a226472 --- /dev/null +++ b/src/pages/manager/worker-schedule/components/RecurrenceSelect.tsx @@ -0,0 +1,42 @@ +import { Fragment } from 'react' +import { cn } from '@/shared/lib/utils' + +export type ScheduleRecurrence = '매주' | '매월' + +const RECURRENCE_OPTIONS: ScheduleRecurrence[] = ['매주', '매월'] + +interface RecurrenceSelectProps { + value: ScheduleRecurrence + onChange: (value: ScheduleRecurrence) => void +} + +export function RecurrenceSelect({ value, onChange }: RecurrenceSelectProps) { + return ( +
+ {RECURRENCE_OPTIONS.map((option, index) => ( + + {index > 0 ? ( + + ) : null} + + + ))} +
+ ) +} diff --git a/src/pages/manager/worker-schedule/components/ScheduleDateRow.tsx b/src/pages/manager/worker-schedule/components/ScheduleDateRow.tsx new file mode 100644 index 0000000..e35fcf4 --- /dev/null +++ b/src/pages/manager/worker-schedule/components/ScheduleDateRow.tsx @@ -0,0 +1,111 @@ +import { + buildDateFromParts, + dateToPartStrings, +} from '@/pages/manager/worker-schedule/lib/scheduleDateParts' + +interface DatePartInputProps { + value: string + unit: string + maxDigits: number + max: number + inputWidthClass: string + onChange: (value: string) => void +} + +function DatePartInput({ + value, + unit, + maxDigits, + max, + inputWidthClass, + onChange, +}: DatePartInputProps) { + const handleChange = (e: React.ChangeEvent) => { + let inputValue = e.target.value.replace(/\D/g, '') + + if (!inputValue) { + onChange('') + return + } + + if (inputValue.length > maxDigits) { + inputValue = inputValue.slice(-maxDigits) + } + + const num = Math.min(Number.parseInt(inputValue, 10), max) + const next = + unit === '년' ? String(num) : num.toString().padStart(maxDigits, '0') + onChange(next) + } + + return ( + <> + + {unit} + + ) +} + +interface ScheduleDateRowProps { + label: string + date: Date + onDateChange: (date: Date) => void +} + +export function ScheduleDateRow({ + label, + date, + onDateChange, +}: ScheduleDateRowProps) { + const parts = dateToPartStrings(date) + + const updatePart = (patch: Partial) => { + const next = buildDateFromParts( + patch.year ?? parts.year, + patch.month ?? parts.month, + patch.day ?? parts.day, + date + ) + onDateChange(next) + } + + return ( +
+ {label} +
+ updatePart({ year })} + /> + updatePart({ month })} + /> + updatePart({ day })} + /> +
+
+ ) +} diff --git a/src/pages/manager/worker-schedule/components/ScheduleSaveButton.tsx b/src/pages/manager/worker-schedule/components/ScheduleSaveButton.tsx new file mode 100644 index 0000000..b2340f9 --- /dev/null +++ b/src/pages/manager/worker-schedule/components/ScheduleSaveButton.tsx @@ -0,0 +1,20 @@ +interface ScheduleSaveButtonProps { + onClick?: () => void + disabled?: boolean +} + +export function ScheduleSaveButton({ + onClick, + disabled, +}: ScheduleSaveButtonProps) { + return ( + + ) +} diff --git a/src/pages/manager/worker-schedule/components/ScheduleTabBar.tsx b/src/pages/manager/worker-schedule/components/ScheduleTabBar.tsx new file mode 100644 index 0000000..d3fe462 --- /dev/null +++ b/src/pages/manager/worker-schedule/components/ScheduleTabBar.tsx @@ -0,0 +1,36 @@ +import type { ScheduleTab } from '@/features/manager' +import { SCHEDULE_TABS } from '@/features/manager' +import { cn } from '@/shared/lib/utils' + +interface ScheduleTabBarProps { + activeTab: ScheduleTab + onTabChange: (tab: ScheduleTab) => void +} + +export function ScheduleTabBar({ + activeTab, + onTabChange, +}: ScheduleTabBarProps) { + return ( +
+ {SCHEDULE_TABS.map(tab => { + const isActive = activeTab === tab + return ( + + ) + })} +
+ ) +} diff --git a/src/pages/manager/worker-schedule/components/WeekdayPicker.tsx b/src/pages/manager/worker-schedule/components/WeekdayPicker.tsx new file mode 100644 index 0000000..051d7c8 --- /dev/null +++ b/src/pages/manager/worker-schedule/components/WeekdayPicker.tsx @@ -0,0 +1,37 @@ +import { cn } from '@/shared/lib/utils' + +interface WeekdayPickerProps { + options: readonly string[] + selectedDays: string[] + onToggleDay: (day: string) => void +} + +export function WeekdayPicker({ + options, + selectedDays, + onToggleDay, +}: WeekdayPickerProps) { + return ( +
+ {options.map(day => { + const selected = selectedDays.includes(day) + return ( + + ) + })} +
+ ) +} diff --git a/src/pages/manager/worker-schedule/components/WorkTimeRangeField.tsx b/src/pages/manager/worker-schedule/components/WorkTimeRangeField.tsx new file mode 100644 index 0000000..abf9e05 --- /dev/null +++ b/src/pages/manager/worker-schedule/components/WorkTimeRangeField.tsx @@ -0,0 +1,211 @@ +import { Fragment, useState } from 'react' +import { + formatKoreanTimePart, + hour24To12Parts, + partsToHour24, + type TimePeriod, +} from '@/pages/manager/worker-schedule/lib/formatKoreanWorkTime' +import type { WorkTimeEditorState } from '@/pages/manager/worker-schedule/types/workTime' +import { cn } from '@/shared/lib/utils' + +type EditTarget = 'start' | 'end' + +const PERIOD_OPTIONS: TimePeriod[] = ['오전', '오후'] + +interface TimePeriodToggleProps { + value: TimePeriod + onChange: (value: TimePeriod) => void +} + +function TimePeriodToggle({ value, onChange }: TimePeriodToggleProps) { + return ( +
+ {PERIOD_OPTIONS.map((option, index) => ( + + {index > 0 ? ( + + ) : null} + + + ))} +
+ ) +} + +interface TimePartInputProps { + value: string + unit: string + max: number + maxDigits?: number + inputWidthClass?: string + onChange: (value: string) => void +} + +function TimePartInput({ + value, + unit, + max, + maxDigits = 2, + inputWidthClass = 'w-7', + onChange, +}: TimePartInputProps) { + const handleChange = (e: React.ChangeEvent) => { + let inputValue = e.target.value.replace(/\D/g, '') + if (!inputValue) { + onChange('') + return + } + if (inputValue.length > maxDigits) { + inputValue = inputValue.slice(-maxDigits) + } + const num = Math.min(Number.parseInt(inputValue, 10), max) + onChange(num.toString().padStart(maxDigits, '0')) + } + + return ( + <> + + {unit} + + ) +} + +interface WorkTimeSegmentEditorProps { + target: EditTarget + workTime: WorkTimeEditorState +} + +function WorkTimeSegmentEditor({ + target, + workTime, +}: WorkTimeSegmentEditorProps) { + const hour = target === 'start' ? workTime.startHour : workTime.endHour + const minute = target === 'start' ? workTime.startMinute : workTime.endMinute + const setHour = + target === 'start' ? workTime.setStartHour : workTime.setEndHour + const setMinute = + target === 'start' ? workTime.setStartMinute : workTime.setEndMinute + + const { period, hour12 } = hour24To12Parts(hour) + const minuteValue = (minute || '00').padStart(2, '0') + + const applyPeriod = (nextPeriod: TimePeriod) => { + setHour(partsToHour24(nextPeriod, hour12)) + } + + const applyHour12 = (hour12Str: string) => { + const parsed = Number.parseInt(hour12Str || '12', 10) + const clamped = Math.min(12, Math.max(1, parsed || 1)) + setHour(partsToHour24(period, clamped)) + } + + return ( +
+ + + +
+ ) +} + +interface WorkTimeRangeFieldProps { + workTime: WorkTimeEditorState + className?: string +} + +export function WorkTimeRangeField({ + workTime, + className, +}: WorkTimeRangeFieldProps) { + const [editingTarget, setEditingTarget] = useState(null) + + const startLabel = formatKoreanTimePart( + workTime.startHour, + workTime.startMinute + ) + const endLabel = formatKoreanTimePart(workTime.endHour, workTime.endMinute) + + const toggleTarget = (target: EditTarget) => { + setEditingTarget(prev => (prev === target ? null : target)) + } + + return ( +
+

근무 시간

+ +
+ + + +
+ + {editingTarget ? ( + + ) : null} +
+ ) +} diff --git a/src/pages/manager/worker-schedule/components/WorkerSelectSection.tsx b/src/pages/manager/worker-schedule/components/WorkerSelectSection.tsx new file mode 100644 index 0000000..d9a0b6e --- /dev/null +++ b/src/pages/manager/worker-schedule/components/WorkerSelectSection.tsx @@ -0,0 +1,121 @@ +import { WorkerRoleBadge } from '@/shared/ui/home/WorkerRoleBadge' +import chevronDownIcon from '@/assets/icons/home/chevron-down.svg' +import type { ManagerWorkerItem } from '@/features/manager/home/types/worker' +import { cn } from '@/shared/lib/utils' + +interface WorkerSelectSectionProps { + worker: ManagerWorkerItem + workers: ManagerWorkerItem[] + isOpen: boolean + onToggle: () => void + onSelectWorker: (workerId: number) => void +} + +function WorkerAvatar() { + return ( +