diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index cc40285..c1c306d 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -4,7 +4,7 @@ import { TOOLS, getGroupedTools } from './toolRegistry.js'; import CalendarWidget, { CalendarSettings } from './calendar.jsx'; import { DateiSettings } from './tools/dateien.jsx'; import { useConfirm } from './confirm.jsx'; -import { HomeIcon, AppsIcon, AdminIcon, MoreIcon, UserIcon, LogOutIcon, ChevronIcon, UpdateIcon, TrashIcon } from './icons.jsx'; +import { HomeIcon, AppsIcon, AdminIcon, MoreIcon, UserIcon, LogOutIcon, ChevronIcon, UpdateIcon, TrashIcon, MessageIcon } from './icons.jsx'; const ICONS = ['๐Ÿ”—','๐ŸŒ','๐Ÿ“Š','๐Ÿ“','๐Ÿ› ','๐Ÿ“ง','๐Ÿ—“','๐Ÿ’พ','๐Ÿ–ฅ','๐Ÿ“ก','๐Ÿ”’','๐Ÿ“–','๐ŸŽฏ','โšก','๐Ÿ ','๐Ÿ”ง','๐Ÿ“ฑ','๐ŸŽฎ','๐Ÿš€','๐Ÿ’ก']; @@ -167,7 +167,7 @@ function UpdateModal({ data, onClose }) { } // โ”€โ”€ Desktop Sidebar โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -function Sidebar({ active, setActive, user, onLogout, updateInfo, onUpdateClick }) { +function Sidebar({ active, setActive, user, onLogout, updateInfo, onUpdateClick, unreadMsgs=0 }) { const [appsOpen, setAppsOpen] = useState(true); const [collapsed, setCollapsed] = useState(false); @@ -183,12 +183,21 @@ function Sidebar({ active, setActive, user, onLogout, updateInfo, onUpdateClick cursor:'pointer', fontSize:12, fontFamily:"'Space Mono',monospace", textAlign:'left', marginBottom:1, borderLeft: active===item.id ? '2px solid #4ecdc4' : '2px solid transparent', + position:'relative', }}> {!collapsed && sub && } - + {item.Icon ? (() => { const I = item.Icon; return ; })() : {item.icon}} + {collapsed && item.id==='nachrichten' && unreadMsgs>0 && ( + + )} {!collapsed && {item.label}} + {!collapsed && item.id==='nachrichten' && unreadMsgs>0 && ( + {unreadMsgs} + )} ); }; @@ -279,7 +288,7 @@ function Sidebar({ active, setActive, user, onLogout, updateInfo, onUpdateClick } // โ”€โ”€ Mobile Bottom Navigation โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ -function BottomNav({ active, setActive, user, onLogout, updateInfo, onUpdateClick }) { +function BottomNav({ active, setActive, user, onLogout, updateInfo, onUpdateClick, unreadMsgs=0 }) { const [menuOpen, setMenuOpen] = useState(false); const [appsOpen, setAppsOpen] = useState(false); const NAV_H = 56; @@ -384,6 +393,11 @@ function BottomNav({ active, setActive, user, onLogout, updateInfo, onUpdateClic width:8, height:8, borderRadius:'50%', background:'#ffe66d', boxShadow:'0 0 6px #ffe66d', display:'inline-block' }} /> )} + {isApps && unreadMsgs>0 && ( + + )} {(() => { const I = item.Icon; return I ? : null; })()} {item.label} @@ -687,7 +701,109 @@ function BestellStats() { ); } -function Dashboard({ toast, mobile }) { +// โ”€โ”€ Nachrichten Dashboard Widget โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function NachrichtenWidget({ unreadMsgs, setActive }) { + const [isOpen, setIsOpen] = useState(false); + return ( +
+ + {isOpen && ( +
+ 0 ? '#4ecdc4' : 'rgba(255,255,255,0.35)', + fontSize:12, fontFamily:'monospace' }}> + {unreadMsgs > 0 + ? `${unreadMsgs} ungelesene Nachricht${unreadMsgs !== 1 ? 'en' : ''}` + : 'Keine neuen Nachrichten'} + + +
+ )} +
+ ); +} + +// โ”€โ”€ Pushover Settings โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +function PushoverSettings({ toast }) { + const [form, setForm] = useState({ user_key:'', app_token:'' }); + const [busy, setBusy] = useState(false); + const [hasSaved, setHasSaved] = useState(false); + + useEffect(() => { + api('/tools/nachrichten/pushover') + .then(d => { + const v = { user_key: d.user_key||'', app_token: d.app_token||'' }; + setForm(v); + setHasSaved(!!(v.user_key && v.app_token)); + }).catch(()=>{}); + }, []); + + const save = async () => { + if (!form.user_key.trim() || !form.app_token.trim()) { toast('Beide Felder ausfรผllen', 'error'); return; } + setBusy(true); + try { await api('/tools/nachrichten/pushover', { body: form }); toast('Gespeichert'); setHasSaved(true); } + catch(e) { toast(e.message, 'error'); } + setBusy(false); + }; + + const test = async () => { + setBusy(true); + try { await api('/tools/nachrichten/pushover/test', { method:'POST' }); toast('Testbenachrichtigung gesendet โœ“'); } + catch(e) { toast(e.message, 'error'); } + setBusy(false); + }; + + const clear = async () => { + try { + await api('/tools/nachrichten/pushover', { method:'DELETE' }); + setForm({ user_key:'', app_token:'' }); + setHasSaved(false); + toast('Pushover entfernt'); + } catch(e) { toast(e.message, 'error'); } + }; + + return ( +
+

+ Push-Benachrichtigungen bei neuen Nachrichten via{' '} + pushover.net. + Du brauchst einen Account und eine eigene App (kostenlos). +

+ setForm(p=>({...p,user_key:e.target.value}))} /> + setForm(p=>({...p,app_token:e.target.value}))} /> +
+ + {hasSaved && ( + + )} + {hasSaved && ( + + )} +
+
+ ); +} + +function Dashboard({ toast, mobile, setActive, unreadMsgs=0 }) { return (
@@ -699,6 +815,7 @@ function Dashboard({ toast, mobile }) { +
@@ -1111,6 +1228,9 @@ function AdminPanel({ toast, mobile, user }) {

+ + +
)} @@ -1171,6 +1291,7 @@ export default function App() { const [toastMsg,setToastMsg]=useState({msg:'',type:'success'}); const [updateInfo,setUpdateInfo]=useState(null); const [showUpd,setShowUpd]=useState(false); + const [unreadMsgs,setUnreadMsgs]=useState(0); const [authChecked, setAuthChecked] = useState(false); useEffect(()=>{ @@ -1192,6 +1313,14 @@ export default function App() { return()=>clearInterval(t); },[user]); + useEffect(()=>{ + if(!user)return; + const poll=()=>api('/tools/nachrichten/unread').then(d=>setUnreadMsgs(d.count||0)).catch(()=>{}); + poll(); + const t=setInterval(poll,30*1000); + return()=>clearInterval(t); + },[user]); + const toast=useCallback((msg,type='success')=>{ setToastMsg({msg,type}); setTimeout(()=>setToastMsg({msg:'',type:'success'}),3500); @@ -1222,10 +1351,11 @@ export default function App() { {!mobile && ( {localStorage.removeItem('sk_token');setUser(null);}} - updateInfo={updateInfo} onUpdateClick={()=>setShowUpd(true)}/> + updateInfo={updateInfo} onUpdateClick={()=>setShowUpd(true)} + unreadMsgs={unreadMsgs}/> )}
- {active==='dashboard' && } + {active==='dashboard' && } {active==='admin' && } {activeTool && }
@@ -1233,7 +1363,8 @@ export default function App() { {mobile && ( {localStorage.removeItem('sk_token');setUser(null);}} - updateInfo={updateInfo} onUpdateClick={()=>setShowUpd(true)}/> + updateInfo={updateInfo} onUpdateClick={()=>setShowUpd(true)} + unreadMsgs={unreadMsgs}/> )} {showUpd&&updateInfo&&setShowUpd(false)}/>} diff --git a/frontend/src/icons.jsx b/frontend/src/icons.jsx index c7a085a..60a114c 100644 --- a/frontend/src/icons.jsx +++ b/frontend/src/icons.jsx @@ -139,3 +139,6 @@ export const LinkIcon = ({size=20,color='currentColor',sw}) => base(<> export const ZapIcon = ({size=20,color='currentColor',sw}) => base(<> , size, color, sw); +export const MessageIcon = ({size=20,color='currentColor',sw}) => base(<> + +, size, color, sw); diff --git a/frontend/src/toolRegistry.js b/frontend/src/toolRegistry.js index 328460d..d80209d 100644 --- a/frontend/src/toolRegistry.js +++ b/frontend/src/toolRegistry.js @@ -1,7 +1,8 @@ import Kalkulator3D, { SavedList } from './tools/kalkulator3d.jsx'; import Bestellungen from './tools/bestellungen.jsx'; import Dateien from './tools/dateien.jsx'; -import { CalculatorIcon, ArchiveIcon, DatabaseIcon, AppsIcon } from './icons.jsx'; +import Nachrichten from './tools/nachrichten.jsx'; +import { CalculatorIcon, ArchiveIcon, DatabaseIcon, AppsIcon, MessageIcon } from './icons.jsx'; export const TOOLS = [ { @@ -36,6 +37,14 @@ export const TOOLS = [ group: 'Werkzeuge', component: Dateien, }, + { + id: 'nachrichten', + Icon: MessageIcon, + label: 'Nachrichten', + navLabel: 'Nachrichten', + group: 'Werkzeuge', + component: Nachrichten, + }, // Neues Tool: { id:'...', Icon:..., label:'...', navLabel:'...', group:'...', component:... }, ];