diff --git a/main.py b/main.py
index df85b4fd..0846156d 100644
--- a/main.py
+++ b/main.py
@@ -1368,6 +1368,7 @@ async def add_issue(issue: dict):
"category": issue.get("category", "other"),
"author": issue.get("author", "Anonymous").strip(),
"created": datetime.utcnow().isoformat(),
+ "resolved": False,
}
if not new_issue["title"]:
raise HTTPException(status_code=400, detail="Title is required")
@@ -1376,9 +1377,26 @@ async def add_issue(issue: dict):
return new_issue
+@app.patch("/issues/{issue_id}")
+async def update_issue(issue_id: str, update: dict):
+ """Update an issue (e.g. toggle resolved state)."""
+ issues = _load_issues()
+ found = None
+ for i in issues:
+ if i.get("id") == issue_id:
+ if "resolved" in update:
+ i["resolved"] = bool(update["resolved"])
+ found = i
+ break
+ if not found:
+ raise HTTPException(status_code=404, detail="Issue not found")
+ _save_issues(issues)
+ return found
+
+
@app.delete("/issues/{issue_id}")
async def delete_issue(issue_id: str):
- """Resolve (delete) an issue."""
+ """Permanently delete an issue."""
issues = _load_issues()
issues = [i for i in issues if i.get("id") != issue_id]
_save_issues(issues)
diff --git a/static/script.js b/static/script.js
index 952978ef..4b6f8882 100644
--- a/static/script.js
+++ b/static/script.js
@@ -4209,30 +4209,68 @@ async function refreshIssuesList(win) {
return;
}
+ // Sort: unresolved first, then resolved
+ issues.sort((a, b) => (a.resolved ? 1 : 0) - (b.resolved ? 1 : 0));
+
listDiv.innerHTML = '';
issues.forEach(issue => {
const cat = ISSUE_CATEGORIES[issue.category] || ISSUE_CATEGORIES.other;
const date = issue.created ? new Date(issue.created).toLocaleDateString('sv-SE') : '';
+ const isResolved = !!issue.resolved;
const row = document.createElement('div');
- row.className = 'issue-row';
+ row.className = 'issue-row' + (isResolved ? ' issue-resolved' : '');
row.innerHTML = `
${cat.label}
- ${issue.title}
+ ${issue.title}
${date}
- ${issue.description ? `${issue.description}
` : ''}
+ ${issue.description ? `${issue.description}
` : ''}
`;
- const resolveBtn = document.createElement('button');
- resolveBtn.textContent = '✓ Resolve';
- resolveBtn.className = 'issue-resolve-btn';
- resolveBtn.addEventListener('click', async () => {
- await fetch(`/issues/${issue.id}`, { method: 'DELETE' });
- refreshIssuesList(win);
- });
- row.querySelector('div').appendChild(resolveBtn);
+ const headerDiv = row.querySelector('div');
+
+ if (isResolved) {
+ // Reopen button
+ const reopenBtn = document.createElement('button');
+ reopenBtn.textContent = '↺ Reopen';
+ reopenBtn.className = 'issue-reopen-btn';
+ reopenBtn.addEventListener('click', async () => {
+ await fetch(`/issues/${issue.id}`, {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ resolved: false })
+ });
+ refreshIssuesList(win);
+ });
+ headerDiv.appendChild(reopenBtn);
+
+ // Delete button
+ const deleteBtn = document.createElement('button');
+ deleteBtn.textContent = '🗑 Delete';
+ deleteBtn.className = 'issue-delete-btn';
+ deleteBtn.addEventListener('click', async () => {
+ if (!confirm(`Delete issue "${issue.title}"?`)) return;
+ await fetch(`/issues/${issue.id}`, { method: 'DELETE' });
+ refreshIssuesList(win);
+ });
+ headerDiv.appendChild(deleteBtn);
+ } else {
+ // Resolve button (marks as resolved, doesn't delete)
+ const resolveBtn = document.createElement('button');
+ resolveBtn.textContent = '✓ Resolve';
+ resolveBtn.className = 'issue-resolve-btn';
+ resolveBtn.addEventListener('click', async () => {
+ await fetch(`/issues/${issue.id}`, {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ resolved: true })
+ });
+ refreshIssuesList(win);
+ });
+ headerDiv.appendChild(resolveBtn);
+ }
listDiv.appendChild(row);
});
diff --git a/static/style.css b/static/style.css
index 22812b01..61254ed8 100644
--- a/static/style.css
+++ b/static/style.css
@@ -2664,12 +2664,35 @@ table.ts-allegiance td:first-child {
.issue-row {
padding: 6px 8px;
border-bottom: 1px solid #333;
+ border-left: 3px solid transparent;
+ color: #ddd;
}
.issue-row:hover {
background: #1a1a2a;
}
+.issue-row .issue-description {
+ color: #aaa;
+ font-size: 0.75rem;
+ margin-top: 2px;
+ padding-left: 4px;
+}
+
+.issue-row.issue-resolved {
+ background: rgba(74, 170, 74, 0.12);
+ border-left-color: #4a4;
+}
+
+.issue-row.issue-resolved strong {
+ text-decoration: line-through;
+ color: #888;
+}
+
+.issue-row.issue-resolved .issue-description {
+ color: #666;
+}
+
.issue-category {
display: inline-block;
padding: 1px 6px;
@@ -2680,15 +2703,20 @@ table.ts-allegiance td:first-child {
text-transform: uppercase;
}
-.issue-resolve-btn {
+.issue-resolve-btn,
+.issue-reopen-btn,
+.issue-delete-btn {
padding: 1px 6px;
font-size: 0.65rem;
background: transparent;
- color: #4a4;
- border: 1px solid #4a4;
cursor: pointer;
border-radius: 3px;
- margin-left: 8px;
+ margin-left: 4px;
+}
+
+.issue-resolve-btn {
+ color: #4a4;
+ border: 1px solid #4a4;
}
.issue-resolve-btn:hover {
@@ -2696,6 +2724,26 @@ table.ts-allegiance td:first-child {
color: #fff;
}
+.issue-reopen-btn {
+ color: #88a;
+ border: 1px solid #88a;
+}
+
+.issue-reopen-btn:hover {
+ background: #88a;
+ color: #fff;
+}
+
+.issue-delete-btn {
+ color: #c44;
+ border: 1px solid #c44;
+}
+
+.issue-delete-btn:hover {
+ background: #c44;
+ color: #fff;
+}
+
.issues-form {
padding: 6px 8px;
border-top: 1px solid #444;