feat(07-unified-service-catalog): rewire /admin/catalog CRUD + query layer to services table
- Task 1: Update getAllServices() and activeServices queries to read from services table instead of service_catalog - Both ClientFullDetail and ProjectFullDetail now return Service[] (not ServiceCatalog[]) - Quote items label join remains unchanged — still references service_catalog for historical quote_items.service_id FK integrity - Task 2: Rewire /admin/catalog actions and components to operate on services table - src/app/admin/catalog/actions.ts: createService/updateService/toggleServiceActive now write to services with optional category field - ServiceTable.tsx: Updated to Service[] type, added category column rendering - ServiceForm.tsx: Added optional category input field - QuoteTab.tsx: Updated activeServices type annotation to Service[] - New services created via /admin/catalog have migrated_from=null, migrated_id=null (not migrated) - TypeScript compiles successfully - npm run build: PASS Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
time_entries,
|
||||
quote_items,
|
||||
service_catalog,
|
||||
services,
|
||||
settings,
|
||||
offer_micros,
|
||||
project_offers,
|
||||
@@ -28,6 +29,7 @@ import type {
|
||||
Note,
|
||||
Comment,
|
||||
ServiceCatalog,
|
||||
Service,
|
||||
OfferMicro,
|
||||
ProjectOffer,
|
||||
} from "@/db/schema";
|
||||
@@ -221,7 +223,7 @@ export type ClientFullDetail = {
|
||||
notes: Note[];
|
||||
comments: Comment[];
|
||||
quoteItems: QuoteItemWithLabel[];
|
||||
activeServices: ServiceCatalog[];
|
||||
activeServices: Service[];
|
||||
};
|
||||
|
||||
export async function getClientFullDetail(id: string): Promise<ClientFullDetail | null> {
|
||||
@@ -238,9 +240,9 @@ export async function getClientFullDetail(id: string): Promise<ClientFullDetail
|
||||
|
||||
const activeServiceRows = await db
|
||||
.select()
|
||||
.from(service_catalog)
|
||||
.where(eq(service_catalog.active, true))
|
||||
.orderBy(asc(service_catalog.name));
|
||||
.from(services)
|
||||
.where(eq(services.active, true))
|
||||
.orderBy(asc(services.name));
|
||||
|
||||
if (projectIds.length === 0) {
|
||||
return {
|
||||
@@ -344,11 +346,11 @@ export async function getClientFullDetail(id: string): Promise<ClientFullDetail
|
||||
};
|
||||
}
|
||||
|
||||
export async function getAllServices(): Promise<ServiceCatalog[]> {
|
||||
export async function getAllServices(): Promise<Service[]> {
|
||||
return db
|
||||
.select()
|
||||
.from(service_catalog)
|
||||
.orderBy(asc(service_catalog.name));
|
||||
.from(services)
|
||||
.orderBy(asc(services.name));
|
||||
}
|
||||
|
||||
// ── ProjectWithPayments — used by /admin/projects list ───────────────────────
|
||||
@@ -456,7 +458,7 @@ export type ProjectFullDetail = {
|
||||
notes: Note[];
|
||||
comments: Comment[];
|
||||
quoteItems: QuoteItemWithLabel[];
|
||||
activeServices: ServiceCatalog[];
|
||||
activeServices: Service[];
|
||||
activeTimerEntryId: string | null;
|
||||
activeTimerStartedAt: Date | null;
|
||||
totalTrackedSeconds: number;
|
||||
@@ -527,7 +529,7 @@ export async function getProjectFullDetail(id: string): Promise<ProjectFullDetai
|
||||
.leftJoin(service_catalog, eq(quote_items.service_id, service_catalog.id))
|
||||
.where(eq(quote_items.project_id, id))
|
||||
.orderBy(asc(quote_items.id)),
|
||||
db.select().from(service_catalog).where(eq(service_catalog.active, true)).orderBy(asc(service_catalog.name)),
|
||||
db.select().from(services).where(eq(services.active, true)).orderBy(asc(services.name)),
|
||||
db
|
||||
.select({ id: time_entries.id, started_at: time_entries.started_at })
|
||||
.from(time_entries)
|
||||
|
||||
Reference in New Issue
Block a user