import { useState } from 'react' import { useNavigate } from 'react-router' import { Box, Chip, Typography, } from '@mui/material' import { DndContext, DragOverlay, PointerSensor, useSensor, useSensors, closestCenter, } from '@dnd-kit/core' import type { DragEndEvent, DragStartEvent } from '@dnd-kit/core' import { TrendingUp } from 'lucide-react' import { usePipelineItems, useMoveStage } from '../../hooks/usePipeline' import { useInquiryStore } from '../../stores/inquiryStore' import { AddToPipelineDialog } from '../../components/shortlist' import { InquiryQuickDialog } from '../../components/match-detail/InquiryQuickDialog' import type { PipelineItem, PipelineStage } from '../../domain/pipeline' import { STAGES } from '../../components/pipeline/pipelineConstants' import { DraggableCard } from '../../components/pipeline/PipelineCard' import { DroppableColumn } from '../../components/pipeline/PipelineColumn' import { DetailPanel } from '../../components/pipeline/PipelineDetailPanel' // ── Pipeline page ───────────────────────────────────────────────────────────── export default function Pipeline() { const navigate = useNavigate() const { data: items = [] } = usePipelineItems() const { mutate: moveStage } = useMoveStage() const openInquiryDialog = useInquiryStore(s => s.openInquiryDialog) const [selectedItem, setSelectedItem] = useState(null) const [activeId, setActiveId] = useState(null) const [overId, setOverId] = useState(null) const sensors = useSensors( useSensor(PointerSensor, { activationConstraint: { distance: 6 } }) ) const activeItem = activeId ? items.find(i => i.id === activeId) ?? null : null const syncedSelected = selectedItem ? items.find(i => i.id === selectedItem.id) ?? null : null const activeCount = items.filter(i => i.stage !== 'CLOSED_WON' && i.stage !== 'CLOSED_LOST').length const wonItems = items.filter(i => i.stage === 'CLOSED_WON') const wonScore = wonItems.length > 0 ? Math.round(wonItems.reduce((s, i) => s + i.matchScore, 0) / wonItems.length) : 0 function handleDragStart({ active }: DragStartEvent) { setActiveId(active.id as string) } function handleDragOver({ over }: { over: { id: string | number } | null }) { setOverId(over ? String(over.id) : null) } function handleDragEnd({ active, over }: DragEndEvent) { setActiveId(null) setOverId(null) if (!over) return const targetStage = over.id as PipelineStage const item = items.find(i => i.id === active.id) if (item && item.stage !== targetStage) { moveStage({ id: item.id, stage: targetStage }) } } function handleSelect(item: PipelineItem) { if (activeId) return // ignore clicks that fire after a drag setSelectedItem(prev => prev?.id === item.id ? null : item) } return ( {/* Header */} Deal Pipeline Von der ersten Idee bis zum Abschluss — per Drag & Drop verschieben {wonItems.length > 0 && ( } label={`${wonItems.length} gewonnen · ø ${wonScore}%`} size="small" sx={{ bgcolor: '#f0fdf4', color: '#1a7a4a', fontWeight: 600, border: '1px solid #86efac' }} /> )} {/* Body */} {/* Kanban */} {STAGES.map(stage => { const columnItems = items.filter(i => i.stage === stage.key) const isOver = overId === stage.key return ( {stage.label} navigate(`/demand/anfragen?inquiry=${inquiryId}`)} onCardChatClick={(item) => openInquiryDialog({ propertyTitle: item.title, location: item.location ?? '', matchScore: item.matchScore ?? 0, propertyId: item.propertyId, })} isOver={isOver} /> ) })} {/* Drag overlay — the card that floats under the cursor */} {activeItem && ( {}} isDragOverlay /> )} {/* Detail panel */} {syncedSelected && ( setSelectedItem(null)} /> )} ) }