feat(catalog): Notion-style shared select fields (tag/pacchetto/categoria/fase)

Turn the catalog into Notion/Airtable select properties:
- Tag + Pacchetto: multi-select with a SHARED pool — created values persist and
  are selectable from a dropdown across all services (no more re-typing)
- Categoria + Fase: single-select chips with the same dropdown + create-on-the-fly
- Rename an option once -> propagates to every row (renameServiceOption)
- Deterministic colors per value (shared option-colors util)
- Quick-add row now sets name+description+categoria+fase+prezzo then Enter (active)
- Search broadened to name/categoria/fase/tag/pacchetto

Data model (additive): tag/pacchetto in polymorphic tags table (entity_type
services / services.pacchetto); categoria/fase as single-select columns on
services (new: services.fase). Pools derived from distinct values.

New: OptionSelect, OptionMultiSelect, option-colors. Removed tag-multi-select.
Migration 0007_add_services_fase.sql must be applied before runtime.

tsc + eslint + next build clean. CSV bulk import (OFFER-12) stays Phase 12.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 21:40:34 +02:00
parent 42c16e1bab
commit e858a8f577
12 changed files with 834 additions and 249 deletions
+27
View File
@@ -0,0 +1,27 @@
// PRODUCTION DEPLOY NOTE: additive-only (ADD COLUMN IF NOT EXISTS, no drops/truncates).
// Per CLAUDE.md Data Safety, apply to the live DB via SSH tunnel / docker exec BEFORE
// deploying the schema-dependent catalog code. Idempotent — safe to re-run.
// Run: npx tsx scripts/push-11b-fase-column.ts (with DATABASE_URL pointing at the DB)
import postgres from "postgres";
async function push() {
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
console.error("DATABASE_URL environment variable is required");
process.exit(1);
}
const client = postgres(databaseUrl);
try {
console.log("Adding services.fase column...");
await client`ALTER TABLE services ADD COLUMN IF NOT EXISTS fase text`;
console.log("✓ services.fase column ready");
process.exit(0);
} catch (err: unknown) {
console.error("Error pushing migration:", err instanceof Error ? err.message : err);
process.exit(1);
}
}
push();