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?: Comment[]; } interface Props { id: string; zIndex: number; } 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 { const data = await apiFetch<{ issues: Issue[] }>('/issues'); setIssues((data.issues ?? []).sort((a, b) => (a.resolved ? 1 : 0) - (b.resolved ? 1 : 0))); } catch { /* ignore */ } }, []); useEffect(() => { refresh(); }, [refresh]); const apiCall = async (url: string, opts: RequestInit) => { await fetch(`/api${url}`, { ...opts, credentials: 'include', headers: { 'Content-Type': 'application/json', ...opts.headers } }); 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 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 }} />