'use client'; import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { apiFetch } from '../../../lib/api'; import { useSession } from '../../../components/session-provider'; interface Deal { id: string; title: string; stage: string; organizationId: string | null; contactId: string | null; valueMinorUnits: number | null; currency: string | null; probability: number | null; expectedCloseDate: string | null; notes: string | null; createdAt: string; } interface Organization { id: string; name: string; } const STAGES = [ { key: 'identified', label: 'Identificat', color: 'bg-sky-400' }, { key: 'qualified', label: 'Calificat', color: 'bg-primary' }, { key: 'proposal', label: 'Propunere', color: 'bg-amber-400' }, { key: 'negotiation', label: 'Negociere', color: 'bg-orange-500' }, { key: 'won', label: 'Câștigat', color: 'bg-signal-ok' }, { key: 'lost', label: 'Pierdut', color: 'bg-signal-danger' }, ]; function fmtMoney(minor: number | null, currency: string | null) { if (!minor) return '—'; return new Intl.NumberFormat('ro-RO', { style: 'currency', currency: currency ?? 'RON', minimumFractionDigits: 0 }).format(minor / 100); } export default function PipelinePage() { const { activeTenant } = useSession(); const tenantId = activeTenant?.tenantId ?? ''; const qc = useQueryClient(); const [showCreate, setShowCreate] = useState(false); const [form, setForm] = useState({ title: '', stage: 'identified', probability: '50', expectedCloseDate: '' }); const { data: deals = [], isLoading } = useQuery({ queryKey: ['pipeline', tenantId], queryFn: () => apiFetch('/v1/pipeline', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000, }); const { data: orgs = [] } = useQuery({ queryKey: ['orgs', tenantId], queryFn: () => apiFetch('/v1/organizations', { tenantId }), enabled: Boolean(tenantId), staleTime: 300_000, }); const orgMap = Object.fromEntries(orgs.map((o) => [o.id, o.name])); const { mutate: create, isPending } = useMutation({ mutationFn: () => apiFetch('/v1/pipeline', { method: 'POST', body: { ...form, probability: parseInt(form.probability) || 50 }, tenantId, }), onSuccess: () => { qc.invalidateQueries({ queryKey: ['pipeline', tenantId] }); setShowCreate(false); setForm({ title: '', stage: 'identified', probability: '50', expectedCloseDate: '' }); }, }); const { mutate: advanceStage } = useMutation({ mutationFn: ({ id, stage }: { id: string; stage: string }) => apiFetch(`/v1/pipeline/${id}`, { method: 'PATCH', body: { stage }, tenantId }), onSuccess: () => qc.invalidateQueries({ queryKey: ['pipeline', tenantId] }), }); const active = deals.filter((d) => !['won', 'lost'].includes(d.stage)); const closed = deals.filter((d) => ['won', 'lost'].includes(d.stage)); const pipelineValue = active.reduce((s, d) => s + (d.valueMinorUnits ?? 0) * ((d.probability ?? 50) / 100), 0); const primaryCurrency = deals.find((d) => d.currency)?.currency ?? 'RON'; // Group by stage const byStage: Record = {}; for (const s of STAGES) byStage[s.key] = []; for (const d of deals) { if (!byStage[d.stage]) byStage[d.stage] = []; byStage[d.stage].push(d); } const stageIdx = Object.fromEntries(STAGES.map((s, i) => [s.key, i])); const nextStage = (stage: string) => STAGES[stageIdx[stage] + 1]?.key; return (

Pipeline Vânzări

{active.length} deal-uri active · Valoare ponderată: {fmtMoney(Math.round(pipelineValue), primaryCurrency)}

{showCreate && (

Deal nou

setForm((f) => ({ ...f, title: e.target.value }))} placeholder="Contract SaaS — Firma XYZ" className="w-full rounded-lg border bg-card px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring" />
setForm((f) => ({ ...f, probability: e.target.value }))} className="w-full" />
setForm((f) => ({ ...f, expectedCloseDate: e.target.value }))} className="w-full rounded-lg border bg-card px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring" />
)} {isLoading ?
Se încarcă…
: deals.length === 0 ? (

🎯

Niciun deal în pipeline.

) : ( <> {/* Stage columns */}
{STAGES.filter((s) => !['won','lost'].includes(s.key)).map((stage) => { const stageDeals = byStage[stage.key] ?? []; return (

{stage.label}

{stageDeals.length}
{stageDeals.length === 0 &&

Gol

} {stageDeals.map((d) => { const next = nextStage(d.stage); return (

{d.title}

{d.organizationId && orgMap[d.organizationId] && (

@ {orgMap[d.organizationId]}

)}
{d.probability ?? 50}% {fmtMoney(d.valueMinorUnits, d.currency) !== '—' && ( {fmtMoney(d.valueMinorUnits, d.currency)} )}
{next && ( )}
); })}
); })}
{/* Won/Lost summary */} {closed.length > 0 && (
{[['won','Câștigate'],['lost','Pierdute']].map(([stage, label]) => { const stageDeals = byStage[stage] ?? []; return (

{label} ({stageDeals.length})

{stageDeals.slice(0, 3).map((d) => (

{d.title} — {fmtMoney(d.valueMinorUnits, d.currency)}

))}
); })}
)} ) }
); }