import React, { useState, useMemo, useRef, useEffect } from 'react'; import { Tender, Expert, Customer, KanbanColumn } from '../types'; import { supabase, TABLES } from '../lib/supabase'; interface TendersProps { tenders: Tender[]; setTenders: React.Dispatch>; customers: Customer[]; experts: Expert[]; refreshData: () => Promise; } type SortConfig = { key: keyof Tender | 'none'; direction: 'asc' | 'desc'; }; type FilterStatus = 'Alle' | 'Aktiv' | 'Inaktiv'; export const Tenders: React.FC = ({ tenders, setTenders, customers, experts, refreshData }) => { const [expandedId, setExpandedId] = useState(null); const [tenderToDelete, setTenderToDelete] = useState(null); const [sortConfig, setSortConfig] = useState({ key: 'none', direction: 'asc' }); const [filterStatus, setFilterStatus] = useState('Alle'); const [isFilterMenuOpen, setIsFilterMenuOpen] = useState(false); const filterMenuRef = useRef(null); const [isModalOpen, setIsModalOpen] = useState(false); const [editingTender, setEditingTender] = useState(null); const [formState, setFormState] = useState({ title: '', source: '', status: 'Aktiv' as const, link: '' }); const [isExpertModalOpen, setIsExpertModalOpen] = useState(false); const [isCustomerModalOpen, setIsCustomerModalOpen] = useState(false); const [activeTenderIdForSelection, setActiveTenderIdForSelection] = useState(null); useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (filterMenuRef.current && !filterMenuRef.current.contains(event.target as Node)) { setIsFilterMenuOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => document.removeEventListener('mousedown', handleClickOutside); }, []); const displayTenders = useMemo(() => { let items = [...tenders]; if (filterStatus === 'Aktiv') { items = items.filter(t => t.status === 'Aktiv'); } else if (filterStatus === 'Inaktiv') { items = items.filter(t => t.status === 'Inaktiv'); } if (sortConfig.key !== 'none') { items.sort((a, b) => { const aValue = a[sortConfig.key as keyof Tender]; const bValue = b[sortConfig.key as keyof Tender]; if (aValue === undefined || bValue === undefined) return 0; if (typeof aValue === 'string' && typeof bValue === 'string') { return sortConfig.direction === 'asc' ? aValue.localeCompare(bValue) : bValue.localeCompare(aValue); } return 0; }); } return items; }, [tenders, sortConfig, filterStatus]); const requestSort = (key: keyof Tender) => { let direction: 'asc' | 'desc' = 'asc'; if (sortConfig.key === key && sortConfig.direction === 'asc') direction = 'desc'; setSortConfig({ key, direction }); }; const getSortIndicator = (key: keyof Tender) => { if (sortConfig.key !== key) return ↕; return sortConfig.direction === 'asc' ? ↑ : ↓; }; const openModal = (tender?: Tender) => { if (tender) { setEditingTender(tender); setFormState({ title: tender.title, source: tender.source, status: tender.status as any, link: tender.link || '' }); } else { setEditingTender(null); setFormState({ title: '', source: '', status: 'Aktiv', link: '' }); } setIsModalOpen(true); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (editingTender) { await supabase.from(TABLES.TENDERS).update({ title: formState.title, source: formState.source, status: formState.status, link: formState.link }).eq('id', editingTender.id); } else { await supabase.from(TABLES.TENDERS).insert([{ title: formState.title, source: formState.source, status: formState.status, kanban_status: 'Ausschreibung', link: formState.link || null, notes: '' }]); } setIsModalOpen(false); refreshData(); }; const confirmDelete = async () => { if (tenderToDelete) { await supabase.from(TABLES.TENDERS).delete().eq('id', tenderToDelete); if (expandedId === tenderToDelete) setExpandedId(null); setTenderToDelete(null); refreshData(); } }; const handleUpdateNotes = async (id: string, newNotes: string) => { setTenders(prev => prev.map(t => t.id === id ? { ...t, notes: newNotes } : t)); await supabase.from(TABLES.TENDERS).update({ notes: newNotes }).eq('id', id); }; const assignCustomer = async (tenderId: string, customer: Customer) => { await supabase.from(TABLES.TENDERS).update({ customer_id: customer.id }).eq('id', tenderId); setIsCustomerModalOpen(false); refreshData(); }; const toggleExpertSelection = async (tenderId: string, expertId: string) => { const tender = tenders.find(t => t.id === tenderId); const isAlreadySelected = tender?.selectedExperts?.some(e => e.id === expertId); if (isAlreadySelected) { await supabase.from(TABLES.TENDER_EXPERTS).delete().match({ tender_id: tenderId, expert_id: expertId }); } else { await supabase.from(TABLES.TENDER_EXPERTS).insert([{ tender_id: tenderId, expert_id: expertId }]); } refreshData(); }; return (

Ausschreibungen

{(['Alle', 'Aktiv', 'Inaktiv'] as FilterStatus[]).map(s => ( ))}
{displayTenders.map((tender) => ( setExpandedId(expandedId === tender.id ? null : tender.id)}> {expandedId === tender.id && (
requestSort('title')}>
Titel {getSortIndicator('title')}
Kunde / Kontakt requestSort('source')}>
Quelle {getSortIndicator('source')}
Experten Status
{tender.title} {tender.selectedCustomer ? (
{tender.selectedCustomer.name} {tender.selectedCustomer.company}
) : Nicht zugewiesen}
{tender.source} {tender.applicantsCount} {tender.status === 'Inaktiv' ? 'Nicht aktiv' : tender.status}
{/* Experts */}

Experten

{tender.selectedExperts && tender.selectedExperts.length > 0 ? tender.selectedExperts.map(expert => (
{expert.name.charAt(0)}

{expert.name}

{expert.role}

)) :

Noch keine Experten zugewiesen.

}
{/* Customer */}

Kundenkontakt

{tender.selectedCustomer ? (

Zugeordneter Kontakt

{tender.selectedCustomer.name}

{tender.selectedCustomer.company}

) :

Kein Kunde ausgewählt

}
{/* Notes */}

Notizen