54d5d6150c
Release package / release (pull_request) Successful in 3m23s
- Translated payment page text from Vietnamese to English. - Updated staff creation page with English labels and messages. - Changed staff schedule page labels and navigation items to English. - Modified shift card and search bar components to use English text. - Updated delete confirmation and product modal components with English messages. - Translated product management tab and category sidebar to English. - Changed shift schedule components to display English text for shifts and actions. - Updated header logo alt text to improve accessibility.
123 lines
4.3 KiB
TypeScript
123 lines
4.3 KiB
TypeScript
"use client";
|
|
|
|
import { useShift } from "@/lib/shift-context";
|
|
import { useMemo } from "react";
|
|
|
|
import type { MonthlyCalendarProps } from "./ShiftSchedule.types";
|
|
|
|
const DAY_HEADERS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
|
|
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");
|
|
return `${y}-${m}-${day}`;
|
|
}
|
|
|
|
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()
|
|
);
|
|
}
|
|
|
|
export default function MonthlyCalendar({ onShiftClick, onDateSelect }: MonthlyCalendarProps) {
|
|
const { currentDate, getShiftsForDate } = useShift();
|
|
|
|
const calendarDays = useMemo(() => {
|
|
const year = currentDate.getFullYear();
|
|
const month = currentDate.getMonth();
|
|
const firstDay = new Date(year, month, 1);
|
|
const lastDay = new Date(year, month + 1, 0);
|
|
|
|
let startOffset = firstDay.getDay() - 1;
|
|
if (startOffset < 0) startOffset = 6;
|
|
|
|
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));
|
|
while (days.length % 7 !== 0) days.push(null);
|
|
return days;
|
|
}, [currentDate]);
|
|
|
|
return (
|
|
<div className="overflow-hidden rounded-2xl border border-(--color-border-light) shadow-sm">
|
|
{/* Day headers */}
|
|
<div className="grid grid-cols-7 border-b border-(--color-border-light) bg-gradient-to-r from-(--color-primary)/8 to-(--color-primary)/3">
|
|
{DAY_HEADERS.map((day, i) => (
|
|
<div
|
|
key={day}
|
|
className={`px-2 py-3 text-center text-[11px] font-bold tracking-wider uppercase ${
|
|
i >= 5 ? "text-rose-400" : "text-(--color-primary)"
|
|
}`}
|
|
>
|
|
{day}
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{/* Calendar grid */}
|
|
<div className="grid grid-cols-7 bg-white">
|
|
{calendarDays.map((date, i) => {
|
|
if (!date) {
|
|
return (
|
|
<div
|
|
key={`empty-${i}`}
|
|
className="min-h-[100px] border-r border-b border-(--color-border-light) bg-gray-50/50"
|
|
/>
|
|
);
|
|
}
|
|
|
|
const today = isToday(date);
|
|
const shifts = getShiftsForDate(date);
|
|
const shiftCount = shifts.length;
|
|
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
key={i}
|
|
onClick={() => onDateSelect?.(date)}
|
|
className={`group min-h-[100px] cursor-pointer border-r border-b border-(--color-border-light) bg-transparent p-2 text-left transition hover:bg-(--color-primary)/5 ${
|
|
today ? "bg-(--color-primary)/8" : ""
|
|
} ${isWeekend && !today ? "bg-rose-50/30" : ""}`}
|
|
>
|
|
{/* Date number */}
|
|
<span
|
|
className={`inline-flex h-7 w-7 items-center justify-center rounded-full text-sm font-bold transition ${
|
|
today
|
|
? "bg-(--color-primary) text-white shadow-sm"
|
|
: isWeekend
|
|
? "text-rose-500"
|
|
: "text-(--color-text-secondary) group-hover:text-(--color-primary)"
|
|
}`}
|
|
>
|
|
{date.getDate()}
|
|
</span>
|
|
|
|
{/* Shift count badge */}
|
|
{shiftCount > 0 && (
|
|
<div className="mt-1.5 space-y-1">
|
|
<span className="inline-flex items-center gap-1 rounded-full bg-(--color-primary)/10 px-1.5 py-0.5 text-[10px] font-semibold text-(--color-primary)">
|
|
<i className="fa-solid fa-clock text-[8px]"></i>
|
|
{shiftCount} shifts
|
|
</span>
|
|
</div>
|
|
)}
|
|
|
|
{/* Add indicator for manager */}
|
|
{shiftCount === 0 && onDateSelect && (
|
|
<p className="mt-1 text-[10px] text-(--color-text-muted) opacity-0 transition group-hover:opacity-100">
|
|
+ Add shift
|
|
</p>
|
|
)}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|