feat: detailed pet profile attributes and grooming visit history (closes #13)
- Add cut_style, shampoo_preference, special_care_notes, custom_fields columns to pets table - Add grooming_visit_logs table to track per-visit grooming details (cut, products, notes) - Extend pets API to accept and return new profile fields - Add /api/grooming-logs endpoint (GET by petId, POST, DELETE) - Update Pet type with new fields; add GroomingVisitLog type - Update Clients page: grooming preferences section in pet card, "Log visit" button, visit history panel showing last 3 visits, expanded pet form with grooming preferences Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
-- Extend pet profiles with grooming-specific attributes (closes groombook/groombook#13)
|
||||
ALTER TABLE "pets"
|
||||
ADD COLUMN "cut_style" text,
|
||||
ADD COLUMN "shampoo_preference" text,
|
||||
ADD COLUMN "special_care_notes" text,
|
||||
ADD COLUMN "custom_fields" jsonb DEFAULT '{}' NOT NULL;
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "grooming_visit_logs" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"pet_id" uuid NOT NULL,
|
||||
"appointment_id" uuid,
|
||||
"staff_id" uuid,
|
||||
"cut_style" text,
|
||||
"products_used" text,
|
||||
"notes" text,
|
||||
"groomed_at" timestamp NOT NULL DEFAULT now(),
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "grooming_visit_logs"
|
||||
ADD CONSTRAINT "grooming_visit_logs_pet_id_pets_id_fk"
|
||||
FOREIGN KEY ("pet_id") REFERENCES "public"."pets"("id") ON DELETE cascade ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "grooming_visit_logs"
|
||||
ADD CONSTRAINT "grooming_visit_logs_appointment_id_appointments_id_fk"
|
||||
FOREIGN KEY ("appointment_id") REFERENCES "public"."appointments"("id") ON DELETE set null ON UPDATE no action;
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "grooming_visit_logs"
|
||||
ADD CONSTRAINT "grooming_visit_logs_staff_id_staff_id_fk"
|
||||
FOREIGN KEY ("staff_id") REFERENCES "public"."staff"("id") ON DELETE set null ON UPDATE no action;
|
||||
@@ -36,6 +36,20 @@
|
||||
"when": 1773779939000,
|
||||
"tag": "0004_reminder_logs",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"version": "7",
|
||||
"when": 1773783000000,
|
||||
"tag": "0005_appointment_groups",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "7",
|
||||
"when": 1773783600000,
|
||||
"tag": "0006_pet_profile_attributes",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
boolean,
|
||||
integer,
|
||||
jsonb,
|
||||
numeric,
|
||||
pgEnum,
|
||||
pgTable,
|
||||
@@ -68,6 +69,10 @@ export const pets = pgTable("pets", {
|
||||
dateOfBirth: timestamp("date_of_birth"),
|
||||
healthAlerts: text("health_alerts"),
|
||||
groomingNotes: text("grooming_notes"),
|
||||
cutStyle: text("cut_style"),
|
||||
shampooPreference: text("shampoo_preference"),
|
||||
specialCareNotes: text("special_care_notes"),
|
||||
customFields: jsonb("custom_fields").$type<Record<string, string>>().notNull().default({}),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at").notNull().defaultNow(),
|
||||
});
|
||||
@@ -194,3 +199,21 @@ export const reminderLogs = pgTable(
|
||||
},
|
||||
(t) => [unique().on(t.appointmentId, t.reminderType)]
|
||||
);
|
||||
|
||||
export const groomingVisitLogs = pgTable("grooming_visit_logs", {
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
petId: uuid("pet_id")
|
||||
.notNull()
|
||||
.references(() => pets.id, { onDelete: "cascade" }),
|
||||
appointmentId: uuid("appointment_id").references(() => appointments.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
staffId: uuid("staff_id").references(() => staff.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
cutStyle: text("cut_style"),
|
||||
productsUsed: text("products_used"),
|
||||
notes: text("notes"),
|
||||
groomedAt: timestamp("groomed_at").notNull().defaultNow(),
|
||||
createdAt: timestamp("created_at").notNull().defaultNow(),
|
||||
});
|
||||
|
||||
@@ -30,10 +30,26 @@ export interface Pet {
|
||||
dateOfBirth: string | null;
|
||||
healthAlerts: string | null;
|
||||
groomingNotes: string | null;
|
||||
cutStyle: string | null;
|
||||
shampooPreference: string | null;
|
||||
specialCareNotes: string | null;
|
||||
customFields: Record<string, string>;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface GroomingVisitLog {
|
||||
id: string;
|
||||
petId: string;
|
||||
appointmentId: string | null;
|
||||
staffId: string | null;
|
||||
cutStyle: string | null;
|
||||
productsUsed: string | null;
|
||||
notes: string | null;
|
||||
groomedAt: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface Service {
|
||||
id: string;
|
||||
name: string;
|
||||
|
||||
Reference in New Issue
Block a user