fix(GRO-1489): resolve 7 lint errors blocking dev CI #429

Merged
The Dogfather merged 1 commits from flea-flicker/gro-1489-lint-fixes into dev 2026-05-23 19:10:13 +00:00
6 changed files with 13 additions and 14 deletions
+1 -1
View File
@@ -79,7 +79,7 @@ GroomBook is an open-source, self-hostable pet grooming business management & CR
| TC-APP-4.5.5 | Appointment groups | 1. Create multiple appointments for same time slot<br>2. View in calendar | Appointments are grouped/linked appropriately | | TC-APP-4.5.5 | Appointment groups | 1. Create multiple appointments for same time slot<br>2. View in calendar | Appointments are grouped/linked appropriately |
| TC-APP-4.5.6 | Appointment availability check | 1. Attempt to book appointment during unavailable slot | System shows conflict or prevents double-booking | | TC-APP-4.5.6 | Appointment availability check | 1. Attempt to book appointment during unavailable slot | System shows conflict or prevents double-booking |
| TC-APP-4.5.7 | Booking wizard — size/coat selection | 1. Start new appointment booking wizard<br>2. Select a pet with sizeCategory and coatType set<br>3. Observe the service/slot selection step | Size and coat type dropdowns are displayed and persist the pet's existing values | | TC-APP-4.5.7 | Booking wizard — size/coat selection | 1. Start new appointment booking wizard<br>2. Select a pet with sizeCategory and coatType set<br>3. Observe the service/slot selection step | Size and coat type dropdowns are displayed and persist the pet's existing values |
| TC-APP-4.5.8 | Large/X-Large pet slot duration reflects buffer | 1. Add a pet with sizeCategory = "large" or "x-large" to an appointment<br>2. Note the service duration<br>3. Complete booking and inspect the appointment | Appointment slot includes the service duration plus the configured buffer for the pet's size category | | TC-APP-4.5.8 | Large/Xlarge pet slot duration reflects buffer | 1. Add a pet with sizeCategory = "large" or "xlarge" to an appointment<br>2. Note the service duration<br>3. Complete booking and inspect the appointment | Appointment slot includes the service duration plus the configured buffer for the pet's size category |
| TC-APP-4.5.9 | Appointment overrun cascades downstream | 1. Book three consecutive same-groomer appointments (A → B → C)<br>2. Manually extend appointment A's endTime so it overlaps B's startTime by ≥15 min<br>3. Observe appointment B | Appointment B (and C if still overlapping) is automatically shifted forward by the overrun delta + buffer; no error thrown | | TC-APP-4.5.9 | Appointment overrun cascades downstream | 1. Book three consecutive same-groomer appointments (A → B → C)<br>2. Manually extend appointment A's endTime so it overlaps B's startTime by ≥15 min<br>3. Observe appointment B | Appointment B (and C if still overlapping) is automatically shifted forward by the overrun delta + buffer; no error thrown |
| TC-APP-4.5.10 | Cascaded appointments appear at new times | 1. Complete TC-APP-4.5.9<br>2. Check the calendar/list view | Appointments B and C are now shown at their shifted start/end times | | TC-APP-4.5.10 | Cascaded appointments appear at new times | 1. Complete TC-APP-4.5.9<br>2. Check the calendar/list view | Appointments B and C are now shown at their shifted start/end times |
| TC-APP-4.5.11 | Client receives reschedule notification email | 1. Complete TC-APP-4.5.9<br>2. Check the client's email (or notification log) | Client receives an email with subject/lines indicating their appointment was rescheduled from original time to new time | | TC-APP-4.5.11 | Client receives reschedule notification email | 1. Complete TC-APP-4.5.9<br>2. Check the client's email (or notification log) | Client receives an email with subject/lines indicating their appointment was rescheduled from original time to new time |
+7 -7
View File
@@ -1,4 +1,4 @@
import { eq, and, gt, gte, lt, ne, or, asc } from "@groombook/db"; import { eq, and, gt, or, asc } from "@groombook/db";
import { appointments, clients, pets, services, staff, type Db } from "@groombook/db"; import { appointments, clients, pets, services, staff, type Db } from "@groombook/db";
import { resolveBufferMinutes } from "./buffer.js"; import { resolveBufferMinutes } from "./buffer.js";
import { sendEmail, buildRescheduleNotificationEmail } from "../services/email.js"; import { sendEmail, buildRescheduleNotificationEmail } from "../services/email.js";
@@ -53,12 +53,12 @@ export async function detectAndCascadeOverrun({
db, db,
overrunningAppointmentId, overrunningAppointmentId,
newEndTime, newEndTime,
originalEndTime, _originalEndTime,
}: { }: {
db: Db; db: Db;
overrunningAppointmentId: string; overrunningAppointmentId: string;
newEndTime: Date; newEndTime: Date;
originalEndTime: Date; _originalEndTime: Date;
}): Promise<CascadeResult> { }): Promise<CascadeResult> {
const result: CascadeResult = { shifted: [], flaggedForReview: [] }; const result: CascadeResult = { shifted: [], flaggedForReview: [] };
@@ -178,16 +178,16 @@ export async function detectAndCascadeOverrun({
export function isOverrun({ export function isOverrun({
originalEndTime, originalEndTime,
newEndTime, newEndTime,
originalStartTime, _originalStartTime,
newStartTime, _newStartTime,
status, status,
currentTime, currentTime,
bufferMinutes, bufferMinutes,
}: { }: {
originalEndTime: Date; originalEndTime: Date;
newEndTime: Date; newEndTime: Date;
originalStartTime: Date; _originalStartTime: Date;
newStartTime?: Date; _newStartTime?: Date;
status: string; status: string;
currentTime: Date; currentTime: Date;
bufferMinutes: number; bufferMinutes: number;
+2 -2
View File
@@ -700,7 +700,7 @@ appointmentsRouter.patch(
isOverrun({ isOverrun({
originalEndTime, originalEndTime,
newEndTime: new Date(updateFields.endTime), newEndTime: new Date(updateFields.endTime),
originalStartTime: row.startTime, _originalStartTime: row.startTime,
status: row.status, status: row.status,
currentTime: new Date(), currentTime: new Date(),
bufferMinutes: row.bufferMinutes ?? 0, bufferMinutes: row.bufferMinutes ?? 0,
@@ -710,7 +710,7 @@ appointmentsRouter.patch(
db, db,
overrunningAppointmentId: id, overrunningAppointmentId: id,
newEndTime: new Date(updateFields.endTime), newEndTime: new Date(updateFields.endTime),
originalEndTime, _originalEndTime: originalEndTime,
}); });
return c.json({ ...row, cascade: cascadeResult }); return c.json({ ...row, cascade: cascadeResult });
} }
-1
View File
@@ -44,7 +44,6 @@ bookRouter.get("/availability", async (c) => {
const serviceId = c.req.query("serviceId"); const serviceId = c.req.query("serviceId");
const dateStr = c.req.query("date"); const dateStr = c.req.query("date");
const petSizeCategory = c.req.query("petSizeCategory") ?? undefined; const petSizeCategory = c.req.query("petSizeCategory") ?? undefined;
const petCoatType = c.req.query("petCoatType") ?? undefined;
if (!serviceId || !dateStr) { if (!serviceId || !dateStr) {
return c.json({ error: "serviceId and date are required" }, 400); return c.json({ error: "serviceId and date are required" }, 400);
+1 -1
View File
@@ -19,7 +19,7 @@ export default defineConfig({
reporter: process.env.CI ? "github" : "list", reporter: process.env.CI ? "github" : "list",
use: { use: {
baseURL: "http://localhost:8080", baseURL: process.env.PLAYWRIGHT_BASE_URL ?? "http://localhost:8080",
trace: "on-first-retry", trace: "on-first-retry",
screenshot: "only-on-failure", screenshot: "only-on-failure",
serviceWorkers: "block", serviceWorkers: "block",
+2 -2
View File
@@ -515,7 +515,7 @@ export function BookPage() {
<option value="small">Small (under 15 lbs)</option> <option value="small">Small (under 15 lbs)</option>
<option value="medium">Medium (1540 lbs)</option> <option value="medium">Medium (1540 lbs)</option>
<option value="large">Large (4080 lbs)</option> <option value="large">Large (4080 lbs)</option>
<option value="x-large">X-Large (over 80 lbs)</option> <option value="xlarge">X-Large (over 80 lbs)</option>
</select> </select>
</div> </div>
<div> <div>
@@ -568,7 +568,7 @@ export function BookPage() {
<div> <div>
<div style={{ color: "#9ca3af", fontSize: 12, fontWeight: 600, textTransform: "uppercase" }}>Service</div> <div style={{ color: "#9ca3af", fontSize: 12, fontWeight: 600, textTransform: "uppercase" }}>Service</div>
<div style={{ fontWeight: 600 }}>{selectedService.name}</div> <div style={{ fontWeight: 600 }}>{selectedService.name}</div>
<div style={{ color: "#6b7280" }}>{fmtPrice(selectedService.basePriceCents)} · {fmtDuration(selectedService.durationMinutes + ((form.petSizeCategory === "large" || form.petSizeCategory === "x-large") ? (selectedService.defaultBufferMinutes ?? 0) : 0))}</div> <div style={{ color: "#6b7280" }}>{fmtPrice(selectedService.basePriceCents)} · {fmtDuration(selectedService.durationMinutes + ((form.petSizeCategory === "large" || form.petSizeCategory === "xlarge") ? (selectedService.defaultBufferMinutes ?? 0) : 0))}</div>
</div> </div>
<div> <div>
<div style={{ color: "#9ca3af", fontSize: 12, fontWeight: 600, textTransform: "uppercase" }}>Date & Time</div> <div style={{ color: "#9ca3af", fontSize: 12, fontWeight: 600, textTransform: "uppercase" }}>Date & Time</div>