Staff / Owner

Merged staff and owner settings
This commit is contained in:
Stefan Lazic committed 2026-08-05 16:35:52 +01:00
1 parent cf3a28f3cd
commit 3300045d9e
23 files changed
+693 -236

No files matched your search

+16 -10
View File
@@ -10,7 +10,7 @@ import { EmptyState } from "@/components/feedback/empty-state";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { TopNav } from "@/components/navigation/top-nav";
import { prisma } from "@/lib/db/prisma";
import { getActingStaff } from "@/lib/staff/get-acting-staff";
import { getActingBusinessContext } from "@/lib/business/get-acting-business-context";
import { getLoyaltyHistory, getLoyaltySummary } from "@/services/loyalty/loyalty.service";
import { resolveRewardThreshold } from "@/services/loyalty/reward-threshold";
import { listAppointments } from "@/services/scheduling/appointment.service";
@@ -38,8 +38,8 @@ export default async function StaffCustomerDetailsPage({
}: {
params: Promise<{ id: string }>;
}) {
const staff = await getActingStaff();
if (!staff) redirect("/home");
const context = await getActingBusinessContext();
if (!context) redirect("/home");
const { id } = await params;
const customer = await prisma.customerProfile.findUnique({
@@ -54,7 +54,7 @@ export default async function StaffCustomerDetailsPage({
// "Belongs to this business" is via the appointment relation — see
// docs/phase-5-summary.md, same reasoning as the clients list query.
const belongsToBusiness = await prisma.appointment.findFirst({
where: { customerId: id, businessId: staff.businessId },
where: { customerId: id, businessId: context.businessId },
select: { id: true },
});
if (!belongsToBusiness) notFound();
@@ -62,17 +62,17 @@ export default async function StaffCustomerDetailsPage({
const [upcoming, past, cancelled, notesRaw, loyalty] = await Promise.all([
listAppointments(prisma, {
customerId: id,
businessId: staff.businessId,
businessId: context.businessId,
statuses: ["PENDING", "CONFIRMED", "CHECKED_IN", "IN_PROGRESS"],
}),
listAppointments(prisma, {
customerId: id,
businessId: staff.businessId,
businessId: context.businessId,
statuses: ["COMPLETED"],
}),
listAppointments(prisma, {
customerId: id,
businessId: staff.businessId,
businessId: context.businessId,
statuses: ["CANCELLED", "NO_SHOW"],
}),
prisma.customerNote.findMany({
@@ -80,7 +80,7 @@ export default async function StaffCustomerDetailsPage({
orderBy: { createdAt: "desc" },
include: { author: { include: { user: { select: { name: true } } } } },
}),
getLoyaltySummary(prisma, { customerId: id, businessId: staff.businessId }),
getLoyaltySummary(prisma, { customerId: id, businessId: context.businessId }),
]);
const loyaltyTransactions = loyalty?.accountId
@@ -96,7 +96,7 @@ export default async function StaffCustomerDetailsPage({
authorName: n.author.user.name ?? "Staff",
}));
const tz = staff.business.timezone;
const tz = context.businessTimezone;
const renderList = (appointments: typeof upcoming, emptyLabel: string) => {
if (appointments.length === 0) {
@@ -263,7 +263,13 @@ export default async function StaffCustomerDetailsPage({
<Card className="p-6">
<h2 className="text-muted mb-4 text-sm font-bold tracking-widest uppercase">Notes</h2>
<CustomerNotes customerId={customer.id} notes={notes} actingStaffId={staff.id} />
{/* A Super Administrator has no StaffProfile/staffId — passing "" is a
deliberate never-matches sentinel (see CustomerNotes' isOwn check),
so they can view every note but own none. Adding a note as a
Super Administrator fails gracefully server-side (see
addCustomerNoteAction: "You must be signed in as staff.") rather
than fabricating a staff identity for them. */}
<CustomerNotes customerId={customer.id} notes={notes} actingStaffId={context.staffId ?? ""} />
</Card>
</div>
</main>