85 lines
3.3 KiB
TypeScript
85 lines
3.3 KiB
TypeScript
import type { Metadata } from "next";
|
||
import { notFound, redirect } from "next/navigation";
|
||
import { auth } from "@/auth";
|
||
import { TopNav } from "@/components/navigation/top-nav";
|
||
import { prisma } from "@/lib/db/prisma";
|
||
import { hasPermission } from "@/lib/auth/permissions";
|
||
import { Permission, Weekday } from "@/generated/prisma/enums";
|
||
import { getActingBusinessContext } from "@/lib/business/get-acting-business-context";
|
||
import { getWorkingHours } from "@/services/staff/staff.service";
|
||
import { WorkingHoursForm } from "@/features/staff/components/working-hours-form";
|
||
import { updateStaffWorkingHoursAction } from "@/features/owner/actions/staff";
|
||
|
||
export const metadata: Metadata = { title: "Staff Working Hours" };
|
||
export const dynamic = "force-dynamic";
|
||
|
||
const DAY_ORDER: Weekday[] = [
|
||
Weekday.MONDAY,
|
||
Weekday.TUESDAY,
|
||
Weekday.WEDNESDAY,
|
||
Weekday.THURSDAY,
|
||
Weekday.FRIDAY,
|
||
Weekday.SATURDAY,
|
||
Weekday.SUNDAY,
|
||
];
|
||
|
||
function formatMinutes(m: number): string {
|
||
const h = Math.floor(m / 60);
|
||
const min = m % 60;
|
||
return `${String(h).padStart(2, "0")}:${String(min).padStart(2, "0")}`;
|
||
}
|
||
|
||
export default async function OwnerStaffWorkingHoursPage({ params }: { params: Promise<{ id: string }> }) {
|
||
const session = await auth();
|
||
if (!session?.user) redirect("/sign-in");
|
||
|
||
const canManage = await hasPermission(session.user.id, Permission.MANAGE_STAFF);
|
||
if (!canManage) redirect("/staff");
|
||
|
||
const context = await getActingBusinessContext();
|
||
if (!context) redirect("/staff");
|
||
|
||
const { id } = await params;
|
||
const target = await prisma.staffProfile.findFirst({
|
||
where: { id, businessId: context.businessId },
|
||
include: { user: { select: { name: true } } },
|
||
});
|
||
if (!target) notFound();
|
||
|
||
const [workingHours, businessHours] = await Promise.all([
|
||
getWorkingHours(prisma, id),
|
||
prisma.businessHours.findMany({ where: { businessId: context.businessId } }),
|
||
]);
|
||
|
||
const businessHoursLabel = Object.fromEntries(
|
||
DAY_ORDER.map((day) => {
|
||
const hours = businessHours.find((h) => h.dayOfWeek === day);
|
||
const label =
|
||
!hours || hours.isClosed || hours.opensAtMinutes === null || hours.closesAtMinutes === null
|
||
? "Closed"
|
||
: `${formatMinutes(hours.opensAtMinutes)}–${formatMinutes(hours.closesAtMinutes)}`;
|
||
return [day, label];
|
||
}),
|
||
) as Record<Weekday, string>;
|
||
|
||
return (
|
||
<div className="pb-12">
|
||
<TopNav title={`${target.user.name ?? "Staff"} — Working Hours`} onBack sticky className="bg-stone" />
|
||
<main className="mx-auto max-w-2xl px-6 py-6">
|
||
{/* .bind(null, id), not an arrow-function wrapper — a Client Component can only
|
||
receive an actual Server Action reference as a prop. An inline arrow closure
|
||
here throws "Event handlers cannot be passed to Client Component props" at
|
||
render time (found empirically in Phase 7 — this page never actually worked
|
||
until this fix; see docs/phase-7-summary.md and
|
||
src/features/owner/actions/break-action-set.ts for the same issue). */}
|
||
<WorkingHoursForm
|
||
initialRows={workingHours}
|
||
businessHoursLabel={businessHoursLabel}
|
||
onSave={updateStaffWorkingHoursAction.bind(null, id)}
|
||
appointmentHrefBase="/owner/appointments"
|
||
/>
|
||
</main>
|
||
</div>
|
||
);
|
||
}
|