Staf Booking
fixed staff ability to book a customer in
This commit is contained in:
8 files changed
+378
-174
No files matched your search
@@ -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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in new issue
Block a user