feat: wire offer→project phases/tasks, redo Offerte tab, cleanup project tabs
- importOfferIntoProject(): materializes project phases/tasks from the assigned tier's services grouped by services.fase (merge-by-title, dedup tasks) - assignOfferToProject: optional import_phases flag triggers the import - getProjectFullDetail: projectOffers/availableMicros now include macro name + tier_letter (dropdown shows "Offerta — Tier X"); availableMicros filters non-archived macros - OffersTab redone: shows macro + tier badge, "Importa fasi e task" checkbox - removed project "Preventivo" tab + deleted QuoteTab.tsx (legacy quote_items) - sidebar: Lead before Clienti - no DB migration (reuses existing tables) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,8 @@ type AvailableMicro = {
|
||||
internal_name: string;
|
||||
public_name: string;
|
||||
duration_months: number;
|
||||
tier_letter: string | null;
|
||||
macro_internal_name: string;
|
||||
};
|
||||
|
||||
interface OffersTabProps {
|
||||
@@ -22,6 +24,10 @@ interface OffersTabProps {
|
||||
availableMicros: AvailableMicro[];
|
||||
}
|
||||
|
||||
function durationLabel(months: number): string {
|
||||
return `${months} ${months === 1 ? "mese" : "mesi"}`;
|
||||
}
|
||||
|
||||
export function OffersTab({ projectId, projectOffers, availableMicros }: OffersTabProps) {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const router = useRouter();
|
||||
@@ -64,10 +70,16 @@ export function OffersTab({ projectId, projectOffers, availableMicros }: OffersT
|
||||
className="bg-white rounded-lg border border-[#e5e7eb] p-4 flex items-start justify-between gap-4"
|
||||
>
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium text-[#1a1a1a]">{offer.micro_internal_name}</p>
|
||||
<p className="text-sm font-medium text-[#1a1a1a]">
|
||||
{offer.macro_internal_name}
|
||||
{offer.tier_letter && (
|
||||
<span className="ml-2 inline-block rounded bg-[#1A463C]/10 text-[#1A463C] text-[11px] font-semibold px-1.5 py-0.5 align-middle">
|
||||
Tier {offer.tier_letter}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
<p className="text-xs text-[#71717a]">
|
||||
Pubblico: {offer.micro_public_name} · {offer.micro_duration_months}{" "}
|
||||
{offer.micro_duration_months === 1 ? "mese" : "mesi"}
|
||||
Pubblico: {offer.micro_public_name} · {durationLabel(offer.micro_duration_months)}
|
||||
</p>
|
||||
<p className="text-xs text-[#71717a] mt-1">
|
||||
Inizio: {new Date(offer.start_date).toLocaleDateString("it-IT")}
|
||||
@@ -107,7 +119,7 @@ export function OffersTab({ projectId, projectOffers, availableMicros }: OffersT
|
||||
|
||||
{/* Assignment form */}
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-[#1a1a1a] mb-3">Assegna Micro-Offerta</h3>
|
||||
<h3 className="text-sm font-semibold text-[#1a1a1a] mb-3">Assegna Offerta</h3>
|
||||
<form
|
||||
ref={formRef}
|
||||
action={handleAssign}
|
||||
@@ -116,17 +128,18 @@ export function OffersTab({ projectId, projectOffers, availableMicros }: OffersT
|
||||
<input type="hidden" name="project_id" value={projectId} />
|
||||
|
||||
<div>
|
||||
<label className="text-xs text-[#71717a] block mb-1">Micro-offerta</label>
|
||||
<label className="text-xs text-[#71717a] block mb-1">Offerta</label>
|
||||
<select
|
||||
name="micro_id"
|
||||
required
|
||||
className="w-full border rounded px-3 py-1.5 text-sm"
|
||||
>
|
||||
<option value="">Seleziona micro-offerta...</option>
|
||||
<option value="">Seleziona offerta...</option>
|
||||
{availableMicros.map((m) => (
|
||||
<option key={m.id} value={m.id}>
|
||||
{m.internal_name} ({m.duration_months}{" "}
|
||||
{m.duration_months === 1 ? "mese" : "mesi"})
|
||||
{m.macro_internal_name}
|
||||
{m.tier_letter ? ` — Tier ${m.tier_letter}` : ""} · {m.public_name} (
|
||||
{durationLabel(m.duration_months)})
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
@@ -144,6 +157,21 @@ export function OffersTab({ projectId, projectOffers, availableMicros }: OffersT
|
||||
/>
|
||||
</div>
|
||||
|
||||
<label className="flex items-start gap-2 text-xs text-[#1a1a1a] cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="import_phases"
|
||||
defaultChecked
|
||||
className="mt-0.5 h-3.5 w-3.5 accent-[#1A463C]"
|
||||
/>
|
||||
<span>
|
||||
Importa fasi e task dall'offerta
|
||||
<span className="block text-[#71717a]">
|
||||
Crea le fasi raggruppando i servizi per «Fase»; ogni servizio diventa un task.
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isPending || availableMicros.length === 0}
|
||||
@@ -154,7 +182,7 @@ export function OffersTab({ projectId, projectOffers, availableMicros }: OffersT
|
||||
|
||||
{availableMicros.length === 0 && (
|
||||
<p className="text-xs text-[#71717a]">
|
||||
Nessuna micro-offerta disponibile. Crea prima una micro-offerta in{" "}
|
||||
Nessuna offerta disponibile. Crea prima un'offerta in{" "}
|
||||
<a href="/admin/offers" className="underline">Offerte</a>.
|
||||
</p>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user