From 363bcae39d72626fc68f0cbeb9a9dc2ea74c91d6 Mon Sep 17 00:00:00 2001 From: Thanh Quy- wolf <524H0124@student.tdtu.edu.vn> Date: Thu, 14 May 2026 17:26:24 +0700 Subject: [PATCH] Refactor shift scheduling components for improved functionality and UI - Updated MonthlyCalendar to utilize getShiftsForDate and simplified date rendering logic. - Enhanced ShiftDetailModal to handle asynchronous shift registration and deletion. - Refined WeeklySchedule for better mobile and desktop views, including improved date handling and shift display. - Modified shift-context to support asynchronous operations for shift registration and improved date comparison logic. - Cleaned up unused code and comments for better readability and maintainability. --- app/(staff)/staff/schedule/page.tsx | 386 ++++++++-------- components/molecules/cards/ShiftCard.tsx | 144 +++--- .../shift-schedule/MobileShiftView.tsx | 253 ++++------- .../shift-schedule/MonthlyCalendar.tsx | 121 ++--- .../shift-schedule/ShiftDetailModal.tsx | 33 +- .../shift-schedule/WeeklySchedule.tsx | 418 ++++++++---------- lib/shift-context.tsx | 86 ++-- 7 files changed, 658 insertions(+), 783 deletions(-) diff --git a/app/(staff)/staff/schedule/page.tsx b/app/(staff)/staff/schedule/page.tsx index f1cede4..7b07d6a 100644 --- a/app/(staff)/staff/schedule/page.tsx +++ b/app/(staff)/staff/schedule/page.tsx @@ -12,18 +12,8 @@ import Link from "next/link"; import { useState } from "react"; const MONTH_NAMES = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", ]; function getMonday(d: Date): Date { @@ -41,15 +31,10 @@ function formatDateShort(d: Date): string { export default function StaffSchedulePage() { const { user, logout } = useAuth(); const { - view, - setView, - currentDate, - goToNextWeek, - goToPrevWeek, - goToNextMonth, - goToPrevMonth, - goToToday, - getWeeklyBudget, + view, setView, currentDate, + goToNextWeek, goToPrevWeek, + goToNextMonth, goToPrevMonth, + goToToday, getWeeklyBudget, shifts, } = useShift(); const [selectedShift, setSelectedShift] = useState(null); @@ -70,136 +55,171 @@ export default function StaffSchedulePage() { }; const handleDateSelect = (date: Date) => { - // In month view on desktop, clicking a date could open create modal for managers if (isManager) { setCreateDate(date); setCreateOpen(true); } }; - // Week range label const monday = getMonday(currentDate); const sunday = new Date(monday); sunday.setDate(monday.getDate() + 6); const weekLabel = `${formatDateShort(monday)} – ${formatDateShort(sunday)}`; - const weeklyBudget = getWeeklyBudget(); + // Count shifts this week for stats + const totalShiftsThisWeek = shifts.filter((s) => { + if (!s.date) return false; + const d = s.date instanceof Date ? s.date : new Date(s.date as unknown as string); + return d >= monday && d <= sunday; + }).length; + return ( -
- {/* ── Sidebar (Desktop) ── */} +
+ {/* ── Sidebar (Desktop) ─────────────────────────────────────────────────── */} - {/* ── Main content ── */} + {/* ── Main content ──────────────────────────────────────────────────────── */}
{/* Header */} -
-
-

- Register Shift -

-

- {view === "week" - ? weekLabel - : `${MONTH_NAMES[currentDate.getMonth()]} ${currentDate.getFullYear()}`} -

-
- -
- {/* View toggle (mobile) */} -
- - +
+
+ {/* Title */} +
+

+ {isManager ? "Quản lý ca làm" : "Đăng ký ca làm"} +

+

+ {view === "week" + ? `Tuần: ${weekLabel}` + : `Tháng: ${MONTH_NAMES[currentDate.getMonth()]} ${currentDate.getFullYear()}`} +

- {/* Navigation arrows */} -
- - - -
+
+ {/* Mobile view toggle */} +
+ + +
- {/* Create shift button (manager only) */} - {isManager && ( - - )} + {/* Desktop navigation arrows */} +
+ + + +
- {/* Mobile nav */} -
- - + {/* Create shift (manager, desktop) */} + {isManager && ( + + )} + + {/* Mobile navigation arrows */} +
+ + +
{/* Content */}
- {/* Desktop views */} + {/* Desktop */}
{view === "week" ? ( - {/* Mobile view */} + {/* Mobile */}
{view === "week" ? ( - {/* Mobile FAB for manager */} + {/* FAB for mobile manager */} {isManager && ( @@ -371,10 +388,7 @@ export default function StaffSchedulePage() { { - setDetailOpen(false); - setSelectedShift(null); - }} + onClose={() => { setDetailOpen(false); setSelectedShift(null); }} /> = 1000) { - return `${(wage / 1000).toFixed(0)}k`; - } + if (wage >= 1_000_000) return `${(wage / 1_000_000).toFixed(1)}M`; + if (wage >= 1000) return `${(wage / 1000).toFixed(0)}k`; return wage.toLocaleString("vi-VN"); } -export default function ShiftCard({ - shift, - compact = false, - onClick, -}: ShiftCardProps) { - console.log(shift); +function getShiftPeriod(startTime: string): "morning" | "afternoon" | "evening" { + const h = parseInt(startTime.split(":")[0] ?? "0", 10); + if (h < 12) return "morning"; + if (h < 18) return "afternoon"; + return "evening"; +} + +const PERIOD_STYLES = { + morning: { + border: "border-l-indigo-500", + text: "text-indigo-700", + badge: "bg-indigo-100 text-indigo-700", + dot: "bg-indigo-400", + }, + afternoon: { + border: "border-l-amber-500", + text: "text-amber-700", + badge: "bg-amber-100 text-amber-700", + dot: "bg-amber-400", + }, + evening: { + border: "border-l-violet-500", + text: "text-violet-700", + badge: "bg-violet-100 text-violet-700", + dot: "bg-violet-400", + }, +}; + +export default function ShiftCard({ shift, compact = false, onClick }: ShiftCardProps) { + const registeredCount = shift.registeredStaff?.length ?? 0; + const period = getShiftPeriod(shift.startTime); + const s = PERIOD_STYLES[period]; if (compact) { return ( ); } @@ -39,56 +67,44 @@ export default function ShiftCard({ ); } diff --git a/components/organisms/shift-schedule/MobileShiftView.tsx b/components/organisms/shift-schedule/MobileShiftView.tsx index 7a7254b..8e8f812 100644 --- a/components/organisms/shift-schedule/MobileShiftView.tsx +++ b/components/organisms/shift-schedule/MobileShiftView.tsx @@ -1,15 +1,18 @@ "use client"; import ShiftCard from "@/components/molecules/cards/ShiftCard"; -import { DEPARTMENTS } from "@/lib/constants"; import { useShift } from "@/lib/shift-context"; import { useMemo, useState } from "react"; import type { MobileShiftViewProps } from "./ShiftSchedule.types"; const DAY_HEADERS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; +const MONTH_NAMES = [ + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", +]; -function formatDateISO(d: Date): string { +function formatDateKey(d: Date): string { const y = d.getFullYear(); const m = (d.getMonth() + 1).toString().padStart(2, "0"); const day = d.getDate().toString().padStart(2, "0"); @@ -18,32 +21,11 @@ function formatDateISO(d: Date): string { function isToday(d: Date): boolean { const today = new Date(2026, 3, 10); - return ( - d.getDate() === today.getDate() && - d.getMonth() === today.getMonth() && - d.getFullYear() === today.getFullYear() - ); + return formatDateKey(d) === formatDateKey(today); } -const MONTH_NAMES = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", -]; - -export default function MobileShiftView({ - onShiftClick, -}: MobileShiftViewProps) { - const { currentDate, shifts, goToNextMonth, goToPrevMonth } = useShift(); +export default function MobileShiftView({ onShiftClick }: MobileShiftViewProps) { + const { currentDate, getShiftsForDate, goToNextMonth, goToPrevMonth } = useShift(); const [selectedDate, setSelectedDate] = useState(new Date(2026, 3, 10)); const calendarDays = useMemo(() => { @@ -57,187 +39,126 @@ export default function MobileShiftView({ const days: (Date | null)[] = []; for (let i = 0; i < startOffset; i++) days.push(null); - for (let d = 1; d <= lastDay.getDate(); d++) { - days.push(new Date(year, month, d)); - } + for (let d = 1; d <= lastDay.getDate(); d++) days.push(new Date(year, month, d)); while (days.length % 7 !== 0) days.push(null); return days; }, [currentDate]); - const getDotColors = (date: Date): string[] => { - const dayShifts = shifts.filter((s) => s.date.getDate() === date.getDate()); - const dots: string[] = []; - // if (dayShifts.some((s) => s.status === "available")) - // dots.push("bg-amber-400"); - // if (dayShifts.some((s) => s.status === "registered")) - // dots.push("bg-green-500"); - // if (dayShifts.some((s) => s.status === "approved_leave")) - // dots.push("bg-purple-400"); - // if (dayShifts.some((s) => s.status === "absent")) dots.push("bg-red-400"); - return dots; - }; + const selectedShifts = useMemo(() => getShiftsForDate(selectedDate), [selectedDate, getShiftsForDate]); - const selectedShifts = useMemo(() => { - return shifts.filter((s) => s.date.getDate() === selectedDate.getDate()); - }, [shifts, selectedDate]); - - const selectedDateObj = new Date(selectedDate + "T00:00:00"); - const dayOfWeek = DAY_HEADERS[(selectedDateObj.getDay() + 6) % 7]; + const dayOfWeek = DAY_HEADERS[(selectedDate.getDay() + 6) % 7]; return (
- {/* Compact month calendar */} -
+ {/* Month calendar card */} +
{/* Month navigation */} -
+
-

+

{MONTH_NAMES[currentDate.getMonth()]} {currentDate.getFullYear()}

- {/* Day headers */} -
- {DAY_HEADERS.map((day) => ( -
- {day} -
- ))} -
- - {/* Calendar grid */} -
- {calendarDays.map((date, i) => { - if (!date) { - return
; - } - - const today = isToday(date); - const selected = date === selectedDate; - const dots = getDotColors(date); - - return ( - - ); - })} -
+ {day} +
+ ))} +
- {/* Legend */} -
-
- - - Available - -
-
- - - Registered - -
-
- - - On leave - -
-
- - - Absent - + {/* Calendar grid */} +
+ {calendarDays.map((date, i) => { + if (!date) return
; + + const today = isToday(date); + const selected = formatDateKey(date) === formatDateKey(selectedDate); + const dayShifts = getShiftsForDate(date); + const isWeekend = date.getDay() === 0 || date.getDay() === 6; + + return ( + + ); + })}
{/* Selected day shifts */}
-

- {dayOfWeek}, {selectedDateObj.getDate()}/ - {selectedDateObj.getMonth() + 1}/{selectedDateObj.getFullYear()} - - ({selectedShifts.length} shift - {selectedShifts.length !== 1 ? "s" : ""}) +
+

+ {dayOfWeek},{" "} + {selectedDate.getDate()}/{selectedDate.getMonth() + 1}/{selectedDate.getFullYear()} +

+ 0 + ? "bg-(--color-primary)/10 text-(--color-primary)" + : "bg-gray-100 text-(--color-text-muted)" + }`}> + {selectedShifts.length} ca làm -

+
{selectedShifts.length === 0 ? ( -
- -

- No shifts for this day -

+
+ +

Không có ca làm

+

Chưa có ca nào được tạo hôm này

) : ( -
- {DEPARTMENTS.map((dept) => { - const deptShifts = selectedShifts; - if (deptShifts.length === 0) return null; - return ( -
-
- - - {dept.name} - -
-
- {deptShifts.map((shift) => ( - - ))} -
-
- ); - })} +
+ {selectedShifts.map((shift) => ( + + ))}
)}
diff --git a/components/organisms/shift-schedule/MonthlyCalendar.tsx b/components/organisms/shift-schedule/MonthlyCalendar.tsx index 77f2981..9eb18d1 100644 --- a/components/organisms/shift-schedule/MonthlyCalendar.tsx +++ b/components/organisms/shift-schedule/MonthlyCalendar.tsx @@ -23,11 +23,8 @@ function isToday(d: Date): boolean { ); } -export default function MonthlyCalendar({ - onShiftClick, - onDateSelect, -}: MonthlyCalendarProps) { - const { currentDate, shifts } = useShift(); +export default function MonthlyCalendar({ onShiftClick, onDateSelect }: MonthlyCalendarProps) { + const { currentDate, getShiftsForDate } = useShift(); const calendarDays = useMemo(() => { const year = currentDate.getFullYear(); @@ -35,49 +32,26 @@ export default function MonthlyCalendar({ const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); - // Monday = 0, Sunday = 6 let startOffset = firstDay.getDay() - 1; if (startOffset < 0) startOffset = 6; const days: (Date | null)[] = []; - - // Fill leading empty cells - for (let i = 0; i < startOffset; i++) { - days.push(null); - } - - // Fill actual days - for (let d = 1; d <= lastDay.getDate(); d++) { - days.push(new Date(year, month, d)); - } - - // Fill trailing empty cells to complete the grid - while (days.length % 7 !== 0) { - days.push(null); - } - + for (let i = 0; i < startOffset; i++) days.push(null); + for (let d = 1; d <= lastDay.getDate(); d++) days.push(new Date(year, month, d)); + while (days.length % 7 !== 0) days.push(null); return days; }, [currentDate]); - const getShiftSummary = (date: Date) => { - const dayShifts = shifts.filter((s) => s.date.getDate() === date.getDate()); - // const available = dayShifts.filter((s) => s.status === "available").length; - // const registered = dayShifts.filter( - // (s) => s.status === "registered", - // ).length; - // const leave = dayShifts.filter((s) => s.status === "approved_leave").length; - // const absent = dayShifts.filter((s) => s.status === "absent").length; - return { total: dayShifts.length }; - }; - return ( -
+
{/* Day headers */} -
- {DAY_HEADERS.map((day) => ( +
+ {DAY_HEADERS.map((day, i) => (
= 5 ? "text-rose-400" : "text-(--color-primary)" + }`} > {day}
@@ -85,75 +59,60 @@ export default function MonthlyCalendar({
{/* Calendar grid */} -
+
{calendarDays.map((date, i) => { if (!date) { return (
); } - const summary = getShiftSummary(date); const today = isToday(date); + const shifts = getShiftsForDate(date); + const shiftCount = shifts.length; + const isWeekend = date.getDay() === 0 || date.getDay() === 6; return ( ); })} diff --git a/components/organisms/shift-schedule/ShiftDetailModal.tsx b/components/organisms/shift-schedule/ShiftDetailModal.tsx index 41fbee3..249694c 100644 --- a/components/organisms/shift-schedule/ShiftDetailModal.tsx +++ b/components/organisms/shift-schedule/ShiftDetailModal.tsx @@ -35,11 +35,11 @@ export default function ShiftDetailModal({ const isRegistered = user ? registeredStaff.some((s) => s.id === user.id) : false; const isFull = registeredStaff.length >= shift.maxStaff; - const handleRegister = () => { + const handleRegister = async () => { if (!user) return; setError(null); setSuccess(null); - const result = registerShift(shift.id, user.id, user.name); + const result = await registerShift(shift.id, user.id, user.name); if (result.success) { setSuccess("Đăng ký ca thành công!"); setTimeout(onClose, 1200); @@ -61,8 +61,8 @@ export default function ShiftDetailModal({ setSuccess("Đã xóa nhân viên khỏi ca."); }; - const handleDelete = () => { - deleteShift(shift.id); + const handleDelete = async () => { + await deleteShift(shift.id); onClose(); }; @@ -156,7 +156,7 @@ export default function ShiftDetailModal({ Lương ca

- {shift.wage.toLocaleString("vi-VN")} VND + {(shift.wage ?? 0).toLocaleString("vi-VN")} VND

@@ -219,19 +219,16 @@ export default function ShiftDetailModal({ {/* Footer actions */}
- {/* {!isRegistered && - !isFull && - shift.status !== "approved_leave" && - shift.status !== "absent" && ( - - )} */} + {!isManager && !isRegistered && !isFull && ( + + )} {isRegistered && ( -

- {MONTH_NAMES_EN[currentDate.getMonth()]} {currentDate.getFullYear()} -

- -
- -
- {weekDates.map((date, i) => { - const active = date === selectedDateStr; - const dots = statusDotsByDate[date.toISOString()] ?? []; - return ( - - ); - })} -
-
- -
- {DEPARTMENTS.map((dept) => { - const deptShifts = getShiftsForDate(new Date(selectedDateStr)); - // .filter( - // (s) => s.department === dept.id, - // ); - if (deptShifts.length === 0 && !onCreateShift) return null; - return ( -
-
- - - {dept.name} - -
-
- {deptShifts.map((shift) => ( - - ))} - {onCreateShift && ( - - )} -
-
- ); - })} -
-
- ); + // ── Mobile calendar header view ──────────────────────────────────────────── if (mobileCalendarHeader) { - return renderMobileDayView ?? null; + const dayShifts = getShiftsForDate(new Date(selectedDateRef)); + + return ( +
+ {/* Week strip */} +
+
+ +

+ {MONTH_NAMES[currentDate.getMonth()]} {currentDate.getFullYear()} +

+ +
+
+ {weekDates.map((date, i) => { + const active = date === selectedDateRef; + const today = isToday(new Date(date)); + const shifts = getShiftsForDate(new Date(date)); + return ( + + ); + })} +
+
+ + {/* Day shifts */} +
+
+

+ {DAY_LABELS[(new Date(selectedDateRef).getDay() + 6) % 7]},{" "} + {formatDateShort(new Date(selectedDateRef))} +

+ + {dayShifts.length} ca + +
+ + {dayShifts.length === 0 && !onCreateShift ? ( +
+ +

Không có ca làm hôm nay

+
+ ) : ( +
+ {dayShifts.map((shift) => ( + + ))} + {onCreateShift && ( + + )} +
+ )} +
+
+ ); } + // ── Desktop table view ────────────────────────────────────────────────────── + return ( -
- - - - - {weekDates.map((date, i) => ( - + + {DEPARTMENTS.map((dept, deptIdx) => ( + + + {weekDates.map((date, i) => { + const today = isToday(new Date(date)); + const shifts = getShiftsForDate(new Date(date)); + return ( + + ); + })} + + ))} + +
- Department - - {DAY_LABELS[i]} - - {date.toISOString()} - +
+
+ + + + - ))} - - - - {DEPARTMENTS.map((dept) => ( - - {weekDates.map((date, i) => { - const dateStr = date; - const shifts = getShiftsForDate(new Date(date)); - // .filter( - // (s) => s.department === dept.id, - // ); + const today = isToday(new Date(date)); return ( - + + {DAY_LABELS[i]} + + + {date.getDate()} + + + {(date.getMonth() + 1).toString().padStart(2, "0")}/{date.getFullYear()} + + ); })} - ))} - -
+ Bộ phận
-
- - - {dept.name} - -
-
-
- {shifts.map((shift) => ( - - ))} - {onCreateShift && ( - - )} -
-
+
+
+
+ +
+ + {dept.name} + +
+
+
+ {shifts.map((shift) => ( + + ))} + {onCreateShift && ( + + )} +
+
+
); } diff --git a/lib/shift-context.tsx b/lib/shift-context.tsx index 3345adf..97065ba 100644 --- a/lib/shift-context.tsx +++ b/lib/shift-context.tsx @@ -41,7 +41,7 @@ interface ShiftContextType { shiftId: string, staffId: string, staffName: string, - ) => { success: boolean; error?: string }; + ) => Promise<{ success: boolean; error?: string }>; unregisterShift: (shiftId: string, staffId: string) => void; createShift: (shift: Omit) => void; deleteShift: (shiftId: string) => Promise; @@ -51,7 +51,7 @@ interface ShiftContextType { getShiftsForWeek: (weekStart: Date) => ShiftEntity[]; getWeekDates: () => Date[]; hasConflict: ( - date: Date, + date: Date | string, startTime: string, endTime: string, staffId: string, @@ -90,6 +90,13 @@ function toLocalDateISO(d: Date): string { return `${y}-${m}-${day}T00:00:00.000Z`; } +// Normalize Date or ISO string to "YYYY-MM-DD" for safe comparison +function dateKey(d: Date | string | undefined): string { + if (!d) return ""; + if (d instanceof Date) return formatDate(d); + return (d as string).slice(0, 10); +} + function timeToMinutes(time: string): number { const [h, m] = time.split(":").map(Number); return h * 60 + m; @@ -119,6 +126,10 @@ const GET_ALL_SHIFTS = gql` endTime maxStaff wage + registeredStaff { + id + staffId + } } } `; @@ -142,6 +153,14 @@ const DELETE_SHIFT = gql` } `; +const REGISTER_SHIFT = gql` + mutation registerShift($RegisterShiftInput: RegisterShiftInput) { + registerShift(registration: $RegisterShiftInput) { + staffId + } + } +`; + // ─── Provider ───────────────────────────────────────────────────────────────── export function ShiftProvider({ children }: { children: ReactNode }) { @@ -162,6 +181,11 @@ export function ShiftProvider({ children }: { children: ReactNode }) { client: eateryClient, }); + const [mutateRegisterShift] = useMutation< + { registerShift: { staffId: string } }, + { RegisterShiftInput: { shiftId: string } } + >(REGISTER_SHIFT, { client: eateryClient }); + useEffect(() => { if (data) setShifts(data.allShifts); }, [data]); @@ -217,9 +241,8 @@ export function ShiftProvider({ children }: { children: ReactNode }) { const getShiftsForDate = useCallback( (date: Date): ShiftEntity[] => { - return shifts.filter((s) => { - return new Date(s.date).getDate() === date.getDate(); - }); + const key = formatDate(date); + return shifts.filter((s) => dateKey(s.date) === key); }, [shifts], ); @@ -231,22 +254,24 @@ export function ShiftProvider({ children }: { children: ReactNode }) { d.setDate(weekStart.getDate() + i); return d; }); - return shifts.filter((s) => dates.includes(s.date)); + const keys = new Set(dates.map(formatDate)); + return shifts.filter((s) => keys.has(dateKey(s.date))); }, [shifts], ); const hasConflict = useCallback( ( - date: Date, + date: Date | string, startTime: string, endTime: string, staffId: string, excludeShiftId?: string, ): boolean => { + const key = dateKey(date); return shifts.some( (s) => - s.date === date && + dateKey(s.date) === key && s.id !== excludeShiftId && (s.registeredStaff ?? []).some((rs) => rs.id === staffId) && timesOverlap(startTime, endTime, s.startTime, s.endTime), @@ -257,9 +282,10 @@ export function ShiftProvider({ children }: { children: ReactNode }) { const getWeeklyBudget = useCallback((): number => { const weekDates = getWeekDates(); + const weekKeys = new Set(weekDates.map(formatDate)); return shifts .filter( - (s) => weekDates.includes(s.date) && (s.registeredStaff ?? []).length > 0, + (s) => weekKeys.has(dateKey(s.date)) && (s.registeredStaff ?? []).length > 0, ) .reduce((sum, s) => sum + (s.wage ?? 0) * (s.registeredStaff ?? []).length, 0); }, [shifts, getWeekDates]); @@ -267,11 +293,11 @@ export function ShiftProvider({ children }: { children: ReactNode }) { // ── Shift actions ─────────────────────────────────────────────────────── const registerShift = useCallback( - ( + async ( shiftId: string, staffId: string, staffName: string, - ): { success: boolean; error?: string } => { + ): Promise<{ success: boolean; error?: string }> => { const shift = shifts.find((s) => s.id === shiftId); if (!shift) return { success: false, error: "Ca làm không tồn tại." }; @@ -284,13 +310,8 @@ export function ShiftProvider({ children }: { children: ReactNode }) { } if ( - hasConflict( - shift.date, - shift.startTime, - shift.endTime, - staffId, - shiftId, - ) + shift.date && + hasConflict(shift.date, shift.startTime, shift.endTime, staffId, shiftId) ) { return { success: false, @@ -298,24 +319,19 @@ export function ShiftProvider({ children }: { children: ReactNode }) { }; } - // setShifts((prev) => - // prev.map((s) => - // s.id === shiftId - // ? { - // ...s, - // registeredStaff: [ - // ...s.registeredStaff, - // { staffId, name: staffName }, - // ], - // status: "registered" as ShiftStatus, - // } - // : s, - // ), - // ); - - return { success: true }; + try { + await mutateRegisterShift({ + variables: { RegisterShiftInput: { shiftId } }, + }); + refetch(); + return { success: true }; + } catch (err: unknown) { + const message = + err instanceof Error ? err.message : "Đăng ký thất bại, thử lại sau."; + return { success: false, error: message }; + } }, - [shifts, hasConflict], + [shifts, hasConflict, mutateRegisterShift, refetch], ); const unregisterShift = useCallback((shiftId: string, staffId: string) => {