From 851fc5f7cdc93601ca1206063cd7c1e8b3a4d0eb Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 12 Apr 2026 22:41:13 +0200 Subject: [PATCH] =?UTF-8?q?fix(v2):=20issues=20board=20=E2=80=94=20full=20?= =?UTF-8?q?v1=20feature=20parity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now matches v1's Issues Board exactly: - Category badges with v1's exact colors (Plugin=#8844cc, Overlord=#4488cc, Nav=#44aa44, Macro=#cc8844, Other=#888888) - Author name + date per issue - Action buttons: - Unresolved: โœ“ Resolve + โœŽ Edit - Resolved: โ†ป Reopen + ๐Ÿ—‘ Delete (with confirm dialog) + โœŽ Edit - Inline edit form: editable title + category dropdown + description textarea + Save/Cancel buttons (toggle with โœŽ Edit click) - Comments section per issue: always visible inline - Comment list with author (blue), date, text - Add comment input with Post button (Enter key supported) - "No comments yet" placeholder - Add issue form at bottom: title input + category select + description textarea + Add button - Resolved issues dimmed to 55% opacity, sorted below unresolved - All API calls use /api prefix with credentials Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/components/windows/IssuesWindow.tsx | 189 +++++++++++++----- static/v2/assets/index-B6P2bla9.js | 120 +++++++++++ static/v2/assets/index-CWdy6tT9.js | 120 ----------- static/v2/index.html | 2 +- 4 files changed, 264 insertions(+), 167 deletions(-) create mode 100644 static/v2/assets/index-B6P2bla9.js delete mode 100644 static/v2/assets/index-CWdy6tT9.js diff --git a/frontend/src/components/windows/IssuesWindow.tsx b/frontend/src/components/windows/IssuesWindow.tsx index 35d56c21..d48d6ece 100644 --- a/frontend/src/components/windows/IssuesWindow.tsx +++ b/frontend/src/components/windows/IssuesWindow.tsx @@ -2,23 +2,37 @@ import React, { useEffect, useState, useCallback } from 'react'; import { DraggableWindow } from './DraggableWindow'; import { apiFetch } from '../../api/client'; +interface Comment { id: number; text: string; author: string; created: string; } interface Issue { id: number; title: string; description: string; category: string; - created: string; resolved: boolean; author: string; - comments?: Array<{ id: number; text: string; author: string; created: string }>; + created: string; resolved: boolean; author: string; comments?: Comment[]; } interface Props { id: string; zIndex: number; } -const CAT_COLORS: Record = { - plugin: '#4488ff', overlord: '#44cc44', nav: '#ffaa00', macro: '#cc44cc', other: '#888', +const CATS: Record = { + plugin: { label: 'Plugin', color: '#8844cc' }, + overlord: { label: 'Overlord', color: '#4488cc' }, + nav: { label: 'Nav', color: '#44aa44' }, + macro: { label: 'Macro', color: '#cc8844' }, + other: { label: 'Other', color: '#888888' }, }; +const inputStyle: React.CSSProperties = { padding: '3px 6px', fontSize: '0.8rem', border: '1px solid #555', background: '#2a2a2a', color: '#ddd', borderRadius: 0 }; +const selectStyle: React.CSSProperties = { ...inputStyle, fontSize: '0.75rem' }; +const btnBlue: React.CSSProperties = { padding: '4px 12px', background: '#4a80c0', color: '#fff', border: '1px solid #336699', cursor: 'pointer', fontSize: '0.75rem' }; +const btnGray: React.CSSProperties = { padding: '3px 8px', background: '#444', color: '#ccc', border: '1px solid #555', cursor: 'pointer', fontSize: '0.7rem' }; + export const IssuesWindow: React.FC = ({ id, zIndex }) => { const [issues, setIssues] = useState([]); const [title, setTitle] = useState(''); const [desc, setDesc] = useState(''); const [category, setCategory] = useState('plugin'); + const [editingId, setEditingId] = useState(null); + const [editTitle, setEditTitle] = useState(''); + const [editDesc, setEditDesc] = useState(''); + const [editCat, setEditCat] = useState(''); + const [commentText, setCommentText] = useState>({}); const refresh = useCallback(async () => { try { @@ -29,64 +43,147 @@ export const IssuesWindow: React.FC = ({ id, zIndex }) => { useEffect(() => { refresh(); }, [refresh]); - const addIssue = async () => { - if (!title.trim()) return; - await fetch('/api/issues', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', - body: JSON.stringify({ title: title.trim(), description: desc.trim(), category }) }); - setTitle(''); setDesc(''); + const apiCall = async (url: string, opts: RequestInit) => { + await fetch(`/api${url}`, { ...opts, credentials: 'include', headers: { 'Content-Type': 'application/json', ...opts.headers } }); refresh(); }; - const toggleResolve = async (issue: Issue) => { - await fetch(`/api/issues/${issue.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, credentials: 'include', - body: JSON.stringify({ resolved: !issue.resolved }) }); - refresh(); + const addIssue = async () => { + if (!title.trim()) return; + await apiCall('/issues', { method: 'POST', body: JSON.stringify({ title: title.trim(), description: desc.trim(), category }) }); + setTitle(''); setDesc(''); + }; + + const startEdit = (issue: Issue) => { + if (editingId === issue.id) { setEditingId(null); return; } + setEditingId(issue.id); + setEditTitle(issue.title); + setEditDesc(issue.description || ''); + setEditCat(issue.category || 'other'); + }; + + const saveEdit = async (issueId: number) => { + if (!editTitle.trim()) return; + await apiCall(`/issues/${issueId}`, { method: 'PATCH', body: JSON.stringify({ title: editTitle.trim(), description: editDesc.trim(), category: editCat }) }); + setEditingId(null); + }; + + const addComment = async (issueId: number) => { + const text = (commentText[issueId] || '').trim(); + if (!text) return; + await apiCall(`/issues/${issueId}/comments`, { method: 'POST', body: JSON.stringify({ text }) }); + setCommentText(prev => ({ ...prev, [issueId]: '' })); }; return ( {/* Issue list */} -
- {issues.length === 0 ? ( -
No issues
- ) : issues.map(issue => ( -
-
- - {issue.category} - - {issue.title} - +
+ {issues.length === 0 && ( +
No open issues
+ )} + {issues.map(issue => { + const cat = CATS[issue.category] || CATS.other; + const date = issue.created ? new Date(issue.created).toLocaleDateString('sv-SE') : ''; + const comments = issue.comments || []; + return ( +
+ {/* Header */} +
+ {cat.label} + {issue.title} + by {issue.author || 'User'} + {date} +
+ + {/* Description */} + {issue.description &&
{issue.description}
} + + {/* Action buttons */} +
+ {issue.resolved ? ( + <> + + + + ) : ( + + )} + +
+ + {/* Inline edit form */} + {editingId === issue.id && ( +
+
+ setEditTitle(e.target.value)} style={{ ...inputStyle, flex: 1 }} /> + +
+
+