"use client"; import { useState, useEffect } from 'react'; import { motion } from 'framer-motion'; import { Database, Search, BarChart3, Download, Upload, Edit, Eye, Plus, TrendingUp, Users, Star, Tag, FolderOpen, Calendar, Activity } from 'lucide-react'; import { projectService } from '@/lib/prisma'; import { useToast } from './Toast'; interface Project { id: number; title: string; description: string; content: string; imageUrl?: string | null; github?: string | null; liveUrl?: string | null; tags: string[]; category: string; difficulty: string; featured: boolean; published: boolean; createdAt: Date; updatedAt: Date; _count?: { pageViews: number; userInteractions: number; }; } interface AdminDashboardProps { onProjectSelect: (project: Project) => void; onNewProject: () => void; } export default function AdminDashboard({ onProjectSelect, onNewProject }: AdminDashboardProps) { const [projects, setProjects] = useState([]); const [loading, setLoading] = useState(true); const [searchQuery, setSearchQuery] = useState(''); const [selectedCategory, setSelectedCategory] = useState(''); const [sortBy, setSortBy] = useState<'date' | 'title' | 'difficulty' | 'views'>('date'); const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc'); const [selectedProjects, setSelectedProjects] = useState>(new Set()); const [showStats, setShowStats] = useState(false); const { showImportSuccess, showImportError, showError } = useToast(); // Load projects from database useEffect(() => { loadProjects(); }, []); const loadProjects = async () => { try { setLoading(true); const data = await projectService.getAllProjects(); setProjects(data.projects); } catch (error) { console.error('Error loading projects:', error); // Fallback to localStorage if database fails const savedProjects = localStorage.getItem('portfolio-projects'); if (savedProjects) { setProjects(JSON.parse(savedProjects)); } } finally { setLoading(false); } }; // Filter and sort projects const filteredProjects = projects .filter(project => { const matchesSearch = project.title.toLowerCase().includes(searchQuery.toLowerCase()) || project.description.toLowerCase().includes(searchQuery.toLowerCase()) || project.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase())); const matchesCategory = !selectedCategory || project.category === selectedCategory; return matchesSearch && matchesCategory; }) .sort((a, b) => { let aValue: string | number | Date, bValue: string | number | Date; switch (sortBy) { case 'date': aValue = new Date(a.createdAt); bValue = new Date(b.createdAt); break; case 'title': aValue = a.title.toLowerCase(); bValue = b.title.toLowerCase(); break; case 'difficulty': const difficultyOrder = { 'Beginner': 1, 'Intermediate': 2, 'Advanced': 3, 'Expert': 4 }; aValue = difficultyOrder[a.difficulty as keyof typeof difficultyOrder]; bValue = difficultyOrder[b.difficulty as keyof typeof difficultyOrder]; break; case 'views': aValue = a._count?.pageViews || 0; bValue = b._count?.pageViews || 0; break; default: aValue = a.createdAt; bValue = b.createdAt; } if (sortOrder === 'asc') { return aValue > bValue ? 1 : -1; } else { return aValue < bValue ? 1 : -1; } }); // Statistics const stats = { total: projects.length, published: projects.filter(p => p.published).length, featured: projects.filter(p => p.featured).length, categories: new Set(projects.map(p => p.category)).size, totalViews: projects.reduce((sum, p) => sum + (p._count?.pageViews || 0), 0), totalLikes: projects.reduce((sum, p) => sum + (p._count?.userInteractions || 0), 0), avgLighthouse: 0 }; // Bulk operations const handleBulkDelete = async () => { if (selectedProjects.size === 0) return; if (confirm(`Are you sure you want to delete ${selectedProjects.size} projects?`)) { try { for (const id of selectedProjects) { await projectService.deleteProject(id); } setSelectedProjects(new Set()); await loadProjects(); showImportSuccess(selectedProjects.size); // Reuse for success message } catch (error) { console.error('Error deleting projects:', error); showError('Fehler beim Löschen', 'Einige Projekte konnten nicht gelöscht werden.'); } } }; const handleBulkPublish = async (published: boolean) => { if (selectedProjects.size === 0) return; try { for (const id of selectedProjects) { await projectService.updateProject(id, { published }); } setSelectedProjects(new Set()); await loadProjects(); showImportSuccess(selectedProjects.size); // Reuse for success message } catch (error) { console.error('Error updating projects:', error); showError('Fehler beim Aktualisieren', 'Einige Projekte konnten nicht aktualisiert werden.'); } }; // Export/Import const exportProjects = () => { const dataStr = JSON.stringify(projects, null, 2); const dataBlob = new Blob([dataStr], { type: 'application/json' }); const url = URL.createObjectURL(dataBlob); const link = document.createElement('a'); link.href = url; link.download = `portfolio-projects-${new Date().toISOString().split('T')[0]}.json`; link.click(); URL.revokeObjectURL(url); }; const importProjects = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (!file) return; const reader = new FileReader(); reader.onload = async (e) => { try { const importedProjects = JSON.parse(e.target?.result as string); // Validate and import projects let importedCount = 0; for (const project of importedProjects) { if (project.id) delete project.id; // Remove ID for new import await projectService.createProject(project); importedCount++; } await loadProjects(); showImportSuccess(importedCount); } catch (error) { console.error('Error importing projects:', error); showImportError('Bitte überprüfe das Dateiformat und versuche es erneut.'); } }; reader.readAsText(file); }; const categories = Array.from(new Set(projects.map(p => p.category))); if (loading) { return (
); } return (
{/* Header with Stats */}

Project Database

{/* Quick Stats */}

Total Projects

{stats.total}

Published

{stats.published}

Featured

{stats.featured}

Categories

{stats.categories}

{/* Extended Stats */} {showStats && (

Total Views

{stats.totalViews.toLocaleString()}

Total Likes

{stats.totalLikes.toLocaleString()}

Avg Lighthouse

{stats.avgLighthouse}/100

)}
{/* Controls */}
{/* Search */}
setSearchQuery(e.target.value)} className="w-full pl-10 pr-4 py-2 bg-gray-800/50 border border-gray-700 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500" />
{/* Category Filter */}
{/* Sort By */}
{/* Sort Order */}
{/* Bulk Actions */} {selectedProjects.size > 0 && ( {selectedProjects.size} project(s) selected )}
{/* Projects List */}

Projects ({filteredProjects.length})

{filteredProjects.map((project) => (
{ const newSelected = new Set(selectedProjects); if (e.target.checked) { newSelected.add(project.id); } else { newSelected.delete(project.id); } setSelectedProjects(newSelected); }} className="w-4 h-4 text-blue-600 bg-gray-800 border-gray-700 rounded focus:ring-blue-500 focus:ring-2" />

{project.title}

{project.difficulty} {project.featured && ( Featured )} {project.published ? ( Published ) : ( Draft )}

{project.description}

{project.category} {new Date(project.createdAt).toLocaleDateString()} {project._count?.pageViews || 0} views N/A
))}
{filteredProjects.length === 0 && (

No projects found

Try adjusting your search or filters

)}
); }