chore: update
This commit is contained in:
@@ -70,13 +70,13 @@ export default function MobileShiftView({
|
|||||||
const dateStr = formatDateISO(date);
|
const dateStr = formatDateISO(date);
|
||||||
const dayShifts = shifts.filter((s) => s.date === dateStr);
|
const dayShifts = shifts.filter((s) => s.date === dateStr);
|
||||||
const dots: string[] = [];
|
const dots: string[] = [];
|
||||||
if (dayShifts.some((s) => s.status === "available"))
|
// if (dayShifts.some((s) => s.status === "available"))
|
||||||
dots.push("bg-amber-400");
|
// dots.push("bg-amber-400");
|
||||||
if (dayShifts.some((s) => s.status === "registered"))
|
// if (dayShifts.some((s) => s.status === "registered"))
|
||||||
dots.push("bg-green-500");
|
// dots.push("bg-green-500");
|
||||||
if (dayShifts.some((s) => s.status === "approved_leave"))
|
// if (dayShifts.some((s) => s.status === "approved_leave"))
|
||||||
dots.push("bg-purple-400");
|
// dots.push("bg-purple-400");
|
||||||
if (dayShifts.some((s) => s.status === "absent")) dots.push("bg-red-400");
|
// if (dayShifts.some((s) => s.status === "absent")) dots.push("bg-red-400");
|
||||||
return dots;
|
return dots;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -62,13 +62,13 @@ export default function MonthlyCalendar({
|
|||||||
const getShiftSummary = (date: Date) => {
|
const getShiftSummary = (date: Date) => {
|
||||||
const dateStr = formatDateISO(date);
|
const dateStr = formatDateISO(date);
|
||||||
const dayShifts = shifts.filter((s) => s.date === dateStr);
|
const dayShifts = shifts.filter((s) => s.date === dateStr);
|
||||||
const available = dayShifts.filter((s) => s.status === "available").length;
|
// const available = dayShifts.filter((s) => s.status === "available").length;
|
||||||
const registered = dayShifts.filter(
|
// const registered = dayShifts.filter(
|
||||||
(s) => s.status === "registered",
|
// (s) => s.status === "registered",
|
||||||
).length;
|
// ).length;
|
||||||
const leave = dayShifts.filter((s) => s.status === "approved_leave").length;
|
// const leave = dayShifts.filter((s) => s.status === "approved_leave").length;
|
||||||
const absent = dayShifts.filter((s) => s.status === "absent").length;
|
// const absent = dayShifts.filter((s) => s.status === "absent").length;
|
||||||
return { total: dayShifts.length, available, registered, leave, absent };
|
return { total: dayShifts.length };
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -119,7 +119,7 @@ export default function MonthlyCalendar({
|
|||||||
{date.getDate()}
|
{date.getDate()}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
{summary.total > 0 && (
|
{/* {summary.total > 0 && (
|
||||||
<div className="mt-2 space-y-1">
|
<div className="mt-2 space-y-1">
|
||||||
{summary.available > 0 && (
|
{summary.available > 0 && (
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
@@ -154,7 +154,7 @@ export default function MonthlyCalendar({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export default function ShiftCreateModal({
|
|||||||
}
|
}
|
||||||
|
|
||||||
createShift({
|
createShift({
|
||||||
date: new Date(date),
|
date,
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
wage,
|
wage,
|
||||||
|
|||||||
@@ -19,12 +19,12 @@ export default function ShiftDetailModal({
|
|||||||
|
|
||||||
if (!isOpen || !shift) return null;
|
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 isManager = user?.role === "manager";
|
||||||
const isRegistered = user
|
const isRegistered = user
|
||||||
? shift.registeredStaff.some((s) => s.id === user.id)
|
? shift.registeredStaff!.some((s) => s.id === user.id)
|
||||||
: false;
|
: false;
|
||||||
const isFull = shift.registeredStaff.length >= shift.maxStaff;
|
const isFull = shift.registeredStaff!.length >= shift.maxStaff;
|
||||||
|
|
||||||
const handleRegister = () => {
|
const handleRegister = () => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
@@ -47,7 +47,7 @@ export default function ShiftDetailModal({
|
|||||||
setTimeout(onClose, 1200);
|
setTimeout(onClose, 1200);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleManagerUnregister = (staffId: number) => {
|
const handleManagerUnregister = (staffId: string) => {
|
||||||
unregisterShift(shift.id, staffId);
|
unregisterShift(shift.id, staffId);
|
||||||
setSuccess("Đã xóa nhân viên khỏi ca.");
|
setSuccess("Đã xóa nhân viên khỏi ca.");
|
||||||
};
|
};
|
||||||
@@ -81,12 +81,12 @@ export default function ShiftDetailModal({
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between border-b border-(--color-border-light) px-5 py-4">
|
<div className="flex items-center justify-between border-b border-(--color-border-light) px-5 py-4">
|
||||||
<div className="flex items-center gap-3">
|
<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>
|
<div>
|
||||||
<h2 className="text-foreground text-base font-bold">
|
<h2 className="text-foreground text-base font-bold">
|
||||||
Chi tiết ca làm
|
Chi tiết ca làm
|
||||||
</h2>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -102,13 +102,13 @@ export default function ShiftDetailModal({
|
|||||||
{/* Body */}
|
{/* Body */}
|
||||||
<div className="space-y-4 px-5 py-4">
|
<div className="space-y-4 px-5 py-4">
|
||||||
{/* Status badge */}
|
{/* Status badge */}
|
||||||
<div className="flex items-center gap-2">
|
{/* <div className="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
className={`rounded-full px-3 py-1 text-xs font-semibold ${statusColor[shift.status]}`}
|
className={`rounded-full px-3 py-1 text-xs font-semibold ${statusColor[shift.status]}`}
|
||||||
>
|
>
|
||||||
{statusLabel[shift.status]}
|
{statusLabel[shift.status]}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{/* Shift info grid */}
|
{/* Shift info grid */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
@@ -141,7 +141,7 @@ export default function ShiftDetailModal({
|
|||||||
Thời lượng
|
Thời lượng
|
||||||
</p>
|
</p>
|
||||||
<p className="text-foreground mt-1 text-sm font-bold">
|
<p className="text-foreground mt-1 text-sm font-bold">
|
||||||
{shift.durationHours} giờ
|
{""} giờ
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="rounded-xl bg-gray-50 p-3">
|
<div className="rounded-xl bg-gray-50 p-3">
|
||||||
@@ -157,16 +157,16 @@ export default function ShiftDetailModal({
|
|||||||
{/* Registered staff */}
|
{/* Registered staff */}
|
||||||
<div>
|
<div>
|
||||||
<p className="mb-2 text-xs font-semibold text-(--color-text-secondary)">
|
<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})
|
{shift.maxStaff})
|
||||||
</p>
|
</p>
|
||||||
{shift.registeredStaff.length === 0 ? (
|
{(shift.registeredStaff && shift.registeredStaff.length === 0) ? (
|
||||||
<p className="text-xs text-(--color-text-muted) italic">
|
<p className="text-xs text-(--color-text-muted) italic">
|
||||||
Chưa có ai đăng ký
|
Chưa có ai đăng ký
|
||||||
</p>
|
</p>
|
||||||
) : (
|
) : (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{shift.registeredStaff.map((staff) => (
|
{shift.registeredStaff!.map((staff) => (
|
||||||
<div
|
<div
|
||||||
key={staff.id}
|
key={staff.id}
|
||||||
className="flex items-center justify-between rounded-xl bg-gray-50 px-3 py-2"
|
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>
|
<i className="fa-solid fa-user text-[10px] text-(--color-primary)"></i>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-foreground text-sm font-medium">
|
<span className="text-foreground text-sm font-medium">
|
||||||
{staff.name}
|
{staff.staffId}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{isManager && (
|
{isManager && (
|
||||||
@@ -212,7 +212,7 @@ export default function ShiftDetailModal({
|
|||||||
|
|
||||||
{/* Footer actions */}
|
{/* Footer actions */}
|
||||||
<div className="flex gap-2 border-t border-(--color-border-light) px-5 py-4">
|
<div className="flex gap-2 border-t border-(--color-border-light) px-5 py-4">
|
||||||
{!isRegistered &&
|
{/* {!isRegistered &&
|
||||||
!isFull &&
|
!isFull &&
|
||||||
shift.status !== "approved_leave" &&
|
shift.status !== "approved_leave" &&
|
||||||
shift.status !== "absent" && (
|
shift.status !== "absent" && (
|
||||||
@@ -224,7 +224,7 @@ export default function ShiftDetailModal({
|
|||||||
<i className="fa-solid fa-calendar-plus mr-2"></i>
|
<i className="fa-solid fa-calendar-plus mr-2"></i>
|
||||||
Đăng ký ca
|
Đăng ký ca
|
||||||
</button>
|
</button>
|
||||||
)}
|
)} */}
|
||||||
{isRegistered && (
|
{isRegistered && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ function formatDateISO(d: Date): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isToday(d: Date): boolean {
|
function isToday(d: Date): boolean {
|
||||||
const today = Date.now();
|
const today = new Date(Date.now());
|
||||||
return (
|
return (
|
||||||
d.getDate() === today.getDate() &&
|
d.getDate() === today.getDate() &&
|
||||||
d.getMonth() === today.getMonth() &&
|
d.getMonth() === today.getMonth() &&
|
||||||
@@ -81,7 +81,7 @@ export default function WeeklySchedule({
|
|||||||
}, [weekDates, getShiftsForDate]);
|
}, [weekDates, getShiftsForDate]);
|
||||||
|
|
||||||
const selectedDateStr = useMemo(() => {
|
const selectedDateStr = useMemo(() => {
|
||||||
const inWeek = weekDates.find((d) => formatDateISO(d) === selectedDate);
|
const inWeek = weekDates.find((d) => d === selectedDate);
|
||||||
return inWeek ?? weekDates[0] ?? currentDate;
|
return inWeek ?? weekDates[0] ?? currentDate;
|
||||||
}, [selectedDate, weekDates, currentDate]);
|
}, [selectedDate, weekDates, currentDate]);
|
||||||
|
|
||||||
@@ -215,14 +215,14 @@ export default function WeeklySchedule({
|
|||||||
<th
|
<th
|
||||||
key={i}
|
key={i}
|
||||||
className={`border-r border-b border-(--color-border-light) px-2 py-3 text-center text-xs ${
|
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-(--color-primary)/10 font-bold text-(--color-primary)"
|
||||||
: "bg-gray-50 font-semibold text-(--color-text-muted)"
|
: "bg-gray-50 font-semibold text-(--color-text-muted)"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="block uppercase">{DAY_LABELS[i]}</span>
|
<span className="block uppercase">{DAY_LABELS[i]}</span>
|
||||||
<span className="mt-0.5 block text-[11px] font-normal">
|
<span className="mt-0.5 block text-[11px] font-normal">
|
||||||
{formatDateShort(date)}
|
{date}
|
||||||
</span>
|
</span>
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
@@ -242,8 +242,8 @@ export default function WeeklySchedule({
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{weekDates.map((date, i) => {
|
{weekDates.map((date, i) => {
|
||||||
const dateStr = formatDateISO(date);
|
const dateStr = date;
|
||||||
const shifts = getShiftsForDate(date);
|
const shifts = getShiftsForDate(new Date(date));
|
||||||
// .filter(
|
// .filter(
|
||||||
// (s) => s.department === dept.id,
|
// (s) => s.department === dept.id,
|
||||||
// );
|
// );
|
||||||
@@ -251,7 +251,7 @@ export default function WeeklySchedule({
|
|||||||
<td
|
<td
|
||||||
key={i}
|
key={i}
|
||||||
className={`border-r border-b border-(--color-border-light) p-1.5 align-top ${
|
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">
|
<div className="flex min-h-20 flex-col gap-1">
|
||||||
|
|||||||
+5
-5
@@ -146,7 +146,7 @@ function generateMockShifts(): ShiftEntity[] {
|
|||||||
|
|
||||||
// Determine registration status
|
// Determine registration status
|
||||||
const registered: { id: number; name: string }[] = [];
|
const registered: { id: number; name: string }[] = [];
|
||||||
let status: ShiftEntity["status"] = "available";
|
// let status: ShiftEntity["status"] = "available";
|
||||||
|
|
||||||
// Past shifts (before April 10) are mostly registered
|
// Past shifts (before April 10) are mostly registered
|
||||||
if (day < 10) {
|
if (day < 10) {
|
||||||
@@ -180,12 +180,12 @@ function generateMockShifts(): ShiftEntity[] {
|
|||||||
date: dateStr,
|
date: dateStr,
|
||||||
startTime: slot.start,
|
startTime: slot.start,
|
||||||
endTime: slot.end,
|
endTime: slot.end,
|
||||||
durationHours: slot.hours,
|
// durationHours: slot.hours,
|
||||||
wage: slot.wage,
|
wage: slot.wage,
|
||||||
department: dept,
|
// department: dept,
|
||||||
maxStaff: dept === "bar" ? 3 : 2,
|
maxStaff: dept === "bar" ? 3 : 2,
|
||||||
registeredStaff: registered,
|
// registeredStaff: registered,
|
||||||
status,
|
// status,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -2,7 +2,7 @@
|
|||||||
export type UserRole = "manager" | "staff" | "customer";
|
export type UserRole = "manager" | "staff" | "customer";
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
role: UserRole;
|
role: UserRole;
|
||||||
avatar: string | null;
|
avatar: string | null;
|
||||||
@@ -104,6 +104,12 @@ export interface ShiftEntity {
|
|||||||
registeredStaff?: ShiftRegistrationEntity[];
|
registeredStaff?: ShiftRegistrationEntity[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Department {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
icon: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface MenuItemEntity {
|
export interface MenuItemEntity {
|
||||||
id?: string;
|
id?: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user