feat: HA-Entitaeten fuer Push-Erinnerungen und Kanban; neues Feature Sidebar-Sichtbarkeit pro Nutzer
This commit is contained in:
@@ -155,7 +155,7 @@ function Login({ onLogin }) {
|
||||
|
||||
// ── Update Modal ──────────────────────────────────────────────────────────────
|
||||
// ── Desktop Sidebar ───────────────────────────────────────────────────────────
|
||||
function Sidebar({ active, setActive, user, onLogout, unreadMsgs=0, hexWarsTurns=0, whiteboardUnread=0, isAdmin=false }) {
|
||||
function Sidebar({ active, setActive, user, onLogout, unreadMsgs=0, hexWarsTurns=0, whiteboardUnread=0, isAdmin=false, hiddenTools=[] }) {
|
||||
const [appsOpen, setAppsOpen] = useState(true);
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
const [collapsedGroups, setCollapsedGroups] = useState({});
|
||||
@@ -255,7 +255,7 @@ function Sidebar({ active, setActive, user, onLogout, unreadMsgs=0, hexWarsTurns
|
||||
{!collapsed && <span style={{ flex:1, color:'rgba(255,255,255,0.65)' }}>Apps</span>}
|
||||
{!collapsed && <ChevronIcon size={13} color="rgba(255,255,255,0.35)" dir={appsOpen?'down':'right'}/>}
|
||||
</button>
|
||||
{appsOpen && getGroupedTools(isAdmin).map(([group, tools]) => {
|
||||
{appsOpen && getGroupedTools(isAdmin, hiddenTools).map(([group, tools]) => {
|
||||
const isGroupCollapsed = !!collapsedGroups[group];
|
||||
return (
|
||||
<div key={group}>
|
||||
@@ -296,7 +296,7 @@ function Sidebar({ active, setActive, user, onLogout, unreadMsgs=0, hexWarsTurns
|
||||
}
|
||||
|
||||
// ── Mobile Bottom Navigation ──────────────────────────────────────────────────
|
||||
function BottomNav({ active, setActive, user, onLogout, unreadMsgs=0, hexWarsTurns=0, whiteboardUnread=0, isAdmin=false }) {
|
||||
function BottomNav({ active, setActive, user, onLogout, unreadMsgs=0, hexWarsTurns=0, whiteboardUnread=0, isAdmin=false, hiddenTools=[] }) {
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
const [appsOpen, setAppsOpen] = useState(false);
|
||||
const [mobileCollapsedGroups, setMobileCollapsedGroups] = useState({});
|
||||
@@ -360,7 +360,7 @@ function BottomNav({ active, setActive, user, onLogout, unreadMsgs=0, hexWarsTur
|
||||
{/* Apps Sheet mit Gruppenüberschriften */}
|
||||
<Sheet open={appsOpen} onClose={()=>setAppsOpen(false)}>
|
||||
<div style={{ overflowY:'auto', maxHeight:'60vh', paddingBottom:8 }}>
|
||||
{getGroupedTools(user?.role==='admin').map(([group, tools]) => {
|
||||
{getGroupedTools(user?.role==='admin', hiddenTools).map(([group, tools]) => {
|
||||
const isGrpCollapsed = !!mobileCollapsedGroups[group];
|
||||
return (
|
||||
<div key={group}>
|
||||
@@ -2380,6 +2380,8 @@ function UserManagement({ toast }) {
|
||||
const [resetId, setResetId] = useState(null);
|
||||
const [resetPw, setResetPw] = useState('');
|
||||
const [delId, setDelId] = useState(null);
|
||||
const [visId, setVisId] = useState(null);
|
||||
const [visSelection, setVisSelection] = useState([]);
|
||||
const { confirm, ConfirmDialog } = useConfirm();
|
||||
|
||||
const load = () => {
|
||||
@@ -2411,6 +2413,24 @@ function UserManagement({ toast }) {
|
||||
} catch(e) { toast(e.message,'error'); }
|
||||
};
|
||||
|
||||
const openVisibility = (u) => {
|
||||
setVisId(u.id);
|
||||
setVisSelection(u.hidden_tools || []);
|
||||
};
|
||||
|
||||
const toggleVisTool = (toolId) => {
|
||||
setVisSelection(prev => prev.includes(toolId) ? prev.filter(t=>t!==toolId) : [...prev, toolId]);
|
||||
};
|
||||
|
||||
const saveVisibility = async () => {
|
||||
try {
|
||||
await api(`/admin/users/${visId}/hidden-tools`,{method:'PUT',body:{hidden_tools:visSelection}});
|
||||
setUsers(p=>p.map(x=>x.id===visId?{...x,hidden_tools:visSelection}:x));
|
||||
toast('Sichtbarkeit gespeichert');
|
||||
setVisId(null);
|
||||
} catch(e) { toast(e.message,'error'); }
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ConfirmDialog/>
|
||||
@@ -2448,6 +2468,51 @@ function UserManagement({ toast }) {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Sidebar-Sichtbarkeit Modal */}
|
||||
{visId && (() => {
|
||||
const grouped = {};
|
||||
for (const t of TOOLS) {
|
||||
const g = t.group || 'Allgemein';
|
||||
if (!grouped[g]) grouped[g] = [];
|
||||
grouped[g].push(t);
|
||||
}
|
||||
const target = users.find(u=>u.id===visId);
|
||||
return (
|
||||
<div style={{position:'fixed',inset:0,background:'rgba(0,0,0,0.8)',zIndex:5000,
|
||||
display:'flex',alignItems:'center',justifyContent:'center',padding:20}}>
|
||||
<div style={{...S.card,width:'100%',maxWidth:420,maxHeight:'80vh',overflowY:'auto'}}>
|
||||
<div style={{color:'#fff',fontFamily:"'Space Mono',monospace",fontSize:14,fontWeight:700,marginBottom:4}}>
|
||||
Sichtbare Bereiche{target ? ` — ${target.username}` : ''}
|
||||
</div>
|
||||
<div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:10,marginBottom:14}}>
|
||||
Angehakt = ausgeblendet für diesen Nutzer
|
||||
</div>
|
||||
{Object.entries(grouped).map(([group, tools]) => (
|
||||
<div key={group} style={{marginBottom:14}}>
|
||||
<div style={{...S.head,marginBottom:6}}>{group}</div>
|
||||
{tools.map(t => (
|
||||
<label key={t.id} style={{
|
||||
display:'flex',alignItems:'center',gap:8,padding:'6px 2px',cursor:'pointer',
|
||||
}}>
|
||||
<input type="checkbox" checked={visSelection.includes(t.id)}
|
||||
onChange={()=>toggleVisTool(t.id)}
|
||||
style={{width:15,height:15,accentColor:'#ff6b9d',flexShrink:0}}/>
|
||||
<span style={{color:'rgba(255,255,255,0.75)',fontFamily:'monospace',fontSize:12}}>
|
||||
{t.label}{t.adminOnly ? ' (nur Admin)' : ''}
|
||||
</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
<div style={{display:'flex',gap:8,marginTop:8}}>
|
||||
<button onClick={saveVisibility} style={{...S.btn('#4ecdc4'),flex:1,textAlign:'center',padding:'10px 0'}}>✓ Speichern</button>
|
||||
<button onClick={()=>setVisId(null)} style={{...S.btn('#ff6b9d'),flex:1,textAlign:'center',padding:'10px 0'}}>Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
|
||||
{/* Benutzerliste */}
|
||||
<Sec title="BENUTZER">
|
||||
{loading && <div style={{color:'rgba(255,255,255,0.4)',fontFamily:'monospace',fontSize:12}}>Lädt…</div>}
|
||||
@@ -2508,6 +2573,11 @@ function UserManagement({ toast }) {
|
||||
)}
|
||||
<button onClick={()=>{setResetId(u.id);setResetPw('');}}
|
||||
style={{...S.btn('#ffe66d',true),fontSize:10,padding:'4px 8px'}}>🔑</button>
|
||||
<button onClick={()=>openVisibility(u)}
|
||||
title="Sichtbare Bereiche"
|
||||
style={{...S.btn('#4ecdc4',true),fontSize:10,padding:'4px 8px'}}>
|
||||
🧩{u.hidden_tools?.length ? ` ${u.hidden_tools.length}` : ''}
|
||||
</button>
|
||||
<button
|
||||
onClick={async ()=>{
|
||||
if (u.role==='admin') return;
|
||||
@@ -3702,6 +3772,7 @@ export default function App() {
|
||||
return window.matchMedia('(display-mode: standalone)').matches && !sessionStorage.getItem('splash_shown');
|
||||
});
|
||||
const [user,setUser]=useState(null);
|
||||
const [hiddenTools,setHiddenTools]=useState([]);
|
||||
const [active,setActiveRaw]=useState('dashboard');
|
||||
const [devToolsNav, setDevToolsNav] = useState(null);
|
||||
const pollUnackedFavs = () => api('/tools/media/favorites/unacked').then(d=>setUnackedFavs({count:d.count||0,isAdmin:d.isAdmin})).catch(()=>{});
|
||||
@@ -3741,6 +3812,20 @@ export default function App() {
|
||||
if(!user)return;
|
||||
},[user]);
|
||||
|
||||
// Vom Admin ausgeblendete Sidebar-Bereiche laden (einmal pro Login)
|
||||
useEffect(()=>{
|
||||
if(!user)return;
|
||||
api('/auth/hidden-tools').then(setHiddenTools).catch(()=>{});
|
||||
},[user]);
|
||||
|
||||
// Falls der aktuell offene Bereich inzwischen ausgeblendet wurde (z.B. Admin
|
||||
// hat während der laufenden Session die Sichtbarkeit geändert): zurück zum Dashboard
|
||||
useEffect(()=>{
|
||||
if (active !== 'dashboard' && active !== 'admin' && hiddenTools.includes(active)) {
|
||||
setActiveRaw('dashboard');
|
||||
}
|
||||
},[active, hiddenTools]);
|
||||
|
||||
// Schlüssel sofort nach Login initialisieren und Public Key registrieren
|
||||
useEffect(()=>{
|
||||
if(!user)return;
|
||||
@@ -3806,7 +3891,7 @@ export default function App() {
|
||||
if(!authChecked) return <div style={{background:'#0d0d0f',minHeight:'100vh'}}/>;
|
||||
if(!user) return <Login onLogin={u=>setUser(u)}/>;
|
||||
|
||||
const activeTool = TOOLS.find(t=>t.id===active);
|
||||
const activeTool = hiddenTools.includes(active) ? null : TOOLS.find(t=>t.id===active);
|
||||
|
||||
// Mobile padding bottom für fixed nav
|
||||
const isNachrichten = active === 'nachrichten';
|
||||
@@ -3832,9 +3917,9 @@ export default function App() {
|
||||
<div style={{display:'flex',flexDirection:'row',height:'100dvh',background:'#111114'}}>
|
||||
{!mobile && (
|
||||
<Sidebar active={active} setActive={setActive} user={user}
|
||||
onLogout={()=>{localStorage.removeItem('sk_token');setUser(null);}}
|
||||
onLogout={()=>{localStorage.removeItem('sk_token');setUser(null);setHiddenTools([]);}}
|
||||
|
||||
unreadMsgs={unreadMsgs} hexWarsTurns={hexWarsTurns} whiteboardUnread={whiteboardUnread} isAdmin={user?.role==='admin'}/>
|
||||
unreadMsgs={unreadMsgs} hexWarsTurns={hexWarsTurns} whiteboardUnread={whiteboardUnread} isAdmin={user?.role==='admin'} hiddenTools={hiddenTools}/>
|
||||
)}
|
||||
<main ref={mainRef} style={mainStyle}>
|
||||
{active==='dashboard' && <Dashboard key={dashboardKey} toast={toast} mobile={mobile} setActive={setActive} setDevToolsNav={setDevToolsNav} unreadMsgs={unreadMsgs} unreadBoard={unreadBoard} unreadPromoted={unreadPromoted} onBoardRead={()=>{ setUnreadBoard(0); setUnreadPromoted(0); }} unreadChangelog={unreadChangelog} onChangelogRead={()=>setUnreadChangelog(0)} unackedFavs={unackedFavs} onClearUnackedFavs={()=>setUnackedFavs({count:0,isAdmin:false})} user={user}/>}
|
||||
@@ -3844,9 +3929,9 @@ export default function App() {
|
||||
</div>
|
||||
{mobile && (
|
||||
<BottomNav active={active} setActive={setActive} user={user}
|
||||
onLogout={()=>{localStorage.removeItem('sk_token');setUser(null);}}
|
||||
onLogout={()=>{localStorage.removeItem('sk_token');setUser(null);setHiddenTools([]);}}
|
||||
|
||||
unreadMsgs={unreadMsgs} hexWarsTurns={hexWarsTurns} whiteboardUnread={whiteboardUnread} isAdmin={user?.role==='admin'}/>
|
||||
unreadMsgs={unreadMsgs} hexWarsTurns={hexWarsTurns} whiteboardUnread={whiteboardUnread} isAdmin={user?.role==='admin'} hiddenTools={hiddenTools}/>
|
||||
)}
|
||||
<ScrollToTop scrollRef={mainRef}/>
|
||||
<Toast msg={toastMsg.msg} type={toastMsg.type}/>
|
||||
|
||||
Reference in New Issue
Block a user