Dateien nach "frontend/src" hochladen
This commit is contained in:
@@ -92,11 +92,15 @@ export default function CalendarWidget() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [selected, setSelected] = useState(null); // date object
|
const [selected, setSelected] = useState(null); // date object
|
||||||
|
|
||||||
|
const [errors, setErrors] = useState([]);
|
||||||
|
|
||||||
// Load all feeds
|
// Load all feeds
|
||||||
const loadFeeds = async (feedList) => {
|
const loadFeeds = async (feedList) => {
|
||||||
if (!feedList.length) { setEvents([]); return; }
|
if (!feedList.length) { setEvents([]); return; }
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
setErrors([]);
|
||||||
const all = [];
|
const all = [];
|
||||||
|
const errs = [];
|
||||||
for (const f of feedList) {
|
for (const f of feedList) {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/calendar/fetch?url=${encodeURIComponent(f.url)}`, {
|
const res = await fetch(`/api/calendar/fetch?url=${encodeURIComponent(f.url)}`, {
|
||||||
@@ -106,14 +110,18 @@ export default function CalendarWidget() {
|
|||||||
const text = await res.text();
|
const text = await res.text();
|
||||||
const evs = parseIcal(text).map(e => ({ ...e, color: f.color, feedName: f.name }));
|
const evs = parseIcal(text).map(e => ({ ...e, color: f.color, feedName: f.name }));
|
||||||
all.push(...evs);
|
all.push(...evs);
|
||||||
|
} else {
|
||||||
|
const d = await res.json().catch(()=>({error:'Unbekannter Fehler'}));
|
||||||
|
errs.push(`${f.name}: ${d.error}`);
|
||||||
}
|
}
|
||||||
} catch {}
|
} catch(e) { errs.push(`${f.name}: ${e.message}`); }
|
||||||
}
|
}
|
||||||
setEvents(all);
|
setEvents(all);
|
||||||
|
setErrors(errs);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => { if (isOpen) loadFeeds(feeds); }, [isOpen]);
|
useEffect(() => { if (isOpen && feeds.length > 0) loadFeeds(feeds); }, [isOpen]);
|
||||||
|
|
||||||
// Calendar grid
|
// Calendar grid
|
||||||
const days = useMemo(() => {
|
const days = useMemo(() => {
|
||||||
@@ -135,15 +143,37 @@ export default function CalendarWidget() {
|
|||||||
return (
|
return (
|
||||||
<div style={{ ...S.card, marginBottom:10 }}>
|
<div style={{ ...S.card, marginBottom:10 }}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<button onClick={()=>setIsOpen(v=>!v)} style={{ width:'100%', background:'transparent', border:'none',
|
<div style={{ display:'flex', justifyContent:'space-between', alignItems:'center' }}>
|
||||||
|
<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 }}>
|
||||||
<div style={S.head}>KALENDER{loading?' …':''}</div>
|
<div style={S.head}>KALENDER</div>
|
||||||
<span style={{ color:'rgba(255,255,255,0.4)', fontSize:11, display:'inline-block',
|
<span style={{ color:'rgba(255,255,255,0.4)', fontSize:11, display:'inline-block',
|
||||||
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 && (
|
||||||
|
<button onClick={()=>loadFeeds(feeds)} disabled={loading}
|
||||||
|
style={{ background:'transparent', border:'none', color:'rgba(255,255,255,0.4)',
|
||||||
|
cursor:'pointer', fontSize:13, padding:'0 0 0 8px', marginLeft:8 }}
|
||||||
|
title="Neu laden">
|
||||||
|
{loading ? '⏳' : '↻'}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{isOpen && (
|
{isOpen && (
|
||||||
<div style={{ marginTop:12 }}>
|
<div style={{ marginTop:12 }}>
|
||||||
|
{/* Fehler */}
|
||||||
|
{errors.length > 0 && (
|
||||||
|
<div style={{ marginBottom:10 }}>
|
||||||
|
{errors.map((e,i) => (
|
||||||
|
<div key={i} style={{ color:'#ff6b9d', fontFamily:'monospace', fontSize:10,
|
||||||
|
background:'rgba(255,107,157,0.08)', borderRadius:6, padding:'4px 8px', marginBottom:3 }}>
|
||||||
|
✕ {e}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Monat Nav */}
|
{/* Monat Nav */}
|
||||||
<div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:10 }}>
|
<div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:10 }}>
|
||||||
<button onClick={prevMonth} style={{ background:'transparent', border:'none', color:'rgba(255,255,255,0.5)',
|
<button onClick={prevMonth} style={{ background:'transparent', border:'none', color:'rgba(255,255,255,0.5)',
|
||||||
|
|||||||
Reference in New Issue
Block a user