@ikenga/pkg-tasks 0.2.0 → 0.4.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.
@@ -5,17 +5,21 @@ import {
5
5
  cn,
6
6
  Icon,
7
7
  Button,
8
+ useState,
8
9
  useQuery,
9
10
  useMutation,
10
11
  useQueryClient,
11
12
  } from '../../lib/ui.js';
12
- import { getSupabase } from '../../lib/supabase.js';
13
13
  import { queryKeys } from '../../lib/query-keys.js';
14
14
  import {
15
15
  blockingTaskQuery,
16
+ reassignTask,
16
17
  subtasksQuery,
17
18
  taskDetailQuery,
19
+ updateTaskStatus,
18
20
  } from '../../lib/queries.js';
21
+ import { assigneeOptions } from '../../lib/assignees.js';
22
+ import { getContext } from '../../lib/bridge.js';
19
23
  import {
20
24
  assigneeIsAgent,
21
25
  autoCloseSignal,
@@ -49,20 +53,29 @@ export function TaskDetailPane({ taskId, density = 'full', onNavigateTask }) {
49
53
  const updateStatus = useMutation({
50
54
  /** @param {TaskStatus} status */
51
55
  mutationFn: async (status) => {
52
- /** @type {{ status: TaskStatus, completed_at?: string | null }} */
53
- const patch = { status };
54
- if (status === 'completed') patch.completed_at = new Date().toISOString();
55
- else if (task?.completed_at) patch.completed_at = null;
56
- const { error: e } = await getSupabase()
57
- .from('tasks')
58
- .update(patch)
59
- .eq('id', taskId);
60
- if (e) throw e;
56
+ await updateTaskStatus(taskId, status);
61
57
  },
62
58
  onSuccess: () =>
63
59
  void queryClient.invalidateQueries({ queryKey: queryKeys.tasks.all }),
64
60
  });
65
61
 
62
+ // Reassign — toggled open by the head's Reassign button (was dead until now).
63
+ const [reassignOpen, setReassignOpen] = useState(false);
64
+ const reassign = useMutation({
65
+ /** @param {string} value picked assigned_to ('' = unassign) */
66
+ mutationFn: async (value) => {
67
+ const picked = assigneeOptions(getContext()).find((o) => o.value === value);
68
+ await reassignTask(taskId, value || null, picked ? picked.type : null);
69
+ },
70
+ onSuccess: () => {
71
+ // tasks.all covers every list view (assigned_to is shown across them);
72
+ // detail covers this pane's own query.
73
+ void queryClient.invalidateQueries({ queryKey: queryKeys.tasks.all });
74
+ void queryClient.invalidateQueries({ queryKey: queryKeys.tasks.detail(taskId) });
75
+ setReassignOpen(false);
76
+ },
77
+ });
78
+
66
79
  if (isLoading) {
67
80
  return html`
68
81
  <div class=${cn('tk-detail-pane', `is-${density}`)}>
@@ -100,13 +113,18 @@ export function TaskDetailPane({ taskId, density = 'full', onNavigateTask }) {
100
113
 
101
114
  return html`
102
115
  <div class=${cn('tk-detail-pane', `is-${density}`)}>
103
- <div class="tk-det-head">
104
- <div class="tk-det-topline">
116
+ <div class="ip-head">
117
+ <div class="ip-topline">
105
118
  <span class="id">task · ${shortId(task.id)}</span>
106
119
  ${density === 'full' && html`
107
- <div class="tk-det-actions">
120
+ <div class="ip-topline-actions">
108
121
  <${Button} variant="outline" size="sm" type="button">Reschedule</${Button}>
109
- <${Button} variant="outline" size="sm" type="button">Reassign</${Button}>
122
+ <${Button}
123
+ variant=${reassignOpen ? 'default' : 'outline'}
124
+ size="sm"
125
+ type="button"
126
+ onClick=${() => setReassignOpen((v) => !v)}
127
+ >Reassign</${Button}>
110
128
  <${Button}
111
129
  size="sm"
112
130
  type="button"
@@ -119,7 +137,60 @@ export function TaskDetailPane({ taskId, density = 'full', onNavigateTask }) {
119
137
  `}
120
138
  </div>
121
139
 
122
- <h2 class="tk-det-title">${task.title}</h2>
140
+ ${density === 'full' && reassignOpen && html`
141
+ <div
142
+ style=${{
143
+ display: 'flex',
144
+ alignItems: 'center',
145
+ gap: 8,
146
+ marginTop: 8,
147
+ padding: '8px 10px',
148
+ background: 'var(--bg-sunken)',
149
+ border: '1px solid var(--border-soft)',
150
+ borderRadius: 'var(--radius-sm)',
151
+ }}
152
+ >
153
+ <span
154
+ style=${{
155
+ fontFamily: 'var(--font-mono)',
156
+ fontSize: 10.5,
157
+ color: 'var(--fg-faint)',
158
+ letterSpacing: '0.06em',
159
+ textTransform: 'uppercase',
160
+ }}
161
+ >Assign to</span>
162
+ <select
163
+ value=${task.assigned_to ?? ''}
164
+ disabled=${reassign.isPending}
165
+ onChange=${(e) => reassign.mutate(e.target.value)}
166
+ style=${{
167
+ height: 26,
168
+ fontSize: 11.5,
169
+ padding: '0 6px',
170
+ background: 'var(--bg-base)',
171
+ border: '1px solid var(--border)',
172
+ borderRadius: 'var(--radius-xs)',
173
+ color: 'var(--fg)',
174
+ fontFamily: 'inherit',
175
+ }}
176
+ >
177
+ <option value="">Unassigned</option>
178
+ ${task.assigned_to && !assigneeOptions(getContext()).some((o) => o.value === task.assigned_to) &&
179
+ html`<option value=${task.assigned_to}>${task.assigned_to} (current)</option>`}
180
+ ${assigneeOptions(getContext()).map(
181
+ (o) => html`<option key=${o.value} value=${o.value}>${o.label}</option>`,
182
+ )}
183
+ </select>
184
+ ${reassign.isPending && html`<${Icon} name="loader" size=${12} className="tk-spin" />`}
185
+ ${reassign.isError && html`
186
+ <span style=${{ color: 'var(--danger)', fontSize: 11 }}>
187
+ ${(/** @type {Error} */ (reassign.error)).message}
188
+ </span>
189
+ `}
190
+ </div>
191
+ `}
192
+
193
+ <h2 class="ip-title">${task.title}</h2>
123
194
 
124
195
  <div class="tk-det-meta-row">
125
196
  <span class=${cn('tk-badge', statusClass(task.status))}>
@@ -158,7 +229,7 @@ export function TaskDetailPane({ taskId, density = 'full', onNavigateTask }) {
158
229
  </div>
159
230
  </div>
160
231
 
161
- <div class="tk-det-body">
232
+ <div class="ip-body">
162
233
  ${autoClosed && task.outcome_notes && html`
163
234
  <div class="tk-evidence">
164
235
  <div class="tk-evidence-head">
@@ -204,7 +275,7 @@ export function TaskDetailPane({ taskId, density = 'full', onNavigateTask }) {
204
275
  ${task.description && html`
205
276
  <div>
206
277
  <div class="tk-section-label"><span>Description</span></div>
207
- <div class="tk-desc">${task.description}</div>
278
+ <div class="ip-desc">${task.description}</div>
208
279
  </div>
209
280
  `}
210
281
 
@@ -236,8 +307,8 @@ export function TaskDetailPane({ taskId, density = 'full', onNavigateTask }) {
236
307
  ${task.progress_pct !== null && html`
237
308
  <dt>Progress</dt>
238
309
  <dd>
239
- <div class="tk-progress">
240
- <span style=${{ width: `${task.progress_pct}%` }}></span>
310
+ <div class="ip-progress">
311
+ <span class="ip-progress-fill" style=${{ width: `${task.progress_pct}%` }}></span>
241
312
  </div>
242
313
  <span style=${{ fontFamily: 'var(--font-mono)', fontSize: 10.5, color: 'var(--fg-muted)' }}>
243
314
  ${task.progress_pct}%
@@ -376,9 +447,9 @@ export function TaskDetailPane({ taskId, density = 'full', onNavigateTask }) {
376
447
  </div>
377
448
 
378
449
  ${density !== 'full' && html`
379
- <div class="tk-action-bar">
450
+ <div class="ip-action-bar">
380
451
  <${Button} variant="outline" size="sm" type="button">Reschedule</${Button}>
381
- <span class="spacer"></span>
452
+ <span class="ip-action-bar-spacer"></span>
382
453
  <${Button}
383
454
  size="sm"
384
455
  type="button"
@@ -29,12 +29,12 @@ export function TaskRow({ task, selected, onSelect }) {
29
29
  return html`
30
30
  <button
31
31
  type="button"
32
- class=${cn('tk-row', selected && 'is-on', autoClosed && 'is-completed')}
32
+ class=${cn('dense-row dense-row--task', selected && 'is-on', autoClosed && 'is-completed')}
33
33
  onClick=${() => onSelect(task.id)}
34
34
  >
35
- <span class=${cn('pri-dot', priorityClass(task.priority))}></span>
36
- <div class="body">
37
- <div class="title">${task.title}</div>
35
+ <span class=${cn('dense-row-dot', priorityClass(task.priority))}></span>
36
+ <div class="dense-row-body">
37
+ <div class="dense-row-title">${task.title}</div>
38
38
  <div class="meta">
39
39
  <span class=${cn('tk-badge', statusClass(task.status))}>
40
40
  <span class="dot"></span>
@@ -62,8 +62,8 @@ export function TaskRow({ task, selected, onSelect }) {
62
62
  `}
63
63
  </div>
64
64
  </div>
65
- <div class="right">
66
- <span class=${cn('due', due.cls)}>${due.label}</span>
65
+ <div class="dense-row-right">
66
+ <span class=${cn('dense-row-due', due.cls)}>${due.label}</span>
67
67
  </div>
68
68
  </button>
69
69
  `;