From 9e4c60fdafec10bfdf4e70ec012ee391f71389c5 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Jul 2026 09:10:25 +0000 Subject: [PATCH] feat: Vorlagen-Ansicht (Admin) + Typ Vorlage in Projektseite - neuer Typ Vorlage (nur Admin waehlbar), Badge, isTemplate - Dropdown-Filter Vorlagen nur fuer Admins; Vorlagen aus normalen Ansichten ausgeblendet - in Vorlagen-Ansicht: Vorlagen sehen + previewen Co-Authored-By: Claude Opus 4.8 --- src/pages/ProjectsPage.jsx | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/pages/ProjectsPage.jsx b/src/pages/ProjectsPage.jsx index 97bcd32..aa361b2 100644 --- a/src/pages/ProjectsPage.jsx +++ b/src/pages/ProjectsPage.jsx @@ -9,10 +9,12 @@ import { FaGlobe, FaLock, FaCircleInfo, + FaClone, } from 'react-icons/fa6' import { useWebsiteProjects } from '../hooks/useWebsiteProjects' import { useCustomers } from '../hooks/useCustomers' import { useWorkorders } from '../hooks/useWorkorders' +import { useAuth } from '../context/AuthContext' import { createProjectFromTemplate, syncGiteaRepos, @@ -49,6 +51,14 @@ const TYPES = { icon: , hint: 'Oeffentlich erreichbar ohne Login unter .project.webklar.com', }, + template: { + label: 'Vorlage', + short: 'Vorlage', + badge: 'badge-warn', + icon: , + hint: 'Website-Vorlage (login-geschuetzt) - erscheint nur fuer Admins in der Vorlagen-Ansicht', + adminOnly: true, + }, project: { label: 'Internes Projekt (kein Hosting)', short: 'Projekt', @@ -59,6 +69,7 @@ const TYPES = { } function typeOf(project) { + if (project.isTemplate) return 'template' if (project.isPublic) return 'website' if (project.subdomain) return 'preview' return 'project' @@ -68,6 +79,7 @@ export default function ProjectsPage() { const { projects, loading, error, fetchAllProjects, assignProjects, unassignProject } = useWebsiteProjects() const { customers } = useCustomers() const { workorders } = useWorkorders(WORKORDER_FILTERS) + const { isAdmin } = useAuth() const [filter, setFilter] = useState('all') const [showCreateModal, setShowCreateModal] = useState(false) const [createForm, setCreateForm] = useState({ @@ -110,18 +122,21 @@ export default function ProjectsPage() { }, [workorders]) const counts = useMemo(() => { - const active = projects.filter((p) => !p.archived) + // Vorlagen sind aus den normalen Ansichten ausgeblendet (eigene Ansicht, nur Admin) + const active = projects.filter((p) => !p.archived && !p.isTemplate) return { all: active.length, unassigned: active.filter((p) => !p.customerId).length, assigned: active.filter((p) => Boolean(p.customerId)).length, archived: projects.filter((p) => p.archived).length, + templates: projects.filter((p) => p.isTemplate && !p.archived).length, } }, [projects]) const filteredProjects = useMemo(() => { + if (filter === 'templates') return projects.filter((p) => p.isTemplate && !p.archived) if (filter === 'archived') return projects.filter((p) => p.archived) - const active = projects.filter((p) => !p.archived) + const active = projects.filter((p) => !p.archived && !p.isTemplate) if (filter === 'unassigned') return active.filter((p) => !p.customerId) if (filter === 'assigned') return active.filter((p) => Boolean(p.customerId)) return active @@ -245,9 +260,14 @@ export default function ProjectsPage() { { value: 'all', label: `Alle Projekte (${counts.all})` }, { value: 'unassigned', label: `Unzugeordnet (${counts.unassigned})` }, { value: 'assigned', label: `Zugeordnet (${counts.assigned})` }, + // Vorlagen-Ansicht nur fuer Admins + ...(isAdmin ? [{ value: 'templates', label: `Vorlagen (${counts.templates})` }] : []), { value: 'archived', label: `Archiviert (${counts.archived})` }, ] + // Typ-Optionen im Bearbeiten-Modal: adminOnly-Typen (Vorlage) nur fuer Admins + const typeOptions = Object.entries(TYPES).filter(([, meta]) => isAdmin || !meta.adminOnly) + return (
@@ -488,7 +508,7 @@ export default function ProjectsPage() { value={editForm.projectType} onChange={(e) => setEditForm((p) => ({ ...p, projectType: e.target.value }))} > - {Object.entries(TYPES).map(([key, meta]) => ( + {typeOptions.map(([key, meta]) => ( ))}