'use client' import { useEffect, useState } from 'react' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { toast } from 'sonner' import { Loader2 } from 'lucide-react' export interface DealFormValue { id?: string companyId: string title: string valueEuro: string stage: string probability: string expectedClose: string lossReason: string } interface Props { open: boolean onOpenChange: (o: boolean) => void companies: { id: string; name: string }[] // When `deal` is null the dialog is in "create" mode. deal?: DealFormValue | null onSaved: () => void } const STAGES = [ { value: 'lead', label: 'Lead' }, { value: 'qualified', label: 'Qualifiziert' }, { value: 'proposal', label: 'Angebot' }, { value: 'negotiation', label: 'Verhandlung' }, { value: 'won', label: 'Gewonnen' }, { value: 'lost', label: 'Verloren' }, ] const EMPTY: DealFormValue = { companyId: '', title: '', valueEuro: '', stage: 'lead', probability: '10', expectedClose: '', lossReason: '', } // Single dialog for create + edit of a CRM Deal. // - create mode → POST /api/aos/crm/deals // - edit mode → PATCH /api/aos/crm/deals/[id] export function DealFormDialog({ open, onOpenChange, companies, deal, onSaved }: Props) { const [form, setForm] = useState(EMPTY) const [saving, setSaving] = useState(false) const isEdit = !!deal?.id useEffect(() => { if (open) { setForm(deal ?? EMPTY) } }, [open, deal]) // Auto-pick the first company for create mode if none selected. useEffect(() => { if (open && !isEdit && !form.companyId && companies[0]) { setForm((f) => ({ ...f, companyId: companies[0].id })) } }, [open, isEdit, form.companyId, companies]) const submit = async () => { if (!form.companyId) { toast.error('Firma auswählen'); return } if (!form.title.trim()) { toast.error('Titel eingeben'); return } const valueCents = Math.round((parseFloat(form.valueEuro.replace(',', '.')) || 0) * 100) const probability = Math.min(100, Math.max(0, parseInt(form.probability, 10) || 0)) const payload: Record = { companyId: form.companyId, title: form.title.trim(), valueCents, stage: form.stage, probability, expectedClose: form.expectedClose || null, } if (form.stage === 'lost' && form.lossReason.trim()) { payload.lossReason = form.lossReason.trim() } setSaving(true) const t = toast.loading(isEdit ? 'Deal wird gespeichert…' : 'Deal wird angelegt…') try { const url = isEdit ? `/api/aos/crm/deals/${deal!.id}` : '/api/aos/crm/deals' const method = isEdit ? 'PATCH' : 'POST' const r = await fetch(url, { method, headers: { 'content-type': 'application/json' }, body: JSON.stringify(payload), }) if (!r.ok) { const err = await r.json().catch(() => ({})) throw new Error(err.error || `${method} failed`) } toast.success(isEdit ? 'Deal aktualisiert' : 'Deal angelegt', { id: t }) onOpenChange(false) onSaved() } catch (err) { toast.error(err instanceof Error ? err.message : 'Speichern fehlgeschlagen', { id: t }) } finally { setSaving(false) } } return ( {isEdit ? 'Deal bearbeiten' : 'Neuer Deal'} {isEdit ? 'Deal-Daten aktualisieren. Änderungen werden im Audit-Log protokolliert (GoBD).' : 'Legt einen Deal in der Pipeline an. Audit-Log wird automatisch geschrieben.'}
setForm((f) => ({ ...f, title: e.target.value }))} placeholder="z.B. Website-Relaunch" />
setForm((f) => ({ ...f, valueEuro: e.target.value }))} placeholder="9.800" inputMode="decimal" />
setForm((f) => ({ ...f, probability: e.target.value }))} placeholder="50" inputMode="numeric" />
setForm((f) => ({ ...f, expectedClose: e.target.value }))} />
{form.stage === 'lost' && (
setForm((f) => ({ ...f, lossReason: e.target.value }))} placeholder="z.B. Preis, Konkurrenz, Timing" />
)}
) }