Files
frontend/components/organisms/shift-schedule/MobileShiftView.tsx
T
2026-05-14 15:57:15 +00:00

192 lines
6.8 KiB
TypeScript

"use client";
import ShiftCard from "@/components/molecules/cards/ShiftCard";
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 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");
return `${y}-${m}-${day}`;
}
function isToday(d: Date): boolean {
const today = new Date(2026, 3, 10);
return formatDateKey(d) === formatDateKey(today);
}
export default function MobileShiftView({
onShiftClick,
}: MobileShiftViewProps) {
const { currentDate, getShiftsForDate, goToNextMonth, goToPrevMonth } =
useShift();
const [selectedDate, setSelectedDate] = useState<Date>(new Date(2026, 3, 10));
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]);
const selectedShifts = useMemo(
() => getShiftsForDate(selectedDate),
[selectedDate, getShiftsForDate],
);
const dayOfWeek = DAY_HEADERS[(selectedDate.getDay() + 6) % 7];
return (
<div className="space-y-4">
{/* Month calendar card */}
<div className="overflow-hidden rounded-2xl border border-(--color-border-light) bg-white shadow-sm">
{/* Month navigation */}
<div className="flex items-center justify-between border-b border-(--color-border-light) bg-gradient-to-r from-(--color-primary)/8 to-(--color-primary)/3 px-4 py-3">
<button
title="Previous month"
type="button"
onClick={goToPrevMonth}
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full border-none bg-white/70 text-(--color-text-muted) shadow-sm transition hover:bg-white hover:text-(--color-primary)"
>
<i className="fa-solid fa-chevron-left text-xs"></i>
</button>
<h3 className="text-sm font-bold text-(--color-primary-dark)">
{MONTH_NAMES[currentDate.getMonth()]} {currentDate.getFullYear()}
</h3>
<button
title="Next month"
type="button"
onClick={goToNextMonth}
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-full border-none bg-white/70 text-(--color-text-muted) shadow-sm transition hover:bg-white hover:text-(--color-primary)"
>
<i className="fa-solid fa-chevron-right text-xs"></i>
</button>
</div>
<div className="p-3">
{/* Day headers */}
<div className="mb-1 grid grid-cols-7">
{DAY_HEADERS.map((day, i) => (
<div
key={day}
className={`py-1 text-center text-[10px] font-bold uppercase ${
i >= 5 ? "text-rose-400" : "text-(--color-text-muted)"
}`}
>
{day}
</div>
))}
</div>
{/* Calendar grid */}
<div className="grid grid-cols-7">
{calendarDays.map((date, i) => {
if (!date) return <div key={`empty-${i}`} className="p-1" />;
const today = isToday(date);
const selected =
formatDateKey(date) === formatDateKey(selectedDate);
const dayShifts = getShiftsForDate(date);
const isWeekend = date.getDay() === 0 || date.getDay() === 6;
return (
<button
type="button"
key={i}
onClick={() => setSelectedDate(date)}
className="flex cursor-pointer flex-col items-center border-none bg-transparent p-1 transition"
>
<span
className={`flex h-8 w-8 items-center justify-center rounded-full text-xs font-bold transition ${
today
? "bg-(--color-primary) text-white shadow-sm"
: selected
? "bg-(--color-primary)/15 text-(--color-primary) ring-2 ring-(--color-primary)/30"
: isWeekend
? "text-rose-500"
: "text-(--color-text-secondary)"
}`}
>
{date.getDate()}
</span>
<div className="mt-0.5 flex min-h-2 items-center justify-center gap-px">
{dayShifts.length > 0 && (
<span className="h-1.5 w-1.5 rounded-full bg-(--color-primary)/60" />
)}
</div>
</button>
);
})}
</div>
</div>
</div>
{/* Selected day shifts */}
<div>
<div className="mb-3 flex items-center justify-between px-1">
<h3 className="text-sm font-bold text-(--color-primary-dark)">
{dayOfWeek}, {selectedDate.getDate()}/{selectedDate.getMonth() + 1}/
{selectedDate.getFullYear()}
</h3>
<span
className={`rounded-full px-2.5 py-1 text-xs font-semibold ${
selectedShifts.length > 0
? "bg-(--color-primary)/10 text-(--color-primary)"
: "bg-gray-100 text-(--color-text-muted)"
}`}
>
{selectedShifts.length} shifts
</span>
</div>
{selectedShifts.length === 0 ? (
<div className="rounded-2xl border border-dashed border-(--color-border-light) py-10 text-center">
<i className="fa-regular fa-calendar-xmark mb-3 text-3xl text-gray-200"></i>
<p className="text-sm font-medium text-(--color-text-muted)">
No shifts
</p>
<p className="mt-0.5 text-xs text-(--color-text-muted)">
No shifts created for this day
</p>
</div>
) : (
<div className="space-y-2.5">
{selectedShifts.map((shift) => (
<ShiftCard key={shift.id} shift={shift} onClick={onShiftClick} />
))}
</div>
)}
</div>
</div>
);
}