feat(12-04): rewrite /admin/offers page.tsx as new list page entry point
- Replace legacy macro/micro/offer_services CRUD page with thin server component fetching getOfferListCards() + getOfferFieldOptions() - Delegate rendering to new OfferListClient (Task 2)
This commit is contained in:
@@ -1,238 +1,17 @@
|
|||||||
import { getCatalogWithMicros, getAllOfferServices } from "@/lib/offer-queries";
|
import { getOfferListCards, getOfferFieldOptions } from "@/lib/offer-queries";
|
||||||
import {
|
import { OfferListClient } from "@/components/admin/offers/OfferListClient";
|
||||||
createMacro,
|
|
||||||
createMicro,
|
|
||||||
createOfferService,
|
|
||||||
deleteMacro,
|
|
||||||
deleteMicro,
|
|
||||||
toggleOfferServiceActive,
|
|
||||||
} from "./actions";
|
|
||||||
import { ServiceCheckboxList } from "@/components/admin/offers/ServiceCheckboxList";
|
|
||||||
|
|
||||||
export const revalidate = 0;
|
export const revalidate = 0;
|
||||||
|
|
||||||
export default async function OffersPage() {
|
export default async function OffersPage() {
|
||||||
const [catalog, allServices] = await Promise.all([
|
const [cards, options] = await Promise.all([
|
||||||
getCatalogWithMicros(),
|
getOfferListCards(),
|
||||||
getAllOfferServices(),
|
getOfferFieldOptions(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="max-w-5xl mx-auto py-8 px-4 space-y-10">
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-[#1a1a1a]">Catalogo Offerte</h1>
|
<OfferListClient cards={cards} categoryOptions={options.categoria} />
|
||||||
|
|
||||||
{/* ── Sezione macro-offerte ── */}
|
|
||||||
<section>
|
|
||||||
<h2 className="text-lg font-semibold text-[#1a1a1a] mb-4">Macro-Offerte</h2>
|
|
||||||
|
|
||||||
{/* Create macro form */}
|
|
||||||
<form action={createMacro} className="bg-white rounded-lg border border-[#e5e7eb] p-4 mb-6 space-y-3 max-w-lg">
|
|
||||||
<p className="text-sm font-medium">Nuova Macro-Offerta</p>
|
|
||||||
<input
|
|
||||||
name="internal_name"
|
|
||||||
placeholder="Nome interno (es. Entry Offer)"
|
|
||||||
required
|
|
||||||
className="w-full border rounded px-3 py-1.5 text-sm"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
name="public_name"
|
|
||||||
placeholder="Nome pubblico (es. Starter Branding)"
|
|
||||||
required
|
|
||||||
className="w-full border rounded px-3 py-1.5 text-sm"
|
|
||||||
/>
|
|
||||||
<textarea
|
|
||||||
name="transformation_promise"
|
|
||||||
placeholder="Promessa di trasformazione"
|
|
||||||
rows={2}
|
|
||||||
className="w-full border rounded px-3 py-1.5 text-sm"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="bg-[#1A463C] text-white text-sm px-4 py-1.5 rounded hover:bg-[#163a31]"
|
|
||||||
>
|
|
||||||
Aggiungi Macro
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{/* List macros */}
|
|
||||||
<div className="space-y-8">
|
|
||||||
{catalog.map((macro) => (
|
|
||||||
<div key={macro.id} className="bg-white rounded-lg border border-[#e5e7eb] p-6">
|
|
||||||
<div className="flex items-start justify-between mb-1">
|
|
||||||
<div>
|
|
||||||
<p className="font-semibold text-[#1a1a1a]">{macro.internal_name}</p>
|
|
||||||
<p className="text-sm text-[#71717a]">Pubblico: {macro.public_name}</p>
|
|
||||||
{macro.transformation_promise && (
|
|
||||||
<p className="text-xs text-[#71717a] mt-1 italic">{macro.transformation_promise}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<form action={deleteMacro.bind(null, macro.id)}>
|
|
||||||
<button type="submit" className="text-xs text-red-600 hover:underline">
|
|
||||||
Elimina
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Micro-offers under this macro */}
|
|
||||||
<div className="mt-4 space-y-4 pl-4 border-l-2 border-[#e5e7eb]">
|
|
||||||
<p className="text-xs font-semibold text-[#71717a] uppercase tracking-wider">Micro-Offerte</p>
|
|
||||||
|
|
||||||
{macro.micros.map((micro) => (
|
|
||||||
<div key={micro.id} className="bg-[#f9f9f9] rounded-lg p-4 space-y-3">
|
|
||||||
<div className="flex items-start justify-between">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium">{micro.internal_name}</p>
|
|
||||||
<p className="text-xs text-[#71717a]">
|
|
||||||
Pubblico: {micro.public_name} ·{" "}
|
|
||||||
{micro.duration_months} {micro.duration_months === 1 ? "mese" : "mesi"}
|
|
||||||
</p>
|
|
||||||
{micro.transformation_promise && (
|
|
||||||
<p className="text-xs text-[#71717a] italic">{micro.transformation_promise}</p>
|
|
||||||
)}
|
|
||||||
<p className="text-xs text-[#1a1a1a] mt-1 font-medium">
|
|
||||||
Prezzo cumulativo: €{parseFloat(micro.cumulative_price).toFixed(2)}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<form action={deleteMicro.bind(null, micro.id)}>
|
|
||||||
<button type="submit" className="text-xs text-red-600 hover:underline">
|
|
||||||
Elimina
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{/* Service assignment checkbox list */}
|
|
||||||
<div>
|
|
||||||
<p className="text-xs font-medium text-[#71717a] mb-2">Servizi inclusi:</p>
|
|
||||||
<ServiceCheckboxList
|
|
||||||
allServices={allServices.map((s) => ({
|
|
||||||
id: s.id,
|
|
||||||
name: s.name,
|
|
||||||
price: String(s.price),
|
|
||||||
}))}
|
|
||||||
assignedIds={micro.services.map((s) => s.id)}
|
|
||||||
microId={micro.id}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Create micro form */}
|
|
||||||
<form action={createMicro} className="bg-white rounded border border-[#e5e7eb] p-3 space-y-2">
|
|
||||||
<input type="hidden" name="macro_id" value={macro.id} />
|
|
||||||
<p className="text-xs font-medium">Nuova Micro-Offerta</p>
|
|
||||||
<input
|
|
||||||
name="internal_name"
|
|
||||||
placeholder="Nome interno"
|
|
||||||
required
|
|
||||||
className="w-full border rounded px-2 py-1 text-xs"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
name="public_name"
|
|
||||||
placeholder="Nome pubblico"
|
|
||||||
required
|
|
||||||
className="w-full border rounded px-2 py-1 text-xs"
|
|
||||||
/>
|
|
||||||
<textarea
|
|
||||||
name="transformation_promise"
|
|
||||||
placeholder="Promessa di trasformazione"
|
|
||||||
rows={2}
|
|
||||||
className="w-full border rounded px-2 py-1 text-xs"
|
|
||||||
/>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<label className="text-xs">Durata (mesi):</label>
|
|
||||||
<input
|
|
||||||
name="duration_months"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
defaultValue="1"
|
|
||||||
required
|
|
||||||
className="w-16 border rounded px-2 py-1 text-xs"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="bg-[#1A463C] text-white text-xs px-3 py-1 rounded hover:bg-[#163a31]"
|
|
||||||
>
|
|
||||||
Aggiungi Micro
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{catalog.length === 0 && (
|
|
||||||
<p className="text-sm text-[#71717a]">Nessuna macro-offerta ancora. Creane una sopra.</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
{/* ── Sezione servizi offerta ── */}
|
|
||||||
<section>
|
|
||||||
<h2 className="text-lg font-semibold text-[#1a1a1a] mb-4">Servizi Offerta</h2>
|
|
||||||
<p className="text-sm text-[#71717a] mb-4">
|
|
||||||
I servizi qui sono diversi dal catalogo preventivi — hanno una descrizione della
|
|
||||||
trasformazione e vengono raggruppati nelle micro-offerte.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{/* List services */}
|
|
||||||
<div className="bg-white rounded-lg border border-[#e5e7eb] divide-y divide-[#e5e7eb] mb-6">
|
|
||||||
{allServices.map((svc) => (
|
|
||||||
<div key={svc.id} className="flex items-center justify-between px-4 py-3">
|
|
||||||
<div>
|
|
||||||
<p className="text-sm font-medium">{svc.name}</p>
|
|
||||||
{svc.transformation_description && (
|
|
||||||
<p className="text-xs text-[#71717a]">{svc.transformation_description}</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<span className="text-sm font-mono">€{parseFloat(String(svc.price)).toFixed(2)}</span>
|
|
||||||
<form action={toggleOfferServiceActive.bind(null, svc.id, !svc.active)}>
|
|
||||||
<button type="submit" className="text-xs text-[#71717a] hover:text-[#1a1a1a]">
|
|
||||||
{svc.active ? "Disattiva" : "Attiva"}
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
{allServices.length === 0 && (
|
|
||||||
<p className="px-4 py-3 text-sm text-[#71717a]">Nessun servizio ancora.</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Create service form */}
|
|
||||||
<form
|
|
||||||
action={createOfferService}
|
|
||||||
className="bg-white rounded-lg border border-[#e5e7eb] p-4 space-y-3 max-w-lg"
|
|
||||||
>
|
|
||||||
<p className="text-sm font-medium">Nuovo Servizio Offerta</p>
|
|
||||||
<input
|
|
||||||
name="name"
|
|
||||||
placeholder="Nome servizio"
|
|
||||||
required
|
|
||||||
className="w-full border rounded px-3 py-1.5 text-sm"
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
name="price"
|
|
||||||
type="number"
|
|
||||||
step="0.01"
|
|
||||||
min="0"
|
|
||||||
placeholder="Prezzo (€)"
|
|
||||||
required
|
|
||||||
className="w-full border rounded px-3 py-1.5 text-sm"
|
|
||||||
/>
|
|
||||||
<textarea
|
|
||||||
name="transformation_description"
|
|
||||||
placeholder="Descrizione trasformazione (marketing)"
|
|
||||||
rows={2}
|
|
||||||
className="w-full border rounded px-3 py-1.5 text-sm"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="bg-[#1A463C] text-white text-sm px-4 py-1.5 rounded hover:bg-[#163a31]"
|
|
||||||
>
|
|
||||||
Aggiungi Servizio
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user