87 lines
5.2 KiB
TypeScript
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>
|
|
)
|
|
}
|