Push-Erinnerungen: Widget, Backend-Scheduler, Pushover-Versand jede Minute
This commit is contained in:
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user