diff --git a/src/app/admin/offers/page.tsx b/src/app/admin/offers/page.tsx
new file mode 100644
index 0000000..b7a0919
--- /dev/null
+++ b/src/app/admin/offers/page.tsx
@@ -0,0 +1,238 @@
+import { getCatalogWithMicros, getAllOfferServices } from "@/lib/offer-queries";
+import {
+ createMacro,
+ createMicro,
+ createOfferService,
+ deleteMacro,
+ deleteMicro,
+ toggleOfferServiceActive,
+} from "./actions";
+import { ServiceCheckboxList } from "@/components/admin/offers/ServiceCheckboxList";
+
+export const revalidate = 0;
+
+export default async function OffersPage() {
+ const [catalog, allServices] = await Promise.all([
+ getCatalogWithMicros(),
+ getAllOfferServices(),
+ ]);
+
+ return (
+
+
Catalogo Offerte
+
+ {/* ── Sezione macro-offerte ── */}
+
+ Macro-Offerte
+
+ {/* Create macro form */}
+
+
+ {/* List macros */}
+
+ {catalog.map((macro) => (
+
+
+
+
{macro.internal_name}
+
Pubblico: {macro.public_name}
+ {macro.transformation_promise && (
+
{macro.transformation_promise}
+ )}
+
+
+
+
+ {/* Micro-offers under this macro */}
+
+
Micro-Offerte
+
+ {macro.micros.map((micro) => (
+
+
+
+
{micro.internal_name}
+
+ Pubblico: {micro.public_name} ·{" "}
+ {micro.duration_months} {micro.duration_months === 1 ? "mese" : "mesi"}
+
+ {micro.transformation_promise && (
+
{micro.transformation_promise}
+ )}
+
+ Prezzo cumulativo: €{parseFloat(micro.cumulative_price).toFixed(2)}
+
+
+
+
+ {/* Service assignment checkbox list */}
+
+
Servizi inclusi:
+
({
+ id: s.id,
+ name: s.name,
+ price: String(s.price),
+ }))}
+ assignedIds={micro.services.map((s) => s.id)}
+ microId={micro.id}
+ />
+
+
+ ))}
+
+ {/* Create micro form */}
+
+
+
+ ))}
+
+ {catalog.length === 0 && (
+
Nessuna macro-offerta ancora. Creane una sopra.
+ )}
+
+
+
+ {/* ── Sezione servizi offerta ── */}
+
+ Servizi Offerta
+
+ I servizi qui sono diversi dal catalogo preventivi — hanno una descrizione della
+ trasformazione e vengono raggruppati nelle micro-offerte.
+
+
+ {/* List services */}
+
+ {allServices.map((svc) => (
+
+
+
{svc.name}
+ {svc.transformation_description && (
+
{svc.transformation_description}
+ )}
+
+
+ €{parseFloat(String(svc.price)).toFixed(2)}
+
+
+
+ ))}
+ {allServices.length === 0 && (
+
Nessun servizio ancora.
+ )}
+
+
+ {/* Create service form */}
+
+
+
+ );
+}
diff --git a/src/components/admin/NavBar.tsx b/src/components/admin/NavBar.tsx
index 7162484..4999fca 100644
--- a/src/components/admin/NavBar.tsx
+++ b/src/components/admin/NavBar.tsx
@@ -18,9 +18,15 @@ export function NavBar() {
Statistiche
+
+ Forecast
+
Catalogo
+
+ Offerte
+
Impostazioni
diff --git a/src/components/admin/offers/ServiceCheckboxList.tsx b/src/components/admin/offers/ServiceCheckboxList.tsx
new file mode 100644
index 0000000..df760bb
--- /dev/null
+++ b/src/components/admin/offers/ServiceCheckboxList.tsx
@@ -0,0 +1,51 @@
+"use client";
+
+import { useState, useTransition } from "react";
+import { useRouter } from "next/navigation";
+import { updateMicroOfferServices } from "@/app/admin/offers/actions";
+
+export function ServiceCheckboxList({
+ allServices,
+ assignedIds,
+ microId,
+}: {
+ allServices: Array<{ id: string; name: string; price: string }>;
+ assignedIds: string[];
+ microId: string;
+}) {
+ const [selected, setSelected] = useState(new Set(assignedIds));
+ const [isPending, startTransition] = useTransition();
+ const router = useRouter();
+
+ function toggle(serviceId: string) {
+ const next = new Set(selected);
+ if (next.has(serviceId)) next.delete(serviceId);
+ else next.add(serviceId);
+ setSelected(next);
+ startTransition(async () => {
+ await updateMicroOfferServices(microId, [...next]);
+ router.refresh();
+ });
+ }
+
+ return (
+
+ {allServices.map((svc) => (
+
+ ))}
+ {allServices.length === 0 && (
+
Nessun servizio nel catalogo ancora.
+ )}
+
+ );
+}