MBO-Tech-IT-Webseite/modules/06-website-cms/files/components/admin/GalerieVerwaltung.tsx

87 lines
5.2 KiB
TypeScript

'use client'
import { useState, useEffect, useRef } from 'react'
interface GalerieBild { id: string; storage_path: string; alt_text: string; reihenfolge: number; url: string }
export function GalerieVerwaltung() {
const [bilder, setBilder] = useState<GalerieBild[]>([])
const [uploading, setUploading] = useState(false)
const [loading, setLoading] = useState(true)
const fileRef = useRef<HTMLInputElement>(null)
useEffect(() => {
fetch('/api/admin/galerie').then(r => r.json()).then(({ bilder: b }) => { setBilder(b ?? []); setLoading(false) })
}, [])
async function handleUpload(e: React.ChangeEvent<HTMLInputElement>) {
const files = Array.from(e.target.files ?? [])
if (!files.length) return
setUploading(true)
for (const file of files) {
const fd = new FormData(); fd.append('file', file); fd.append('alt_text', file.name.replace(/\.[^.]+$/, ''))
const res = await fetch('/api/admin/galerie', { method: 'POST', body: fd })
const { bild, error } = await res.json()
if (bild) {
const base = process.env.NEXT_PUBLIC_SUPABASE_URL ?? ''
setBilder(prev => [...prev, { ...bild, url: `${base}/storage/v1/object/public/galerie-bilder/${bild.storage_path}` }])
} else { alert(error ?? 'Upload fehlgeschlagen') }
}
setUploading(false)
if (fileRef.current) fileRef.current.value = ''
}
async function handleDelete(bild: GalerieBild) {
if (!confirm(`"${bild.alt_text || bild.storage_path}" löschen?`)) return
await fetch('/api/admin/galerie', { method: 'DELETE', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: bild.id, storagePath: bild.storage_path }) })
setBilder(prev => prev.filter(b => b.id !== bild.id))
}
async function move(index: number, dir: -1 | 1) {
const newBilder = [...bilder]
const target = index + dir
if (target < 0 || target >= newBilder.length) return
;[newBilder[index], newBilder[target]] = [newBilder[target], newBilder[index]]
const withOrder = newBilder.map((b, i) => ({ ...b, reihenfolge: i }))
setBilder(withOrder)
await fetch('/api/admin/galerie/sort', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ order: withOrder.map(b => ({ id: b.id, reihenfolge: b.reihenfolge })) }) })
}
if (loading) return <p style={{ color: 'var(--text-muted)' }}>Lade</p>
return (
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '24px' }}>
<button onClick={() => fileRef.current?.click()} disabled={uploading} style={{ padding: '9px 18px', borderRadius: '6px', background: 'var(--accent)', color: '#fff', fontWeight: 700, border: 'none', cursor: 'pointer', fontSize: '13px' }}>
{uploading ? 'Hochladen…' : '+ Bilder hochladen'}
</button>
<input ref={fileRef} type="file" accept="image/*" multiple onChange={handleUpload} style={{ display: 'none' }} />
<span style={{ fontSize: '13px', color: 'var(--text-muted)' }}>{bilder.length} Bilder · Reihenfolge per </span>
</div>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(180px, 1fr))', gap: '12px' }}>
{bilder.map((bild, i) => (
<div key={bild.id} style={{ background: 'var(--surface)', border: '1px solid var(--border-color)', borderRadius: '8px', overflow: 'hidden' }}>
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={bild.url} alt={bild.alt_text} style={{ width: '100%', height: '120px', objectFit: 'cover', display: 'block' }} />
<div style={{ padding: '8px', display: 'flex', flexDirection: 'column', gap: '6px' }}>
<input
style={{ fontSize: '11px', padding: '4px 8px', borderRadius: '4px', border: '1px solid var(--border-color)', background: 'var(--bg)', color: 'var(--text-muted)', width: '100%', boxSizing: 'border-box' }}
defaultValue={bild.alt_text}
placeholder="Alt-Text"
onBlur={async e => { await fetch('/api/admin/galerie', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: bild.id, alt_text: e.target.value }) }) }}
/>
<div style={{ display: 'flex', gap: '4px', justifyContent: 'space-between' }}>
<div style={{ display: 'flex', gap: '4px' }}>
<button onClick={() => move(i, -1)} disabled={i === 0} style={{ padding: '3px 7px', borderRadius: '4px', border: '1px solid var(--border-color)', background: 'transparent', color: 'var(--text-muted)', cursor: 'pointer', fontSize: '12px' }}></button>
<button onClick={() => move(i, 1)} disabled={i === bilder.length - 1} style={{ padding: '3px 7px', borderRadius: '4px', border: '1px solid var(--border-color)', background: 'transparent', color: 'var(--text-muted)', cursor: 'pointer', fontSize: '12px' }}></button>
</div>
<button onClick={() => handleDelete(bild)} style={{ padding: '3px 7px', borderRadius: '4px', border: '1px solid rgba(220,38,38,0.3)', background: 'transparent', color: '#f87171', cursor: 'pointer', fontSize: '12px' }}></button>
</div>
</div>
</div>
))}
</div>
</div>
)
}