diff --git a/web/src/App.tsx b/web/src/App.tsx index e9a1592..97c82d5 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useMemo, useRef, useState } from 'react'; import ReactDOM from 'react-dom'; -import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume, adminStatus, adminLogin, adminLogout, adminDelete, adminRename, playUrl } from './api'; -import type { VoiceChannelInfo, Sound } from './types'; +import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume, adminStatus, adminLogin, adminLogout, adminDelete, adminRename, playUrl, fetchCategories, createCategory, assignCategories } from './api'; +import type { VoiceChannelInfo, Sound, Category } from './types'; import { getCookie, setCookie } from './cookies'; export default function App() { @@ -9,6 +9,8 @@ export default function App() { const [total, setTotal] = useState(0); const [folders, setFolders] = useState>([]); const [activeFolder, setActiveFolder] = useState('__all__'); + const [categories, setCategories] = useState([]); + const [activeCategoryId, setActiveCategoryId] = useState(''); const [channels, setChannels] = useState([]); const [query, setQuery] = useState(''); const [selected, setSelected] = useState(''); @@ -22,6 +24,8 @@ export default function App() { const [isAdmin, setIsAdmin] = useState(false); const [adminPwd, setAdminPwd] = useState(''); const [selectedSet, setSelectedSet] = useState>({}); + const [assignCategoryId, setAssignCategoryId] = useState(''); + const [newCategoryName, setNewCategoryName] = useState(''); const [showBroccoli, setShowBroccoli] = useState(false); const selectedCount = useMemo(() => Object.values(selectedSet).filter(Boolean).length, [selectedSet]); const [clock, setClock] = useState(() => new Intl.DateTimeFormat('de-DE', { hour: '2-digit', minute: '2-digit', hour12: false, timeZone: 'Europe/Berlin' }).format(new Date())); @@ -47,6 +51,7 @@ export default function App() { setError(e?.message || 'Fehler beim Laden der Channels'); } try { setIsAdmin(await adminStatus()); } catch {} + try { const cats = await fetchCategories(); setCategories(cats.categories || []); } catch {} try { const h = await fetch('/api/health').then(r => r.json()).catch(() => null); if (h && typeof h.totalPlays === 'number') setTotalPlays(h.totalPlays); @@ -67,7 +72,7 @@ export default function App() { (async () => { try { const folderParam = activeFolder === '__favs__' ? '__all__' : activeFolder; - const s = await fetchSounds(query, folderParam); + const s = await fetchSounds(query, folderParam, activeCategoryId || undefined); setSounds(s.items); setTotal(s.total); setFolders(s.folders); @@ -75,7 +80,7 @@ export default function App() { setError(e?.message || 'Fehler beim Laden der Sounds'); } })(); - }, [activeFolder, query]); + }, [activeFolder, query, activeCategoryId]); // Favoriten aus Cookie laden useEffect(() => { @@ -413,7 +418,43 @@ export default function App() { } catch (e:any) { setError(e?.message||'Umbenennen fehlgeschlagen'); } }} /> )} + {/* Kategorien-Zuweisung */} + {selectedCount > 0 && ( + <> + + + + )} +
+ + {/* Kategorie anlegen */} + setNewCategoryName(e.target.value)} style={{maxWidth:200}} /> + +
)} @@ -435,7 +476,7 @@ export default function App() { className={`tag-btn ${activeFolder===f.key?'active':''}`} onClick={async ()=>{ setActiveFolder(f.key); - const resp=await fetchSounds(undefined, f.key); + const resp=await fetchSounds(undefined, f.key, activeCategoryId || undefined); setSounds(resp.items); setTotal(resp.total); setFolders(resp.folders); }} > @@ -443,6 +484,14 @@ export default function App() { ); })} + {categories.length > 0 && ( + <> + + {categories.map(cat => ( + + ))} + + )}