diff --git a/src/components/TicketProjects.jsx b/src/components/TicketProjects.jsx index 140aa67..7bd18cc 100644 --- a/src/components/TicketProjects.jsx +++ b/src/components/TicketProjects.jsx @@ -4,6 +4,8 @@ import { useWebsiteProjects } from '../hooks/useWebsiteProjects' import { createProjectFromTemplate, importNewRepos } from '../lib/projectAdminApi' import { isOutsideOrg, assignSummary } from '../lib/projectAssign' import AssignedProjectCard from './AssignedProjectCard' +import { fetchServices, completeAssignment } from '../lib/projectKindsApi' +import { kindLabelFor } from '../lib/assignFlow' function slugify(v) { return String(v || '').toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 100) @@ -35,6 +37,12 @@ export default function TicketProjects({ ticket }) { const [importing, setImporting] = useState(false) const [importHint, setImportHint] = useState('') const [assignResult, setAssignResult] = useState(null) + const [kinds, setKinds] = useState([]) + const [kindDialog, setKindDialog] = useState(false) + + useEffect(() => { + fetchServices().then((s) => setKinds(s.kinds || [])).catch(() => {}) + }, []) const load = useCallback(async () => { if (!ticket?.$id) return @@ -73,7 +81,17 @@ export default function TicketProjects({ ticket }) { } } - const assign = async () => { + const assign = () => { + const project = available.find((p) => p.$id === selected) + // Ohne Art erst "Was ist das?" fragen + if (project && !project.kind) { + setKindDialog(true) + return + } + assignPlain() + } + + const assignPlain = async () => { setBusy(true) setError('') setAssignResult(null) @@ -91,6 +109,35 @@ export default function TicketProjects({ ticket }) { } } + // Art gewaehlt: completeAssignment weist serverseitig (wie assignProject) zu, + // daher kein zusaetzliches assignProjects. + // Bei 409 (bereits zugeordnet) bewusst NICHT auf assignProjects zurueckfallen: + // das wuerde das Projekt ohne Art zuweisen. Stattdessen Meldung zeigen und neu laden. + const assignWithKind = async (kind) => { + setKindDialog(false) + setBusy(true) + setError('') + setAssignResult(null) + try { + const projectName = available.find((p) => p.$id === selected)?.projectName || '' + const r = await completeAssignment(selected, { + kind, + forCustomer: true, + customerId: ticket.customerId, + ticketId: ticket.$id, + }) + setAssignResult({ text: `${projectName || 'Projekt'} wurde zugewiesen.`, warnings: r.warnings || [] }) + setSelected('') + } catch (err) { + setError(err.status === 409 + ? `${err.message} Die Liste wurde neu geladen.` + : err.message || 'Aktion fehlgeschlagen') + } finally { + try { await load() } catch { /* Anzeige bleibt wie sie ist */ } + setBusy(false) + } + } + const unassign = (projectId) => run(async () => { const r = await unassignProject(projectId) if (!r.success) throw new Error(r.error) @@ -121,14 +168,16 @@ export default function TicketProjects({ ticket }) { {assigned.length === 0 ?

Noch kein Projekt an diesem Ticket.

: assigned.map((p) => ( - +
+ {kindLabelFor(kinds, p.kind)} + +
))}
@@ -161,6 +210,21 @@ export default function TicketProjects({ ticket }) { )} + + {kindDialog && ( +
+ setKindDialog(false)}>× +
+

Was ist das?

+

Bitte wähle die Art des Projekts, bevor es zugewiesen wird.

+
+ {kinds.map((k) => ( + + ))} +
+
+
+ )}
) } diff --git a/src/lib/assignFlow.js b/src/lib/assignFlow.js index ced98e7..083120c 100644 --- a/src/lib/assignFlow.js +++ b/src/lib/assignFlow.js @@ -55,3 +55,9 @@ export function openTicketsFor(tickets, customerId) { (t) => t.customerId === customerId && t.status !== 'Closed' && t.status !== 'Cancelled' ) } + +// Art-Label fuer die Anzeige; leere Art = "Art fehlt" +export function kindLabelFor(kinds, key) { + if (!key) return 'Art fehlt' + return (kinds || []).find((k) => k.key === key)?.label || key +} diff --git a/src/pages/ProjectsPage.jsx b/src/pages/ProjectsPage.jsx index d39149b..506722a 100644 --- a/src/pages/ProjectsPage.jsx +++ b/src/pages/ProjectsPage.jsx @@ -1,4 +1,5 @@ import { useState, useEffect, useMemo } from 'react' +import { useNavigate } from 'react-router-dom' import { FaFolder, FaPlus, @@ -88,6 +89,7 @@ function isProjectReady(project) { } export default function ProjectsPage() { + const navigate = useNavigate() const { projects, loading, error, fetchAllProjects, assignProjects, unassignProject } = useWebsiteProjects() const { customers } = useCustomers() const { workorders } = useWorkorders(WORKORDER_FILTERS) @@ -223,6 +225,8 @@ export default function ProjectsPage() { : 'Projekt (ohne Website) angelegt.' ) await fetchAllProjects() + // Direkt zur Art-/Kunden-Zuordnung des neuen Repos + if (result.projectId) navigate(`/projects/zuordnen/${result.projectId}`) } catch (err) { setCreateError(err.message || 'Projekt konnte nicht angelegt werden') } finally { diff --git a/tests/unit/assignFlow.test.mjs b/tests/unit/assignFlow.test.mjs index 09bfa30..3362908 100644 --- a/tests/unit/assignFlow.test.mjs +++ b/tests/unit/assignFlow.test.mjs @@ -70,3 +70,12 @@ test('openTicketsFor', () => { assert.deepEqual(openTicketsFor(t, 'c1').map((x) => x.$id), ['1', '5']) assert.deepEqual(openTicketsFor(null, 'c1'), []) }) + +test('kindLabelFor: Label aus Katalog, leer = Art fehlt', async () => { + const { kindLabelFor } = await loadModule('src/lib/assignFlow.js') + const kinds = [{ key: 'website', label: 'Website' }, { key: 'app', label: 'App' }] + assert.equal(kindLabelFor(kinds, 'app'), 'App') + assert.equal(kindLabelFor(kinds, ''), 'Art fehlt') + assert.equal(kindLabelFor(kinds, undefined), 'Art fehlt') + assert.equal(kindLabelFor(kinds, 'unbekannt'), 'unbekannt') +})