feat: lead → client conversion + client contact fields (email/phone)
Blocco A+B (milestone v2.3). Migration 0011 is additive (ADD COLUMN only): clients.email, clients.phone, leads.archived. - createClientCore() extracted from createClient and reused by conversion - clients.email/phone added to create + edit forms and shown on client header (never exposed on the public /client/[token] page) - convertLeadToClient(): reuses core, carries over lead transcripts (client_transcripts.client_id), links project.created_from_lead_id, archives the lead while keeping its "won" status; idempotent - "Converti in cliente" button on won leads; "Convertito → Apri cliente" once converted (clientId resolved via created_from_lead_id) - archived leads hidden from list/kanban; detail page fetches by id incl. archived Migration must be applied to the prod DB BEFORE this is deployed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,13 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { OptionMultiSelect } from "@/components/ui/option-multi-select";
|
||||
import { addLeadTag, removeLeadTag, renameLeadTag, deleteTranscript } from "@/app/admin/leads/actions";
|
||||
import {
|
||||
addLeadTag,
|
||||
removeLeadTag,
|
||||
renameLeadTag,
|
||||
deleteTranscript,
|
||||
convertLeadToClient,
|
||||
} from "@/app/admin/leads/actions";
|
||||
import { formatDistanceToNow, format } from "date-fns";
|
||||
import { it } from "date-fns/locale";
|
||||
import Link from "next/link";
|
||||
@@ -39,6 +45,7 @@ export function LeadDetail({
|
||||
tags,
|
||||
tagOptions,
|
||||
transcripts,
|
||||
convertedClientId,
|
||||
}: {
|
||||
lead: Lead;
|
||||
activities: Activity[];
|
||||
@@ -46,13 +53,23 @@ export function LeadDetail({
|
||||
tags: string[];
|
||||
tagOptions: string[];
|
||||
transcripts: ClientTranscript[];
|
||||
convertedClientId?: string | null;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const [, startTransition] = useTransition();
|
||||
const [converting, startConvert] = useTransition();
|
||||
const [tagError, setTagError] = useState<string | null>(null);
|
||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
||||
const [deletingId, setDeletingId] = useState<string | null>(null);
|
||||
|
||||
function handleConvert() {
|
||||
const ok = window.confirm(
|
||||
`Convertire "${lead.name}" in cliente?\n\nVerrà creato un cliente (con email, telefono e transcript del lead) e il lead verrà archiviato mantenendo lo stato "vinto".`
|
||||
);
|
||||
if (!ok) return;
|
||||
startConvert(() => convertLeadToClient(lead.id));
|
||||
}
|
||||
|
||||
function run(fn: () => Promise<unknown>) {
|
||||
setTagError(null);
|
||||
startTransition(async () => {
|
||||
@@ -106,6 +123,25 @@ export function LeadDetail({
|
||||
>
|
||||
Genera preventivo
|
||||
</Link>
|
||||
{convertedClientId ? (
|
||||
<Link
|
||||
href={`/admin/clients/${convertedClientId}`}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-2 bg-[#1A463C]/10 text-[#1A463C] rounded-md text-sm font-semibold hover:bg-[#1A463C]/15 transition-colors"
|
||||
>
|
||||
✓ Convertito → Apri cliente
|
||||
</Link>
|
||||
) : (
|
||||
lead.status === "won" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleConvert}
|
||||
disabled={converting}
|
||||
className="inline-flex items-center gap-1.5 px-3 py-2 bg-[#1A463C] text-white rounded-md text-sm font-semibold hover:bg-[#1A463C]/90 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{converting ? "Conversione..." : "Converti in cliente"}
|
||||
</button>
|
||||
)
|
||||
)}
|
||||
<EditLeadModal lead={lead} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user