import React, { useState } from 'react'; import { Project, Customer, Expert } from '../types'; import { supabase, TABLES } from '../lib/supabase'; interface ProjectsProps { projects: Project[]; setProjects: React.Dispatch>; customers: Customer[]; experts: Expert[]; refreshData: () => Promise; } export const Projects: React.FC = ({ projects, setProjects, customers, experts, refreshData }) => { const [expandedId, setExpandedId] = useState(null); const [editModeId, setEditModeId] = useState(null); const [projectToDelete, setProjectToDelete] = useState(null); const [isSaving, setIsSaving] = useState(false); // Pickers const [isCustomerPickerOpen, setIsCustomerPickerOpen] = useState(false); const [isExpertPickerOpen, setIsExpertPickerOpen] = useState(false); // Local state for editing const [editForm, setEditForm] = useState>({}); const startEditing = (project: Project) => { const customer = getDisplayInfo(project, 'KUNDE'); const expert = getDisplayInfo(project, 'EXPERTE'); const start = getDisplayInfo(project, 'START'); const end = getDisplayInfo(project, 'ENDE'); const formatDateForInput = (val: string) => { if (!val || val === 'N/A' || val === 'TBD') return ''; if (val.includes('.')) { const parts = val.split('.'); if (parts.length === 3) return `${parts[2]}-${parts[1].padStart(2, '0')}-${parts[0].padStart(2, '0')}`; } return val; }; setEditForm({ ...project, customer: customer === 'N/A' ? '' : customer, expert: expert === 'N/A' ? '' : expert, start: formatDateForInput(start), end: formatDateForInput(end), }); setEditModeId(project.id); }; const handleSaveProject = async () => { if (!editModeId || isSaving) return; setIsSaving(true); try { const baseNotes = editForm.notes?.split('---').pop()?.trim() || ''; const structuredNotes = [ `KUNDE: ${editForm.customer || 'Nicht angegeben'}`, `KONTAKT: ${getDisplayInfo(projects.find(p => p.id === editModeId)!, 'KONTAKT')}`, `EXPERTE: ${editForm.expert || 'Kein Experten zugewiesen'}`, `START: ${editForm.start || 'N/A'}`, `ENDE: ${editForm.end || 'TBD'}`, `---`, baseNotes ].join('\n'); const updatePayload = { title: editForm.title, customer: editForm.customer, expert: editForm.expert, start: editForm.start, end: editForm.end, status: editForm.status, notes: structuredNotes }; const { error } = await supabase .from(TABLES.PROJECTS) .update(updatePayload) .eq('id', editModeId); if (error) { console.warn('Full update failed, trying safe mode (notes only):', error.message); await supabase .from(TABLES.PROJECTS) .update({ title: editForm.title, status: editForm.status, notes: structuredNotes }) .eq('id', editModeId); } setEditModeId(null); await refreshData(); } catch (err) { console.error('Save error:', err); } finally { setIsSaving(false); } }; const handleUpdateNotesOnly = async (id: string, newNotes: string) => { setProjects(prev => prev.map(p => p.id === id ? { ...p, notes: newNotes } : p)); await supabase.from(TABLES.PROJECTS).update({ notes: newNotes }).eq('id', id); }; const confirmDelete = async () => { if (projectToDelete) { await supabase.from(TABLES.PROJECTS).delete().eq('id', projectToDelete); if (expandedId === projectToDelete) setExpandedId(null); setProjectToDelete(null); refreshData(); } }; const getDisplayInfo = (project: Project, type: 'KUNDE' | 'EXPERTE' | 'KONTAKT' | 'START' | 'ENDE') => { const key = type.toLowerCase(); const value = (project as any)[key]; if (value && value !== 'Nicht angegeben' && value !== 'Kein Experte zugewiesen' && value !== 'TBD' && value !== 'N/A') return value; if (project.notes) { const lines = project.notes.split('\n'); const prefix = `${type}: `; const line = lines.find(l => l.startsWith(prefix)); if (line) return line.replace(prefix, '').trim(); } if (type === 'START') return project.start || 'N/A'; if (type === 'ENDE') return project.end || 'TBD'; return 'N/A'; }; return (

Laufende Projekte

Übersicht aller aktiven Beauftragungen

{projects.map((project) => { const customerName = getDisplayInfo(project, 'KUNDE'); const expertNamesString = getDisplayInfo(project, 'EXPERTE'); const expertNames = expertNamesString.split(', ').filter(n => n && n !== 'N/A' && n !== 'Kein Experte zugewiesen'); const start = getDisplayInfo(project, 'START'); const end = getDisplayInfo(project, 'ENDE'); return ( { if (editModeId !== project.id) { setExpandedId(expandedId === project.id ? null : project.id); } }} className={`hover:bg-slate-50 dark:hover:bg-slate-800 transition-all cursor-pointer group ${expandedId === project.id ? 'bg-brand-50/30 dark:bg-brand-500/5' : ''}`} > {expandedId === project.id && (
Projekt Kunde Experten Laufzeit Status
{project.title} {customerName}
{expertNames.length > 0 ? ( expertNames.slice(0, 3).map((name, i) => (
{name.charAt(0)}
)) ) : ( Keine Experten )} {expertNames.length > 3 && (
+{expertNames.length - 3}
)}
{start} - {end}
{project.status}

{editModeId === project.id ? 'Bearbeiten' : 'Details'}

{editModeId !== project.id ? ( ) : (
)}
{editModeId === project.id ? (
setEditForm({...editForm, title: e.target.value})} className="w-full p-2.5 text-sm bg-slate-50 dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-lg outline-none focus:ring-2 focus:ring-brand-500/20 text-slate-900 dark:text-white" />
setEditForm({...editForm, start: e.target.value})} className="w-full p-2.5 text-sm bg-slate-50 dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-lg outline-none focus:ring-2 focus:ring-brand-500/20 text-slate-900 dark:text-white" />
setEditForm({...editForm, end: e.target.value})} className="w-full p-2.5 text-sm bg-slate-50 dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-lg outline-none focus:ring-2 focus:ring-brand-500/20 text-slate-900 dark:text-white" />
setIsCustomerPickerOpen(true)} placeholder="Kunden auswählen..." className="w-full p-2.5 text-sm bg-slate-50 dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-lg outline-none focus:ring-2 focus:ring-brand-500/20 text-slate-900 dark:text-white cursor-pointer hover:border-brand-500 transition-colors" />
setIsExpertPickerOpen(true)} placeholder="Experten auswählen..." className="w-full p-2.5 text-sm bg-slate-50 dark:bg-slate-700 border border-slate-200 dark:border-slate-600 rounded-lg outline-none focus:ring-2 focus:ring-brand-500/20 text-slate-900 dark:text-white cursor-pointer hover:border-brand-500 transition-colors truncate" />
) : (
Beginn {start}
Geplantes Ende {end}
Eingesetzte Experten
{expertNames.length > 0 ? expertNames.map((name, i) => ( {name} )) : Keine Experten}
Kunde {customerName}
)}

Projektjournal & Protokoll