Co-authored-by: TakahashiNg <83152264+TakahashiNguyen@users.noreply.github.com> Reviewed-on: #41
This commit was merged in pull request #41.
This commit is contained in:
@@ -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 (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onClick?.(shift)}
|
||||
className={`w-full cursor-pointer rounded-lg border px-2 py-1.5 text-left text-xs transition-shadow hover:shadow-sm ${style.bg} ${style.text}`}
|
||||
className={`w-full cursor-pointer rounded-lg border px-2 py-1.5 text-left text-xs transition-shadow hover:shadow-sm `}
|
||||
>
|
||||
<p className="font-semibold">
|
||||
{shift.startTime} – {shift.endTime}
|
||||
</p>
|
||||
<p className="mt-0.5 opacity-75">
|
||||
{shift.durationHours}h · {formatWage(shift.wage)}
|
||||
{}h · {formatWage(shift.wage)}
|
||||
</p>
|
||||
</button>
|
||||
);
|
||||
@@ -65,7 +39,7 @@ export default function ShiftCard({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onClick?.(shift)}
|
||||
className={`w-full cursor-pointer rounded-xl border p-3 text-left transition-shadow hover:shadow-md ${style.bg} ${style.text}`}
|
||||
className={`w-full cursor-pointer rounded-xl border p-3 text-left transition-shadow hover:shadow-md `}
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div>
|
||||
@@ -73,25 +47,26 @@ export default function ShiftCard({
|
||||
{shift.startTime} – {shift.endTime}
|
||||
</p>
|
||||
<p className="mt-1 text-xs opacity-75">
|
||||
{shift.durationHours}h · {formatWage(shift.wage)} VND
|
||||
{}h · {formatWage(shift.wage)} VND
|
||||
</p>
|
||||
</div>
|
||||
<span
|
||||
className={`rounded-full px-2 py-0.5 text-[10px] font-semibold ${
|
||||
shift.status === "available"
|
||||
? "bg-blue-200 text-blue-800"
|
||||
: shift.status === "registered"
|
||||
? "bg-blue-300 text-blue-900"
|
||||
: shift.status === "approved_leave"
|
||||
? "bg-purple-200 text-purple-800"
|
||||
: "bg-red-200 text-red-800"
|
||||
// shift.status === "available"
|
||||
// ? "bg-blue-200 text-blue-800"
|
||||
// : shift.status === "registered"
|
||||
// ? "bg-blue-300 text-blue-900"
|
||||
// : shift.status === "approved_leave"
|
||||
// ? "bg-purple-200 text-purple-800"
|
||||
// : "bg-red-200 text-red-800"
|
||||
""
|
||||
}`}
|
||||
>
|
||||
{style.label}
|
||||
{/* {style.label} */}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{shift.registeredStaff.length > 0 && (
|
||||
{(shift.registeredStaff && shift.registeredStaff.length > 0) && (
|
||||
<div className="mt-2 border-t border-current/10 pt-2">
|
||||
<p className="text-[10px] font-medium tracking-wide uppercase opacity-60">
|
||||
Nhân viên ({shift.registeredStaff.length}/{shift.maxStaff})
|
||||
@@ -102,18 +77,18 @@ export default function ShiftCard({
|
||||
key={s.id}
|
||||
className="rounded-full bg-white/60 px-2 py-0.5 text-[10px] font-medium"
|
||||
>
|
||||
{s.name}
|
||||
{s.staffId}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{shift.status === "available" && shift.registeredStaff.length === 0 && (
|
||||
{/* {shift.status === "available" && shift.registeredStaff.length === 0 && (
|
||||
<p className="mt-2 text-[10px] italic opacity-50">
|
||||
{shift.maxStaff} vị trí còn trống
|
||||
</p>
|
||||
)}
|
||||
)} */}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -44,9 +44,7 @@ export default function MobileShiftView({
|
||||
onShiftClick,
|
||||
}: MobileShiftViewProps) {
|
||||
const { currentDate, shifts, goToNextMonth, goToPrevMonth } = useShift();
|
||||
const [selectedDate, setSelectedDate] = useState<string>(
|
||||
formatDateISO(new Date(2026, 3, 10)),
|
||||
);
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(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 <div key={`empty-${i}`} className="p-1" />;
|
||||
}
|
||||
|
||||
const dateStr = formatDateISO(date);
|
||||
const today = isToday(date);
|
||||
const selected = dateStr === selectedDate;
|
||||
const selected = date === selectedDate;
|
||||
const dots = getDotColors(date);
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
key={i}
|
||||
onClick={() => setSelectedDate(dateStr)}
|
||||
onClick={() => setSelectedDate(date)}
|
||||
className={`flex cursor-pointer flex-col items-center border-none bg-transparent p-1 transition ${
|
||||
selected ? "rounded-lg bg-(--color-primary)/10" : ""
|
||||
}`}
|
||||
@@ -218,9 +214,7 @@ export default function MobileShiftView({
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
{DEPARTMENTS.map((dept) => {
|
||||
const deptShifts = selectedShifts.filter(
|
||||
(s) => s.department === dept.id,
|
||||
);
|
||||
const deptShifts = selectedShifts;
|
||||
if (deptShifts.length === 0) return null;
|
||||
return (
|
||||
<div key={dept.id}>
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { MonthlyCalendarProps } from "./ShiftSchedule.types";
|
||||
|
||||
const DAY_HEADERS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
||||
|
||||
function formatDateISO(d: Date): string {
|
||||
export function formatDateISO(d: Date): string {
|
||||
const y = d.getFullYear();
|
||||
const m = (d.getMonth() + 1).toString().padStart(2, "0");
|
||||
const day = d.getDate().toString().padStart(2, "0");
|
||||
@@ -60,15 +60,14 @@ export default function MonthlyCalendar({
|
||||
}, [currentDate]);
|
||||
|
||||
const getShiftSummary = (date: Date) => {
|
||||
const dateStr = formatDateISO(date);
|
||||
const dayShifts = shifts.filter((s) => s.date === dateStr);
|
||||
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, available, registered, leave, absent };
|
||||
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 (
|
||||
@@ -104,7 +103,7 @@ export default function MonthlyCalendar({
|
||||
<button
|
||||
type="button"
|
||||
key={i}
|
||||
onClick={() => onDateSelect?.(formatDateISO(date))}
|
||||
onClick={() => onDateSelect?.(date)}
|
||||
className={`min-h-25 cursor-pointer border-r border-b border-(--color-border-light) bg-transparent p-2 text-left transition hover:bg-gray-50 ${
|
||||
today ? "bg-(--color-primary)/5" : ""
|
||||
}`}
|
||||
@@ -119,7 +118,7 @@ export default function MonthlyCalendar({
|
||||
{date.getDate()}
|
||||
</span>
|
||||
|
||||
{summary.total > 0 && (
|
||||
{/* {summary.total > 0 && (
|
||||
<div className="mt-2 space-y-1">
|
||||
{summary.available > 0 && (
|
||||
<div className="flex items-center gap-1.5">
|
||||
@@ -154,7 +153,7 @@ export default function MonthlyCalendar({
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
)} */}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -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<Date>(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({
|
||||
<input
|
||||
title="Date"
|
||||
type="date"
|
||||
value={date}
|
||||
onChange={(e) => 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)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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 */}
|
||||
<div className="flex items-center justify-between border-b border-(--color-border-light) px-5 py-4">
|
||||
<div className="flex items-center gap-3">
|
||||
{dept && <i className={`${dept.icon} text-(--color-primary)`}></i>}
|
||||
{/* {dept && <i className={`${dept.icon} text-(--color-primary)`}></i>} */}
|
||||
<div>
|
||||
<h2 className="text-foreground text-base font-bold">
|
||||
Chi tiết ca làm
|
||||
</h2>
|
||||
<p className="text-xs text-(--color-text-muted)">{dept?.name}</p>
|
||||
{/* <p className="text-xs text-(--color-text-muted)">{dept?.name}</p> */}
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
@@ -102,13 +102,13 @@ export default function ShiftDetailModal({
|
||||
{/* Body */}
|
||||
<div className="space-y-4 px-5 py-4">
|
||||
{/* Status badge */}
|
||||
<div className="flex items-center gap-2">
|
||||
{/* <div className="flex items-center gap-2">
|
||||
<span
|
||||
className={`rounded-full px-3 py-1 text-xs font-semibold ${statusColor[shift.status]}`}
|
||||
>
|
||||
{statusLabel[shift.status]}
|
||||
</span>
|
||||
</div>
|
||||
</div> */}
|
||||
|
||||
{/* Shift info grid */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
@@ -141,7 +141,7 @@ export default function ShiftDetailModal({
|
||||
Thời lượng
|
||||
</p>
|
||||
<p className="text-foreground mt-1 text-sm font-bold">
|
||||
{shift.durationHours} giờ
|
||||
{""} giờ
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-xl bg-gray-50 p-3">
|
||||
@@ -157,16 +157,16 @@ export default function ShiftDetailModal({
|
||||
{/* Registered staff */}
|
||||
<div>
|
||||
<p className="mb-2 text-xs font-semibold text-(--color-text-secondary)">
|
||||
Nhân viên đã đăng ký ({shift.registeredStaff.length}/
|
||||
Nhân viên đã đăng ký ({shift.registeredStaff!.length}/
|
||||
{shift.maxStaff})
|
||||
</p>
|
||||
{shift.registeredStaff.length === 0 ? (
|
||||
{(shift.registeredStaff && shift.registeredStaff.length === 0) ? (
|
||||
<p className="text-xs text-(--color-text-muted) italic">
|
||||
Chưa có ai đăng ký
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{shift.registeredStaff.map((staff) => (
|
||||
{shift.registeredStaff!.map((staff) => (
|
||||
<div
|
||||
key={staff.id}
|
||||
className="flex items-center justify-between rounded-xl bg-gray-50 px-3 py-2"
|
||||
@@ -176,7 +176,7 @@ export default function ShiftDetailModal({
|
||||
<i className="fa-solid fa-user text-[10px] text-(--color-primary)"></i>
|
||||
</div>
|
||||
<span className="text-foreground text-sm font-medium">
|
||||
{staff.name}
|
||||
{staff.staffId}
|
||||
</span>
|
||||
</div>
|
||||
{isManager && (
|
||||
@@ -212,7 +212,7 @@ export default function ShiftDetailModal({
|
||||
|
||||
{/* Footer actions */}
|
||||
<div className="flex gap-2 border-t border-(--color-border-light) px-5 py-4">
|
||||
{!isRegistered &&
|
||||
{/* {!isRegistered &&
|
||||
!isFull &&
|
||||
shift.status !== "approved_leave" &&
|
||||
shift.status !== "absent" && (
|
||||
@@ -224,7 +224,7 @@ export default function ShiftDetailModal({
|
||||
<i className="fa-solid fa-calendar-plus mr-2"></i>
|
||||
Đăng ký ca
|
||||
</button>
|
||||
)}
|
||||
)} */}
|
||||
{isRegistered && (
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import type { ShiftSlot } from "@/lib/types";
|
||||
import type { ShiftEntity } from "@/lib/types";
|
||||
|
||||
export interface WeeklyScheduleProps {
|
||||
onShiftClick: (shift: ShiftSlot) => void;
|
||||
onCreateShift?: (date: string) => void;
|
||||
onShiftClick: (shift: ShiftEntity) => void;
|
||||
onCreateShift?: (date: Date) => void;
|
||||
mobileCalendarHeader?: boolean;
|
||||
}
|
||||
|
||||
export interface MonthlyCalendarProps {
|
||||
onShiftClick: (shift: ShiftSlot) => void;
|
||||
onDateSelect?: (date: string) => void;
|
||||
onShiftClick: (shift: ShiftEntity) => void;
|
||||
onDateSelect?: (date: Date) => void;
|
||||
}
|
||||
|
||||
export interface MobileShiftViewProps {
|
||||
onShiftClick: (shift: ShiftSlot) => void;
|
||||
onShiftClick: (shift: ShiftEntity) => void;
|
||||
}
|
||||
|
||||
export interface ShiftDetailModalProps {
|
||||
shift: ShiftSlot | null;
|
||||
shift: ShiftEntity | null;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
@@ -24,5 +24,5 @@ export interface ShiftDetailModalProps {
|
||||
export interface ShiftCreateModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
defaultDate?: string;
|
||||
defaultDate?: Date;
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ function formatDateISO(d: Date): string {
|
||||
}
|
||||
|
||||
function isToday(d: Date): boolean {
|
||||
const today = new Date(2026, 3, 10);
|
||||
const today = new Date(Date.now());
|
||||
return (
|
||||
d.getDate() === today.getDate() &&
|
||||
d.getMonth() === today.getMonth() &&
|
||||
@@ -59,36 +59,32 @@ export default function WeeklySchedule({
|
||||
} = useShift();
|
||||
const weekDates = getWeekDates();
|
||||
|
||||
const [selectedDate, setSelectedDate] = useState<string>(
|
||||
formatDateISO(weekDates[0] ?? currentDate),
|
||||
const [selectedDate, setSelectedDate] = useState<Date>(
|
||||
weekDates[0] ?? currentDate,
|
||||
);
|
||||
|
||||
const statusDotsByDate = useMemo(() => {
|
||||
const map: Record<string, string[]> = {};
|
||||
weekDates.forEach((date) => {
|
||||
const dateStr = formatDateISO(date);
|
||||
const dayShifts = getShiftsForDate(dateStr);
|
||||
const dots: string[] = [];
|
||||
if (dayShifts.some((s) => s.status === "available"))
|
||||
dots.push("bg-sky-300");
|
||||
if (dayShifts.some((s) => s.status === "registered"))
|
||||
dots.push("bg-blue-600");
|
||||
if (dayShifts.some((s) => s.status === "approved_leave"))
|
||||
dots.push("bg-purple-400");
|
||||
if (dayShifts.some((s) => s.status === "absent"))
|
||||
dots.push("bg-rose-400");
|
||||
map[dateStr] = dots.slice(0, 3);
|
||||
// if (dayShifts.some((s) => s.status === "available"))
|
||||
// dots.push("bg-sky-300");
|
||||
// if (dayShifts.some((s) => s.status === "registered"))
|
||||
// dots.push("bg-blue-600");
|
||||
// if (dayShifts.some((s) => s.status === "approved_leave"))
|
||||
// dots.push("bg-purple-400");
|
||||
// if (dayShifts.some((s) => s.status === "absent"))
|
||||
// dots.push("bg-rose-400");
|
||||
map[date.toISOString()] = dots.slice(0, 3);
|
||||
});
|
||||
return map;
|
||||
}, [weekDates, getShiftsForDate]);
|
||||
|
||||
const selectedDateObj = useMemo(() => {
|
||||
const inWeek = weekDates.find((d) => formatDateISO(d) === selectedDate);
|
||||
const selectedDateStr = useMemo(() => {
|
||||
const inWeek = weekDates.find((d) => d === selectedDate);
|
||||
return inWeek ?? weekDates[0] ?? currentDate;
|
||||
}, [selectedDate, weekDates, currentDate]);
|
||||
|
||||
const selectedDateStr = formatDateISO(selectedDateObj);
|
||||
|
||||
const renderMobileDayView = mobileCalendarHeader && (
|
||||
<div className="space-y-3">
|
||||
<div className="rounded-xl border border-(--color-border-light) bg-white p-3">
|
||||
@@ -116,14 +112,13 @@ export default function WeeklySchedule({
|
||||
|
||||
<div className="grid grid-cols-7 gap-1">
|
||||
{weekDates.map((date, i) => {
|
||||
const dateStr = formatDateISO(date);
|
||||
const active = dateStr === selectedDateStr;
|
||||
const dots = statusDotsByDate[dateStr] ?? [];
|
||||
const active = date === selectedDateStr;
|
||||
const dots = statusDotsByDate[date.toISOString()] ?? [];
|
||||
return (
|
||||
<button
|
||||
key={i}
|
||||
type="button"
|
||||
onClick={() => setSelectedDate(dateStr)}
|
||||
onClick={() => setSelectedDate(date)}
|
||||
className={`flex cursor-pointer flex-col items-center gap-1 rounded-xl border-none p-1 ${
|
||||
active ? "bg-(--color-primary)/10" : "bg-transparent"
|
||||
}`}
|
||||
@@ -135,14 +130,14 @@ export default function WeeklySchedule({
|
||||
</span>
|
||||
<span
|
||||
className={`flex h-9 w-9 items-center justify-center rounded-full text-sm font-semibold ${
|
||||
active || isToday(date)
|
||||
active || isToday(new Date(date))
|
||||
? "bg-indigo-500 text-white"
|
||||
: i >= 5
|
||||
? "text-pink-500"
|
||||
: "text-sky-500"
|
||||
}`}
|
||||
>
|
||||
{date.getDate()}
|
||||
{new Date(date).getDate()}
|
||||
</span>
|
||||
<div className="flex min-h-2 items-center gap-0.5">
|
||||
{dots.map((dot, idx) => (
|
||||
@@ -160,9 +155,10 @@ export default function WeeklySchedule({
|
||||
|
||||
<div className="space-y-3">
|
||||
{DEPARTMENTS.map((dept) => {
|
||||
const deptShifts = getShiftsForDate(selectedDateStr).filter(
|
||||
(s) => s.department === dept.id,
|
||||
);
|
||||
const deptShifts = getShiftsForDate(new Date(selectedDateStr));
|
||||
// .filter(
|
||||
// (s) => s.department === dept.id,
|
||||
// );
|
||||
if (deptShifts.length === 0 && !onCreateShift) return null;
|
||||
return (
|
||||
<div
|
||||
@@ -219,14 +215,14 @@ export default function WeeklySchedule({
|
||||
<th
|
||||
key={i}
|
||||
className={`border-r border-b border-(--color-border-light) px-2 py-3 text-center text-xs ${
|
||||
isToday(date)
|
||||
isToday(new Date(date))
|
||||
? "bg-(--color-primary)/10 font-bold text-(--color-primary)"
|
||||
: "bg-gray-50 font-semibold text-(--color-text-muted)"
|
||||
}`}
|
||||
>
|
||||
<span className="block uppercase">{DAY_LABELS[i]}</span>
|
||||
<span className="mt-0.5 block text-[11px] font-normal">
|
||||
{formatDateShort(date)}
|
||||
{date.toISOString()}
|
||||
</span>
|
||||
</th>
|
||||
))}
|
||||
@@ -246,15 +242,16 @@ export default function WeeklySchedule({
|
||||
</div>
|
||||
</td>
|
||||
{weekDates.map((date, i) => {
|
||||
const dateStr = formatDateISO(date);
|
||||
const shifts = getShiftsForDate(dateStr).filter(
|
||||
(s) => s.department === dept.id,
|
||||
);
|
||||
const dateStr = date;
|
||||
const shifts = getShiftsForDate(new Date(date));
|
||||
// .filter(
|
||||
// (s) => s.department === dept.id,
|
||||
// );
|
||||
return (
|
||||
<td
|
||||
key={i}
|
||||
className={`border-r border-b border-(--color-border-light) p-1.5 align-top ${
|
||||
isToday(date) ? "bg-(--color-primary)/5" : ""
|
||||
isToday(new Date(date)) ? "bg-(--color-primary)/5" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex min-h-20 flex-col gap-1">
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import { ShopCard } from "@/components/molecules/cards";
|
||||
import { MOCK_SHOPS } from "@/lib/constants";
|
||||
// import { ShopCard } from "@/components/molecules/cards";
|
||||
// import { MOCK_SHOPS } from "@/lib/constants";
|
||||
|
||||
import type { ShopGridProps } from "./ShopGrid.types";
|
||||
// import type { ShopGridProps } from "./ShopGrid.types";
|
||||
|
||||
export default function ShopGrid({
|
||||
searchName = "",
|
||||
searchAddress = "",
|
||||
}: ShopGridProps) {
|
||||
const filtered = MOCK_SHOPS.filter((shop) => {
|
||||
const matchesName =
|
||||
searchName.trim() === "" ||
|
||||
shop.name.toLowerCase().includes(searchName.toLowerCase());
|
||||
const matchesAddress =
|
||||
searchAddress.trim() === "" ||
|
||||
shop.address.toLowerCase().includes(searchAddress.toLowerCase());
|
||||
return matchesName && matchesAddress;
|
||||
});
|
||||
// export default function ShopGrid({
|
||||
// searchName = "",
|
||||
// searchAddress = "",
|
||||
// }: ShopGridProps) {
|
||||
// const filtered = MOCK_SHOPS.filter((shop) => {
|
||||
// const matchesName =
|
||||
// searchName.trim() === "" ||
|
||||
// shop.name.toLowerCase().includes(searchName.toLowerCase());
|
||||
// const matchesAddress =
|
||||
// searchAddress.trim() === "" ||
|
||||
// shop.address.toLowerCase().includes(searchAddress.toLowerCase());
|
||||
// return matchesName && matchesAddress;
|
||||
// });
|
||||
|
||||
if (filtered.length === 0) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center gap-4 py-24 text-(--color-text-muted)">
|
||||
<i className="fa-solid fa-store text-5xl opacity-30"></i>
|
||||
<p className="text-base font-medium">
|
||||
No shops found matching your search
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// if (filtered.length === 0) {
|
||||
// return (
|
||||
// <div className="flex flex-col items-center justify-center gap-4 py-24 text-(--color-text-muted)">
|
||||
// <i className="fa-solid fa-store text-5xl opacity-30"></i>
|
||||
// <p className="text-base font-medium">
|
||||
// No shops found matching your search
|
||||
// </p>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{filtered.map((shop) => (
|
||||
<ShopCard
|
||||
key={shop.id}
|
||||
id={shop.id}
|
||||
name={shop.name}
|
||||
address={shop.address}
|
||||
image={shop.image}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// return (
|
||||
// <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
|
||||
// {filtered.map((shop) => (
|
||||
// <ShopCard
|
||||
// key={shop.id}
|
||||
// id={shop.id}
|
||||
// name={shop.name}
|
||||
// address={shop.address}
|
||||
// image={shop.image}
|
||||
// />
|
||||
// ))}
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as ShopGrid } from "./ShopGrid";
|
||||
// export { default as ShopGrid } from "./ShopGrid";
|
||||
export type { ShopGridProps } from "./ShopGrid.types";
|
||||
|
||||
Reference in New Issue
Block a user