Staf Booking

fixed staff ability to book a customer in
This commit is contained in:
Stefan Lazic committed 2026-08-06 14:49:46 +01:00
1 parent 973f18f64c
commit 90ec663e6e
8 files changed
+378 -174

No files matched your search

+17 -27
View File
@@ -21,7 +21,7 @@ import { getAvailableRewardFlags } from "@/services/loyalty/loyalty.service";
import { listAppointments } from "@/services/scheduling/appointment.service";
import { DateStrip } from "@/features/staff/components/date-strip";
import { DayCalendar } from "@/features/staff/components/day-calendar";
import type { CalendarAppointment, CalendarBlock } from "@/features/staff/components/day-calendar";
import type { CalendarAppointment, CalendarBlock, CalendarColumn } from "@/features/staff/components/day-calendar";
import { OwnerBlockTimeButton } from "@/features/owner/components/owner-block-time-button";
import { cn } from "@/lib/utils/cn";
import type { AppointmentStatus } from "@/types/domain";
@@ -74,7 +74,7 @@ export default async function OwnerCalendarPage({
const allStaff = await prisma.staffProfile.findMany({
where: { businessId: context.businessId, status: "ACTIVE" },
include: { user: { select: { id: true, name: true } } },
include: { user: { select: { id: true, name: true, image: true } } },
orderBy: { createdAt: "asc" },
});
const selectedStaff = staffIdParam ? allStaff.find((s) => s.id === staffIdParam) : undefined;
@@ -159,6 +159,20 @@ export default async function OwnerCalendarPage({
return { workingWindow, blocks, wholeDayOff, staffAppointments };
}
const columns: CalendarColumn[] = staffToRender.map((s) => {
const column = buildColumnFor(s.id);
return {
id: s.id,
name: s.user.name ?? "Staff",
avatarUrl: s.user.image,
hoursLabel: column.workingWindow ? formatRange(column.workingWindow.startMinutes, column.workingWindow.endMinutes) : null,
wholeDayOff: column.wholeDayOff,
appointments: column.staffAppointments,
blocks: column.blocks,
workingWindow: column.workingWindow,
};
});
return (
<div className="pb-6">
<div className="flex items-center justify-between px-6 pt-12">
@@ -237,31 +251,7 @@ export default async function OwnerCalendarPage({
<EmptyState icon={CalendarX2} title="No active staff" description="Add a staff member from Team." />
</div>
) : (
<div className="flex gap-4 overflow-x-auto px-6 pb-4 lg:px-0">
{staffToRender.map((s) => {
const column = buildColumnFor(s.id);
return (
<div key={s.id} className="w-[320px] shrink-0 lg:w-auto lg:flex-1">
<p className="text-muted mb-2 px-6 text-xs font-bold tracking-widest uppercase lg:px-0">
{s.user.name}
</p>
{column.wholeDayOff ? (
<div className="px-6 lg:px-0">
<EmptyState icon={CalendarX2} title="Off this day" />
</div>
) : (
<DayCalendar
appointments={column.staffAppointments}
blocks={column.blocks}
workingWindow={column.workingWindow}
currentTimeMinutes={currentTimeMinutes}
appointmentHrefBase="/owner/appointments"
/>
)}
</div>
);
})}
</div>
<DayCalendar columns={columns} currentTimeMinutes={currentTimeMinutes} appointmentHrefBase="/owner/appointments" />
)
) : appointments.length === 0 ? (
<div className="px-6">
+15 -7
View File
@@ -18,7 +18,7 @@ import { getAvailableRewardFlags } from "@/services/loyalty/loyalty.service";
import { listAppointments, selectCurrentAppointment } from "@/services/scheduling/appointment.service";
import { DateStrip } from "@/features/staff/components/date-strip";
import { DayCalendar } from "@/features/staff/components/day-calendar";
import type { CalendarAppointment, CalendarBlock } from "@/features/staff/components/day-calendar";
import type { CalendarAppointment, CalendarBlock, CalendarColumn } from "@/features/staff/components/day-calendar";
import { BlockTimeButton } from "@/features/staff/components/block-time-button";
import { cn } from "@/lib/utils/cn";
import type { AppointmentStatus } from "@/types/domain";
@@ -124,6 +124,19 @@ export default async function StaffCalendarPage({
const currentTimeMinutes =
date === today ? minutesFromMidnight(new Date(), staff.business.timezone) : null;
const columns: CalendarColumn[] = [
{
id: staff.id,
name: staff.user.name ?? "You",
avatarUrl: staff.user.image,
hoursLabel: workingWindow ? formatRange(workingWindow.startMinutes, workingWindow.endMinutes) : null,
wholeDayOff: false,
appointments: calendarAppointments,
blocks,
workingWindow,
},
];
// Desktop-only: a "Right Now" panel beside the calendar so a wide screen
// does more than center the mobile layout (see docs/phase-5-summary.md,
// "Responsive Design"). Only meaningful when viewing today.
@@ -181,12 +194,7 @@ export default async function StaffCalendarPage({
description={wholeDayOff.reason ?? "Approved time off."}
/>
) : view === "day" ? (
<DayCalendar
appointments={calendarAppointments}
blocks={blocks}
workingWindow={workingWindow}
currentTimeMinutes={currentTimeMinutes}
/>
<DayCalendar columns={columns} currentTimeMinutes={currentTimeMinutes} />
) : calendarAppointments.length === 0 ? (
<div className="px-6 lg:px-0">
<EmptyState
@@ -1,6 +1,7 @@
import { NextResponse } from "next/server";
import { auth } from "@/auth";
import { prisma } from "@/lib/db/prisma";
import { isSecureDeployment } from "@/lib/security/cookie-security";
import { createOAuthState } from "@/lib/security/oauth-state";
import { GoogleCalendarProvider } from "@/services/calendar/google-calendar.provider";
@@ -49,7 +50,7 @@ export async function GET() {
const response = NextResponse.redirect(authorizationUrl);
response.cookies.set("gcal_oauth_state", state, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
secure: isSecureDeployment(),
sameSite: "lax",
path: "/",
maxAge: 600,