History: wiederhergestellte Version anzeigen, Lösch-Bestätigung, Admin-Stats Codes
This commit is contained in:
@@ -41,12 +41,13 @@ router.get('/users', authenticate, requireAdmin, (req, res) => {
|
|||||||
const stats = users.map(u => {
|
const stats = users.map(u => {
|
||||||
const archiv = db.prepare('SELECT COUNT(*) c FROM calculations WHERE user_id=?').get(u.id).c;
|
const archiv = db.prepare('SELECT COUNT(*) c FROM calculations WHERE user_id=?').get(u.id).c;
|
||||||
const bestellung = db.prepare('SELECT COUNT(*) c FROM orders WHERE user_id=?').get(u.id).c;
|
const bestellung = db.prepare('SELECT COUNT(*) c FROM orders WHERE user_id=?').get(u.id).c;
|
||||||
|
const snippets_c = db.prepare('SELECT COUNT(*) c FROM snippets WHERE user_id=?').get(u.id).c;
|
||||||
const todos = db.prepare('SELECT COUNT(*) c FROM todos WHERE user_id=?').get(u.id).c;
|
const todos = db.prepare('SELECT COUNT(*) c FROM todos WHERE user_id=?').get(u.id).c;
|
||||||
const links = db.prepare('SELECT COUNT(*) c FROM quick_links WHERE user_id=?').get(u.id).c;
|
const links = db.prepare('SELECT COUNT(*) c FROM quick_links WHERE user_id=?').get(u.id).c;
|
||||||
const files = db.prepare('SELECT COUNT(*) c FROM files WHERE user_id=?').get(u.id).c;
|
const files = db.prepare('SELECT COUNT(*) c FROM files WHERE user_id=?').get(u.id).c;
|
||||||
const noteLen = (db.prepare('SELECT content FROM notes WHERE user_id=?').get(u.id)?.content || '').length;
|
const noteLen = (db.prepare('SELECT content FROM notes WHERE user_id=?').get(u.id)?.content || '').length;
|
||||||
const icals = db.prepare('SELECT COUNT(*) c FROM calendar_feeds WHERE user_id=?').get(u.id).c;
|
const icals = db.prepare('SELECT COUNT(*) c FROM calendar_feeds WHERE user_id=?').get(u.id).c;
|
||||||
return { ...u, archiv, bestellung, todos, links, files, icals, noteLen };
|
return { ...u, archiv, bestellung, snippets_c, todos, links, files, icals, noteLen };
|
||||||
});
|
});
|
||||||
res.json(stats);
|
res.json(stats);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1887,6 +1887,7 @@ function UserManagement({ toast }) {
|
|||||||
{[
|
{[
|
||||||
['◈ Archiv', u.archiv],
|
['◈ Archiv', u.archiv],
|
||||||
['📦 Bestellungen', u.bestellung],
|
['📦 Bestellungen', u.bestellung],
|
||||||
|
['</> Codes', u.snippets_c],
|
||||||
['✓ Todos', u.todos],
|
['✓ Todos', u.todos],
|
||||||
['🔗 Links', u.links],
|
['🔗 Links', u.links],
|
||||||
['📁 Dateien', u.files],
|
['📁 Dateien', u.files],
|
||||||
|
|||||||
@@ -148,23 +148,25 @@ function HistoryModal({ snippet, onClose, onRestore, toast }) {
|
|||||||
useEffect(()=>{ load(); },[]);
|
useEffect(()=>{ load(); },[]);
|
||||||
|
|
||||||
// Entries: [current, ...history] where index 0 = current
|
// Entries: [current, ...history] where index 0 = current
|
||||||
// entries[i].code, entries[i].label
|
|
||||||
const entries = [
|
const entries = [
|
||||||
{ id:'current', code: snippet.code, language: snippet.language,
|
{ id:'current', code: snippet.code, language: snippet.language,
|
||||||
saved_at: snippet.updated_at, label:'Aktuell', isCurrent: true },
|
saved_at: snippet.updated_at, label:'Aktuell', isCurrent: true },
|
||||||
...history.map((h,i) => ({ ...h, label:`v${history.length - i}`, isCurrent: false })),
|
...history.map((h,i) => ({ ...h, label:`v${history.length - i}`, isCurrent: false })),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Check if current code matches a history entry (= was restored)
|
||||||
|
const restoredFromIdx = history.findIndex(h => h.code === snippet.code);
|
||||||
|
const restoredFromLabel = restoredFromIdx >= 0 ? `v${history.length - restoredFromIdx}` : null;
|
||||||
|
|
||||||
const selected = entries[selectedIdx];
|
const selected = entries[selectedIdx];
|
||||||
// Diff: what changed FROM previous version TO selected (selected is newer)
|
const prevEntry = entries[selectedIdx + 1];
|
||||||
const prevEntry = entries[selectedIdx + 1]; // one step older
|
const oldCode = prevEntry?.code ?? null;
|
||||||
const oldCode = prevEntry?.code ?? null; // null = älteste, kein Diff
|
|
||||||
|
|
||||||
const delHistory = async (hid) => {
|
const delHistory = async (hid) => {
|
||||||
|
if (!window.confirm('Diese Version wirklich löschen?')) return;
|
||||||
try {
|
try {
|
||||||
await api(`/tools/snippets/${snippet.id}/history/${hid}`, { method:'DELETE' });
|
await api(`/tools/snippets/${snippet.id}/history/${hid}`, { method:'DELETE' });
|
||||||
await load();
|
await load();
|
||||||
// If deleted entry was selected, jump to current
|
|
||||||
setSelectedIdx(0);
|
setSelectedIdx(0);
|
||||||
toast('Version gelöscht');
|
toast('Version gelöscht');
|
||||||
} catch(e) { toast(e.message,'error'); }
|
} catch(e) { toast(e.message,'error'); }
|
||||||
@@ -221,6 +223,11 @@ function HistoryModal({ snippet, onClose, onRestore, toast }) {
|
|||||||
fontFamily:'monospace',fontSize:10,fontWeight: e.isCurrent?700:400}}>
|
fontFamily:'monospace',fontSize:10,fontWeight: e.isCurrent?700:400}}>
|
||||||
{e.label}
|
{e.label}
|
||||||
{e.isCurrent&&<span style={{color:'rgba(78,205,196,0.5)',fontSize:8,marginLeft:4}}>●</span>}
|
{e.isCurrent&&<span style={{color:'rgba(78,205,196,0.5)',fontSize:8,marginLeft:4}}>●</span>}
|
||||||
|
{e.isCurrent && restoredFromLabel && (
|
||||||
|
<div style={{color:'rgba(255,230,109,0.7)',fontSize:8,fontWeight:400,marginTop:2}}>
|
||||||
|
↩ aus {restoredFromLabel}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:8,marginTop:2}}>
|
<div style={{color:'rgba(255,255,255,0.25)',fontFamily:'monospace',fontSize:8,marginTop:2}}>
|
||||||
{fmtDt(e.saved_at)}
|
{fmtDt(e.saved_at)}
|
||||||
@@ -573,7 +580,7 @@ export default function CodeSchnipsel({ toast, mobile }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = async id => {
|
const handleDelete = async id => {
|
||||||
if (!window.confirm('Schnipsel wirklich löschen?')) return;
|
if (!window.confirm('Schnipsel und alle seine Versionen wirklich löschen?')) return;
|
||||||
await api(`/tools/snippets/${id}`,{method:'DELETE'});
|
await api(`/tools/snippets/${id}`,{method:'DELETE'});
|
||||||
setOwn(p=>p.filter(s=>s.id!==id)); toast('Gelöscht');
|
setOwn(p=>p.filter(s=>s.id!==id)); toast('Gelöscht');
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user