Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/app/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from 'react-router-dom'
import { ManagerHomePage } from '@/pages/manager/home'
import { ManagerWorkerSchedulePage } from '@/pages/manager/worker-schedule'
import { ManagerWorkerScheduleLegacyEntryRedirect } from '@/pages/manager/worker-schedule/LegacyEntryRedirect'
import { SocialPage } from '@/pages/manager/social'
import { SocialChatPage } from '@/pages/manager/social-chat'
import { LoginPage } from '@/pages/login'
Expand Down Expand Up @@ -94,6 +95,10 @@ export function App() {
<Route path={ROUTES.MY.PROFILE} element={<ProfileEditPage />} />
<Route
path={ROUTES.MANAGER.WORKER_SCHEDULE}
element={<ManagerWorkerScheduleLegacyEntryRedirect />}
/>
<Route
path={ROUTES.MANAGER.WORKER_SCHEDULE_PATTERN}
element={<ManagerWorkerSchedulePage />}
/>
<Route
Expand Down
3 changes: 3 additions & 0 deletions src/assets/icons/catppuccin_changelog.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions src/features/home/common/schedule/lib/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,30 @@ export function getDurationHours(startIso: string, endIso: string) {
const diffHours = Math.max((end - start) / (1000 * 60 * 60), 0)
return Number(diffHours.toFixed(1))
}

/** "9:0", "09:00:00" 등 → 시·분 두 자리 */
export function splitClockToParts(clock: string): {
hour: string
minute: string
} {
const [hRaw = '0', mRaw = '0'] = clock.trim().split(':')
const hourNum = Number.parseInt(hRaw, 10)
const minuteNum = Number.parseInt(mRaw, 10)
const hour = Number.isFinite(hourNum)
? String(hourNum).padStart(2, '0')
: '00'
const minute = Number.isFinite(minuteNum)
? String(minuteNum).padStart(2, '0')
: '00'
return { hour, minute }
}

export function formatClockRangeLabel(startClock: string, endClock: string) {
const s = splitClockToParts(startClock)
const e = splitClockToParts(endClock)
return `${s.hour}:${s.minute} ~ ${e.hour}:${e.minute}`
}

export function formatIsoTimeRangeLabel(startIso: string, endIso: string) {
return `${toTimeLabel(startIso)} ~ ${toTimeLabel(endIso)}`
}
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export function MonthYearPickerModal({
if (!isOpen) return null

return (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="fixed inset-0 z-[70] flex items-center justify-center">
<button
type="button"
className="absolute inset-0 bg-black/50"
Expand Down
22 changes: 21 additions & 1 deletion src/features/home/common/schedule/ui/MonthlyCalendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ import { cn } from '@/shared/lib/utils'
interface MonthlyCalendarProps extends MonthlyCalendarPropsBase {
isLoading?: boolean
hideTitle?: boolean
/** 제목(예: 업장명) 줄 우측 — 카드 헤더 액션 */
titleRightAction?: ReactNode
rightAction?: ReactNode
layout?: 'default' | 'manager'
onMonthChange?: (date: Date) => void
/** 현재 표시 월의 날짜만 전달된다. 선택 UI는 MonthlyDateCell이 버튼으로 바뀐다 */
onDaySelect?: (dateKey: string) => void
}

export function MonthlyCalendar({
Expand All @@ -22,9 +26,11 @@ export function MonthlyCalendar({
isLoading = false,
selectedDateKey,
hideTitle = false,
titleRightAction,
rightAction,
layout = 'default',
onMonthChange,
onDaySelect,
}: MonthlyCalendarProps) {
const {
title,
Expand Down Expand Up @@ -63,7 +69,16 @@ export function MonthlyCalendar({
return (
<section className="rounded-2xl bg-white">
<div className={layout === 'manager' ? 'px-6 pt-5' : 'px-[13px]'}>
{!hideTitle && <h3 className="typography-headline01 mb-4">{title}</h3>}
{!hideTitle && (
<div className="mb-4 flex items-center justify-between gap-2">
<h3 className="typography-headline01 min-w-0 flex-1 truncate">
{title}
</h3>
{titleRightAction ? (
<div className="shrink-0">{titleRightAction}</div>
) : null}
</div>
)}

<div className="flex items-center justify-between">
<button
Expand Down Expand Up @@ -120,6 +135,11 @@ export function MonthlyCalendar({
isSelected={cell.isSelected}
isActiveDay={cell.isActiveDay}
gaugeRatio={cell.dayProgress}
onClick={
onDaySelect && cell.isCurrentMonth
? () => onDaySelect(cell.dateKey)
: undefined
}
/>
)
})}
Expand Down
47 changes: 31 additions & 16 deletions src/features/home/common/schedule/ui/MonthlyDateCell.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { MonthlyDateGauge } from '@/features/home/common/schedule/ui/MonthlyDateGauge'
import { cn } from '@/shared/lib/utils'

interface MonthlyDateCellProps {
dayText: string
Expand All @@ -8,6 +9,8 @@ interface MonthlyDateCellProps {
isSelected: boolean
isActiveDay: boolean
gaugeRatio?: number
/** 전달 시 해당 칸은 버튼으로 렌더(일 선택 등) */
onClick?: () => void
}

export function MonthlyDateCell({
Expand All @@ -18,6 +21,7 @@ export function MonthlyDateCell({
isSelected,
isActiveDay,
gaugeRatio = 0,
onClick,
}: MonthlyDateCellProps) {
const dayTextColor = !isCurrentMonth
? 'text-text-50'
Expand All @@ -27,22 +31,33 @@ export function MonthlyDateCell({
? 'text-error'
: 'text-text-50'

return (
<div
className={`mx-auto flex h-12 w-12 items-center justify-center ${
isSelected ? 'bg-bg-light' : ''
}`}
>
{isActiveDay ? (
<div className="relative flex h-8 w-8 items-center justify-center">
<MonthlyDateGauge gaugeRatio={gaugeRatio} />
<span className="typography-body03-semibold text-text-100">
{dayText}
</span>
</div>
) : (
<p className={`typography-body03-regular ${dayTextColor}`}>{dayText}</p>
)}
const shellClass = cn(
'mx-auto flex h-12 w-12 items-center justify-center',
isSelected && 'bg-bg-light',
onClick &&
'cursor-pointer rounded-xl border border-transparent hover:bg-bg-light/80 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-main'
)

const body = isActiveDay ? (
<div className="relative flex h-8 w-8 items-center justify-center">
<MonthlyDateGauge gaugeRatio={gaugeRatio} />
<span className="typography-body03-semibold text-text-100">
{dayText}
</span>
</div>
) : (
<span className={cn('typography-body03-regular', dayTextColor)}>
{dayText}
</span>
)

if (onClick) {
return (
<button type="button" className={shellClass} onClick={onClick}>
{body}
</button>
)
}

return <div className={shellClass}>{body}</div>
}
16 changes: 16 additions & 0 deletions src/features/manager/home/api/workerFixedSchedule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import axiosInstance from '@/shared/lib/axiosInstance'
import type { WorkerFixedScheduleApiResponse } from '@/features/manager/home/types/workerFixedSchedule'

/**
* 매장 소속 근무자의 고정(반복) 근무 스케줄 조회.
* 백엔드 경로가 다르면 이 파일만 수정하면 된다.
*/
export async function fetchWorkerFixedSchedules(
workspaceId: number,
workerId: number
): Promise<WorkerFixedScheduleApiResponse> {
const response = await axiosInstance.get<WorkerFixedScheduleApiResponse>(
`/manager/workspaces/${workspaceId}/workers/${workerId}/fixed-schedules`
)
return response.data
}
12 changes: 12 additions & 0 deletions src/features/manager/home/constants/managerWeekdayKo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/** 고정 스케줄 UI·API 매핑에서 공통으로 쓰는 한글 요일 순서 */
export const MANAGER_WEEKDAY_KO_ORDER = [
'월',
'화',
'수',
'목',
'금',
'토',
'일',
] as const

export type ManagerWeekdayKo = (typeof MANAGER_WEEKDAY_KO_ORDER)[number]
3 changes: 3 additions & 0 deletions src/features/manager/home/hooks/useManagerHomeViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export function useManagerHomeViewModel() {

const {
workers: storeWorkers,
totalCount: storeWorkersTotalCount,
fetchNextPage: fetchMoreWorkers,
hasNextPage: hasMoreWorkers,
isFetchingNextPage: isFetchingMoreWorkers,
Expand Down Expand Up @@ -77,8 +78,10 @@ export function useManagerHomeViewModel() {
}, [isWorkspaceChangeModalOpen])

return {
activeWorkspaceId,
todayWorkers,
storeWorkers,
storeWorkersTotalCount,
fetchMoreWorkers,
hasMoreWorkers,
isFetchingMoreWorkers,
Expand Down
8 changes: 2 additions & 6 deletions src/features/manager/home/hooks/useTodaySchedulesViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@ import { useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import { fetchTodaySchedules } from '@/features/manager/home/api/schedule'
import { queryKeys } from '@/shared/lib/queryKeys'
import { toTimeLabel } from '@/features/home/common/schedule/lib/date'
import { formatIsoTimeRangeLabel } from '@/features/home/common/schedule/lib/date'
import type { TodayWorkerItem } from '@/features/manager/home/ui/TodayWorkerList'

function formatWorkTime(startDateTime: string, endDateTime: string): string {
return `${toTimeLabel(startDateTime)} ~ ${toTimeLabel(endDateTime)}`
}

export function useTodaySchedulesViewModel(workspaceId: number | null) {
const { data, isPending } = useQuery({
queryKey: queryKeys.manager.todaySchedules(workspaceId ?? 0),
Expand All @@ -23,7 +19,7 @@ export function useTodaySchedulesViewModel(workspaceId: number | null) {
name: worker.workerName,
profileImageUrl: worker.profileImageUrl,
workTime: worker.shifts[0]
? formatWorkTime(
? formatIsoTimeRangeLabel(
worker.shifts[0].startDateTime,
worker.shifts[0].endDateTime
)
Expand Down
Loading
Loading