feat: review completed mobile Filed outcomes (Closes #878)
This commit is contained in:
1 parent
eb88355229
commit
8411e907af
9 files changed
+253
-17
No files matched your search
@@ -465,6 +465,10 @@ textarea { resize: vertical; min-height: 120px; }
|
||||
.issue-sheet-panel { width:min(560px,100%); height:100%; overflow:auto; padding:18px; background:#0b1526; border-left:1px solid #2a496e; }
|
||||
.issue-sheet-header { display:flex; align-items:center; justify-content:space-between; gap:10px; }
|
||||
.issue-sheet-header button { min-height:44px; }
|
||||
.completed-filed-actions { position:fixed; right:0; bottom:0; z-index:57; box-sizing:border-box; width:min(560px,100%); display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center; gap:10px; margin:0; padding:10px 12px calc(10px + env(safe-area-inset-bottom)); border:1px solid #4ade80; border-radius:12px 0 0; background:rgba(11,21,38,.98); overflow-wrap:anywhere; }
|
||||
.completed-filed-actions[hidden] { display:none; }
|
||||
.completed-filed-actions button { min-height:44px; min-width:0; }
|
||||
#issue-sheet:has(.completed-filed-actions:not([hidden])) .issue-sheet-panel { padding-bottom:calc(110px + env(safe-area-inset-bottom)); }
|
||||
.issue-sheet-content { overflow-wrap:anywhere; white-space:pre-wrap; }
|
||||
.issue-blockers { max-width:100%; overflow-x:hidden; margin:16px 0; padding:12px; border:1px solid #b45309; border-radius:12px; background:#291b0c; }
|
||||
.issue-blockers h2 { margin-top:0; }
|
||||
|
||||
+38
-3
@@ -216,6 +216,10 @@
|
||||
let confirmedOwnerLogin = '';
|
||||
let planningOwnerLogin = '';
|
||||
let activeFlushLogin = '';
|
||||
const completedFiledReview = createCompletedFiledReview({
|
||||
storage: localStorage,
|
||||
getLogin() { return planningOwnerLogin; },
|
||||
});
|
||||
let activeMyWork = [];
|
||||
let laterMyWork = [];
|
||||
let todayMyWork = [];
|
||||
@@ -2511,7 +2515,7 @@
|
||||
}
|
||||
|
||||
function paintMyWork(data) {
|
||||
lastMyWork = buildMyWork(data);
|
||||
lastMyWork = completedFiledReview.visible(buildMyWork(data));
|
||||
refreshMyWorkView();
|
||||
}
|
||||
|
||||
@@ -2814,7 +2818,7 @@
|
||||
}
|
||||
|
||||
function updateWorkPaginationControls() {
|
||||
const labels = { issue: 'issues', pull: 'pull requests', review: 'review requests' };
|
||||
const labels = { issue: 'issues', filed: 'filed issues', pull: 'pull requests', review: 'review requests' };
|
||||
const streams = activeWorkStreams();
|
||||
const incomplete = streams.filter(stream => workPagination[stream]?.has_more);
|
||||
const summaries = streams.flatMap(stream => {
|
||||
@@ -3322,6 +3326,13 @@
|
||||
qs('#issue-sheet-key').textContent = item.key || '';
|
||||
qs('#issue-sheet-title').textContent = item.title || 'Assigned issue';
|
||||
qs('#issue-sheet-status').textContent = 'Loading issue…';
|
||||
const completedItems = lastMyWork.filter(candidate => candidate?.is_completed && candidate?.is_filed);
|
||||
const completedPosition = completedItems.findIndex(candidate =>
|
||||
candidate.repository === item.repository && candidate.number === item.number
|
||||
);
|
||||
qs('#completed-filed-actions').hidden = !item.is_completed;
|
||||
qs('#completed-filed-progress').textContent = item.is_completed ?
|
||||
'Completed Filed issue ' + (completedPosition + 1) + ' of ' + completedItems.length : '';
|
||||
qs('#issue-sheet-body').textContent = '';
|
||||
qs('#issue-labels').textContent = '';
|
||||
qs('#issue-assignees').textContent = '';
|
||||
@@ -3381,7 +3392,8 @@
|
||||
if (readOnly) paintIssueConversation(issueConversation.snapshot(), null);
|
||||
else renderIssueConversation(issueConversation.snapshot());
|
||||
qs('#open-issue-gitea').href = detail.url || item.url || '#';
|
||||
qs('#issue-sheet-status').textContent = readOnly ? 'Filed issue ready · follow-up enabled' :
|
||||
qs('#issue-sheet-status').textContent = item.is_completed ?
|
||||
'Completed Filed outcome ready · review the conversation' : readOnly ? 'Filed issue ready · follow-up enabled' :
|
||||
'Issue ready · ' + (detail.state || 'open');
|
||||
qs('#edit-issue-content').disabled = false;
|
||||
const dueDraft = issueController.loadDueDateDraft(item);
|
||||
@@ -3422,6 +3434,29 @@
|
||||
if (issueTrigger?.isConnected) issueTrigger.focus();
|
||||
}
|
||||
|
||||
qs('#acknowledge-completed-filed').addEventListener('click', () => {
|
||||
if (!selectedIssue?.is_completed) return;
|
||||
if (!completedFiledReview.acknowledge(selectedIssue)) {
|
||||
qs('#issue-sheet-status').textContent = 'Could not save this acknowledgement on this device. Retry.';
|
||||
return;
|
||||
}
|
||||
const acknowledged = selectedIssue;
|
||||
lastMyWork = completedFiledReview.visible(lastMyWork);
|
||||
closeIssueSheet(false);
|
||||
refreshMyWorkView();
|
||||
const target = filedFollowUpTarget(lastMyWork);
|
||||
qs('#my-work-action-status').textContent = 'Reviewed ' + acknowledged.key + '.' +
|
||||
(target ? ' Opening the next Filed item.' : ' Filed review is complete.');
|
||||
if (target) {
|
||||
const index = lastMyWork.indexOf(target.item);
|
||||
const trigger = qs('#my-work-list [data-' + target.kind + '-index="' + index + '"]');
|
||||
openRoutedWork(target.kind === 'update' ? { ...target.item, kind:'update' } : target.item, trigger);
|
||||
} else {
|
||||
window.location.hash = '#/my-work/filed';
|
||||
qs('#my-work').focus();
|
||||
}
|
||||
});
|
||||
|
||||
function renderCheckSection(prefix, detail, offline = false) {
|
||||
const rendered = createReviewController.renderChecks(detail?.checks, escapeHtml, { offline });
|
||||
qs('#' + prefix + '-checks-summary').textContent = rendered.summary;
|
||||
|
||||
@@ -594,6 +594,10 @@
|
||||
<button id="close-issue-sheet" type="button">Close sheet</button>
|
||||
</div>
|
||||
<div id="issue-sheet-status" class="small" aria-live="polite">Choose an issue.</div>
|
||||
<section class="completed-filed-actions" id="completed-filed-actions" aria-label="Completed Filed review" hidden>
|
||||
<span id="completed-filed-progress" role="status" aria-live="polite">Completed Filed issue ready for review.</span>
|
||||
<button id="acknowledge-completed-filed" type="button">Acknowledge & next</button>
|
||||
</section>
|
||||
<button class="issue-retry" id="retry-issue-load" type="button" hidden>Reload latest issue</button>
|
||||
<div class="row"><span id="issue-labels"></span><span class="small" id="issue-assignees"></span></div>
|
||||
<div class="issue-sheet-content markdown-content" id="issue-sheet-body"></div>
|
||||
|
||||
@@ -39,7 +39,7 @@ function createIssueSheet({ fetchJson, storage, createConversationPager = global
|
||||
|
||||
return {
|
||||
readOnly(item) {
|
||||
return Boolean(item?.is_filed && !item?.is_assigned);
|
||||
return Boolean(item?.is_completed || (item?.is_filed && !item?.is_assigned));
|
||||
},
|
||||
load(item) {
|
||||
const access = this.readOnly(item) ? '?access=filed' : '';
|
||||
|
||||
+43
-2
@@ -71,20 +71,23 @@ function buildMyWork(data, now = new Date()) {
|
||||
const assigned = (item.assignees || []).includes(login);
|
||||
const isReview = (item.work_reasons || []).includes('review_requested');
|
||||
const isFiled = (item.work_reasons || []).includes('created_by_me');
|
||||
const isCompleted = isFiled && item.state === 'closed';
|
||||
const due = item.kind === 'issue' ? issueDueState(item.due_date, now) : null;
|
||||
const normalized = {
|
||||
...item,
|
||||
key: (item.repository || 'unknown') + '#' + item.number,
|
||||
is_review: isReview,
|
||||
is_filed: isFiled,
|
||||
is_completed: isCompleted,
|
||||
is_assigned: assigned,
|
||||
has_update: false,
|
||||
...(due ? { due_label: due.label } : {}),
|
||||
reason: priorityLabel ? priorityLabel + ' priority' :
|
||||
(due && due.priority < 4 ? due.label :
|
||||
(isReview ? 'Needs your review' : (assigned ? 'Assigned to you' : (isFiled ? 'Filed by you' : 'Open work')))),
|
||||
(isReview ? 'Needs your review' : (isCompleted ? 'Completed · review outcome' :
|
||||
(assigned ? 'Assigned to you' : (isFiled ? 'Filed by you' : 'Open work'))))),
|
||||
_priority: priorityLabel ? 0 :
|
||||
(due && due.priority < 4 ? due.priority : (isReview ? 3 : (assigned ? 4 : 5))),
|
||||
(due && due.priority < 4 ? due.priority : (isReview ? 3 : (isCompleted ? 3.5 : (assigned ? 4 : 5)))),
|
||||
};
|
||||
normalized.needs_attention = needsAttention(normalized);
|
||||
normalized.attention_reason = attentionReason(normalized);
|
||||
@@ -666,6 +669,43 @@ function filterMyWork(items, selectedFilter, selectedMilestone = 'all') {
|
||||
);
|
||||
}
|
||||
|
||||
function createCompletedFiledReview({
|
||||
storage,
|
||||
getLogin,
|
||||
key = 'stackchain.completed-filed-review.v1',
|
||||
}) {
|
||||
const login = () => String(getLogin?.() || '').trim();
|
||||
const ownerKey = () => key + ':' + login();
|
||||
const identity = item => String(item?.repository || '') + '#' + String(item?.number || '');
|
||||
const read = () => {
|
||||
if (!login()) return {};
|
||||
try {
|
||||
const value = JSON.parse(storage?.getItem(ownerKey()) || '{}');
|
||||
return value && value.version === 1 && value.items && typeof value.items === 'object' ?
|
||||
value.items : {};
|
||||
} catch (_error) { return {}; }
|
||||
};
|
||||
return {
|
||||
visible(items) {
|
||||
const acknowledged = read();
|
||||
return (items || []).filter(item =>
|
||||
!item?.is_completed || acknowledged[identity(item)] !== String(item.updated_at || '')
|
||||
);
|
||||
},
|
||||
acknowledge(item) {
|
||||
const stamp = String(item?.updated_at || '');
|
||||
if (!login() || !item?.is_completed || !item?.repository || !Number.isInteger(item?.number) || !stamp) {
|
||||
return false;
|
||||
}
|
||||
const entries = Object.entries({ ...read(), [identity(item)]: stamp }).slice(-200);
|
||||
try {
|
||||
storage?.setItem(ownerKey(), JSON.stringify({ version:1, items:Object.fromEntries(entries) }));
|
||||
return true;
|
||||
} catch (_error) { return false; }
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function agendaMyWork(items, now = new Date()) {
|
||||
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
||||
const today = localDay(start);
|
||||
@@ -981,6 +1021,7 @@ function countMyWork(items) {
|
||||
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
buildMyWork.filterMyWork = filterMyWork;
|
||||
buildMyWork.createCompletedFiledReview = createCompletedFiledReview;
|
||||
buildMyWork.agendaMyWork = agendaMyWork;
|
||||
buildMyWork.milestoneLanes = milestoneLanes;
|
||||
buildMyWork.createWorkSession = createWorkSession;
|
||||
|
||||
Reference in new issue
Block a user