Files
tickte-system/src/components/WebpageProjectPanel.jsx
2026-05-25 06:41:35 +00:00

109 lines
5.1 KiB
JavaScript
Raw Blame History

import { useState, useEffect, useCallback } from 'react'
import { FaExternalLinkAlt, FaPlus, FaTrash } from 'react-icons/fa'
import { useWebsiteProjects } from '../hooks/useWebsiteProjects'
import { createProjectFromTemplate } from '../lib/projectAdminApi'
import { WEBPAGE_TICKET_TYPE } from '../lib/appwrite'
function slugify(v) {
return String(v || '').toLowerCase().replace(/[^a-z0-9-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 100)
}
export default function WebpageProjectPanel({ ticket }) {
const { fetchAllProjects, fetchByTicketId, getAvailableProjects, assignProjects, unassignProject } = useWebsiteProjects()
const [assigned, setAssigned] = useState([])
const [available, setAvailable] = useState([])
const [selectedIds, setSelectedIds] = useState([])
const [loading, setLoading] = useState(true)
const [actionLoading, setActionLoading] = useState(false)
const [error, setError] = useState('')
const [createForm, setCreateForm] = useState({ displayName: '', subdomain: '', repoName: '' })
const loadProjects = useCallback(async () => {
if (!ticket?.$id || ticket.type !== WEBPAGE_TICKET_TYPE) return
setLoading(true)
setError('')
try {
const [all, mine] = await Promise.all([fetchAllProjects(), fetchByTicketId(ticket.$id)])
setAssigned(mine)
const ids = new Set(mine.map((p) => p.$id))
setAvailable(getAvailableProjects(all, ticket.customerId).filter((p) => !ids.has(p.$id)))
} catch (err) {
setError(err.message)
} finally {
setLoading(false)
}
}, [ticket, fetchAllProjects, fetchByTicketId, getAvailableProjects])
useEffect(() => { loadProjects() }, [loadProjects])
if (ticket?.type !== WEBPAGE_TICKET_TYPE) return null
const handleAssign = async () => {
if (!selectedIds.length || !ticket.customerId) return
setActionLoading(true)
const r = await assignProjects(selectedIds, { customerId: ticket.customerId, ticketId: ticket.$id })
setActionLoading(false)
if (r.success) { setSelectedIds([]); await loadProjects() } else setError(r.error)
}
const handleCreate = async (e) => {
e.preventDefault()
if (!ticket.customerId) { setError('Kein Kunde am Ticket.'); return }
setActionLoading(true)
setError('')
try {
await createProjectFromTemplate({
repoName: createForm.repoName || createForm.subdomain,
displayName: createForm.displayName,
subdomain: slugify(createForm.subdomain),
customerId: ticket.customerId,
ticketId: ticket.$id,
})
setCreateForm({ displayName: '', subdomain: '', repoName: '' })
await loadProjects()
} catch (err) {
setError(err.message)
} finally {
setActionLoading(false)
}
}
return (
<div style={{ background: 'rgba(45,55,72,0.5)', borderRadius: 12, padding: 20, border: '1px solid rgba(59,130,246,0.3)', marginTop: 20 }}>
<h5 style={{ fontWeight: 'bold', marginBottom: 12 }}>Website-Projekte</h5>
{error && <div className="bg-red text-white p-2 mb-2">{error}</div>}
{loading ? <p className="text-grey">Laden...</p> : (
<>
<section style={{ marginBottom: 16 }}>
<h6>Zugewiesen</h6>
{assigned.length === 0 ? <p className="text-grey">Keine Projekte.</p> : assigned.map((p) => (
<div key={p.$id} style={{ display: 'flex', gap: 8, alignItems: 'center', marginBottom: 8 }}>
<span>{p.projectName} ({p.subdomain})</span>
{p.previewUrl && <a href={p.previewUrl} target="_blank" rel="noreferrer"><FaExternalLinkAlt /></a>}
<button type="button" className="btn btn-sm" onClick={() => unassignProject(p.$id).then(loadProjects)}><FaTrash /></button>
</div>
))}
</section>
<section style={{ marginBottom: 16 }}>
<h6>Verf<EFBFBD>gbar zuweisen</h6>
{available.map((p) => (
<label key={p.$id} style={{ display: 'block' }}>
<input type="checkbox" checked={selectedIds.includes(p.$id)} onChange={() => setSelectedIds((s) => s.includes(p.$id) ? s.filter((x) => x !== p.$id) : [...s, p.$id])} />
{' '}{p.projectName} ({p.subdomain})
</label>
))}
<button type="button" className="btn btn-teal mt-2" disabled={!selectedIds.length || actionLoading} onClick={handleAssign}>Zuweisen</button>
</section>
<section>
<h6><FaPlus /> Neues Projekt</h6>
<form onSubmit={handleCreate} style={{ display: 'grid', gap: 8, maxWidth: 400 }}>
<input className="form-control" placeholder="Anzeigename" value={createForm.displayName} onChange={(e) => setCreateForm((f) => ({ ...f, displayName: e.target.value }))} required />
<input className="form-control" placeholder="Subdomain" value={createForm.subdomain} onChange={(e) => setCreateForm((f) => ({ ...f, subdomain: e.target.value, repoName: f.repoName || slugify(e.target.value) }))} required />
<button type="submit" className="btn btn-dark" disabled={actionLoading}>Anlegen & zuweisen</button>
</form>
</section>
</>
)}
</div>
)
}