Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9be160c1b | |||
| dcb929be5b | |||
| 0ace23de53 |
+5
-2
@@ -12,7 +12,8 @@ RUN pnpm install --frozen-lockfile
|
|||||||
|
|
||||||
# Build
|
# Build
|
||||||
FROM deps AS builder
|
FROM deps AS builder
|
||||||
RUN mkdir -p /home/node/.cache/node/corepack
|
RUN mkdir -p /home/node/.cache/node/corepack && \
|
||||||
|
corepack prepare pnpm@9.15.4 --activate
|
||||||
COPY packages/ packages/
|
COPY packages/ packages/
|
||||||
COPY apps/api/ apps/api/
|
COPY apps/api/ apps/api/
|
||||||
RUN pnpm --filter @groombook/types build && \
|
RUN pnpm --filter @groombook/types build && \
|
||||||
@@ -21,7 +22,9 @@ RUN pnpm --filter @groombook/types build && \
|
|||||||
|
|
||||||
# Runtime
|
# Runtime
|
||||||
FROM node:20-alpine AS runner
|
FROM node:20-alpine AS runner
|
||||||
RUN corepack enable && corepack prepare pnpm@9.15.4 --activate
|
RUN corepack enable && \
|
||||||
|
mkdir -p /home/node/.cache/node/corepack && \
|
||||||
|
corepack prepare pnpm@9.15.4 --activate
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ portalRouter.get("/appointments", async (c) => {
|
|||||||
const db = getDb();
|
const db = getDb();
|
||||||
const clientId = c.get("portalClientId");
|
const clientId = c.get("portalClientId");
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
const allAppts = await db
|
const allAppts = await db
|
||||||
.select({
|
.select({
|
||||||
id: appointments.id,
|
id: appointments.id,
|
||||||
@@ -60,12 +59,15 @@ portalRouter.get("/appointments", async (c) => {
|
|||||||
|
|
||||||
const petIds = allAppts.map(a => a.petId).filter((id): id is string => id !== null);
|
const petIds = allAppts.map(a => a.petId).filter((id): id is string => id !== null);
|
||||||
const staffIds = allAppts.map(a => a.staffId).filter((id): id is string => id !== null);
|
const staffIds = allAppts.map(a => a.staffId).filter((id): id is string => id !== null);
|
||||||
|
const serviceIds = allAppts.map(a => a.serviceId).filter((id): id is string => id !== null);
|
||||||
|
|
||||||
const petRows = petIds.length ? await db.select().from(pets).where(inArray(pets.id, petIds)) : [];
|
const petRows = petIds.length ? await db.select().from(pets).where(inArray(pets.id, petIds)) : [];
|
||||||
const staffRows = staffIds.length ? await db.select().from(staff).where(inArray(staff.id, staffIds)) : [];
|
const staffRows = staffIds.length ? await db.select().from(staff).where(inArray(staff.id, staffIds)) : [];
|
||||||
|
const serviceRows = serviceIds.length ? await db.select().from(services).where(inArray(services.id, serviceIds)) : [];
|
||||||
|
|
||||||
const petMap = Object.fromEntries(petRows.map(p => [p.id, p]));
|
const petMap = Object.fromEntries(petRows.map(p => [p.id, p]));
|
||||||
const staffMap = Object.fromEntries(staffRows.map(s => [s.id, s]));
|
const staffMap = Object.fromEntries(staffRows.map(s => [s.id, s]));
|
||||||
|
const serviceMap = Object.fromEntries(serviceRows.map(s => [s.id, s]));
|
||||||
|
|
||||||
const appts = allAppts.map(a => ({
|
const appts = allAppts.map(a => ({
|
||||||
id: a.id,
|
id: a.id,
|
||||||
@@ -76,14 +78,11 @@ portalRouter.get("/appointments", async (c) => {
|
|||||||
customerNotes: a.customerNotes,
|
customerNotes: a.customerNotes,
|
||||||
notes: a.notes,
|
notes: a.notes,
|
||||||
pet: a.petId ? { id: petMap[a.petId]?.id, name: petMap[a.petId]?.name, photo: petMap[a.petId]?.photoKey } : null,
|
pet: a.petId ? { id: petMap[a.petId]?.id, name: petMap[a.petId]?.name, photo: petMap[a.petId]?.photoKey } : null,
|
||||||
service: a.serviceId ? { id: a.serviceId } : null,
|
service: a.serviceId ? { id: a.serviceId, name: serviceMap[a.serviceId]?.name, duration: serviceMap[a.serviceId]?.durationMinutes, price: serviceMap[a.serviceId]?.basePriceCents } : null,
|
||||||
staff: a.staffId ? { id: staffMap[a.staffId]?.id, name: staffMap[a.staffId]?.name } : null,
|
staff: a.staffId ? { id: staffMap[a.staffId]?.id, name: staffMap[a.staffId]?.name } : null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const upcoming = appts.filter(a => a.startTime > now && a.status !== "cancelled");
|
return c.json({ appointments: appts });
|
||||||
const past = appts.filter(a => a.startTime <= now || a.status === "cancelled");
|
|
||||||
|
|
||||||
return c.json({ upcoming, past });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
portalRouter.get("/pets", async (c) => {
|
portalRouter.get("/pets", async (c) => {
|
||||||
|
|||||||
@@ -123,7 +123,28 @@ export const AppointmentsSection: React.FC<AppointmentsSectionProps> = ({ sessio
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const fetchedAppointments: Appointment[] = data.appointments || data || [];
|
const rawAppointments: Record<string, unknown>[] = data.appointments || data || [];
|
||||||
|
|
||||||
|
// Transform API response (startTime) to client format (date + time)
|
||||||
|
const fetchedAppointments: Appointment[] = rawAppointments.map((a) => {
|
||||||
|
const start = new Date(a.startTime as string);
|
||||||
|
const dateStr = start.toISOString().split('T')[0];
|
||||||
|
const hours = start.getHours();
|
||||||
|
const minutes = start.getMinutes().toString().padStart(2, '0');
|
||||||
|
const period = hours >= 12 ? 'PM' : 'AM';
|
||||||
|
const hour12 = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
||||||
|
const timeStr = `${hour12}:${minutes} ${period}`;
|
||||||
|
return {
|
||||||
|
...a,
|
||||||
|
date: dateStr,
|
||||||
|
time: timeStr,
|
||||||
|
petName: (a.pet as { name?: string })?.name,
|
||||||
|
serviceName: (a.service as { name?: string })?.name,
|
||||||
|
groomerName: (a.staff as { name?: string })?.name,
|
||||||
|
duration: (a.service as { duration?: number })?.duration,
|
||||||
|
price: (a.service as { price?: number })?.price,
|
||||||
|
} as Appointment;
|
||||||
|
});
|
||||||
|
|
||||||
const upcoming = fetchedAppointments.filter((appt) => isUpcoming(appt));
|
const upcoming = fetchedAppointments.filter((appt) => isUpcoming(appt));
|
||||||
const past = fetchedAppointments.filter((appt) => !isUpcoming(appt));
|
const past = fetchedAppointments.filter((appt) => !isUpcoming(appt));
|
||||||
|
|||||||
@@ -116,7 +116,24 @@ export function Dashboard({
|
|||||||
const invoicesData = await invoicesRes.json();
|
const invoicesData = await invoicesRes.json();
|
||||||
const brandingData = await brandingRes.json();
|
const brandingData = await brandingRes.json();
|
||||||
|
|
||||||
setAppointments(appointmentsData.appointments || []);
|
const rawAppointments: Record<string, unknown>[] = appointmentsData.appointments || [];
|
||||||
|
const transformedAppointments = rawAppointments.map((a) => {
|
||||||
|
const start = new Date(a.startTime as string);
|
||||||
|
const dateStr = start.toISOString().split('T')[0];
|
||||||
|
const hours = start.getHours();
|
||||||
|
const minutes = start.getMinutes().toString().padStart(2, '0');
|
||||||
|
const period = hours >= 12 ? 'PM' : 'AM';
|
||||||
|
const hour12 = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
||||||
|
const timeStr = `${hour12}:${minutes} ${period}`;
|
||||||
|
return {
|
||||||
|
...a,
|
||||||
|
date: dateStr,
|
||||||
|
time: timeStr,
|
||||||
|
petName: (a.pet as { name?: string })?.name ?? '',
|
||||||
|
serviceName: (a.service as { name?: string })?.name ?? '',
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setAppointments(transformedAppointments as Appointment[]);
|
||||||
setPets(petsData.pets || []);
|
setPets(petsData.pets || []);
|
||||||
|
|
||||||
// Filter for pending invoices only (not "outstanding")
|
// Filter for pending invoices only (not "outstanding")
|
||||||
|
|||||||
@@ -44,8 +44,25 @@ export function ReportCards({ sessionId }: Props) {
|
|||||||
|
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
const allAppointments: Appointment[] = data.appointments || data || [];
|
const rawAppointments: Record<string, unknown>[] = data.appointments || data || [];
|
||||||
const reportCardAppointments = allAppointments.filter(
|
const transformed: Appointment[] = rawAppointments.map((a) => {
|
||||||
|
const start = new Date(a.startTime as string);
|
||||||
|
const dateStr = start.toISOString().split('T')[0];
|
||||||
|
const hours = start.getHours();
|
||||||
|
const minutes = start.getMinutes().toString().padStart(2, '0');
|
||||||
|
const period = hours >= 12 ? 'PM' : 'AM';
|
||||||
|
const hour12 = hours === 0 ? 12 : hours > 12 ? hours - 12 : hours;
|
||||||
|
const timeStr = `${hour12}:${minutes} ${period}`;
|
||||||
|
return {
|
||||||
|
...a,
|
||||||
|
date: dateStr,
|
||||||
|
time: timeStr,
|
||||||
|
petName: (a.pet as { name?: string })?.name,
|
||||||
|
serviceName: (a.service as { name?: string })?.name,
|
||||||
|
groomerName: (a.staff as { name?: string })?.name,
|
||||||
|
} as Appointment;
|
||||||
|
});
|
||||||
|
const reportCardAppointments = transformed.filter(
|
||||||
(appt) => appt.reportCardId
|
(appt) => appt.reportCardId
|
||||||
);
|
);
|
||||||
setAppointments(reportCardAppointments);
|
setAppointments(reportCardAppointments);
|
||||||
|
|||||||
Reference in New Issue
Block a user