diff --git a/src/pages/ProjectsPage.jsx b/src/pages/ProjectsPage.jsx index b35da03..c6a07f0 100644 --- a/src/pages/ProjectsPage.jsx +++ b/src/pages/ProjectsPage.jsx @@ -14,6 +14,7 @@ import { import { useWebsiteProjects } from '../hooks/useWebsiteProjects' import { useCustomers } from '../hooks/useCustomers' import { useWorkorders } from '../hooks/useWorkorders' +import { useEmployees } from '../hooks/useEmployees' import { useAuth } from '../context/AuthContext' import { createProjectFromTemplate, @@ -89,8 +90,12 @@ export default function ProjectsPage() { const { projects, loading, error, fetchAllProjects, assignProjects, unassignProject } = useWebsiteProjects() const { customers } = useCustomers() const { workorders } = useWorkorders(WORKORDER_FILTERS) + const { employees } = useEmployees() const { isAdmin } = useAuth() const [filter, setFilter] = useState('all') + const [search, setSearch] = useState('') + const [customerFilter, setCustomerFilter] = useState('') + const [employeeFilter, setEmployeeFilter] = useState('') const [showCreateModal, setShowCreateModal] = useState(false) const [createForm, setCreateForm] = useState({ displayName: '', @@ -132,6 +137,22 @@ export default function ProjectsPage() { return map }, [workorders]) + // Ticket-Dokument-ID -> Workorder (fuer Mitarbeiter-Zuordnung ueber assignedTo) + const workorderById = useMemo(() => { + const map = {} + for (const wo of workorders) map[wo.$id] = wo + return map + }, [workorders]) + + const sortedCustomers = useMemo( + () => [...customers].sort((a, b) => String(a.name || '').localeCompare(String(b.name || ''))), + [customers] + ) + const sortedEmployees = useMemo( + () => [...employees].sort((a, b) => String(a.displayName || '').localeCompare(String(b.displayName || ''))), + [employees] + ) + const counts = useMemo(() => { // Vorlagen sind aus den normalen Ansichten ausgeblendet (eigene Ansicht, nur Admin) const active = projects.filter((p) => !p.archived && !p.isTemplate) @@ -145,13 +166,40 @@ export default function ProjectsPage() { }, [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 && !p.isTemplate) - if (filter === 'unassigned') return active.filter((p) => !p.customerId) - if (filter === 'assigned') return active.filter((p) => Boolean(p.customerId)) - return active - }, [projects, filter]) + // 1) Kategorie (Dropdown) + let base + if (filter === 'templates') base = projects.filter((p) => p.isTemplate && !p.archived) + else if (filter === 'archived') base = projects.filter((p) => p.archived) + else { + const active = projects.filter((p) => !p.archived && !p.isTemplate) + if (filter === 'unassigned') base = active.filter((p) => !p.customerId) + else if (filter === 'assigned') base = active.filter((p) => Boolean(p.customerId)) + else base = active + } + + // 2) Textsuche (Titel/Subdomain/Repo) + const q = search.trim().toLowerCase() + if (q) { + base = base.filter((p) => + [p.projectName, p.subdomain, p.repoFullName, p.giteaRepoName].some((v) => + String(v || '').toLowerCase().includes(q) + ) + ) + } + + // 3) Kunde + if (customerFilter) base = base.filter((p) => p.customerId === customerFilter) + + // 4) Mitarbeiter (ueber das zugeordnete Ticket -> assignedTo) + if (employeeFilter) { + base = base.filter((p) => { + const wo = p.ticketId ? workorderById[p.ticketId] : null + return wo && wo.assignedTo === employeeFilter + }) + } + + return base + }, [projects, filter, search, customerFilter, employeeFilter, workorderById]) const handleCreate = async (e) => { e.preventDefault() @@ -304,10 +352,18 @@ export default function ProjectsPage() {

Website-Projekte

-
+
+ setSearch(e.target.value)} + style={{ flex: '1 1 220px', minWidth: '180px', maxWidth: '320px' }} + /> - -