diff --git a/frontend/dashboard.css b/frontend/dashboard.css index 45679c1..0cfd1a4 100644 --- a/frontend/dashboard.css +++ b/frontend/dashboard.css @@ -10,6 +10,9 @@ header { position: sticky; top: 0; z-index: 20; padding: 12px 16px; display:flex .app-menu > summary { display:none; } .app-menu-panel { display:flex; gap:10px; align-items:center; flex-wrap:wrap; } button { background: linear-gradient(180deg,#1f3a5f,#15324d); border:1px solid #2a496e; color:#e5e7eb; padding:8px 12px; border-radius:10px; cursor:pointer; } +.notification-undo { position:fixed; z-index:110; left:50%; bottom:calc(88px + env(safe-area-inset-bottom)); transform:translateX(-50%); box-sizing:border-box; width:min(520px,calc(100vw - 24px)); display:flex; align-items:center; justify-content:space-between; gap:12px; padding:10px 12px; border:1px solid #60a5fa; border-radius:12px; background:#10233d; box-shadow:0 12px 36px rgba(0,0,0,.5); overflow-wrap:anywhere; } +.notification-undo[hidden] { display:none; } +.notification-undo button { min-height:44px; min-width:64px; flex:none; } .draft-capacity-sheet { position:fixed; inset:0; z-index:96; display:flex; align-items:flex-end; justify-content:center; background:rgba(5,12,21,.82); backdrop-filter:blur(4px); } .draft-capacity-sheet[hidden] { display:none; } .draft-capacity-panel { box-sizing:border-box; width:min(620px,100%); max-height:100dvh; overflow:auto; overflow-x:hidden; padding:18px; padding-bottom:calc(18px + env(safe-area-inset-bottom)); border:1px solid #b45309; border-radius:18px 18px 0 0; background:#0b1526; } diff --git a/frontend/dashboard.js b/frontend/dashboard.js index 3163d24..69cd98c 100644 --- a/frontend/dashboard.js +++ b/frontend/dashboard.js @@ -786,6 +786,15 @@ if (state.message) qs('#update-sheet-status').textContent = state.message; }, }); + const notificationUndo = createDashboardNotificationUndo({ + restore: requestNotificationUnread, + getItems: () => lastMyWork, + setItems: items => { lastMyWork = items; }, + getNotifications: () => lastNotifications, + setNotifications: items => { lastNotifications = items; }, + refresh: refreshMyWorkView, + select: qs, + }); const notificationReader = createNotificationReader({ load: fetchNotificationDetail, loadConversation: fetchNotificationConversation, @@ -5034,6 +5043,9 @@ if (result?.accepted) qs('#my-work-action-status').textContent = result.delivery === 'posted' ? 'Reply posted and update marked read.' : 'Reply and read acknowledgement queued for sync.'; + if (result?.delivery === 'posted' && result?.next) { + notificationUndo.offer(item, result.next.items); + } } catch (error) { qs('#update-reply-status').textContent = error.message + ' Your draft is safe; retry.'; qs('#update-reply').focus(); @@ -5046,7 +5058,8 @@ qs('#mark-update-read-next').addEventListener('click', async () => { qs('#mark-update-read-next').disabled = true; try { - await notificationReader.markReadAndNext(lastMyWork); + const result = await notificationReader.markReadAndNext(lastMyWork); + if (result) notificationUndo.offer(result.item, result.items); } finally { qs('#mark-update-read-next').disabled = false; } @@ -5056,11 +5069,17 @@ button.disabled = true; try { const result = await notificationReader.acknowledgeAndNext(lastMyWork); - if (result) qs('#my-work-action-status').textContent = 'Update acknowledged with 👍.'; + if (result) notificationUndo.offer(result.item, result.items); } finally { button.disabled = offlineWorkMode; } }); + qs('#undo-notification').addEventListener('click', async () => { + const button = qs('#undo-notification'); + button.disabled = true; + const restored = await notificationUndo.run(); + button.disabled = restored; + }); qs('#close-review-sheet').addEventListener('click', closeReviewSheet); qs('#retry-review-load').addEventListener('click', () => { if (selectedReview) openReviewSheet(selectedReview, reviewTrigger); diff --git a/frontend/index.html b/frontend/index.html index ef3aa98..ffc6ab3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -688,6 +688,11 @@ + +