+
+
-
โ
+
โ
DICKENDOCK
@@ -57,109 +70,31 @@ function Login({ onLogin }) {
{[['BENUTZERNAME',u,setU,'text'],['PASSWORT',p,setP,'password']].map(([l,v,s,t])=>(
- s(e.target.value)} onKeyDown={e=>e.key==='Enter'&&go()} style={S.inp} />
+ s(e.target.value)}
+ onKeyDown={e=>e.key==='Enter'&&go()}
+ style={{ ...S.inp, fontSize:16 }} autoCapitalize="none" />
))}
{err &&
โ {err}
}
);
}
-// โโ Sidebar โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-function Sidebar({ active, setActive, user, onLogout, updateInfo, onUpdateClick }) {
- const coreNav = [{ id:'dashboard', icon:'โฌก', label:'Dashboard' }];
- const adminNav = user?.role==='admin' ? [{ id:'admin', icon:'โ', label:'Admin' }] : [];
-
- const NavBtn = ({ item }) => (
-
- );
-
- return (
-
- );
-}
-
// โโ Update Modal โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
function UpdateModal({ data, onClose }) {
return (
-
+
Installiert: {data.currentVersion}
- {' โ '}Aktuell: {data.latestVersion}
+ {' โ '}{data.latestVersion}
{(data.releases||[]).map((r,i)=>(
@@ -168,27 +103,175 @@ function UpdateModal({ data, onClose }) {
border:`1px solid ${i===0?'rgba(78,205,196,0.2)':'rgba(255,255,255,0.05)'}`, borderRadius:8 }}>
{r.name||r.version}
- {r.publishedAt?new Date(r.publishedAt).toLocaleDateString('de-DE'):''}
+
+ {r.publishedAt?new Date(r.publishedAt).toLocaleDateString('de-DE'):''}
+
{r.body}
))}
- Update: sh build.sh && docker compose up -d
+ Update: docker compose up -d --build
);
}
-// โโ Dashboard Widgets โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-function QuickLinks({ toast }) {
- const [links, setLinks] = useState([]);
- const [editMode, setEdit] = useState(false);
- const [showForm, setForm] = useState(false);
- const [form, setF] = useState({ title:'', url:'', icon:'๐' });
- const [editId, setEditId] = useState(null);
+// โโ Desktop Sidebar โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+function Sidebar({ active, setActive, user, onLogout, updateInfo, onUpdateClick }) {
+ const allNav = [
+ { id:'dashboard', icon:'โฌก', label:'Dashboard' },
+ ...TOOLS.map(t => ({ id:t.id, icon:t.icon, label:t.label })),
+ ...(user?.role==='admin' ? [{ id:'admin', icon:'โ', label:'Admin' }] : []),
+ ];
+ return (
+
+ );
+}
- useEffect(() => { api('/dashboard/links').then(setLinks).catch(()=>{}); }, []);
+// โโ Mobile Bottom Navigation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+function BottomNav({ active, setActive, user, updateInfo, onUpdateClick }) {
+ const [menuOpen, setMenuOpen] = useState(false);
+ const mainNav = [
+ { id:'dashboard', icon:'โฌก', label:'Home' },
+ ...TOOLS.slice(0,3).map(t => ({ id:t.id, icon:t.icon, label:t.label.length>8?t.label.slice(0,7)+'โฆ':t.label })),
+ { id:'_menu', icon:'โฏ', label:'Mehr' },
+ ];
+
+ return (
+ <>
+ {/* Menu Overlay */}
+ {menuOpen && (
+
setMenuOpen(false)}>
+
e.stopPropagation()}>
+ {/* Update badge */}
+ {updateInfo?.hasUpdate && (
+
+ )}
+ {/* Extra tools */}
+ {TOOLS.slice(3).map(t => (
+
+ ))}
+ {user?.role==='admin' && (
+
+ )}
+
+
+ )}
+
+ {/* Bottom Bar */}
+
+ >
+ );
+}
+
+// โโ Dashboard Widgets โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
+function QuickLinks({ toast, mobile }) {
+ const [links,setLinks]=useState([]); const [editMode,setEdit]=useState(false);
+ const [showForm,setForm]=useState(false); const [form,setF]=useState({title:'',url:'',icon:'๐'});
+ const [editId,setEditId]=useState(null);
+
+ useEffect(()=>{ api('/dashboard/links').then(setLinks).catch(()=>{}); },[]);
const save = async () => {
if (!form.title.trim()||!form.url.trim()) { toast('Titel und URL erforderlich','error'); return; }
@@ -196,33 +279,35 @@ function QuickLinks({ toast }) {
if (!/^https?:\/\//i.test(url)) url = 'https://'+url;
try {
if (editId) {
- const u = await api(`/dashboard/links/${editId}`,{method:'PUT',body:{...form,url}});
+ const u=await api(`/dashboard/links/${editId}`,{method:'PUT',body:{...form,url}});
setLinks(p=>p.map(l=>l.id===editId?u:l));
} else {
- const n = await api('/dashboard/links',{body:{...form,url}});
+ const n=await api('/dashboard/links',{body:{...form,url}});
setLinks(p=>[...p,n]);
}
- toast(editId?'Link aktualisiert':'Link gespeichert');
+ toast(editId?'Aktualisiert':'Gespeichert');
setF({title:'',url:'',icon:'๐'}); setForm(false); setEditId(null);
} catch(e) { toast(e.message,'error'); }
};
const del = async id => {
- try { await api(`/dashboard/links/${id}`,{method:'DELETE'}); setLinks(p=>p.filter(l=>l.id!==id)); toast('Link gelรถscht'); }
+ try { await api(`/dashboard/links/${id}`,{method:'DELETE'}); setLinks(p=>p.filter(l=>l.id!==id)); toast('Gelรถscht'); }
catch(e) { toast(e.message,'error'); }
};
return (
-
-
+
+
SCHNELLZUGRIFF
{editMode && }
-
+
-
- {links.length===0&&!editMode&&
Klicke "Bearbeiten" um Links hinzuzufรผgen.
}
+
+ {links.length===0&&!editMode&&
Tippe auf "โ" um Links hinzuzufรผgen.
}
{links.map(l=>(
{editMode&&(
@@ -231,35 +316,34 @@ function QuickLinks({ toast }) {
)}
-
e.preventDefault():undefined}
- style={{ display:'flex',flexDirection:'column',alignItems:'center',gap:5,padding:'10px 14px',
+ e.preventDefault():undefined}
+ style={{ display:'flex',flexDirection:'column',alignItems:'center',gap:4,padding:'10px 12px',
background:'rgba(255,255,255,0.04)',border:'1px solid rgba(255,255,255,0.08)',
- borderRadius:10,textDecoration:'none',minWidth:70,cursor:editMode?'default':'pointer' }}>
- {l.icon}
- {l.title}
+ borderRadius:10,textDecoration:'none',minWidth:64,cursor:editMode?'default':'pointer' }}>
+ {l.icon}
+ {l.title}
))}
{showForm&&(
-
+
-
ICON WรHLEN
+
ICON
{ICONS.map(ic=>(
-
+
))}
-
-
-
- setF(f=>({...f,title:e.target.value}))} style={S.inp} placeholder="Mein Link" />
-
-
-
- setF(f=>({...f,url:e.target.value}))} onKeyDown={e=>e.key==='Enter'&&save()} style={S.inp} placeholder="https://example.com" />
-
+
+
+ setF(f=>({...f,title:e.target.value}))} style={{ ...S.inp,fontSize:16 }} placeholder="Mein Link" />
+
+
+
+ setF(f=>({...f,url:e.target.value}))} style={{ ...S.inp,fontSize:16 }} placeholder="https://example.com" inputMode="url" autoCapitalize="none" />
@@ -274,7 +358,7 @@ function QuickLinks({ toast }) {
function TodoList({ toast }) {
const [items,setItems]=useState([]); const [text,setText]=useState('');
useEffect(()=>{api('/dashboard/todos').then(setItems).catch(()=>{});},[]);
- const add = async()=>{
+ const add=async()=>{
if(!text.trim())return;
try{const n=await api('/dashboard/todos',{body:{text:text.trim()}});setItems(p=>[...p,n]);setText('');}
catch(e){toast(e.message,'error');}
@@ -289,31 +373,32 @@ function TodoList({ toast }) {
};
const open=items.filter(i=>!i.done), done=items.filter(i=>i.done);
return(
-
+
TO-DO LISTE
-
-
setText(e.target.value)} onKeyDown={e=>e.key==='Enter'&&add()} style={{...S.inp,flex:1}} placeholder="Neue Aufgabeโฆ"/>
-
+
+ setText(e.target.value)} onKeyDown={e=>e.key==='Enter'&&add()}
+ style={{...S.inp,flex:1,fontSize:16}} placeholder="Neue Aufgabeโฆ"/>
+
-
+
{open.map(item=>(
-
-
);
@@ -331,40 +416,40 @@ function Notepad({ toast }) {
},900);
};
return(
-
-
+
+
NOTIZBLOCK
- {saved?(saveTime?`gespeichert ${new Date(saveTime).toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'})}`:'gespeichert'):'โฆ'}
+ {saved?(saveTime?`โ ${new Date(saveTime).toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'})}`:'โ'):'โฆ'}
);
}
-function Dashboard({ toast }) {
+function Dashboard({ toast, mobile }) {
return(
-
-
-
Dashboard
-
+
+
+
Dashboard
+
{new Date().toLocaleDateString('de-DE',{weekday:'long',year:'numeric',month:'long',day:'numeric'})}
-
-
+
+
+
);
}
// โโ Admin Panel โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-function AdminPanel({ toast }) {
+function AdminPanel({ toast, mobile }) {
const [pw,setPw]=useState({current:'',newPw:'',confirm:''}); const [busy,setBusy]=useState(false);
const [file,setFile]=useState(null); const [restoring,setRestoring]=useState(false);
-
const changePw=async()=>{
if(pw.newPw!==pw.confirm){toast('Passwรถrter stimmen nicht รผberein','error');return;}
if(pw.newPw.length<8){toast('Mindestens 8 Zeichen','error');return;}
@@ -383,32 +468,30 @@ function AdminPanel({ toast }) {
try{const f=new FormData();f.append('database',file);await api('/admin/restore',{method:'POST',body:f,isFile:true});toast('Wiederhergestellt');setFile(null);}
catch(e){toast(e.message,'error');}finally{setRestoring(false);}
};
- const Sec=({title,children})=>
;
+ const Sec=({title,children})=>
;
return(
-
-
Admin-Bereich
+
+
Admin-Bereich
{[['Aktuelles Passwort','current'],['Neues Passwort','newPw'],['Bestรคtigen','confirm']].map(([l,k])=>(
- setPw(p=>({...p,[k]:e.target.value}))} style={S.inp}/>
+ setPw(p=>({...p,[k]:e.target.value}))} style={{...S.inp,fontSize:16}}/>
))}
{busy?'โฆ':'โ Passwort รคndern'}
- Lรคdt die vollstรคndige SQLite-Datenbank herunter.
+ Vollstรคndige SQLite-Datenbank herunterladen.
โ Backup herunterladen
- โ ๏ธ Aktuelle Datenbank wird vollstรคndig ersetzt.
-
-
-
- setFile(e.target.files[0])} style={{...S.inp,padding:'6px 10px',cursor:'pointer'}}/>
-
-
{restoring?'โฆ':'โ Einspielen'}
+
โ ๏ธ Aktuelle Datenbank wird vollstรคndig ersetzt.
+
+
+ setFile(e.target.files[0])} style={{...S.inp,padding:'6px 10px',cursor:'pointer'}}/>
+
{restoring?'โฆ':'โ Einspielen'}
{file&&
{file.name}
}
@@ -417,11 +500,12 @@ function AdminPanel({ toast }) {
// โโ App Shell โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
export default function App() {
- const [user, setUser] = useState(null);
- const [active, setActive] = useState('dashboard');
- const [toastMsg, setToastMsg] = useState({ msg:'', type:'success' });
- const [updateInfo,setUpdateInfo]= useState(null);
- const [showUpd, setShowUpd] = useState(false);
+ const mobile = useIsMobile();
+ const [user,setUser]=useState(null);
+ const [active,setActive]=useState('dashboard');
+ const [toastMsg,setToastMsg]=useState({msg:'',type:'success'});
+ const [updateInfo,setUpdateInfo]=useState(null);
+ const [showUpd,setShowUpd]=useState(false);
useEffect(()=>{
const token=localStorage.getItem('sk_token');
@@ -447,30 +531,39 @@ export default function App() {
if(!user) return setUser(u)}/>;
- // Aktive Tool-Komponente ermitteln
const activeTool = TOOLS.find(t=>t.id===active);
+ // Mobile padding bottom fรผr fixed nav
+ const mainStyle = { flex:1, overflowY:'auto', ...(mobile ? { paddingBottom:64 } : {}) };
+
return(
<>
-
{localStorage.removeItem('sk_token');setUser(null);}}
- updateInfo={updateInfo} onUpdateClick={()=>setShowUpd(true)}/>
-
- {active==='dashboard' && }
- {active==='admin' && user?.role==='admin' && }
- {activeTool && }
+ {!mobile && (
+ {localStorage.removeItem('sk_token');setUser(null);}}
+ updateInfo={updateInfo} onUpdateClick={()=>setShowUpd(true)}/>
+ )}
+
+ {active==='dashboard' && }
+ {active==='admin' && user?.role==='admin' && }
+ {activeTool && }
+ {mobile && (
+ setShowUpd(true)}/>
+ )}
{showUpd&&updateInfo&&setShowUpd(false)}/>}
>