dashboard: question_added / question_resolved mutation events + client derived state
This commit is contained in:
parent
56d615b51f
commit
1879b2f485
6 changed files with 175 additions and 15 deletions
|
|
@ -477,15 +477,57 @@
|
|||
root.append(ul);
|
||||
}
|
||||
|
||||
function renderQuestions(s) {
|
||||
// Derived question state — cold-loaded from /api/state, then mutated
|
||||
// live by `question_added` / `question_resolved` dashboard events.
|
||||
const QUESTION_HISTORY_LIMIT = 20;
|
||||
const questionsState = { pending: [], history: [] };
|
||||
function syncQuestionsFromSnapshot(s) {
|
||||
questionsState.pending = (s.questions || []).slice();
|
||||
questionsState.history = (s.question_history || []).slice();
|
||||
}
|
||||
function applyQuestionAdded(ev) {
|
||||
if (questionsState.pending.some((q) => q.id === ev.id)) return;
|
||||
questionsState.pending.push({
|
||||
id: ev.id,
|
||||
asker: ev.asker,
|
||||
question: ev.question,
|
||||
options: ev.options || [],
|
||||
multi: !!ev.multi,
|
||||
asked_at: ev.asked_at,
|
||||
deadline_at: ev.deadline_at ?? null,
|
||||
});
|
||||
renderQuestions();
|
||||
}
|
||||
function applyQuestionResolved(ev) {
|
||||
const idx = questionsState.pending.findIndex((q) => q.id === ev.id);
|
||||
const existing = idx >= 0 ? questionsState.pending[idx] : null;
|
||||
if (idx >= 0) questionsState.pending.splice(idx, 1);
|
||||
questionsState.history.unshift({
|
||||
id: ev.id,
|
||||
asker: existing?.asker || '?',
|
||||
question: existing?.question || '',
|
||||
options: existing?.options || [],
|
||||
multi: existing?.multi || false,
|
||||
asked_at: existing?.asked_at || ev.answered_at,
|
||||
answered_at: ev.answered_at,
|
||||
answer: ev.answer,
|
||||
answerer: ev.answerer,
|
||||
});
|
||||
if (questionsState.history.length > QUESTION_HISTORY_LIMIT) {
|
||||
questionsState.history.length = QUESTION_HISTORY_LIMIT;
|
||||
}
|
||||
renderQuestions();
|
||||
}
|
||||
function renderQuestions() {
|
||||
const root = $('questions-section');
|
||||
root.innerHTML = '';
|
||||
const fmt = (n) => new Date(n * 1000).toISOString().replace('T', ' ').slice(0, 19);
|
||||
if (!s.questions || !s.questions.length) {
|
||||
const pending = questionsState.pending;
|
||||
if (!pending.length) {
|
||||
root.append(el('p', { class: 'empty' }, 'no pending questions'));
|
||||
}
|
||||
const ul = el('ul', { class: 'questions' });
|
||||
for (const q of s.questions) {
|
||||
for (const q of pending) {
|
||||
const li = el('li', { class: 'question' });
|
||||
const head = el('div', { class: 'q-head' },
|
||||
el('span', { class: 'msg-ts' }, fmt(q.asked_at)), ' ',
|
||||
|
|
@ -567,10 +609,10 @@
|
|||
li.append(cancelForm);
|
||||
ul.append(li);
|
||||
}
|
||||
if (s.questions && s.questions.length) root.append(ul);
|
||||
if (pending.length) root.append(ul);
|
||||
|
||||
// Answered question history
|
||||
const hist = s.question_history || [];
|
||||
const hist = questionsState.history;
|
||||
if (hist.length) {
|
||||
const details = el('details', { class: 'q-history', 'data-restore-key': 'q-history' });
|
||||
details.append(el('summary', {}, '◆ answ3red (' + hist.length + ')'));
|
||||
|
|
@ -997,12 +1039,13 @@
|
|||
const openDetails = snapshotOpenDetails();
|
||||
renderContainers(s);
|
||||
renderTombstones(s);
|
||||
renderQuestions(s);
|
||||
// Sync the derived approvals + questions stores from the
|
||||
// snapshot, then render. Live `*_added` / `*_resolved` events
|
||||
// mutate the stores directly and re-render without a snapshot
|
||||
// refetch.
|
||||
syncQuestionsFromSnapshot(s);
|
||||
renderQuestions();
|
||||
renderInbox();
|
||||
// Sync the derived approvals store from the snapshot, then
|
||||
// render. Live `approval_added` / `approval_resolved` events
|
||||
// mutate the store directly and call renderApprovals() without
|
||||
// a snapshot refetch.
|
||||
syncApprovalsFromSnapshot(s);
|
||||
renderApprovals();
|
||||
renderMetaInputs(s);
|
||||
|
|
@ -1069,6 +1112,8 @@
|
|||
// for broker traffic, not state-change chatter).
|
||||
approval_added: (ev) => { applyApprovalAdded(ev); },
|
||||
approval_resolved: (ev) => { applyApprovalResolved(ev); },
|
||||
question_added: (ev) => { applyQuestionAdded(ev); },
|
||||
question_resolved: (ev) => { applyQuestionResolved(ev); },
|
||||
},
|
||||
// Both history backfill and live frames flow through here, so the
|
||||
// inbox section ends up populated correctly on first paint and
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue