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:
+27
-13
@@ -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),
|
||||
]),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
Reference in New Issue
Block a user