Dateien nach "frontend/src" hochladen
This commit is contained in:
@@ -609,9 +609,14 @@ function Notepad({ toast }) {
|
|||||||
return (
|
return (
|
||||||
<div style={{ ...S.card }}>
|
<div style={{ ...S.card }}>
|
||||||
<button onClick={()=>setIsOpen(v=>!v)} style={{width:'100%',background:'transparent',border:'none',
|
<button onClick={()=>setIsOpen(v=>!v)} style={{width:'100%',background:'transparent',border:'none',
|
||||||
display:'flex',justifyContent:'space-between',alignItems:'center',cursor:'pointer',padding:0}}>
|
display:'flex',alignItems:'center',gap:8,cursor:'pointer',padding:0}}>
|
||||||
<div style={S.head}>NOTIZEN</div>
|
<div style={S.head}>NOTIZEN</div>
|
||||||
<span style={{color:'rgba(255,255,255,0.4)',fontSize:11,display:'inline-block',
|
{!isOpen && content.length > 0 && (
|
||||||
|
<span style={{color:'rgba(255,255,255,0.45)',fontFamily:'monospace',fontSize:10}}>
|
||||||
|
{content.length} Zeichen
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span style={{color:'rgba(255,255,255,0.4)',fontSize:11,display:'inline-block',marginLeft:'auto',
|
||||||
transform:isOpen?'rotate(90deg)':'rotate(0)',transition:'transform 0.2s'}}>▶</span>
|
transform:isOpen?'rotate(90deg)':'rotate(0)',transition:'transform 0.2s'}}>▶</span>
|
||||||
</button>
|
</button>
|
||||||
{isOpen && <div style={{ color: saved ? 'rgba(78,205,196,0.5)' : 'rgba(255,230,109,0.6)',
|
{isOpen && <div style={{ color: saved ? 'rgba(78,205,196,0.5)' : 'rgba(255,230,109,0.6)',
|
||||||
@@ -1158,7 +1163,10 @@ function AdminPanel({ toast, mobile, user }) {
|
|||||||
// ── App Shell ─────────────────────────────────────────────────────────────────
|
// ── App Shell ─────────────────────────────────────────────────────────────────
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const mobile = useIsMobile();
|
const mobile = useIsMobile();
|
||||||
const [splash, setSplash] = useState(() => window.matchMedia('(display-mode: standalone)').matches);
|
const [splash, setSplash] = useState(() => {
|
||||||
|
// Only show splash in standalone PWA mode, and only once per session
|
||||||
|
return window.matchMedia('(display-mode: standalone)').matches && !sessionStorage.getItem('splash_shown');
|
||||||
|
});
|
||||||
const [user,setUser]=useState(null);
|
const [user,setUser]=useState(null);
|
||||||
const [active,setActive]=useState('dashboard');
|
const [active,setActive]=useState('dashboard');
|
||||||
const [toastMsg,setToastMsg]=useState({msg:'',type:'success'});
|
const [toastMsg,setToastMsg]=useState({msg:'',type:'success'});
|
||||||
@@ -1187,7 +1195,7 @@ export default function App() {
|
|||||||
setTimeout(()=>setToastMsg({msg:'',type:'success'}),3500);
|
setTimeout(()=>setToastMsg({msg:'',type:'success'}),3500);
|
||||||
},[]);
|
},[]);
|
||||||
|
|
||||||
if(splash) return <SplashScreen onDone={()=>setSplash(false)}/>;
|
if(splash) return <SplashScreen onDone={()=>{ setSplash(false); sessionStorage.setItem('splash_shown','1'); }}/>;
|
||||||
if(!user) return <Login onLogin={u=>setUser(u)}/>;
|
if(!user) return <Login onLogin={u=>setUser(u)}/>;
|
||||||
|
|
||||||
const activeTool = TOOLS.find(t=>t.id===active);
|
const activeTool = TOOLS.find(t=>t.id===active);
|
||||||
|
|||||||
@@ -122,6 +122,9 @@ export default function CalendarWidget() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const refresh = () => { api('/calendar/feeds').then(f=>{ setFeeds(f); loadFeeds(f); }).catch(()=>{}); };
|
const refresh = () => { api('/calendar/feeds').then(f=>{ setFeeds(f); loadFeeds(f); }).catch(()=>{}); };
|
||||||
|
// Load on mount so next event shows without opening
|
||||||
|
useEffect(() => { refresh(); }, []);
|
||||||
|
// Reload when opened
|
||||||
useEffect(() => { if (isOpen) refresh(); }, [isOpen]);
|
useEffect(() => { if (isOpen) refresh(); }, [isOpen]);
|
||||||
|
|
||||||
// Nächster oder aktueller Termin (für zugeklappte Ansicht)
|
// Nächster oder aktueller Termin (für zugeklappte Ansicht)
|
||||||
@@ -155,9 +158,27 @@ export default function CalendarWidget() {
|
|||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div style={{ display:'flex', justifyContent:'space-between', alignItems:'center' }}>
|
<div style={{ display:'flex', justifyContent:'space-between', alignItems:'center' }}>
|
||||||
<button onClick={()=>setIsOpen(v=>!v)} style={{ flex:1, background:'transparent', border:'none',
|
<button onClick={()=>setIsOpen(v=>!v)} style={{ flex:1, background:'transparent', border:'none',
|
||||||
display:'flex', justifyContent:'space-between', alignItems:'center', cursor:'pointer', padding:0 }}>
|
display:'flex', justifyContent:'space-between', alignItems:'center', cursor:'pointer', padding:0, minWidth:0 }}>
|
||||||
|
<div style={{ display:'flex', alignItems:'center', gap:10, flex:1, minWidth:0 }}>
|
||||||
<div style={S.head}>KALENDER</div>
|
<div style={S.head}>KALENDER</div>
|
||||||
<span style={{ color:'rgba(255,255,255,0.4)', fontSize:11, display:'inline-block',
|
{!isOpen && nextEvent && (
|
||||||
|
<div style={{ display:'flex', alignItems:'center', gap:6, flex:1, minWidth:0, overflow:'hidden' }}>
|
||||||
|
<span style={{ width:6, height:6, borderRadius:'50%', background:nextEvent.color||'#4ecdc4',
|
||||||
|
flexShrink:0, display:'inline-block' }}/>
|
||||||
|
<span style={{ color:'rgba(255,255,255,0.6)', fontFamily:'monospace', fontSize:10,
|
||||||
|
overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
|
||||||
|
{nextEvent.summary}
|
||||||
|
</span>
|
||||||
|
<span style={{ color:'rgba(255,255,255,0.35)', fontFamily:'monospace', fontSize:9, flexShrink:0 }}>
|
||||||
|
{nextEvent.allDay
|
||||||
|
? nextEvent.dtstart.toLocaleDateString('de-DE',{day:'numeric',month:'short'})
|
||||||
|
: nextEvent.dtstart.toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'})
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span style={{ color:'rgba(255,255,255,0.4)', fontSize:11, display:'inline-block', flexShrink:0,
|
||||||
transform:isOpen?'rotate(90deg)':'rotate(0)', transition:'transform 0.2s' }}>▶</span>
|
transform:isOpen?'rotate(90deg)':'rotate(0)', transition:'transform 0.2s' }}>▶</span>
|
||||||
</button>
|
</button>
|
||||||
{isOpen && feeds.length > 0 && (
|
{isOpen && feeds.length > 0 && (
|
||||||
@@ -170,23 +191,7 @@ export default function CalendarWidget() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Nächster Termin auch im zugeklappten Zustand */}
|
|
||||||
{!isOpen && nextEvent && (
|
|
||||||
<div style={{ marginTop:8, display:'flex', alignItems:'center', gap:8 }}>
|
|
||||||
<span style={{ width:6, height:6, borderRadius:'50%', background:nextEvent.color||'#4ecdc4',
|
|
||||||
flexShrink:0, display:'inline-block' }}/>
|
|
||||||
<div style={{ flex:1, minWidth:0 }}>
|
|
||||||
<div style={{ color:'rgba(255,255,255,0.75)', fontFamily:'monospace', fontSize:11,
|
|
||||||
overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{nextEvent.summary}</div>
|
|
||||||
<div style={{ color:'rgba(255,255,255,0.4)', fontFamily:'monospace', fontSize:9 }}>
|
|
||||||
{nextEvent.allDay
|
|
||||||
? nextEvent.dtstart.toLocaleDateString('de-DE',{weekday:'short',day:'numeric',month:'short'})
|
|
||||||
: `${nextEvent.dtstart.toLocaleDateString('de-DE',{weekday:'short',day:'numeric',month:'short'})} ${nextEvent.dtstart.toLocaleTimeString('de-DE',{hour:'2-digit',minute:'2-digit'})}`
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div style={{ marginTop:12 }}>
|
<div style={{ marginTop:12 }}>
|
||||||
@@ -210,20 +215,18 @@ export default function CalendarWidget() {
|
|||||||
<span style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:13, fontWeight:700 }}>
|
<span style={{ color:'#fff', fontFamily:"'Space Mono',monospace", fontSize:13, fontWeight:700 }}>
|
||||||
{MONTHS[cur.m]} {cur.y}
|
{MONTHS[cur.m]} {cur.y}
|
||||||
</span>
|
</span>
|
||||||
{(cur.m !== today.getMonth() || cur.y !== today.getFullYear()) && (
|
|
||||||
<button onClick={() => { setCur({ y:today.getFullYear(), m:today.getMonth() }); setSelected(new Date()); }}
|
<button onClick={() => { setCur({ y:today.getFullYear(), m:today.getMonth() }); setSelected(new Date()); }}
|
||||||
style={{ background:'rgba(78,205,196,0.15)', border:'1px solid rgba(78,205,196,0.3)',
|
style={{ background:'rgba(78,205,196,0.15)', border:'1px solid rgba(78,205,196,0.3)',
|
||||||
borderRadius:6, color:'#4ecdc4', fontFamily:'monospace', fontSize:9,
|
borderRadius:6, color:'#4ecdc4', fontFamily:'monospace', fontSize:9,
|
||||||
cursor:'pointer', padding:'2px 8px' }}>Heute</button>
|
cursor:'pointer', padding:'2px 8px' }}>Heute</button>
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<button onClick={nextMonth} style={{ background:'transparent', border:'none', color:'rgba(255,255,255,0.5)',
|
<button onClick={nextMonth} style={{ background:'transparent', border:'none', color:'rgba(255,255,255,0.5)',
|
||||||
cursor:'pointer', fontSize:18, padding:'0 6px' }}>›</button>
|
cursor:'pointer', fontSize:18, padding:'0 6px' }}>›</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display:'flex', gap:16, alignItems:'flex-start' }}>
|
<div style={{ display: window.innerWidth>=768 ? 'flex' : 'block', gap:16, alignItems:'flex-start' }}>
|
||||||
{/* Kalender-Grid */}
|
{/* Kalender-Grid */}
|
||||||
<div style={{ flex:'0 0 auto', width: window.innerWidth>=768 ? 'min(320px,50%)' : '100%' }}>
|
<div style={{ flex:'0 0 auto', width: window.innerWidth>=768 ? 'min(300px,50%)' : '100%' }}>
|
||||||
|
|
||||||
{/* Wochentage */}
|
{/* Wochentage */}
|
||||||
<div style={{ display:'grid', gridTemplateColumns:'repeat(7,1fr)', gap:2, marginBottom:4 }}>
|
<div style={{ display:'grid', gridTemplateColumns:'repeat(7,1fr)', gap:2, marginBottom:4 }}>
|
||||||
@@ -268,8 +271,8 @@ export default function CalendarWidget() {
|
|||||||
|
|
||||||
</div>{/* Ende Kalender-Grid */}
|
</div>{/* Ende Kalender-Grid */}
|
||||||
|
|
||||||
{/* Termine – auf PC rechts, auf Mobile darunter */}
|
{/* Termine */}
|
||||||
<div style={{ flex:1, minWidth:0, ...(window.innerWidth<768 ? {width:'100%'} : {}) }}>
|
<div style={{ flex:1, minWidth:0, width:'100%', marginTop: window.innerWidth>=768 ? 0 : 10 }}>
|
||||||
{/* Ausgewählter Tag Events */}
|
{/* Ausgewählter Tag Events */}
|
||||||
{selected && (
|
{selected && (
|
||||||
<div style={{ marginTop:10, borderTop:'1px solid rgba(255,255,255,0.07)', paddingTop:10 }}>
|
<div style={{ marginTop:10, borderTop:'1px solid rgba(255,255,255,0.07)', paddingTop:10 }}>
|
||||||
|
|||||||
Reference in New Issue
Block a user