feat(11-02): add inline-edit, tag, and quick-add server actions
- updateServiceField: single-field inline edit (name, description, category, unit_price, active) with per-field validation - addTagToService / removeTagFromService: tag assignment scoped to entity_type="services" via onConflictDoNothing on the unique index - quickAddService: creates a unit_price=0.00 row from name only (D-12) - All actions admin-gated via requireAdmin() and revalidate /admin/catalog
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
"use server";
|
||||
|
||||
import { db } from "@/db";
|
||||
import { services } from "@/db/schema";
|
||||
import { services, tags } from "@/db/schema";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { getServerSession } from "next-auth";
|
||||
import { authOptions } from "@/lib/auth";
|
||||
@@ -65,3 +65,88 @@ export async function toggleServiceActive(serviceId: string, active: boolean) {
|
||||
await db.update(services).set({ active }).where(eq(services.id, serviceId));
|
||||
revalidatePath("/admin/catalog");
|
||||
}
|
||||
|
||||
// ── Inline-edit, tag, and quick-add actions (Phase 11 database-view) ────────
|
||||
|
||||
const EDITABLE_FIELDS = ["name", "description", "category", "unit_price", "active"] as const;
|
||||
type EditableField = (typeof EDITABLE_FIELDS)[number];
|
||||
|
||||
export async function updateServiceField(
|
||||
serviceId: string,
|
||||
fieldName: EditableField,
|
||||
value: string | boolean
|
||||
) {
|
||||
await requireAdmin();
|
||||
|
||||
if (!EDITABLE_FIELDS.includes(fieldName)) {
|
||||
throw new Error(`Campo non editabile: ${fieldName}`);
|
||||
}
|
||||
|
||||
if (fieldName === "name") {
|
||||
const s = String(value).trim();
|
||||
if (s.length === 0) throw new Error("Nome richiesto");
|
||||
await db.update(services).set({ name: s }).where(eq(services.id, serviceId));
|
||||
} else if (fieldName === "description") {
|
||||
const s = String(value).trim();
|
||||
await db.update(services).set({ description: s || null }).where(eq(services.id, serviceId));
|
||||
} else if (fieldName === "category") {
|
||||
const s = String(value).trim();
|
||||
await db.update(services).set({ category: s || null }).where(eq(services.id, serviceId));
|
||||
} else if (fieldName === "unit_price") {
|
||||
const num = parseFloat(String(value));
|
||||
if (isNaN(num) || num < 0) throw new Error("Prezzo invalido");
|
||||
await db.update(services).set({ unit_price: num.toFixed(2) }).where(eq(services.id, serviceId));
|
||||
} else if (fieldName === "active") {
|
||||
const boolValue = typeof value === "boolean" ? value : value === "true";
|
||||
await db.update(services).set({ active: boolValue }).where(eq(services.id, serviceId));
|
||||
}
|
||||
|
||||
revalidatePath("/admin/catalog");
|
||||
}
|
||||
|
||||
export async function addTagToService(serviceId: string, tagName: string) {
|
||||
await requireAdmin();
|
||||
const trimmed = tagName.trim();
|
||||
if (trimmed.length === 0) throw new Error("Nome tag richiesto");
|
||||
|
||||
await db
|
||||
.insert(tags)
|
||||
.values({
|
||||
entity_type: "services",
|
||||
entity_id: serviceId,
|
||||
name: trimmed,
|
||||
})
|
||||
.onConflictDoNothing();
|
||||
|
||||
revalidatePath("/admin/catalog");
|
||||
}
|
||||
|
||||
export async function removeTagFromService(serviceId: string, tagName: string) {
|
||||
await requireAdmin();
|
||||
|
||||
await db
|
||||
.delete(tags)
|
||||
.where(
|
||||
and(
|
||||
eq(tags.entity_type, "services"),
|
||||
eq(tags.entity_id, serviceId),
|
||||
eq(tags.name, tagName)
|
||||
)
|
||||
);
|
||||
|
||||
revalidatePath("/admin/catalog");
|
||||
}
|
||||
|
||||
export async function quickAddService(name: string) {
|
||||
await requireAdmin();
|
||||
const trimmed = name.trim();
|
||||
if (trimmed.length === 0) throw new Error("Nome richiesto");
|
||||
|
||||
await db.insert(services).values({
|
||||
name: trimmed,
|
||||
unit_price: "0.00",
|
||||
active: true,
|
||||
});
|
||||
|
||||
revalidatePath("/admin/catalog");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user