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),
]),
};
}
+21
View File
@@ -5,6 +5,9 @@ import { revalidatePath } from "next/cache";
export const SETTINGS_KEYS = {
TARGET_HOURLY_RATE: "target_hourly_rate",
OFFER_TIPO_POOL: "offer_tipo_pool",
OFFER_OBIETTIVO_POOL: "offer_obiettivo_pool",
OFFER_CATEGORIA_POOL: "offer_categoria_pool",
} as const;
export async function getSetting(key: string): Promise<string | null> {
@@ -32,4 +35,22 @@ export async function updateSetting(key: string, value: string): Promise<void> {
export async function getTargetHourlyRate(): Promise<number> {
const value = await getSetting(SETTINGS_KEYS.TARGET_HOURLY_RATE);
return value ? parseFloat(value) : 50;
}
export async function getJsonPool(key: string): Promise<string[]> {
const raw = await getSetting(key);
if (!raw) return [];
try {
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? (parsed as string[]) : [];
} catch {
return [];
}
}
export async function updateJsonPool(key: string, values: string[]): Promise<void> {
const sorted = [...new Set(values.filter((v) => v.trim()))].sort((a, b) =>
a.localeCompare(b, "it")
);
await updateSetting(key, JSON.stringify(sorted));
}