import { useState, useEffect } from "react" import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query" import { Copy, Trash2, Edit3, X, Plus, Archive, ArchiveRestore, Euro, Search, Loader2 } from "lucide-react" import { api } from "../lib/api" import { useToast } from "../hooks/use-toast" import { Breadcrumb } from "../components/ui/Breadcrumb" function TableSkeleton() { return (
{[...Array(5)].map((_, i) => (
))}
) } export default function Projects() { const queryClient = useQueryClient() const { toast } = useToast() const [name, setName] = useState("") const [icon, setIcon] = useState("") const [customerId, setCustomerId] = useState("") const [billingRate, setBillingRate] = useState("") const [selectedIds, setSelectedIds] = useState([]) const [bulkPrefix, setBulkPrefix] = useState("") const [editingProject, setEditingProject] = useState<{ id: string; name: string; budget: number } | null>(null) const [showArchived, setShowArchived] = useState(false) const [searchTerm, setSearchTerm] = useState("") const { data: projects, isLoading: projectsLoading, isError: projectsError } = useQuery({ queryKey: ["projects"], queryFn: () => api.listProjects() }) const { data: customers, isLoading: customersLoading } = useQuery({ queryKey: ["customers"], queryFn: () => api.listCustomers() }) useEffect(() => { if (projects) { projects.forEach((project: any) => { const budget = project.budget || 0 const usedHours = project.usedHours || 0 if (budget === 0) return const ratio = usedHours / budget if (ratio > 1) { toast.error(`Budget für ${project.name} überschritten!`) } else if (ratio >= 0.8) { toast.info(`Budget für ${project.name} bei ${Math.round(ratio * 100)}%`) } }) } }, [projects, toast]) const createMutation = useMutation({ mutationFn: ({ name, customerId, icon, billingRate }: { name: string; customerId: string; icon: string; billingRate: number }) => api.createProject({ name, customerId, icon, billingRate }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["projects"] }) setName("") setCustomerId("") setIcon("") setBillingRate("") toast.success("Projekt erstellt") } }) const updateMutation = useMutation({ mutationFn: ({ id, data }: { id: string; data: any }) => api.updateProject(id, data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["projects"] }) setEditingProject(null) toast.success("Projekt aktualisiert") } }) const cloneMutation = useMutation({ mutationFn: (id: string) => api.cloneProject(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["projects"] }) toast.success("Projekt geklont") } }) const deleteMutation = useMutation({ mutationFn: (id: string) => api.deleteProject(id), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["projects"] }) toast.success("Projekt gelöscht") } }) const bulkRenameMutation = useMutation({ mutationFn: ({ ids, prefix }: { ids: string[]; prefix: string }) => api.bulkRenameProjects(ids, prefix), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["projects"] }) setSelectedIds([]) setBulkPrefix("") toast.success("Projekte umbenannt") } }) const bulkDeleteMutation = useMutation({ mutationFn: (ids: string[]) => api.bulkDeleteProjects(ids), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["projects"] }) setSelectedIds([]) toast.success("Projekte gelöscht") } }) const handleSubmit = (e: React.FormEvent) => { e.preventDefault() if (!name.trim() || !customerId) return const rateInCents = Math.round(parseFloat(billingRate || "0") * 100) createMutation.mutate({ name, customerId, icon, billingRate: rateInCents }) } const handleBulkRename = (e: React.FormEvent) => { e.preventDefault() if (!bulkPrefix.trim() || selectedIds.length === 0) return bulkRenameMutation.mutate({ ids: selectedIds, prefix: bulkPrefix }) } const toggleSelectAll = () => { const filtered = projects?.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()) && (showArchived ? p.archived : !p.archived) ) || [] if (selectedIds.length === filtered.length) { setSelectedIds([]) } else { setSelectedIds(filtered.map((p: any) => p.id)) } } const filteredProjects = projects?.filter((p: any) => { const matchesSearch = p.name.toLowerCase().includes(searchTerm.toLowerCase()) const matchesArchive = showArchived ? p.archived : !p.archived return matchesSearch && matchesArchive }) || [] if (projectsLoading || customersLoading) { return (
) } if (projectsError) { return
Error loading projects.
} return (

New Project

setName(e.target.value)} className="w-full p-2 rounded border dark:bg-gray-800 dark:border-gray-700" placeholder="e.g. Website Redesign" />
setIcon(e.target.value)} className="w-full p-2 rounded border dark:bg-gray-800 dark:border-gray-700" placeholder="🚀" />
setBillingRate(e.target.value)} className="w-full p-2 rounded border dark:bg-gray-800 dark:border-gray-700" placeholder="0.00" />
setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 rounded-lg border dark:bg-gray-900 dark:border-gray-800" placeholder="Search projects..." />
{selectedIds.length > 0 && (
{selectedIds.length} selected
setBulkPrefix(e.target.value)} className="p-1 text-sm rounded border dark:bg-gray-800 dark:border-gray-700" placeholder="Prefix for rename..." />
)}
{filteredProjects.length === 0 ? ( ) : ( filteredProjects.map((project: any) => ( )) )}
0} /> Project Rate Actions
No projects found.
{ if (e.target.checked) setSelectedIds([...selectedIds, project.id]) else setSelectedIds(selectedIds.filter(id => id !== project.id)) }} />
{project.icon || "📁"}
{project.name}
{project.customer?.name || "No customer"}
{(project.billingRate / 100).toFixed(2)}
{editingProject && (

Edit Project

setEditingProject({ ...editingProject, name: e.target.value })} className="w-full p-2 rounded border dark:bg-gray-800 dark:border-gray-700" />
setEditingProject({ ...editingProject, budget: parseFloat(e.target.value) })} className="w-full p-2 rounded border dark:bg-gray-800 dark:border-gray-700" />
)}
) }