From 9ed48893108fb5917b196c49f449a03405ab62e3 Mon Sep 17 00:00:00 2001 From: TakahashiNguyen Date: Thu, 14 May 2026 03:13:06 +0000 Subject: [PATCH] fix: add shift management (#41) Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com> Reviewed-on: https://git.demonkernel.io.vn/FoodSurf/frontend/pulls/41 --- .gitea/workflows/release.yaml | 4 +- app/(feed)/feed/page.tsx | 86 ---------- app/(feed)/layout.tsx | 9 - app/(staff)/staff/schedule/page.tsx | 12 +- components/molecules/cards/ShiftCard.tsx | 63 +++---- components/molecules/cards/ShiftCard.types.ts | 6 +- components/organisms/index.ts | 2 +- .../shift-schedule/MobileShiftView.tsx | 32 ++-- .../shift-schedule/MonthlyCalendar.tsx | 25 ++- .../shift-schedule/ShiftCreateModal.tsx | 15 +- .../shift-schedule/ShiftDetailModal.tsx | 30 ++-- .../shift-schedule/ShiftSchedule.types.ts | 16 +- .../shift-schedule/WeeklySchedule.tsx | 65 ++++--- components/organisms/shop-grid/ShopGrid.tsx | 80 ++++----- components/organisms/shop-grid/index.ts | 2 +- lib/constants.ts | 82 ++------- lib/shift-context.tsx | 158 ++++++++++++------ lib/types.ts | 40 +++-- 18 files changed, 305 insertions(+), 422 deletions(-) delete mode 100644 app/(feed)/feed/page.tsx delete mode 100644 app/(feed)/layout.tsx diff --git a/.gitea/workflows/release.yaml b/.gitea/workflows/release.yaml index b4aeb7a..a42775c 100644 --- a/.gitea/workflows/release.yaml +++ b/.gitea/workflows/release.yaml @@ -95,7 +95,6 @@ jobs: password: ${{ secrets.ACCESS_TOKEN }} - name: Prepare Docker Metadata - if: gitea.ref == 'refs/heads/main' id: meta run: | # Lowercase Repository @@ -108,11 +107,10 @@ jobs: echo "version=$VERSION_LOWER" >> $GITHUB_OUTPUT - name: Build and Push Docker Image - if: gitea.ref == 'refs/heads/main' uses: docker/build-push-action@v4 with: context: . - push: true + push: ${{ github.ref_name == 'main' }} tags: | vps.demonkernel.io.vn/${{ steps.meta.outputs.repo }}:latest vps.demonkernel.io.vn/${{ steps.meta.outputs.repo }}:${{ steps.meta.outputs.version }} diff --git a/app/(feed)/feed/page.tsx b/app/(feed)/feed/page.tsx deleted file mode 100644 index ab70930..0000000 --- a/app/(feed)/feed/page.tsx +++ /dev/null @@ -1,86 +0,0 @@ -"use client"; - -import { SearchBar } from "@/components/molecules/search-bar"; -import { ShopGrid } from "@/components/organisms/shop-grid"; -import { useState } from "react"; - -export default function FeedPage() { - const [searchName, setSearchName] = useState(""); - const [searchAddress, setSearchAddress] = useState(""); - - const hasFilters = searchName || searchAddress; - - return ( -
-
- {/* Page title */} -
-

- Explore Shops -

-

- Find and choose your favorite shop -

-
- - {/* Shop grid */} -
- - {hasFilters && ( -
- -
- )} -
- - {/* Filter / Search bar — sticky bottom */} -
-
-
- - Filter shops -
- - {/* Name search */} - setSearchName("")} - placeholder="Search by shop name..." - className="min-w-0 flex-1" - /> - - {/* Address search — different icon so not using SearchBar atom */} -
- - setSearchAddress(e.target.value)} - placeholder="Search by address..." - className="bg-background text-foreground focus:ring-opacity-20 w-full rounded-xl border border-(--color-border) py-2.5 pr-9 pl-9 text-sm transition-all duration-150 outline-none placeholder:text-(--color-text-muted) focus:border-(--color-primary) focus:ring-2 focus:ring-(--color-primary)" - /> - {searchAddress && ( - - )} -
-
-
-
-
- ); -} diff --git a/app/(feed)/layout.tsx b/app/(feed)/layout.tsx deleted file mode 100644 index 575cada..0000000 --- a/app/(feed)/layout.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { FeedLayout } from "@/components/templates/feed-layout"; - -export default function RootFeedLayout({ - children, -}: { - children: React.ReactNode; -}) { - return {children}; -} diff --git a/app/(staff)/staff/schedule/page.tsx b/app/(staff)/staff/schedule/page.tsx index 625f8ec..f1cede4 100644 --- a/app/(staff)/staff/schedule/page.tsx +++ b/app/(staff)/staff/schedule/page.tsx @@ -7,7 +7,7 @@ import ShiftDetailModal from "@/components/organisms/shift-schedule/ShiftDetailM import WeeklySchedule from "@/components/organisms/shift-schedule/WeeklySchedule"; import { useAuth } from "@/lib/auth-context"; import { useShift } from "@/lib/shift-context"; -import type { ShiftSlot } from "@/lib/types"; +import type { ShiftEntity } from "@/lib/types"; import Link from "next/link"; import { useState } from "react"; @@ -52,24 +52,24 @@ export default function StaffSchedulePage() { getWeeklyBudget, } = useShift(); - const [selectedShift, setSelectedShift] = useState(null); + const [selectedShift, setSelectedShift] = useState(null); const [detailOpen, setDetailOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false); - const [createDate, setCreateDate] = useState(); + const [createDate, setCreateDate] = useState(); const isManager = user?.role === "manager"; - const handleShiftClick = (shift: ShiftSlot) => { + const handleShiftClick = (shift: ShiftEntity) => { setSelectedShift(shift); setDetailOpen(true); }; - const handleCreateShift = (date: string) => { + const handleCreateShift = (date: Date) => { setCreateDate(date); setCreateOpen(true); }; - const handleDateSelect = (date: string) => { + const handleDateSelect = (date: Date) => { // In month view on desktop, clicking a date could open create modal for managers if (isManager) { setCreateDate(date); diff --git a/components/molecules/cards/ShiftCard.tsx b/components/molecules/cards/ShiftCard.tsx index 479f726..217b9df 100644 --- a/components/molecules/cards/ShiftCard.tsx +++ b/components/molecules/cards/ShiftCard.tsx @@ -1,35 +1,9 @@ "use client"; -import type { ShiftSlot } from "@/lib/types"; +import type { ShiftEntity } from "@/lib/types"; import type { ShiftCardProps } from "./ShiftCard.types"; -const STATUS_STYLES: Record< - ShiftSlot["status"], - { bg: string; text: string; label: string } -> = { - available: { - bg: "bg-blue-50 border-blue-200", - text: "text-blue-700", - label: "Còn trống", - }, - registered: { - bg: "bg-blue-100 border-blue-400", - text: "text-blue-900", - label: "Đã đăng ký", - }, - approved_leave: { - bg: "bg-purple-50 border-purple-300", - text: "text-purple-700", - label: "Nghỉ phép", - }, - absent: { - bg: "bg-red-50 border-red-300", - text: "text-red-700", - label: "Vắng mặt", - }, -}; - function formatWage(wage: number): string { if (wage >= 1000) { return `${(wage / 1000).toFixed(0)}k`; @@ -42,20 +16,20 @@ export default function ShiftCard({ compact = false, onClick, }: ShiftCardProps) { - const style = STATUS_STYLES[shift.status]; + console.log(shift); if (compact) { return ( ); @@ -65,7 +39,7 @@ export default function ShiftCard({ ); } diff --git a/components/molecules/cards/ShiftCard.types.ts b/components/molecules/cards/ShiftCard.types.ts index 47f0c02..1723b78 100644 --- a/components/molecules/cards/ShiftCard.types.ts +++ b/components/molecules/cards/ShiftCard.types.ts @@ -1,7 +1,7 @@ -import type { ShiftSlot } from "@/lib/types"; +import type { ShiftEntity } from "@/lib/types"; export interface ShiftCardProps { - shift: ShiftSlot; + shift: ShiftEntity; compact?: boolean; - onClick?: (shift: ShiftSlot) => void; + onClick?: (shift: ShiftEntity) => void; } diff --git a/components/organisms/index.ts b/components/organisms/index.ts index ff8605e..2a94e25 100644 --- a/components/organisms/index.ts +++ b/components/organisms/index.ts @@ -15,7 +15,7 @@ export { ReviewModal } from "./modals"; export type { ReviewModalProps, ConfirmModalProps } from "./modals"; // Shop Grid -export { ShopGrid } from "./shop-grid"; +// export { ShopGrid } from "./shop-grid"; export type { ShopGridProps } from "./shop-grid"; // Manager diff --git a/components/organisms/shift-schedule/MobileShiftView.tsx b/components/organisms/shift-schedule/MobileShiftView.tsx index 5da3fc3..7a7254b 100644 --- a/components/organisms/shift-schedule/MobileShiftView.tsx +++ b/components/organisms/shift-schedule/MobileShiftView.tsx @@ -44,9 +44,7 @@ export default function MobileShiftView({ onShiftClick, }: MobileShiftViewProps) { const { currentDate, shifts, goToNextMonth, goToPrevMonth } = useShift(); - const [selectedDate, setSelectedDate] = useState( - formatDateISO(new Date(2026, 3, 10)), - ); + const [selectedDate, setSelectedDate] = useState(new Date(2026, 3, 10)); const calendarDays = useMemo(() => { const year = currentDate.getFullYear(); @@ -67,21 +65,20 @@ export default function MobileShiftView({ }, [currentDate]); const getDotColors = (date: Date): string[] => { - const dateStr = formatDateISO(date); - const dayShifts = shifts.filter((s) => s.date === dateStr); + 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"); + // 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(() => { - return shifts.filter((s) => s.date === selectedDate); + return shifts.filter((s) => s.date.getDate() === selectedDate.getDate()); }, [shifts, selectedDate]); const selectedDateObj = new Date(selectedDate + "T00:00:00"); @@ -133,16 +130,15 @@ export default function MobileShiftView({ return
; } - const dateStr = formatDateISO(date); const today = isToday(date); - const selected = dateStr === selectedDate; + const selected = date === selectedDate; const dots = getDotColors(date); return ( ); })} diff --git a/components/organisms/shift-schedule/ShiftCreateModal.tsx b/components/organisms/shift-schedule/ShiftCreateModal.tsx index 9a0dddb..91ec295 100644 --- a/components/organisms/shift-schedule/ShiftCreateModal.tsx +++ b/components/organisms/shift-schedule/ShiftCreateModal.tsx @@ -4,6 +4,7 @@ import { DEPARTMENTS } from "@/lib/constants"; import { useShift } from "@/lib/shift-context"; import { useEffect, useState } from "react"; +import { formatDateISO } from "./MonthlyCalendar"; import type { ShiftCreateModalProps } from "./ShiftSchedule.types"; export default function ShiftCreateModal({ @@ -13,7 +14,7 @@ export default function ShiftCreateModal({ }: ShiftCreateModalProps) { const { createShift } = useShift(); - const [date, setDate] = useState(defaultDate ?? "2026-04-10"); + const [date, setDate] = useState(new Date(defaultDate ?? "2026-04-10")); const [startTime, setStartTime] = useState("08:00"); const [endTime, setEndTime] = useState("12:00"); const [department, setDepartment] = useState("bar"); @@ -23,7 +24,7 @@ export default function ShiftCreateModal({ useEffect(() => { if (isOpen) { - setDate(defaultDate ?? "2026-04-10"); + setDate(new Date(defaultDate ?? "2026-04-10")); } }, [defaultDate, isOpen]); @@ -49,18 +50,12 @@ export default function ShiftCreateModal({ return; } - const durationHours = (endMinutes - startMinutes) / 60; - createShift({ date, startTime, endTime, - durationHours, wage, - department, maxStaff, - registeredStaff: [], - status: "available", }); onClose(); @@ -103,8 +98,8 @@ export default function ShiftCreateModal({ setDate(e.target.value)} + value={formatDateISO(date)} + onChange={(e) => setDate(new Date(e.target.value))} className="text-foreground w-full rounded-xl border border-(--color-border-light) px-3 py-2.5 text-sm transition outline-none focus:border-(--color-primary) focus:ring-1 focus:ring-(--color-primary)" />
diff --git a/components/organisms/shift-schedule/ShiftDetailModal.tsx b/components/organisms/shift-schedule/ShiftDetailModal.tsx index b513612..45105a9 100644 --- a/components/organisms/shift-schedule/ShiftDetailModal.tsx +++ b/components/organisms/shift-schedule/ShiftDetailModal.tsx @@ -19,12 +19,12 @@ export default function ShiftDetailModal({ if (!isOpen || !shift) return null; - const dept = DEPARTMENTS.find((d) => d.id === shift.department); + // const dept = DEPARTMENTS.find((d) => d.id === shift.department); const isManager = user?.role === "manager"; const isRegistered = user - ? shift.registeredStaff.some((s) => s.id === user.id) + ? shift.registeredStaff!.some((s) => s.id === user.id) : false; - const isFull = shift.registeredStaff.length >= shift.maxStaff; + const isFull = shift.registeredStaff!.length >= shift.maxStaff; const handleRegister = () => { if (!user) return; @@ -47,7 +47,7 @@ export default function ShiftDetailModal({ setTimeout(onClose, 1200); }; - const handleManagerUnregister = (staffId: number) => { + const handleManagerUnregister = (staffId: string) => { unregisterShift(shift.id, staffId); setSuccess("Đã xóa nhân viên khỏi ca."); }; @@ -81,12 +81,12 @@ export default function ShiftDetailModal({ {/* Header */}
- {dept && } + {/* {dept && } */}

Chi tiết ca làm

-

{dept?.name}

+ {/*

{dept?.name}

*/}
- )} + )} */} {isRegistered && (