Nightly: Admin Emoji-Picker für Custom-Badges; Kategorien UI: Umbenennen/Löschen + Anlegen; Badges im UI dargestellt
This commit is contained in:
parent
8795657f69
commit
9e5ba70711
3 changed files with 81 additions and 16 deletions
|
|
@ -1,6 +1,6 @@
|
|||
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, fetchCategories, createCategory, assignCategories, assignBadges } from './api';
|
||||
import { fetchChannels, fetchSounds, playSound, setVolumeLive, getVolume, adminStatus, adminLogin, adminLogout, adminDelete, adminRename, playUrl, fetchCategories, createCategory, assignCategories, assignBadges, updateCategory, deleteCategory } from './api';
|
||||
import type { VoiceChannelInfo, Sound, Category } from './types';
|
||||
import { getCookie, setCookie } from './cookies';
|
||||
|
||||
|
|
@ -26,6 +26,16 @@ export default function App() {
|
|||
const [selectedSet, setSelectedSet] = useState<Record<string, boolean>>({});
|
||||
const [assignCategoryId, setAssignCategoryId] = useState<string>('');
|
||||
const [newCategoryName, setNewCategoryName] = useState<string>('');
|
||||
const [editingCategoryId, setEditingCategoryId] = useState<string>('');
|
||||
const [editingCategoryName, setEditingCategoryName] = useState<string>('');
|
||||
const [showEmojiPicker, setShowEmojiPicker] = useState<boolean>(false);
|
||||
const emojiPickerRef = useRef<HTMLDivElement|null>(null);
|
||||
const EMOJIS = useMemo(()=>{
|
||||
// einfache, breite Auswahl gängiger Emojis; kann später erweitert/extern geladen werden
|
||||
const groups = [
|
||||
'😀😁😂🤣😅😊🙂😉😍😘😜🤪🤗🤔🤩🥳😎😴🤤','😇🥰🥺😡🤬😱😭🙈🙉🙊💀👻🤖🎃','👍👎👏🙌🙏🤝💪🔥✨💥🎉🎊','❤️🧡💛💚💙💜🖤🤍🤎💖💘💝','⭐🌟🌈☀️🌙⚡❄️☔🌊🍀','🎵🎶🎧🎤🎸🥁🎹🎺🎻','🍕🍔🍟🌭🌮🍣🍺🍻🍷🥂','🐶🐱🐼🐸🦄🐧🐢🦖🐙','🚀🛸✈️🚁🚗🏎️🚓🚒','🏆🥇🥈🥉🎯🎮🎲🧩']
|
||||
return groups.join('').split('');
|
||||
}, []);
|
||||
const [showBroccoli, setShowBroccoli] = useState<boolean>(false);
|
||||
const selectedCount = useMemo(() => Object.values(selectedSet).filter(Boolean).length, [selectedSet]);
|
||||
const [clock, setClock] = useState<string>(() => new Intl.DateTimeFormat('de-DE', { hour: '2-digit', minute: '2-digit', hour12: false, timeZone: 'Europe/Berlin' }).format(new Date()));
|
||||
|
|
@ -439,26 +449,35 @@ export default function App() {
|
|||
}}
|
||||
>Zu Kategorie</button>
|
||||
|
||||
{/* Custom Badge setzen */}
|
||||
{/* Custom Badge Picker */}
|
||||
<div style={{ position:'relative' }}>
|
||||
<button
|
||||
className="bg-gray-700 hover:bg-gray-600 text-white font-bold py-3 px-6 rounded-lg transition duration-300"
|
||||
onClick={async ()=>{
|
||||
onClick={()=> setShowEmojiPicker(v=>!v)}
|
||||
>Custom Emoji</button>
|
||||
{showEmojiPicker && (
|
||||
<div ref={emojiPickerRef as any} className="emoji-picker" style={{ position:'absolute', top:'110%', right:0, zIndex: 99999 }}>
|
||||
{EMOJIS.map((e, i)=> (
|
||||
<button key={i} onClick={async ()=>{
|
||||
try{
|
||||
const files = Object.entries(selectedSet).filter(([,v])=>v).map(([k])=>k);
|
||||
// Beispiel: Herz-Emoji als Badge; später UI-Eingabe möglich
|
||||
await assignBadges(files, ['❤'], []);
|
||||
await assignBadges(files, [e], []);
|
||||
setShowEmojiPicker(false);
|
||||
setInfo('Badge gesetzt'); setError(null);
|
||||
const resp = await fetchSounds(query, activeFolder === '__favs__' ? '__all__' : activeFolder, activeCategoryId || undefined);
|
||||
setSounds(resp.items); setTotal(resp.total); setFolders(resp.folders);
|
||||
}catch(e:any){ setError(e?.message||'Badge-Update fehlgeschlagen'); setInfo(null); }
|
||||
}}
|
||||
>Badge ❤</button>
|
||||
}catch(err:any){ setError(err?.message||'Badge-Update fehlgeschlagen'); setInfo(null); }
|
||||
}}>{e}</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
{/* Kategorie anlegen */}
|
||||
{/* Kategorien: anlegen/umbenennen/löschen */}
|
||||
<input className="input-field" placeholder="Neue Kategorie" value={newCategoryName} onChange={(e)=>setNewCategoryName(e.target.value)} style={{maxWidth:200}} />
|
||||
<button className="bg-gray-700 hover:bg-gray-600 text-white font-bold py-3 px-6 rounded-lg transition duration-300" onClick={async()=>{
|
||||
try{
|
||||
|
|
@ -470,6 +489,29 @@ export default function App() {
|
|||
}catch(e:any){ setError(e?.message||'Anlegen fehlgeschlagen'); setInfo(null); }
|
||||
}}>Anlegen</button>
|
||||
|
||||
<select className="input-field" value={editingCategoryId} onChange={(e)=>{ setEditingCategoryId(e.target.value); const c = categories.find(x=>x.id===e.target.value); setEditingCategoryName(c?.name||''); }} style={{maxWidth:200}}>
|
||||
<option value="">Kategorie wählen…</option>
|
||||
{categories.map(c=> <option key={c.id} value={c.id}>{c.name}</option>)}
|
||||
</select>
|
||||
<input className="input-field" placeholder="Neuer Name" value={editingCategoryName} onChange={(e)=>setEditingCategoryName(e.target.value)} style={{maxWidth:200}} />
|
||||
<button className="bg-gray-700 hover:bg-gray-600 text-white font-bold py-3 px-6 rounded-lg transition duration-300" onClick={async()=>{
|
||||
try{
|
||||
if(!editingCategoryId){ setError('Bitte Kategorie wählen'); return; }
|
||||
await updateCategory(editingCategoryId, { name: editingCategoryName.trim() });
|
||||
const cats = await fetchCategories(); setCategories(cats.categories || []);
|
||||
setInfo('Kategorie umbenannt'); setError(null);
|
||||
}catch(e:any){ setError(e?.message||'Umbenennen fehlgeschlagen'); setInfo(null); }
|
||||
}}>Umbenennen</button>
|
||||
<button className="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-lg transition duration-300" onClick={async()=>{
|
||||
try{
|
||||
if(!editingCategoryId){ setError('Bitte Kategorie wählen'); return; }
|
||||
await deleteCategory(editingCategoryId);
|
||||
setEditingCategoryId(''); setEditingCategoryName('');
|
||||
const cats = await fetchCategories(); setCategories(cats.categories || []);
|
||||
setInfo('Kategorie gelöscht'); setError(null);
|
||||
}catch(e:any){ setError(e?.message||'Löschen fehlgeschlagen'); setInfo(null); }
|
||||
}}>Löschen</button>
|
||||
|
||||
<button className="bg-red-600 hover:bg-red-700 text-white font-bold py-3 px-6 rounded-lg transition duration-300" onClick={async ()=>{ try{ await adminLogout(); setIsAdmin(false); clearSelection(); } catch{} }}>Logout</button>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,23 @@ export async function createCategory(name: string, color?: string) {
|
|||
return res.json();
|
||||
}
|
||||
|
||||
export async function updateCategory(id: string, payload: { name?: string; color?: string; sort?: number }) {
|
||||
const res = await fetch(`${API_BASE}/categories/${encodeURIComponent(id)}`, {
|
||||
method: 'PATCH', headers: { 'Content-Type': 'application/json' }, credentials: 'include',
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!res.ok) throw new Error('Kategorie aktualisieren fehlgeschlagen');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function deleteCategory(id: string) {
|
||||
const res = await fetch(`${API_BASE}/categories/${encodeURIComponent(id)}`, {
|
||||
method: 'DELETE', credentials: 'include'
|
||||
});
|
||||
if (!res.ok) throw new Error('Kategorie löschen fehlgeschlagen');
|
||||
return res.json();
|
||||
}
|
||||
|
||||
export async function assignCategories(files: string[], add: string[], remove: string[] = []) {
|
||||
const res = await fetch(`${API_BASE}/categories/assign`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include',
|
||||
|
|
|
|||
|
|
@ -440,6 +440,12 @@ header p {
|
|||
max-height: 280px; overflow-y: auto;
|
||||
z-index: 20000;
|
||||
}
|
||||
.emoji-picker {
|
||||
display: grid; grid-template-columns: repeat(10, 2rem); gap: .25rem; padding: .5rem;
|
||||
max-height: 260px; overflow: auto; background: #0f1530; border:1px solid rgba(255,255,255,.28); border-radius: 12px;
|
||||
}
|
||||
.emoji-picker button { background: transparent; border: 0; font-size: 1.25rem; cursor: pointer; }
|
||||
.emoji-picker button:hover { filter: brightness(1.2); }
|
||||
.select-item {
|
||||
width: 100%; text-align: left; padding: 10px 12px; color: #e7e7ee;
|
||||
background: transparent; border: 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue