Push-Erinnerungen: Widget, Backend-Scheduler, Pushover-Versand jede Minute

This commit is contained in:
2026-05-28 19:00:48 +02:00
parent cc6a1c6dc7
commit 7ec2879f73
4 changed files with 204 additions and 0 deletions

View File

@@ -990,6 +990,131 @@ function BoardModal({ user, toast, onClose, onRead }) {
}
// ── Push-Zeitplaner Widget ────────────────────────────────────────────────────
function PushScheduler({ toast }) {
const [isOpen, setIsOpen] = useState(false);
const [schedules, setSchedules] = useState([]);
const [msg, setMsg] = useState('');
const [dt, setDt] = useState('');
const [editId, setEditId] = useState(null);
const [editMsg, setEditMsg] = useState('');
const [editDt, setEditDt] = useState('');
const [hasPushover,setHasPushover]= useState(null); // null=unbekannt
useEffect(() => {
if (!isOpen) return;
api('/dashboard/push-schedules').then(setSchedules).catch(()=>{});
if (hasPushover === null)
api('/tools/nachrichten/pushover').then(d=>setHasPushover(!!(d.user_key&&d.app_token))).catch(()=>{});
}, [isOpen]);
// Vorauswahl: jetzt + 1 Stunde
const defaultDt = () => {
const d = new Date(Date.now() + 60*60*1000);
return d.toISOString().slice(0,16);
};
const fmtDt = s => { if (!s) return ''; const d = new Date(s.replace(' ','T')); return isNaN(d)?s:d.toLocaleString('de-DE',{day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}); };
const add = async () => {
if (!msg.trim()) { toast('Nachricht eingeben','error'); return; }
if (!dt) { toast('Datum und Uhrzeit wählen','error'); return; }
try {
const r = await api('/dashboard/push-schedules',{body:{message:msg.trim(),scheduled_at:dt}});
setSchedules(p=>[...p,r].sort((a,b)=>a.scheduled_at.localeCompare(b.scheduled_at)));
setMsg(''); setDt(''); toast('Erinnerung gespeichert ✓');
} catch(e){ toast(e.message,'error'); }
};
const del = async id => {
try { await api(`/dashboard/push-schedules/${id}`,{method:'DELETE'}); setSchedules(p=>p.filter(s=>s.id!==id)); }
catch(e){ toast(e.message,'error'); }
};
const saveEdit = async () => {
try {
const r = await api(`/dashboard/push-schedules/${editId}`,{method:'PUT',body:{message:editMsg,scheduled_at:editDt}});
setSchedules(p=>p.map(s=>s.id===editId?r:s).sort((a,b)=>a.scheduled_at.localeCompare(b.scheduled_at)));
setEditId(null); toast('Gespeichert ✓');
} catch(e){ toast(e.message,'error'); }
};
return (
<div style={{...S.card, marginBottom:10}}>
<button onClick={()=>setIsOpen(v=>!v)} style={{
width:'100%',background:'transparent',border:'none',cursor:'pointer',
display:'flex',alignItems:'center',padding:0,gap:8}}>
<span style={{fontSize:13}}>🔔</span>
<span style={{...S.head,marginBottom:0,flex:1,textAlign:'left'}}>PUSH-ERINNERUNGEN</span>
{schedules.length>0&&!isOpen&&(
<span style={{color:'rgba(78,205,196,0.7)',fontFamily:'monospace',fontSize:9}}>{schedules.length} aktiv</span>
)}
<span style={{color:'rgba(255,255,255,0.4)',fontSize:11,
transform:isOpen?'rotate(90deg)':'rotate(0)',transition:'transform 0.2s'}}></span>
</button>
{isOpen && (
<div style={{marginTop:12}}>
{hasPushover===false && (
<div style={{background:'rgba(255,230,109,0.07)',border:'1px solid rgba(255,230,109,0.2)',
borderRadius:7,padding:'8px 10px',color:'#ffe66d',fontFamily:'monospace',fontSize:10,
marginBottom:10,lineHeight:1.6}}>
Kein Pushover eingerichtet. Bitte erst unter Einstellungen Profil Pushover konfigurieren.
</div>
)}
{/* Neue Erinnerung */}
<div style={{marginBottom:14}}>
<input value={msg} onChange={e=>setMsg(e.target.value)}
placeholder="Nachricht…" style={{...S.inp,marginBottom:6,fontSize:13}}
onKeyDown={e=>e.key==='Enter'&&add()}/>
<div style={{display:'flex',gap:6}}>
<input type="datetime-local" value={dt||defaultDt()}
onChange={e=>setDt(e.target.value)}
style={{...S.inp,flex:1,fontSize:12,colorScheme:'dark'}}/>
<button onClick={add} style={{...S.btn('#4ecdc4'),padding:'0 14px',flexShrink:0}}>+</button>
</div>
</div>
{/* Liste */}
{schedules.length===0 ? (
<div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:11}}>Keine aktiven Erinnerungen.</div>
) : schedules.map(s=>(
<div key={s.id} style={{padding:'8px 0',borderBottom:'1px solid rgba(255,255,255,0.05)'}}>
{editId===s.id ? (
<div>
<input value={editMsg} onChange={e=>setEditMsg(e.target.value)}
style={{...S.inp,marginBottom:6,fontSize:13}} autoFocus/>
<div style={{display:'flex',gap:6}}>
<input type="datetime-local" value={editDt} onChange={e=>setEditDt(e.target.value)}
style={{...S.inp,flex:1,fontSize:12,colorScheme:'dark'}}/>
<button onClick={saveEdit} style={{...S.btn('#4ecdc4'),padding:'0 10px'}}></button>
<button onClick={()=>setEditId(null)} style={{...S.btn('#ff6b9d',true),padding:'0 10px'}}></button>
</div>
</div>
) : (
<div style={{display:'flex',alignItems:'center',gap:8}}>
<div style={{flex:1,minWidth:0}}>
<div style={{color:'#fff',fontFamily:'monospace',fontSize:12,
overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{s.message}</div>
<div style={{color:'#4ecdc4',fontFamily:'monospace',fontSize:10,marginTop:2}}>
🕐 {fmtDt(s.scheduled_at)}
</div>
</div>
<button onClick={()=>{setEditId(s.id);setEditMsg(s.message);setEditDt(s.scheduled_at?.slice(0,16)||'');}}
style={{...S.btn('#4ecdc4',true),padding:'3px 8px',fontSize:11,flexShrink:0}}></button>
<button onClick={()=>del(s.id)}
style={{...S.btn('#ff6b9d',true),padding:'3px 8px',fontSize:11,flexShrink:0}}></button>
</div>
)}
</div>
))}
</div>
)}
</div>
);
}
function Dashboard({ toast, mobile, setActive, unreadMsgs=0, unreadBoard=0, unreadPromoted=0, onBoardRead, user }) {
const [now, setNow] = useState(new Date());
const [showBoard, setShowBoard] = useState(false);
@@ -1013,6 +1138,7 @@ function Dashboard({ toast, mobile, setActive, unreadMsgs=0, unreadBoard=0, unre
fontFamily:'monospace', marginTop:2, flexShrink:0 }}></button>
</div>
<QuickLinks toast={toast} mobile={mobile} />
<PushScheduler toast={toast}/>
<CalendarWidget/>
<BestellStats/>
<TodoList toast={toast} />