fix: flag A/B/C persistence, catalog sort+reorder, offer category pools

- Fix duplicate tier INSERT bug (useState not syncing after router.refresh)
- Add column sort by clicking headers in service catalog
- Add drag-and-drop column reordering (persisted in localStorage)
- Add Categorie Offerta section in Impostazioni (tipo/obiettivo/categoria pools)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 12:12:49 +02:00
parent ba3e824157
commit 320827e13a
9 changed files with 654 additions and 216 deletions
+27 -13
View File
@@ -8,6 +8,7 @@ import {
services,
tags,
} from "@/db/schema";
import { getJsonPool, SETTINGS_KEYS } from "@/lib/settings";
import type { OfferMacro, OfferMicro, OfferService } from "@/db/schema";
import { eq, and, asc, inArray, sql } from "drizzle-orm";
@@ -272,15 +273,21 @@ export type OfferFieldOptions = {
};
// Shared option pools for the offer editor's select fields (Notion-style
// dropdowns), mirroring getCatalogFieldOptions from admin-queries.ts.
// dropdowns). Tipo, Obiettivo, and Categoria are merged from the managed
// settings pools + any values already in use (retrocompatibility).
export async function getOfferFieldOptions(): Promise<OfferFieldOptions> {
const categoriaRows = await db.selectDistinct({ value: offer_macros.category }).from(offer_macros);
const ticketRows = await db.selectDistinct({ value: offer_macros.ticket }).from(offer_macros);
const tagRows = await db
.selectDistinct({ name: tags.name, type: tags.entity_type })
.from(tags)
.where(inArray(tags.entity_type, [OFFER_TIPO_ENTITY, OFFER_OBIETTIVO_ENTITY]));
const [categoriaRows, ticketRows, tagRows, tipoPool, obiettivoPool, categoriaPool] =
await Promise.all([
db.selectDistinct({ value: offer_macros.category }).from(offer_macros),
db.selectDistinct({ value: offer_macros.ticket }).from(offer_macros),
db
.selectDistinct({ name: tags.name, type: tags.entity_type })
.from(tags)
.where(inArray(tags.entity_type, [OFFER_TIPO_ENTITY, OFFER_OBIETTIVO_ENTITY])),
getJsonPool(SETTINGS_KEYS.OFFER_TIPO_POOL),
getJsonPool(SETTINGS_KEYS.OFFER_OBIETTIVO_POOL),
getJsonPool(SETTINGS_KEYS.OFFER_CATEGORIA_POOL),
]);
const sortUnique = (arr: (string | null)[]) =>
Array.from(new Set(arr.filter((v): v is string => !!v && v.trim().length > 0))).sort(
@@ -288,11 +295,18 @@ export async function getOfferFieldOptions(): Promise<OfferFieldOptions> {
);
return {
categoria: sortUnique(categoriaRows.map((r) => r.value)),
categoria: sortUnique([
...categoriaPool,
...categoriaRows.map((r) => r.value),
]),
ticket: sortUnique(ticketRows.map((r) => r.value)),
tipo: sortUnique(tagRows.filter((r) => r.type === OFFER_TIPO_ENTITY).map((r) => r.name)),
obiettivo: sortUnique(
tagRows.filter((r) => r.type === OFFER_OBIETTIVO_ENTITY).map((r) => r.name)
),
tipo: sortUnique([
...tipoPool,
...tagRows.filter((r) => r.type === OFFER_TIPO_ENTITY).map((r) => r.name),
]),
obiettivo: sortUnique([
...obiettivoPool,
...tagRows.filter((r) => r.type === OFFER_OBIETTIVO_ENTITY).map((r) => r.name),
]),
};
}