109 lines
5.1 KiB
JavaScript
109 lines
5.1 KiB
JavaScript
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>
|
||
)
|
||
}
|