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 }} /> + +
+
+