Projekte: zur Vorlage gemachtes Projekt verschwindet nicht mehr

Vorlagen stehen absichtlich nur in der Ansicht "Vorlagen" (Admins). Nach
dem Speichern als Vorlage fiel das Projekt deshalb kommentarlos aus
"Aktive Projekte" (SHK-vorlage, 04.10.2026).

- nach dem Bearbeiten in die Ansicht wechseln, in der das Projekt jetzt
  steht, mit Hinweis ("steht jetzt unter Vorlagen")
- Suche zeigt Treffer in ausgeblendeten Ansichten (Vorlagen, Archiv) mit
  Klick zum Wechseln
- Hilfen viewShowing / matchesSearch / hiddenMatches in lib/projectList.js

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-04 20:01:38 +00:00
1 parent c262268015
commit 603d08c9c2
3 files changed
+140 -34

No files matched your search

+53 -33
View File
@@ -1,4 +1,4 @@
import { useState, useEffect, useMemo } from 'react'
import { useState, useEffect, useMemo, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import PreviewBlock from '../components/PreviewBlock'
import {
@@ -27,7 +27,7 @@ import {
importNewReposThrottled,
} from '../lib/projectAdminApi'
import { assignSummary, ticketIdForCustomer } from '../lib/projectAssign'
import { projectCounts, projectsInView } from '../lib/projectList'
import { projectCounts, projectsInView, viewShowing, matchesSearch, hiddenMatches, VIEW_LABELS } from '../lib/projectList'
import { slugify } from '../lib/subdomain'
import PreviewLinkButton from '../components/PreviewLinkButton'
import GiteaLinkButton from '../components/GiteaLinkButton'
@@ -163,33 +163,28 @@ export default function ProjectsPage() {
// Vorlagen sind aus den normalen Ansichten ausgeblendet (eigene Ansicht, nur Admin)
const counts = useMemo(() => projectCounts(projects, { isAdmin }), [projects, isAdmin])
const filteredProjects = useMemo(() => {
// 1) Kategorie (Dropdown)
let base = projectsInView(projects, filter, { isAdmin })
// 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)
// Filter neben der Kategorie: Textsuche (Titel/Subdomain/Repo), Kunde, Mitarbeiter (ueber das
// zugeordnete Ticket -> assignedTo)
const pageMatches = useCallback((p) => {
if (!matchesSearch(p, search)) return false
if (customerFilter && p.customerId !== customerFilter) return false
if (employeeFilter) {
base = base.filter((p) => {
const wo = p.ticketId ? workorderById[p.ticketId] : null
return wo && wo.assignedTo === employeeFilter
})
const wo = p.ticketId ? workorderById[p.ticketId] : null
if (!wo || wo.assignedTo !== employeeFilter) return false
}
return true
}, [search, customerFilter, employeeFilter, workorderById])
return base
}, [projects, filter, isAdmin, search, customerFilter, employeeFilter, workorderById])
const filteredProjects = useMemo(
() => projectsInView(projects, filter, { isAdmin }).filter(pageMatches),
[projects, filter, isAdmin, pageMatches]
)
// Suchtreffer in ausgeblendeten Ansichten (Vorlagen, Archiv) - sonst wirkt ein Projekt verschwunden
const hiddenHits = useMemo(
() => (search.trim() ? hiddenMatches(projects, filter, pageMatches, { isAdmin }) : []),
[projects, filter, isAdmin, search, pageMatches]
)
const handleCreate = async (e) => {
e.preventDefault()
@@ -314,14 +309,21 @@ export default function ProjectsPage() {
if (editForm.projectType !== 'project') {
payload.subdomain = slugify(editForm.subdomain)
}
const result = await updateWebsiteProject(editProject.$id, payload)
const projectId = editProject.$id
const result = await updateWebsiteProject(projectId, payload)
setEditProject(null)
setNotice(
result.previewUrl
? `Projekt aktualisiert - ${result.isPublic ? 'oeffentlich' : 'Preview'}: ${result.previewUrl}`
: 'Projekt aktualisiert.'
)
await fetchAllProjects()
let text = result.previewUrl
? `Projekt aktualisiert - ${result.isPublic ? 'oeffentlich' : 'Preview'}: ${result.previewUrl}`
: 'Projekt aktualisiert.'
// Faellt das Projekt aus der aktuellen Ansicht (z. B. zur Vorlage gemacht), dorthin wechseln,
// wo es jetzt steht - sonst wirkt es verschwunden
const fresh = (await fetchAllProjects()).find((p) => p.$id === projectId)
const view = viewShowing(fresh, filter, { isAdmin })
if (fresh && view !== filter) {
setFilter(view)
text += ` – „${fresh.projectName}“ steht jetzt unter „${VIEW_LABELS[view]}“.`
}
setNotice(text)
} catch (err) {
setEditError(err.message || 'Projekt konnte nicht aktualisiert werden')
} finally {
@@ -448,6 +450,24 @@ export default function ProjectsPage() {
</div>
)}
{hiddenHits.length > 0 && (
<div className="text-center mb-2" data-hidden-hits>
<span className="text-grey">Weitere Treffer: </span>
{hiddenHits.map((h, i) => (
<span key={h.view}>
{i > 0 && <span className="text-grey"> · </span>}
<button
type="button"
style={{ padding: 0, border: 'none', background: 'none', color: 'var(--accent)', textDecoration: 'underline', cursor: 'pointer', font: 'inherit' }}
onClick={() => setFilter(h.view)}
>
{h.count} unter „{VIEW_LABELS[h.view]}“
</button>
</span>
))}
</div>
)}
{error && (
<div className="bg-red text-white p-2 mb-2" style={{ borderRadius: '4px' }}>
{error}