Dateien nach "frontend/src" hochladen

This commit is contained in:
2026-05-26 11:33:22 +02:00
parent 1d61867153
commit df767ff3b2

View File

@@ -55,6 +55,32 @@ function Modal({ title, onClose, children }) {
);
}
// ── PWA Splash Screen ─────────────────────────────────────────────────────────
function SplashScreen({ onDone }) {
const [fade, setFade] = useState(false);
useEffect(() => {
const t1 = setTimeout(() => setFade(true), 1200);
const t2 = setTimeout(() => onDone(), 1700);
return () => { clearTimeout(t1); clearTimeout(t2); };
}, []);
return (
<div style={{
position:'fixed', inset:0, background:'#0d0d0f',
display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center',
zIndex:99999, transition:'opacity 0.5s ease',
opacity: fade ? 0 : 1, pointerEvents: fade ? 'none' : 'auto',
}}>
<div style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:22, fontWeight:700, marginBottom:24, letterSpacing:2 }}>
DICKEN<span style={{color:'#4ecdc4'}}>DOCK</span>
</div>
<div style={{
width:72, height:72, background:'linear-gradient(135deg,#4ecdc4,#ff6b9d)',
borderRadius:18, display:'flex', alignItems:'center', justifyContent:'center', fontSize:36,
}}></div>
</div>
);
}
// ── Login ─────────────────────────────────────────────────────────────────────
function Login({ onLogin }) {
const [u,setU]=useState(''); const [p,setP]=useState('');
@@ -142,17 +168,19 @@ function Sidebar({ active, setActive, user, onLogout, updateInfo, onUpdateClick
const NavBtn = ({ item, sub=false }) => {
const ic = active===item.id ? '#4ecdc4' : 'rgba(255,255,255,0.55)';
return (
<button onClick={()=>setActive(item.id)} style={{
<button onClick={()=>setActive(item.id)} title={collapsed ? item.label : undefined} style={{
width:'100%', display:'flex', alignItems:'center', gap:9,
padding:'8px 12px', borderRadius:7, border:'none',
padding: collapsed ? '8px 0' : '8px 12px',
justifyContent: collapsed ? 'center' : 'flex-start',
borderRadius:7, border:'none',
background: active===item.id ? 'linear-gradient(90deg,rgba(78,205,196,0.14),rgba(78,205,196,0.02))' : 'transparent',
cursor:'pointer', fontSize:12,
fontFamily:"'Space Mono',monospace", textAlign:'left', marginBottom:1,
borderLeft: active===item.id ? '2px solid #4ecdc4' : '2px solid transparent',
}}>
{sub && <span style={{ width:1, alignSelf:'stretch', background:'rgba(255,255,255,0.08)', marginRight:3, flexShrink:0 }}/>}
<span style={{ display:'flex', alignItems:'center', width:18, justifyContent:'center', flexShrink:0 }}>
{item.Icon ? (() => { const I = item.Icon; return <I size={15} color={ic}/>; })() : <span style={{color:ic,fontSize:13}}>{item.icon}</span>}
{!collapsed && sub && <span style={{ width:1, alignSelf:'stretch', background:'rgba(255,255,255,0.08)', marginRight:3, flexShrink:0 }}/>}
<span style={{ display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0 }}>
{item.Icon ? (() => { const I = item.Icon; return <I size={collapsed?18:15} color={ic}/>; })() : <span style={{color:ic,fontSize:collapsed?16:13}}>{item.icon}</span>}
</span>
{!collapsed && <span style={{ flex:1, color:ic }}>{item.label}</span>}
</button>
@@ -507,7 +535,7 @@ function TodoList({ toast }) {
</div>
<div style={{ maxHeight:220, overflowY:'auto' }}>
{open.length === 0 && done.length === 0 && (
<div style={{ color:'rgba(255,255,255,0.15)', fontSize:11, fontFamily:'monospace', padding:'6px 0' }}>Noch keine Aufgaben.</div>
<div style={{ color:'rgba(255,255,255,0.45)', fontSize:11, fontFamily:'monospace', padding:'6px 0' }}>Noch keine Aufgaben.</div>
)}
{open.map(item => (
<div key={item.id} style={{ display:'flex', alignItems:'center', gap:10, padding:'11px 0', borderBottom:'1px solid rgba(255,255,255,0.04)' }}>
@@ -636,7 +664,7 @@ function Dashboard({ toast, mobile }) {
<div style={{ padding: mobile ? '14px 12px 90px' : '36px 44px', maxWidth:1100 }}>
<div style={{ marginBottom:16 }}>
<h1 style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:mobile ? 17 : 22, margin:0 }}>Dashboard</h1>
<p style={{ color:'rgba(255,255,255,0.25)', fontFamily:'monospace', fontSize:10, marginTop:3 }}>
<p style={{ color:'rgba(255,255,255,0.55)', fontFamily:'monospace', fontSize:10, marginTop:3 }}>
{new Date().toLocaleDateString('de-DE', { weekday:'long', day:'numeric', month:'long' })}
</p>
</div>
@@ -721,19 +749,19 @@ function AdminPanel({ toast, mobile }) {
<div style={{ padding: mobile ? '14px 12px 90px' : '36px 44px', maxWidth:560 }}>
<h1 style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:mobile?17:22, marginBottom:16 }}>Einstellungen</h1>
{/* Subnavigation */}
<div style={{ display:'flex', gap:6, marginBottom:18 }}>
{[['profil','Profil'],['backup','Backup']].map(([k,l]) => (
<button key={k} onClick={()=>setSection(k)} style={{
padding:'6px 16px', borderRadius:20, fontFamily:'monospace', fontSize:12, cursor:'pointer',
border:`1px solid ${section===k?'rgba(78,205,196,0.5)':'rgba(255,255,255,0.1)'}`,
border:`1px solid ${section===k?'rgba(78,205,196,0.5)':'rgba(255,255,255,0.15)'}`,
background: section===k?'rgba(78,205,196,0.12)':'transparent',
color: section===k?'#4ecdc4':'rgba(255,255,255,0.45)',
color: section===k?'#4ecdc4':'rgba(255,255,255,0.55)',
}}>{l}</button>
))}
</div>
{section === 'profil' && <div>
{section === 'profil' && (
<div>
<Sec title="AVATAR">
<div style={{ display:'flex', alignItems:'center', gap:14, marginBottom:12 }}>
<div style={{ width:60, height:60, borderRadius:'50%', overflow:'hidden', flexShrink:0,
@@ -768,7 +796,6 @@ function AdminPanel({ toast, mobile }) {
{avatar && <button onClick={()=>{setAvatar(null);localStorage.removeItem('dd_avatar');toast('Avatar entfernt');}}
style={{...S.btn('#ff6b9d',true)}}> Entfernen</button>}
</Sec>
<Sec title="BENUTZERNAME ÄNDERN">
<Field label="NEUER BENUTZERNAME" value={un.newUsername} onChange={e=>setUn(p=>({...p,newUsername:e.target.value}))} />
<Field label="PASSWORT ZUR BESTÄTIGUNG" type="password" value={un.unPw} onChange={e=>setUn(p=>({...p,unPw:e.target.value}))} />
@@ -776,7 +803,6 @@ function AdminPanel({ toast, mobile }) {
{busy.un ? '…' : '✓ Benutzername ändern'}
</button>
</Sec>
<Sec title="PASSWORT ÄNDERN">
<Field label="AKTUELLES PASSWORT" type="password" value={pw.current} onChange={e=>setPw(p=>({...p,current:e.target.value}))} />
<Field label="NEUES PASSWORT" type="password" value={pw.newPw} onChange={e=>setPw(p=>({...p,newPw:e.target.value}))} />
@@ -785,12 +811,13 @@ function AdminPanel({ toast, mobile }) {
{busy.pw ? '…' : '✓ Passwort ändern'}
</button>
</Sec>
</div>
)}
</div>}
{section === 'backup' && <div>
{section === 'backup' && (
<div>
<Sec title="DATENBANK">
<p style={{ color:'rgba(255,255,255,0.3)', fontSize:12, fontFamily:'monospace', marginBottom:12, lineHeight:1.6 }}>
<p style={{ color:'rgba(255,255,255,0.55)', fontSize:12, fontFamily:'monospace', marginBottom:12, lineHeight:1.6 }}>
Vollständige SQLite-Datenbank sichern oder wiederherstellen.
</p>
<button onClick={backup} style={{ ...S.btn('#ffe66d'), width:'100%', textAlign:'center', padding:'11px 0', marginBottom:12 }}>
@@ -805,9 +832,10 @@ function AdminPanel({ toast, mobile }) {
{busy.restore ? '…' : '↑'}
</button>
</div>
{file && <div style={{ color:'rgba(255,255,255,0.25)', fontSize:10, fontFamily:'monospace', marginTop:5 }}>{file.name}</div>}
{file && <div style={{ color:'rgba(255,255,255,0.55)', fontSize:10, fontFamily:'monospace', marginTop:5 }}>{file.name}</div>}
</Sec>
</div>}
</div>
)}
</div>
);
}
@@ -816,6 +844,7 @@ function AdminPanel({ toast, mobile }) {
// ── App Shell ─────────────────────────────────────────────────────────────────
export default function App() {
const mobile = useIsMobile();
const [splash, setSplash] = useState(() => window.matchMedia('(display-mode: standalone)').matches);
const [user,setUser]=useState(null);
const [active,setActive]=useState('dashboard');
const [toastMsg,setToastMsg]=useState({msg:'',type:'success'});
@@ -844,6 +873,7 @@ export default function App() {
setTimeout(()=>setToastMsg({msg:'',type:'success'}),3500);
},[]);
if(splash) return <SplashScreen onDone={()=>setSplash(false)}/>;
if(!user) return <Login onLogin={u=>setUser(u)}/>;
const activeTool = TOOLS.find(t=>t.id===active);