feat: service offer tags, catalog cleanup, offer editor filter

- Import 58 offer membership tags for all 55 services (entity_type="services")
  via scripts/import-service-offer-tags.ts (already run on prod)
- ServiceTable: remove Categoria column, rename Tag → Offerta; QuickAddRow
  no longer has a category field (offer tags set post-creation)
- offer-queries: getOfferEditorData fetches offerTags per service (join on
  tags WHERE entity_type="services") and exposes them in OfferEditorData
- OfferEditorClient: filter chips above "Servizi Inclusi" — Tutti / Entry
  Offer / Signature Offer / Retainer Offer, derived from actual tag data

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 21:58:34 +02:00
parent 696a95950c
commit 43238341c1
4 changed files with 187 additions and 33 deletions
+24 -3
View File
@@ -145,6 +145,7 @@ export type OfferEditorData = {
name: string;
unit_price: string;
category: string | null;
offerTags: string[];
}>;
tipoTags: string[];
obiettivoTags: string[];
@@ -206,9 +207,8 @@ export async function getOfferEditorData(macroId: string): Promise<OfferEditorDa
servicesTotal: totalsMap.get(tier.id) ?? "0",
}));
// Full service catalog (unfiltered by category) — category filtering happens
// client-side so changing the category field doesn't wipe the service list.
const availableServices = await db
// Full service catalog with offer membership tags (entity_type = "services").
const allServices = await db
.select({
id: services.id,
name: services.name,
@@ -218,6 +218,27 @@ export async function getOfferEditorData(macroId: string): Promise<OfferEditorDa
.from(services)
.where(eq(services.active, true));
const serviceIds = allServices.map((s) => s.id);
const offerTagRows =
serviceIds.length > 0
? await db
.select({ entity_id: tags.entity_id, name: tags.name })
.from(tags)
.where(and(eq(tags.entity_type, "services"), inArray(tags.entity_id, serviceIds)))
: [];
const tagsByServiceId = new Map<string, string[]>();
for (const row of offerTagRows) {
const list = tagsByServiceId.get(row.entity_id) ?? [];
list.push(row.name);
tagsByServiceId.set(row.entity_id, list);
}
const availableServices = allServices.map((s) => ({
...s,
offerTags: tagsByServiceId.get(s.id) ?? [],
}));
// Tipo/Obiettivo tags for this macro.
const tagRows = await db
.select({ name: tags.name, type: tags.entity_type })