idea-manager 0.8.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/task/TaskDetail.tsx +119 -109
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useState, useEffect, useCallback } from 'react';
|
|
3
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
4
4
|
import type { ITask, TaskStatus, ItemPriority } from '@/types';
|
|
5
5
|
import StatusFlow from './StatusFlow';
|
|
6
6
|
import PromptEditor from './PromptEditor';
|
|
@@ -24,20 +24,15 @@ export default function TaskDetail({
|
|
|
24
24
|
const [promptContent, setPromptContent] = useState('');
|
|
25
25
|
const [refining, setRefining] = useState(false);
|
|
26
26
|
const [editingTitle, setEditingTitle] = useState(false);
|
|
27
|
-
const [
|
|
27
|
+
const [showPromptModal, setShowPromptModal] = useState(false);
|
|
28
28
|
|
|
29
29
|
const basePath = `/api/projects/${projectId}/sub-projects/${subProjectId}/tasks/${task.id}`;
|
|
30
|
-
|
|
31
|
-
// Auto-show chat when task is being executed by watcher
|
|
32
|
-
useEffect(() => {
|
|
33
|
-
if (task.status === 'testing') setShowChat(true);
|
|
34
|
-
}, [task.status]);
|
|
30
|
+
const overlayRef = useRef<HTMLDivElement>(null);
|
|
35
31
|
|
|
36
32
|
// Load prompt
|
|
37
33
|
useEffect(() => {
|
|
38
34
|
setTitle(task.title);
|
|
39
35
|
setDescription(task.description);
|
|
40
|
-
setShowChat(task.status === 'testing');
|
|
41
36
|
fetch(`${basePath}/prompt`)
|
|
42
37
|
.then(r => r.json())
|
|
43
38
|
.then(data => setPromptContent(data.content || ''));
|
|
@@ -97,113 +92,128 @@ export default function TaskDetail({
|
|
|
97
92
|
|
|
98
93
|
return (
|
|
99
94
|
<div className="flex flex-col h-full">
|
|
100
|
-
{/*
|
|
101
|
-
<div className=
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
95
|
+
{/* Compact header: Title + Status + Actions */}
|
|
96
|
+
<div className="px-4 py-3 border-b border-border flex-shrink-0 space-y-2">
|
|
97
|
+
{/* Title */}
|
|
98
|
+
{editingTitle ? (
|
|
99
|
+
<input
|
|
100
|
+
value={title}
|
|
101
|
+
onChange={(e) => setTitle(e.target.value)}
|
|
102
|
+
onBlur={saveTitle}
|
|
103
|
+
onKeyDown={(e) => { if (e.key === 'Enter') saveTitle(); if (e.key === 'Escape') { setTitle(task.title); setEditingTitle(false); } }}
|
|
104
|
+
className="w-full bg-transparent text-lg font-semibold border-b border-primary
|
|
105
|
+
focus:outline-none pb-1 text-foreground"
|
|
106
|
+
autoFocus
|
|
107
|
+
/>
|
|
108
|
+
) : (
|
|
109
|
+
<h2
|
|
110
|
+
onClick={() => setEditingTitle(true)}
|
|
111
|
+
className="text-lg font-semibold cursor-text hover:text-primary transition-colors"
|
|
112
|
+
>
|
|
113
|
+
{task.title}
|
|
114
|
+
</h2>
|
|
115
|
+
)}
|
|
116
|
+
|
|
117
|
+
{/* Status + Priority + Today + Prompt + Delete */}
|
|
118
|
+
<div className="flex items-center gap-3 flex-wrap">
|
|
119
|
+
<StatusFlow status={task.status} onChange={(status: TaskStatus) => onUpdate({ status })} />
|
|
120
|
+
<div className="flex items-center gap-1">
|
|
121
|
+
{priorities.map(p => (
|
|
122
|
+
<button
|
|
123
|
+
key={p}
|
|
124
|
+
onClick={() => onUpdate({ priority: p })}
|
|
125
|
+
className={`px-2 py-0.5 text-xs rounded transition-colors ${
|
|
126
|
+
task.priority === p
|
|
127
|
+
? p === 'high' ? 'bg-destructive/20 text-destructive' : p === 'medium' ? 'bg-warning/20 text-warning' : 'bg-muted text-muted-foreground'
|
|
128
|
+
: 'text-muted-foreground/40 hover:text-muted-foreground'
|
|
129
|
+
}`}
|
|
119
130
|
>
|
|
120
|
-
{
|
|
121
|
-
</
|
|
122
|
-
)}
|
|
123
|
-
</div>
|
|
124
|
-
|
|
125
|
-
{/* Status + Priority + Today */}
|
|
126
|
-
<div className="flex items-center gap-4 flex-wrap">
|
|
127
|
-
<StatusFlow status={task.status} onChange={(status: TaskStatus) => onUpdate({ status })} />
|
|
128
|
-
<div className="flex items-center gap-1">
|
|
129
|
-
{priorities.map(p => (
|
|
130
|
-
<button
|
|
131
|
-
key={p}
|
|
132
|
-
onClick={() => onUpdate({ priority: p })}
|
|
133
|
-
className={`px-2.5 py-1 text-sm rounded transition-colors ${
|
|
134
|
-
task.priority === p
|
|
135
|
-
? p === 'high' ? 'bg-destructive/20 text-destructive' : p === 'medium' ? 'bg-warning/20 text-warning' : 'bg-muted text-muted-foreground'
|
|
136
|
-
: 'text-muted-foreground/40 hover:text-muted-foreground'
|
|
137
|
-
}`}
|
|
138
|
-
>
|
|
139
|
-
{p}
|
|
140
|
-
</button>
|
|
141
|
-
))}
|
|
142
|
-
</div>
|
|
143
|
-
<button
|
|
144
|
-
onClick={() => onUpdate({ is_today: !task.is_today })}
|
|
145
|
-
className={`text-sm px-2.5 py-1 rounded transition-colors ${
|
|
146
|
-
task.is_today
|
|
147
|
-
? 'bg-primary/20 text-primary'
|
|
148
|
-
: 'text-muted-foreground hover:text-foreground'
|
|
149
|
-
}`}
|
|
150
|
-
>
|
|
151
|
-
{task.is_today ? 'Today *' : 'Mark today'}
|
|
152
|
-
</button>
|
|
153
|
-
</div>
|
|
154
|
-
|
|
155
|
-
{/* Description */}
|
|
156
|
-
<div>
|
|
157
|
-
<textarea
|
|
158
|
-
value={description}
|
|
159
|
-
onChange={(e) => setDescription(e.target.value)}
|
|
160
|
-
onBlur={saveDescription}
|
|
161
|
-
placeholder="Background, conditions, notes..."
|
|
162
|
-
className="w-full bg-input border border-border rounded-lg px-3 py-2.5 text-sm
|
|
163
|
-
focus:border-primary focus:outline-none text-foreground resize-y min-h-[60px]
|
|
164
|
-
leading-relaxed"
|
|
165
|
-
rows={3}
|
|
166
|
-
/>
|
|
131
|
+
{p}
|
|
132
|
+
</button>
|
|
133
|
+
))}
|
|
167
134
|
</div>
|
|
135
|
+
<button
|
|
136
|
+
onClick={() => onUpdate({ is_today: !task.is_today })}
|
|
137
|
+
className={`text-xs px-2 py-0.5 rounded transition-colors ${
|
|
138
|
+
task.is_today
|
|
139
|
+
? 'bg-primary/20 text-primary'
|
|
140
|
+
: 'text-muted-foreground hover:text-foreground'
|
|
141
|
+
}`}
|
|
142
|
+
>
|
|
143
|
+
{task.is_today ? 'Today *' : 'Mark today'}
|
|
144
|
+
</button>
|
|
145
|
+
|
|
146
|
+
<span className="text-border">|</span>
|
|
147
|
+
|
|
148
|
+
<button
|
|
149
|
+
onClick={() => setShowPromptModal(true)}
|
|
150
|
+
className={`text-xs px-2 py-0.5 rounded transition-colors border ${
|
|
151
|
+
promptContent
|
|
152
|
+
? 'bg-accent/15 text-accent border-accent/30 hover:bg-accent/25'
|
|
153
|
+
: 'text-muted-foreground border-border hover:text-foreground hover:border-muted-foreground'
|
|
154
|
+
}`}
|
|
155
|
+
>
|
|
156
|
+
Prompt{promptContent ? ' *' : ''}
|
|
157
|
+
</button>
|
|
158
|
+
|
|
159
|
+
<button
|
|
160
|
+
onClick={onDelete}
|
|
161
|
+
className="text-xs text-muted-foreground hover:text-destructive transition-colors ml-auto"
|
|
162
|
+
>
|
|
163
|
+
Delete
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
168
166
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
167
|
+
{/* Description - compact */}
|
|
168
|
+
<textarea
|
|
169
|
+
value={description}
|
|
170
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
171
|
+
onBlur={saveDescription}
|
|
172
|
+
placeholder="Background, conditions, notes..."
|
|
173
|
+
className="w-full bg-input border border-border rounded-lg px-3 py-2 text-sm
|
|
174
|
+
focus:border-primary focus:outline-none text-foreground resize-none
|
|
175
|
+
leading-relaxed"
|
|
176
|
+
rows={2}
|
|
177
|
+
/>
|
|
178
|
+
</div>
|
|
176
179
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
: 'text-muted-foreground hover:text-foreground border-border hover:border-muted-foreground'
|
|
185
|
-
}`}
|
|
186
|
-
>
|
|
187
|
-
{showChat ? 'Hide AI Chat' : 'AI Chat'}
|
|
188
|
-
</button>
|
|
189
|
-
<button
|
|
190
|
-
onClick={onDelete}
|
|
191
|
-
className="text-xs text-muted-foreground hover:text-destructive transition-colors"
|
|
192
|
-
>
|
|
193
|
-
Delete task
|
|
194
|
-
</button>
|
|
195
|
-
</div>
|
|
196
|
-
</div>
|
|
180
|
+
{/* AI Chat - takes remaining space */}
|
|
181
|
+
<div className="flex-1 min-h-0">
|
|
182
|
+
<TaskChat
|
|
183
|
+
basePath={basePath}
|
|
184
|
+
taskStatus={task.status}
|
|
185
|
+
onApplyToPrompt={handleApplyToPrompt}
|
|
186
|
+
/>
|
|
197
187
|
</div>
|
|
198
188
|
|
|
199
|
-
{/*
|
|
200
|
-
{
|
|
201
|
-
<div
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
189
|
+
{/* Prompt Modal */}
|
|
190
|
+
{showPromptModal && (
|
|
191
|
+
<div
|
|
192
|
+
ref={overlayRef}
|
|
193
|
+
onClick={(e) => { if (e.target === overlayRef.current) setShowPromptModal(false); }}
|
|
194
|
+
className="fixed inset-0 z-50 flex items-center justify-center"
|
|
195
|
+
style={{ background: 'rgba(0,0,0,0.5)', backdropFilter: 'blur(2px)' }}
|
|
196
|
+
>
|
|
197
|
+
<div className="bg-card border border-border rounded-xl shadow-2xl shadow-black/40
|
|
198
|
+
w-full max-w-2xl mx-4 max-h-[80vh] flex flex-col animate-dialog-in">
|
|
199
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-border">
|
|
200
|
+
<h3 className="text-sm font-semibold text-foreground">Prompt</h3>
|
|
201
|
+
<button
|
|
202
|
+
onClick={() => setShowPromptModal(false)}
|
|
203
|
+
className="text-muted-foreground hover:text-foreground transition-colors text-sm"
|
|
204
|
+
>
|
|
205
|
+
Close
|
|
206
|
+
</button>
|
|
207
|
+
</div>
|
|
208
|
+
<div className="flex-1 overflow-y-auto p-5">
|
|
209
|
+
<PromptEditor
|
|
210
|
+
content={promptContent}
|
|
211
|
+
onSave={savePrompt}
|
|
212
|
+
onRefine={handleRefine}
|
|
213
|
+
refining={refining}
|
|
214
|
+
/>
|
|
215
|
+
</div>
|
|
216
|
+
</div>
|
|
207
217
|
</div>
|
|
208
218
|
)}
|
|
209
219
|
</div>
|