@fayz-ai/plugin-tasks 0.9.3 → 0.10.0
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/dist/components/TasksDrawer.d.ts +1 -1
- package/dist/components/TasksDrawer.d.ts.map +1 -1
- package/dist/components/TasksLauncher.d.ts +11 -0
- package/dist/components/TasksLauncher.d.ts.map +1 -0
- package/dist/context.d.ts +0 -11
- package/dist/context.d.ts.map +1 -1
- package/dist/data/registries.d.ts.map +1 -1
- package/dist/index.d.ts +5 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +128 -875
- package/dist/index.js.map +1 -1
- package/dist/migrations/index.d.ts +1 -0
- package/dist/migrations/index.d.ts.map +1 -1
- package/package.json +7 -7
- package/dist/components/TaskCard.d.ts +0 -10
- package/dist/components/TaskCard.d.ts.map +0 -1
- package/dist/components/TaskDetail.d.ts +0 -5
- package/dist/components/TaskDetail.d.ts.map +0 -1
- package/dist/components/TaskLabelBadge.d.ts +0 -9
- package/dist/components/TaskLabelBadge.d.ts.map +0 -1
- package/dist/components/TaskPriorityBadge.d.ts +0 -7
- package/dist/components/TaskPriorityBadge.d.ts.map +0 -1
- package/dist/components/TaskQuickAdd.d.ts +0 -5
- package/dist/components/TaskQuickAdd.d.ts.map +0 -1
- package/dist/components/TaskStatusGroup.d.ts +0 -7
- package/dist/components/TaskStatusGroup.d.ts.map +0 -1
- package/dist/components/TasksFilterBar.d.ts +0 -3
- package/dist/components/TasksFilterBar.d.ts.map +0 -1
- package/dist/components/TasksPanel.d.ts +0 -7
- package/dist/components/TasksPanel.d.ts.map +0 -1
- package/dist/components/TasksTopbarButton.d.ts +0 -15
- package/dist/components/TasksTopbarButton.d.ts.map +0 -1
- package/dist/data/capability.test.d.ts +0 -2
- package/dist/data/capability.test.d.ts.map +0 -1
- package/dist/data/mock.d.ts +0 -3
- package/dist/data/mock.d.ts.map +0 -1
- package/dist/data/supabase.d.ts +0 -3
- package/dist/data/supabase.d.ts.map +0 -1
- package/dist/data/tables.d.ts +0 -5
- package/dist/data/tables.d.ts.map +0 -1
- package/dist/data/types.d.ts +0 -13
- package/dist/data/types.d.ts.map +0 -1
- package/dist/lib/tenant.d.ts +0 -3
- package/dist/lib/tenant.d.ts.map +0 -1
- package/dist/store.d.ts +0 -27
- package/dist/store.d.ts.map +0 -1
- package/dist/types.d.ts +0 -68
- package/dist/types.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,375 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import { createStore } from 'zustand/vanilla';
|
|
1
|
+
import { registerTranslations, useTranslation } from '@fayz-ai/core';
|
|
2
|
+
import { getTasksProvider, registerNotesTasksAdapter, useTasksStore, NOTE_LABEL_PREFIX, useTenantPluginSettings, useRightRailStore, useWidgetSurface, useTasksSummaryLine, RightRailPage, TasksPanel } from '@fayz-ai/admin';
|
|
3
|
+
export { TaskCard, TaskDetail, TaskQuickAdd, TasksFilterBar, TasksPanel, createMockTasksProvider, createSupabaseTasksProvider, useTasksStore, useTasksSummaryLine } from '@fayz-ai/admin';
|
|
5
4
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { useEffect } from 'react';
|
|
6
|
+
import { CheckSquare } from 'lucide-react';
|
|
7
|
+
import { useLayoutStore, useRailRow, cn, SheetBody } from '@fayz-ai/ui';
|
|
9
8
|
|
|
10
9
|
// src/index.ts
|
|
11
|
-
var ctx = createPluginContext("TasksContextProvider");
|
|
12
|
-
var TasksContextProvider = ctx.ContextProvider;
|
|
13
|
-
ctx.useConfig;
|
|
14
|
-
ctx.useProvider;
|
|
15
|
-
var useTasksStore = ctx.useStore;
|
|
16
|
-
|
|
17
|
-
// src/data/mock.ts
|
|
18
|
-
function uid() {
|
|
19
|
-
return crypto.randomUUID?.() ?? String(Date.now());
|
|
20
|
-
}
|
|
21
|
-
function now() {
|
|
22
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
23
|
-
}
|
|
24
|
-
function today() {
|
|
25
|
-
return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
26
|
-
}
|
|
27
|
-
var SEED_LABELS = [
|
|
28
|
-
{ id: "lbl-bug", name: "Bug", color: "#ef4444", isActive: true, tenantId: "mock", createdAt: now() },
|
|
29
|
-
{ id: "lbl-feature", name: "Feature", color: "#3b82f6", isActive: true, tenantId: "mock", createdAt: now() },
|
|
30
|
-
{ id: "lbl-urgent", name: "Urgent", color: "#f59e0b", isActive: true, tenantId: "mock", createdAt: now() },
|
|
31
|
-
{ id: "lbl-personal", name: "Personal", color: "#8b5cf6", isActive: true, tenantId: "mock", createdAt: now() }
|
|
32
|
-
];
|
|
33
|
-
function makeSeedTasks() {
|
|
34
|
-
const base = {
|
|
35
|
-
tenantId: "mock",
|
|
36
|
-
description: null,
|
|
37
|
-
assignedToId: null,
|
|
38
|
-
assignedToName: null,
|
|
39
|
-
parentId: null,
|
|
40
|
-
labels: [],
|
|
41
|
-
createdById: null,
|
|
42
|
-
createdByName: null,
|
|
43
|
-
createdAt: now(),
|
|
44
|
-
updatedAt: now()
|
|
45
|
-
};
|
|
46
|
-
return [
|
|
47
|
-
{ ...base, id: uid(), title: "Setup payment gateway", status: "todo", priority: "high", dueDate: today(), position: 0 },
|
|
48
|
-
{ ...base, id: uid(), title: "Update menu photos", status: "todo", priority: "medium", dueDate: null, position: 1 },
|
|
49
|
-
{ ...base, id: uid(), title: "Review monthly report", status: "in_progress", priority: "medium", dueDate: null, position: 0 },
|
|
50
|
-
{ ...base, id: uid(), title: "Fix login bug", status: "in_progress", priority: "urgent", dueDate: today(), position: 1, labels: ["lbl-bug"] },
|
|
51
|
-
{ ...base, id: uid(), title: "Onboard new employee", status: "done", priority: "low", dueDate: null, position: 0 }
|
|
52
|
-
];
|
|
53
|
-
}
|
|
54
|
-
function matchesQuery(task, query) {
|
|
55
|
-
if (query.status) {
|
|
56
|
-
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
57
|
-
if (!statuses.includes(task.status)) return false;
|
|
58
|
-
}
|
|
59
|
-
if (query.priority) {
|
|
60
|
-
const priorities = Array.isArray(query.priority) ? query.priority : [query.priority];
|
|
61
|
-
if (!priorities.includes(task.priority)) return false;
|
|
62
|
-
}
|
|
63
|
-
if (query.assignedToId && task.assignedToId !== query.assignedToId) return false;
|
|
64
|
-
if (query.parentId !== void 0) {
|
|
65
|
-
if (query.parentId === null && task.parentId !== null) return false;
|
|
66
|
-
if (query.parentId !== null && task.parentId !== query.parentId) return false;
|
|
67
|
-
}
|
|
68
|
-
if (query.search) {
|
|
69
|
-
const s = query.search.toLowerCase();
|
|
70
|
-
if (!task.title.toLowerCase().includes(s)) return false;
|
|
71
|
-
}
|
|
72
|
-
if (query.overdue && task.dueDate) {
|
|
73
|
-
if (task.dueDate >= today()) return false;
|
|
74
|
-
}
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
function createMockTasksProvider() {
|
|
78
|
-
let tasks = makeSeedTasks();
|
|
79
|
-
const labels = [...SEED_LABELS];
|
|
80
|
-
return {
|
|
81
|
-
async getTasks(query) {
|
|
82
|
-
return tasks.filter((t) => matchesQuery(t, query)).sort((a, b) => a.position - b.position);
|
|
83
|
-
},
|
|
84
|
-
async getTaskById(id) {
|
|
85
|
-
return tasks.find((t) => t.id === id) ?? null;
|
|
86
|
-
},
|
|
87
|
-
async getSubtasks(parentId) {
|
|
88
|
-
return tasks.filter((t) => t.parentId === parentId).sort((a, b) => a.position - b.position);
|
|
89
|
-
},
|
|
90
|
-
async createTask(input) {
|
|
91
|
-
const statusTasks = tasks.filter((t) => t.status === (input.status ?? "todo") && t.parentId === (input.parentId ?? null));
|
|
92
|
-
const maxPos = statusTasks.reduce((max, t) => Math.max(max, t.position), -1);
|
|
93
|
-
const task = {
|
|
94
|
-
id: uid(),
|
|
95
|
-
title: input.title,
|
|
96
|
-
description: input.description ?? null,
|
|
97
|
-
status: input.status ?? "todo",
|
|
98
|
-
priority: input.priority ?? "medium",
|
|
99
|
-
dueDate: input.dueDate ?? null,
|
|
100
|
-
assignedToId: input.assignedToId ?? null,
|
|
101
|
-
assignedToName: null,
|
|
102
|
-
parentId: input.parentId ?? null,
|
|
103
|
-
labels: input.labels ?? [],
|
|
104
|
-
position: maxPos + 1,
|
|
105
|
-
createdById: null,
|
|
106
|
-
createdByName: null,
|
|
107
|
-
tenantId: "mock",
|
|
108
|
-
createdAt: now(),
|
|
109
|
-
updatedAt: now()
|
|
110
|
-
};
|
|
111
|
-
tasks.push(task);
|
|
112
|
-
return task;
|
|
113
|
-
},
|
|
114
|
-
async updateTask(id, input) {
|
|
115
|
-
const idx = tasks.findIndex((t) => t.id === id);
|
|
116
|
-
if (idx === -1) throw new Error("Task not found");
|
|
117
|
-
tasks[idx] = { ...tasks[idx], ...input, updatedAt: now() };
|
|
118
|
-
return tasks[idx];
|
|
119
|
-
},
|
|
120
|
-
async deleteTask(id) {
|
|
121
|
-
tasks = tasks.filter((t) => t.id !== id && t.parentId !== id);
|
|
122
|
-
},
|
|
123
|
-
async reorderTask(id, newPosition, status) {
|
|
124
|
-
const task = tasks.find((t) => t.id === id);
|
|
125
|
-
if (!task) return;
|
|
126
|
-
task.status = status;
|
|
127
|
-
task.position = newPosition;
|
|
128
|
-
task.updatedAt = now();
|
|
129
|
-
},
|
|
130
|
-
async getLabels() {
|
|
131
|
-
return labels.filter((l) => l.isActive);
|
|
132
|
-
},
|
|
133
|
-
async getSummary() {
|
|
134
|
-
const topLevel = tasks.filter((t) => t.parentId === null);
|
|
135
|
-
const d = today();
|
|
136
|
-
return {
|
|
137
|
-
total: topLevel.length,
|
|
138
|
-
todo: topLevel.filter((t) => t.status === "todo").length,
|
|
139
|
-
inProgress: topLevel.filter((t) => t.status === "in_progress").length,
|
|
140
|
-
done: topLevel.filter((t) => t.status === "done").length,
|
|
141
|
-
overdue: topLevel.filter((t) => t.dueDate != null && t.dueDate < d && t.status !== "done" && t.status !== "cancelled").length,
|
|
142
|
-
dueToday: topLevel.filter((t) => t.dueDate === d && t.status !== "done" && t.status !== "cancelled").length,
|
|
143
|
-
highPriority: topLevel.filter((t) => (t.priority === "high" || t.priority === "urgent") && t.status !== "done" && t.status !== "cancelled").length
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// src/data/tables.ts
|
|
150
|
-
var T = {
|
|
151
|
-
tasks: "plg_tasks_tasks",
|
|
152
|
-
labels: "plg_tasks_labels"
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
// src/data/supabase.ts
|
|
156
|
-
function getTenantId() {
|
|
157
|
-
return getActiveTenantId();
|
|
158
|
-
}
|
|
159
|
-
function getClient() {
|
|
160
|
-
const supabase = getSupabaseClientOptional();
|
|
161
|
-
if (!supabase) throw new Error("Supabase not initialized");
|
|
162
|
-
return supabase;
|
|
163
|
-
}
|
|
164
|
-
function snakeToCamel(obj) {
|
|
165
|
-
const result = {};
|
|
166
|
-
for (const [key, value] of Object.entries(obj)) {
|
|
167
|
-
result[key.replace(/_([a-z])/g, (_, c) => c.toUpperCase())] = value;
|
|
168
|
-
}
|
|
169
|
-
return result;
|
|
170
|
-
}
|
|
171
|
-
function toTask(row) {
|
|
172
|
-
const t = snakeToCamel(row);
|
|
173
|
-
if (!Array.isArray(t.labels)) t.labels = [];
|
|
174
|
-
return t;
|
|
175
|
-
}
|
|
176
|
-
function createSupabaseTasksProvider() {
|
|
177
|
-
return {
|
|
178
|
-
async getTasks(query) {
|
|
179
|
-
const tenantId = getTenantId();
|
|
180
|
-
if (!tenantId) return [];
|
|
181
|
-
let q = getClient().from(T.tasks).select("*").eq("tenant_id", tenantId);
|
|
182
|
-
if (query.parentId === null) {
|
|
183
|
-
q = q.is("parent_id", null);
|
|
184
|
-
} else if (query.parentId) {
|
|
185
|
-
q = q.eq("parent_id", query.parentId);
|
|
186
|
-
}
|
|
187
|
-
if (query.status) {
|
|
188
|
-
const statuses = Array.isArray(query.status) ? query.status : [query.status];
|
|
189
|
-
q = q.in("status", statuses);
|
|
190
|
-
}
|
|
191
|
-
if (query.priority) {
|
|
192
|
-
const priorities = Array.isArray(query.priority) ? query.priority : [query.priority];
|
|
193
|
-
q = q.in("priority", priorities);
|
|
194
|
-
}
|
|
195
|
-
if (query.assignedToId) {
|
|
196
|
-
q = q.eq("assigned_to_id", query.assignedToId);
|
|
197
|
-
}
|
|
198
|
-
if (query.search) {
|
|
199
|
-
q = q.ilike("title", `%${query.search}%`);
|
|
200
|
-
}
|
|
201
|
-
if (query.overdue) {
|
|
202
|
-
q = q.lt("due_date", (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)).not("status", "in", '("done","cancelled")');
|
|
203
|
-
}
|
|
204
|
-
q = q.order("position", { ascending: true }).order("created_at", { ascending: false });
|
|
205
|
-
const { data } = await q;
|
|
206
|
-
return (data ?? []).map(toTask);
|
|
207
|
-
},
|
|
208
|
-
async getTaskById(id) {
|
|
209
|
-
const { data } = await getClient().from(T.tasks).select("*").eq("id", id).maybeSingle();
|
|
210
|
-
return data ? toTask(data) : null;
|
|
211
|
-
},
|
|
212
|
-
async getSubtasks(parentId) {
|
|
213
|
-
const { data } = await getClient().from(T.tasks).select("*").eq("parent_id", parentId).order("position", { ascending: true });
|
|
214
|
-
return (data ?? []).map(toTask);
|
|
215
|
-
},
|
|
216
|
-
async createTask(input) {
|
|
217
|
-
const tenantId = getTenantId();
|
|
218
|
-
if (!tenantId) throw new Error("No active tenant");
|
|
219
|
-
const { data: maxData } = await getClient().from(T.tasks).select("position").eq("tenant_id", tenantId).eq("status", input.status ?? "todo").is("parent_id", input.parentId ?? null).order("position", { ascending: false }).limit(1);
|
|
220
|
-
const maxPos = maxData?.[0]?.position ?? -1;
|
|
221
|
-
const { data, error } = await getClient().from(T.tasks).insert({
|
|
222
|
-
tenant_id: tenantId,
|
|
223
|
-
title: input.title,
|
|
224
|
-
description: input.description ?? null,
|
|
225
|
-
status: input.status ?? "todo",
|
|
226
|
-
priority: input.priority ?? "medium",
|
|
227
|
-
due_date: input.dueDate ?? null,
|
|
228
|
-
assigned_to_id: input.assignedToId ?? null,
|
|
229
|
-
parent_id: input.parentId ?? null,
|
|
230
|
-
labels: input.labels ?? [],
|
|
231
|
-
position: maxPos + 1
|
|
232
|
-
}).select("*").single();
|
|
233
|
-
if (error) throw error;
|
|
234
|
-
return toTask(data);
|
|
235
|
-
},
|
|
236
|
-
async updateTask(id, input) {
|
|
237
|
-
const payload = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
238
|
-
if (input.title !== void 0) payload.title = input.title;
|
|
239
|
-
if (input.description !== void 0) payload.description = input.description;
|
|
240
|
-
if (input.status !== void 0) payload.status = input.status;
|
|
241
|
-
if (input.priority !== void 0) payload.priority = input.priority;
|
|
242
|
-
if (input.dueDate !== void 0) payload.due_date = input.dueDate;
|
|
243
|
-
if (input.assignedToId !== void 0) payload.assigned_to_id = input.assignedToId;
|
|
244
|
-
if (input.labels !== void 0) payload.labels = input.labels;
|
|
245
|
-
if (input.position !== void 0) payload.position = input.position;
|
|
246
|
-
const { data, error } = await getClient().from(T.tasks).update(payload).eq("id", id).select("*").single();
|
|
247
|
-
if (error) throw error;
|
|
248
|
-
return toTask(data);
|
|
249
|
-
},
|
|
250
|
-
async deleteTask(id) {
|
|
251
|
-
await getClient().from(T.tasks).delete().eq("id", id);
|
|
252
|
-
},
|
|
253
|
-
async reorderTask(id, newPosition, status) {
|
|
254
|
-
await getClient().from(T.tasks).update({ position: newPosition, status, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
|
|
255
|
-
},
|
|
256
|
-
async getLabels() {
|
|
257
|
-
const tenantId = getTenantId();
|
|
258
|
-
if (!tenantId) return [];
|
|
259
|
-
const { data } = await getClient().from(T.labels).select("*").eq("tenant_id", tenantId).eq("is_active", true).order("name");
|
|
260
|
-
return (data ?? []).map((r) => snakeToCamel(r));
|
|
261
|
-
},
|
|
262
|
-
async getSummary() {
|
|
263
|
-
const tenantId = getTenantId();
|
|
264
|
-
if (!tenantId) return { total: 0, todo: 0, inProgress: 0, done: 0, overdue: 0, dueToday: 0, highPriority: 0 };
|
|
265
|
-
const { data } = await getClient().from(T.tasks).select("status, priority, due_date").eq("tenant_id", tenantId).is("parent_id", null);
|
|
266
|
-
const tasks = data ?? [];
|
|
267
|
-
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
268
|
-
return {
|
|
269
|
-
total: tasks.length,
|
|
270
|
-
todo: tasks.filter((t) => t.status === "todo").length,
|
|
271
|
-
inProgress: tasks.filter((t) => t.status === "in_progress").length,
|
|
272
|
-
done: tasks.filter((t) => t.status === "done").length,
|
|
273
|
-
overdue: tasks.filter((t) => t.due_date && t.due_date < today2 && t.status !== "done" && t.status !== "cancelled").length,
|
|
274
|
-
dueToday: tasks.filter((t) => t.due_date === today2 && t.status !== "done" && t.status !== "cancelled").length,
|
|
275
|
-
highPriority: tasks.filter((t) => (t.priority === "high" || t.priority === "urgent") && t.status !== "done" && t.status !== "cancelled").length
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
function createTasksStore(provider) {
|
|
281
|
-
return createStore((set, get) => ({
|
|
282
|
-
tasks: [],
|
|
283
|
-
tasksLoading: false,
|
|
284
|
-
labels: [],
|
|
285
|
-
summary: null,
|
|
286
|
-
summaryLoading: false,
|
|
287
|
-
drawerOpen: false,
|
|
288
|
-
selectedTaskId: null,
|
|
289
|
-
activeFilter: "all",
|
|
290
|
-
// ----- Data actions -----
|
|
291
|
-
async fetchTasks(query) {
|
|
292
|
-
return dedup("tasks:list", async () => {
|
|
293
|
-
set({ tasksLoading: true });
|
|
294
|
-
try {
|
|
295
|
-
const filter = get().activeFilter;
|
|
296
|
-
const q = {
|
|
297
|
-
parentId: null,
|
|
298
|
-
...query,
|
|
299
|
-
...filter !== "all" ? { status: filter } : {}
|
|
300
|
-
};
|
|
301
|
-
const tasks = await provider.getTasks(q);
|
|
302
|
-
set({ tasks, tasksLoading: false });
|
|
303
|
-
} catch {
|
|
304
|
-
set({ tasksLoading: false });
|
|
305
|
-
}
|
|
306
|
-
});
|
|
307
|
-
},
|
|
308
|
-
async fetchLabels() {
|
|
309
|
-
return dedup("tasks:labels", async () => {
|
|
310
|
-
try {
|
|
311
|
-
const labels = await provider.getLabels();
|
|
312
|
-
set({ labels });
|
|
313
|
-
} catch {
|
|
314
|
-
}
|
|
315
|
-
});
|
|
316
|
-
},
|
|
317
|
-
async fetchSummary() {
|
|
318
|
-
return dedup("tasks:summary", async () => {
|
|
319
|
-
set({ summaryLoading: true });
|
|
320
|
-
try {
|
|
321
|
-
const summary = await provider.getSummary();
|
|
322
|
-
set({ summary, summaryLoading: false });
|
|
323
|
-
} catch {
|
|
324
|
-
set({ summaryLoading: false });
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
},
|
|
328
|
-
async createTask(input) {
|
|
329
|
-
const task = await provider.createTask(input);
|
|
330
|
-
await get().fetchTasks();
|
|
331
|
-
await get().fetchSummary();
|
|
332
|
-
return task;
|
|
333
|
-
},
|
|
334
|
-
async updateTask(id, input) {
|
|
335
|
-
const task = await provider.updateTask(id, input);
|
|
336
|
-
set((s) => ({ tasks: s.tasks.map((t) => t.id === id ? task : t) }));
|
|
337
|
-
await get().fetchSummary();
|
|
338
|
-
return task;
|
|
339
|
-
},
|
|
340
|
-
async deleteTask(id) {
|
|
341
|
-
await provider.deleteTask(id);
|
|
342
|
-
set((s) => ({ tasks: s.tasks.filter((t) => t.id !== id), selectedTaskId: null }));
|
|
343
|
-
await get().fetchSummary();
|
|
344
|
-
},
|
|
345
|
-
async reorderTask(id, newPosition, status) {
|
|
346
|
-
await provider.reorderTask(id, newPosition, status);
|
|
347
|
-
await get().fetchTasks();
|
|
348
|
-
},
|
|
349
|
-
// ----- UI actions -----
|
|
350
|
-
openDrawer() {
|
|
351
|
-
set({ drawerOpen: true });
|
|
352
|
-
get().fetchTasks();
|
|
353
|
-
get().fetchLabels();
|
|
354
|
-
get().fetchSummary();
|
|
355
|
-
},
|
|
356
|
-
closeDrawer() {
|
|
357
|
-
set({ drawerOpen: false, selectedTaskId: null });
|
|
358
|
-
},
|
|
359
|
-
toggleDrawer() {
|
|
360
|
-
const { drawerOpen } = get();
|
|
361
|
-
if (drawerOpen) get().closeDrawer();
|
|
362
|
-
else get().openDrawer();
|
|
363
|
-
},
|
|
364
|
-
selectTask(id) {
|
|
365
|
-
set({ selectedTaskId: id });
|
|
366
|
-
},
|
|
367
|
-
setFilter(filter) {
|
|
368
|
-
set({ activeFilter: filter });
|
|
369
|
-
get().fetchTasks();
|
|
370
|
-
}
|
|
371
|
-
}));
|
|
372
|
-
}
|
|
373
10
|
|
|
374
11
|
// src/locales/en.ts
|
|
375
12
|
var en = {
|
|
@@ -466,6 +103,7 @@ var tasksLocales = {
|
|
|
466
103
|
};
|
|
467
104
|
|
|
468
105
|
// src/data/registries.ts
|
|
106
|
+
var LABELS_TABLE = "plg_tasks_labels";
|
|
469
107
|
var taskLabelEntity = {
|
|
470
108
|
name: "Task Label",
|
|
471
109
|
namePlural: "Task Labels",
|
|
@@ -477,7 +115,7 @@ var taskLabelEntity = {
|
|
|
477
115
|
{ key: "color", label: "Color", type: "text", showInTable: true, defaultValue: "#6366f1" },
|
|
478
116
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
479
117
|
],
|
|
480
|
-
data: { table:
|
|
118
|
+
data: { table: LABELS_TABLE, tenantScoped: true }
|
|
481
119
|
};
|
|
482
120
|
var tasksRegistries = [
|
|
483
121
|
{
|
|
@@ -524,516 +162,86 @@ function TasksGeneralSettings() {
|
|
|
524
162
|
] }) })
|
|
525
163
|
] });
|
|
526
164
|
}
|
|
527
|
-
|
|
528
|
-
{ key: "all", labelKey: "tasks.filter.all" },
|
|
529
|
-
{ key: "todo", labelKey: "tasks.filter.todo" },
|
|
530
|
-
{ key: "in_progress", labelKey: "tasks.filter.inProgress" },
|
|
531
|
-
{ key: "done", labelKey: "tasks.filter.done" }
|
|
532
|
-
];
|
|
533
|
-
function TasksFilterBar() {
|
|
534
|
-
const t = useTranslation();
|
|
535
|
-
const activeFilter = useTasksStore((s) => s.activeFilter);
|
|
536
|
-
const setFilter = useTasksStore((s) => s.setFilter);
|
|
537
|
-
const summary = useTasksStore((s) => s.summary);
|
|
538
|
-
const getCount = (key) => {
|
|
539
|
-
if (!summary) return void 0;
|
|
540
|
-
switch (key) {
|
|
541
|
-
case "all":
|
|
542
|
-
return summary.total;
|
|
543
|
-
case "todo":
|
|
544
|
-
return summary.todo;
|
|
545
|
-
case "in_progress":
|
|
546
|
-
return summary.inProgress;
|
|
547
|
-
case "done":
|
|
548
|
-
return summary.done;
|
|
549
|
-
default:
|
|
550
|
-
return void 0;
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 px-3 py-2 border-b overflow-x-auto", children: FILTERS.map(({ key, labelKey }) => {
|
|
554
|
-
const active = activeFilter === key;
|
|
555
|
-
const count = getCount(key);
|
|
556
|
-
return /* @__PURE__ */ jsxs(
|
|
557
|
-
"button",
|
|
558
|
-
{
|
|
559
|
-
type: "button",
|
|
560
|
-
onClick: () => setFilter(key),
|
|
561
|
-
className: `inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium transition-colors whitespace-nowrap ${active ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"}`,
|
|
562
|
-
children: [
|
|
563
|
-
t(labelKey),
|
|
564
|
-
count != null && /* @__PURE__ */ jsx("span", { className: `text-[10px] ${active ? "text-primary-foreground/80" : "text-muted-foreground"}`, children: count })
|
|
565
|
-
]
|
|
566
|
-
},
|
|
567
|
-
key
|
|
568
|
-
);
|
|
569
|
-
}) });
|
|
570
|
-
}
|
|
571
|
-
function TaskQuickAdd({ parentId }) {
|
|
165
|
+
function TasksDrawer() {
|
|
572
166
|
const t = useTranslation();
|
|
573
|
-
const
|
|
574
|
-
const
|
|
575
|
-
const
|
|
576
|
-
const handleSubmit = useCallback(async (e) => {
|
|
577
|
-
e.preventDefault();
|
|
578
|
-
const trimmed = title.trim();
|
|
579
|
-
if (!trimmed || loading) return;
|
|
580
|
-
setLoading(true);
|
|
581
|
-
try {
|
|
582
|
-
await createTask({
|
|
583
|
-
title: trimmed,
|
|
584
|
-
parentId: parentId ?? null
|
|
585
|
-
});
|
|
586
|
-
setTitle("");
|
|
587
|
-
} finally {
|
|
588
|
-
setLoading(false);
|
|
589
|
-
}
|
|
590
|
-
}, [title, loading, createTask, parentId]);
|
|
591
|
-
return /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, className: "flex items-center gap-2 px-3 py-2 border-b", children: [
|
|
592
|
-
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
|
|
593
|
-
/* @__PURE__ */ jsx(
|
|
594
|
-
"input",
|
|
595
|
-
{
|
|
596
|
-
type: "text",
|
|
597
|
-
value: title,
|
|
598
|
-
onChange: (e) => setTitle(e.target.value),
|
|
599
|
-
placeholder: parentId ? t("tasks.detail.addSubtask") : t("tasks.quickAdd.placeholder"),
|
|
600
|
-
className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground/60",
|
|
601
|
-
disabled: loading
|
|
602
|
-
}
|
|
603
|
-
)
|
|
604
|
-
] });
|
|
605
|
-
}
|
|
606
|
-
var PRIORITY_CONFIG = {
|
|
607
|
-
low: { color: "text-muted-foreground", bg: "bg-muted", icon: ArrowDown },
|
|
608
|
-
medium: { color: "text-info-soft-foreground", bg: "bg-info-soft", icon: Minus },
|
|
609
|
-
high: { color: "text-warning-soft-foreground", bg: "bg-warning-soft", icon: ArrowUp },
|
|
610
|
-
urgent: { color: "text-destructive-soft-foreground", bg: "bg-destructive-soft", icon: AlertTriangle }
|
|
611
|
-
};
|
|
612
|
-
function TaskPriorityBadge({ priority, showLabel }) {
|
|
613
|
-
const cfg = PRIORITY_CONFIG[priority];
|
|
614
|
-
const Icon = cfg.icon;
|
|
615
|
-
return /* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-medium ${cfg.color} ${cfg.bg}`, children: [
|
|
616
|
-
/* @__PURE__ */ jsx(Icon, { className: "h-3 w-3" }),
|
|
617
|
-
showLabel && /* @__PURE__ */ jsx("span", { className: "capitalize", children: priority })
|
|
618
|
-
] });
|
|
619
|
-
}
|
|
620
|
-
function TaskLabelBadge({ labelId }) {
|
|
621
|
-
const labels = useTasksStore((s) => s.labels);
|
|
622
|
-
const label = labels.find((l) => l.id === labelId);
|
|
623
|
-
if (!label) return null;
|
|
624
|
-
return /* @__PURE__ */ jsx(
|
|
625
|
-
"span",
|
|
626
|
-
{
|
|
627
|
-
className: "inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium leading-none",
|
|
628
|
-
style: {
|
|
629
|
-
backgroundColor: label.color + "20",
|
|
630
|
-
color: label.color
|
|
631
|
-
},
|
|
632
|
-
children: label.name
|
|
633
|
-
}
|
|
634
|
-
);
|
|
635
|
-
}
|
|
636
|
-
function TaskLabelBadges({ labelIds, max = 2 }) {
|
|
637
|
-
if (labelIds.length === 0) return null;
|
|
638
|
-
const visible = labelIds.slice(0, max);
|
|
639
|
-
const overflow = labelIds.length - max;
|
|
640
|
-
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1", children: [
|
|
641
|
-
visible.map((id) => /* @__PURE__ */ jsx(TaskLabelBadge, { labelId: id }, id)),
|
|
642
|
-
overflow > 0 && /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
|
|
643
|
-
"+",
|
|
644
|
-
overflow
|
|
645
|
-
] })
|
|
646
|
-
] });
|
|
647
|
-
}
|
|
648
|
-
function isOverdue(dueDate) {
|
|
649
|
-
if (!dueDate) return false;
|
|
650
|
-
return dueDate < (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
651
|
-
}
|
|
652
|
-
function isToday(dueDate) {
|
|
653
|
-
if (!dueDate) return false;
|
|
654
|
-
return dueDate === (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
655
|
-
}
|
|
656
|
-
function formatDueDate(dueDate) {
|
|
657
|
-
const d = /* @__PURE__ */ new Date(dueDate + "T00:00:00");
|
|
658
|
-
return d.toLocaleDateString(void 0, { month: "short", day: "numeric" });
|
|
659
|
-
}
|
|
660
|
-
function TaskCard({ task, subtaskCount }) {
|
|
661
|
-
const selectTask = useTasksStore((s) => s.selectTask);
|
|
662
|
-
const updateTask = useTasksStore((s) => s.updateTask);
|
|
663
|
-
const isDone = task.status === "done";
|
|
664
|
-
const overdue = !isDone && isOverdue(task.dueDate);
|
|
665
|
-
const dueToday = !isDone && isToday(task.dueDate);
|
|
666
|
-
const handleCheck = (e) => {
|
|
667
|
-
e.stopPropagation();
|
|
668
|
-
updateTask(task.id, { status: isDone ? "todo" : "done" });
|
|
669
|
-
};
|
|
167
|
+
const drawerOpen = useTasksStore((s) => s.drawerOpen);
|
|
168
|
+
const closeDrawer = useTasksStore((s) => s.closeDrawer);
|
|
169
|
+
const summaryLine = useTasksSummaryLine();
|
|
670
170
|
return /* @__PURE__ */ jsxs(
|
|
671
|
-
|
|
171
|
+
RightRailPage,
|
|
672
172
|
{
|
|
673
|
-
|
|
674
|
-
|
|
173
|
+
title: t("tasks.drawer.title"),
|
|
174
|
+
open: drawerOpen,
|
|
175
|
+
onClose: closeDrawer,
|
|
176
|
+
width: "max-w-lg",
|
|
675
177
|
children: [
|
|
676
|
-
/* @__PURE__ */ jsx("
|
|
677
|
-
|
|
678
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
|
|
679
|
-
/* @__PURE__ */ jsx("p", { className: `text-sm leading-snug ${isDone ? "line-through text-muted-foreground" : ""}`, children: task.title }),
|
|
680
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
|
|
681
|
-
/* @__PURE__ */ jsx(TaskPriorityBadge, { priority: task.priority }),
|
|
682
|
-
task.dueDate && /* @__PURE__ */ jsxs("span", { className: `inline-flex items-center gap-1 text-[11px] ${overdue ? "text-destructive font-medium" : dueToday ? "text-warning font-medium" : "text-muted-foreground"}`, children: [
|
|
683
|
-
/* @__PURE__ */ jsx(Calendar, { className: "h-3 w-3" }),
|
|
684
|
-
formatDueDate(task.dueDate)
|
|
685
|
-
] }),
|
|
686
|
-
subtaskCount && subtaskCount.total > 0 && /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground", children: [
|
|
687
|
-
/* @__PURE__ */ jsx(ListChecks, { className: "h-3 w-3" }),
|
|
688
|
-
subtaskCount.done,
|
|
689
|
-
"/",
|
|
690
|
-
subtaskCount.total
|
|
691
|
-
] }),
|
|
692
|
-
/* @__PURE__ */ jsx(TaskLabelBadges, { labelIds: task.labels, max: 2 })
|
|
693
|
-
] })
|
|
694
|
-
] }),
|
|
695
|
-
task.assignedToName && /* @__PURE__ */ jsx("div", { className: "flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-medium text-primary", title: task.assignedToName, children: task.assignedToName.split(" ").map((n) => n[0]).join("").slice(0, 2).toUpperCase() })
|
|
178
|
+
/* @__PURE__ */ jsx("p", { className: "shrink-0 border-b border-border/40 px-5 py-2 text-xs text-muted-foreground", children: summaryLine || t("tasks.drawer.subtitle") }),
|
|
179
|
+
/* @__PURE__ */ jsx(SheetBody, { className: "flex flex-col px-0 py-0", children: /* @__PURE__ */ jsx(TasksPanel, {}) })
|
|
696
180
|
]
|
|
697
181
|
}
|
|
698
182
|
);
|
|
699
183
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
in_progress: "bg-info",
|
|
703
|
-
done: "bg-success",
|
|
704
|
-
cancelled: "bg-muted-foreground/60"
|
|
705
|
-
};
|
|
706
|
-
function TaskStatusGroup({ status, tasks }) {
|
|
707
|
-
const t = useTranslation();
|
|
708
|
-
const [collapsed, setCollapsed] = useState(status === "cancelled");
|
|
709
|
-
if (tasks.length === 0) return null;
|
|
710
|
-
const Chevron = collapsed ? ChevronRight : ChevronDown;
|
|
711
|
-
return /* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
|
|
712
|
-
/* @__PURE__ */ jsxs(
|
|
713
|
-
"button",
|
|
714
|
-
{
|
|
715
|
-
type: "button",
|
|
716
|
-
onClick: () => setCollapsed(!collapsed),
|
|
717
|
-
className: "flex w-full items-center gap-2 px-3 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider hover:text-foreground transition-colors",
|
|
718
|
-
children: [
|
|
719
|
-
/* @__PURE__ */ jsx("span", { className: `h-2 w-2 rounded-full ${STATUS_COLORS[status]}` }),
|
|
720
|
-
/* @__PURE__ */ jsx("span", { children: t(`tasks.status.${status}`) }),
|
|
721
|
-
/* @__PURE__ */ jsxs("span", { className: "text-[10px] font-normal", children: [
|
|
722
|
-
"(",
|
|
723
|
-
tasks.length,
|
|
724
|
-
")"
|
|
725
|
-
] }),
|
|
726
|
-
/* @__PURE__ */ jsx(Chevron, { className: "ml-auto h-3.5 w-3.5" })
|
|
727
|
-
]
|
|
728
|
-
}
|
|
729
|
-
),
|
|
730
|
-
!collapsed && /* @__PURE__ */ jsx("div", { children: tasks.map((task) => /* @__PURE__ */ jsx(TaskCard, { task }, task.id)) })
|
|
731
|
-
] });
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// src/types.ts
|
|
735
|
-
var TASK_STATUSES = ["todo", "in_progress", "done", "cancelled"];
|
|
736
|
-
var TASK_PRIORITIES = ["low", "medium", "high", "urgent"];
|
|
737
|
-
var STATUS_DOT = {
|
|
738
|
-
todo: "bg-muted-foreground",
|
|
739
|
-
in_progress: "bg-info",
|
|
740
|
-
done: "bg-success",
|
|
741
|
-
cancelled: "bg-muted-foreground/60"
|
|
742
|
-
};
|
|
743
|
-
function formatDateTime(iso) {
|
|
744
|
-
const d = new Date(iso);
|
|
745
|
-
const sameDay = d.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
|
|
746
|
-
return sameDay ? d.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString(void 0, { day: "2-digit", month: "2-digit", year: "2-digit" });
|
|
747
|
-
}
|
|
748
|
-
function TaskDetail({ taskId }) {
|
|
749
|
-
const t = useTranslation();
|
|
750
|
-
const tasks = useTasksStore((s) => s.tasks);
|
|
751
|
-
const selectTask = useTasksStore((s) => s.selectTask);
|
|
752
|
-
const updateTask = useTasksStore((s) => s.updateTask);
|
|
753
|
-
const deleteTask = useTasksStore((s) => s.deleteTask);
|
|
754
|
-
const task = tasks.find((tt) => tt.id === taskId);
|
|
755
|
-
const delegate = useDelegateToAssistant();
|
|
756
|
-
const [title, setTitle] = useState("");
|
|
757
|
-
const [description, setDescription] = useState("");
|
|
758
|
-
const [subtasks, setSubtasks] = useState([]);
|
|
759
|
-
useEffect(() => {
|
|
760
|
-
if (task) {
|
|
761
|
-
setTitle(task.title);
|
|
762
|
-
setDescription(task.description ?? "");
|
|
763
|
-
}
|
|
764
|
-
}, [task]);
|
|
765
|
-
useEffect(() => {
|
|
766
|
-
setSubtasks([]);
|
|
767
|
-
}, [taskId]);
|
|
768
|
-
const handleTitleBlur = useCallback(() => {
|
|
769
|
-
if (task && title.trim() && title !== task.title) {
|
|
770
|
-
updateTask(task.id, { title: title.trim() });
|
|
771
|
-
}
|
|
772
|
-
}, [task, title, updateTask]);
|
|
773
|
-
const handleDescriptionBlur = useCallback(() => {
|
|
774
|
-
if (task && description !== (task.description ?? "")) {
|
|
775
|
-
updateTask(task.id, { description: description || null });
|
|
776
|
-
}
|
|
777
|
-
}, [task, description, updateTask]);
|
|
778
|
-
const handleDelete = useCallback(() => {
|
|
779
|
-
if (task && confirm(t("tasks.detail.deleteConfirm"))) {
|
|
780
|
-
deleteTask(task.id);
|
|
781
|
-
}
|
|
782
|
-
}, [task, deleteTask, t]);
|
|
783
|
-
if (!task) {
|
|
784
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-muted-foreground", children: [
|
|
785
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm", children: "Task not found" }),
|
|
786
|
-
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => selectTask(null), className: "mt-2 text-xs text-primary hover:underline", children: t("tasks.detail.back") })
|
|
787
|
-
] });
|
|
788
|
-
}
|
|
789
|
-
return (
|
|
790
|
-
// Same frame as the Notes editor: header row with back + icon actions,
|
|
791
|
-
// full-bleed px-4 content, sectioned by hairlines, thin timestamp footer.
|
|
792
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-h-full flex-col", children: [
|
|
793
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 border-b border-border/40 px-2 py-1.5", children: [
|
|
794
|
-
/* @__PURE__ */ jsxs(
|
|
795
|
-
"button",
|
|
796
|
-
{
|
|
797
|
-
type: "button",
|
|
798
|
-
onClick: () => selectTask(null),
|
|
799
|
-
className: "inline-flex items-center gap-1 rounded-lg px-1.5 py-1 text-[11.5px] text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
|
|
800
|
-
children: [
|
|
801
|
-
/* @__PURE__ */ jsx(ArrowLeft, { className: "h-3.5 w-3.5" }),
|
|
802
|
-
t("tasks.drawer.title")
|
|
803
|
-
]
|
|
804
|
-
}
|
|
805
|
-
),
|
|
806
|
-
/* @__PURE__ */ jsx("span", { className: "flex-1" }),
|
|
807
|
-
/* @__PURE__ */ jsx(
|
|
808
|
-
"button",
|
|
809
|
-
{
|
|
810
|
-
type: "button",
|
|
811
|
-
onClick: handleDelete,
|
|
812
|
-
"aria-label": t("tasks.detail.delete"),
|
|
813
|
-
title: t("tasks.detail.delete"),
|
|
814
|
-
className: "inline-flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-destructive",
|
|
815
|
-
children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
|
|
816
|
-
}
|
|
817
|
-
)
|
|
818
|
-
] }),
|
|
819
|
-
/* @__PURE__ */ jsx(
|
|
820
|
-
"input",
|
|
821
|
-
{
|
|
822
|
-
type: "text",
|
|
823
|
-
value: title,
|
|
824
|
-
onChange: (e) => setTitle(e.target.value),
|
|
825
|
-
onBlur: handleTitleBlur,
|
|
826
|
-
onKeyDown: (e) => {
|
|
827
|
-
if (e.key === "Enter") e.currentTarget.blur();
|
|
828
|
-
},
|
|
829
|
-
className: "bg-transparent px-4 pt-3 text-sm font-semibold text-foreground outline-none"
|
|
830
|
-
}
|
|
831
|
-
),
|
|
832
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-1.5 px-4 pt-2", children: [
|
|
833
|
-
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
834
|
-
/* @__PURE__ */ jsx(
|
|
835
|
-
"select",
|
|
836
|
-
{
|
|
837
|
-
value: task.status,
|
|
838
|
-
onChange: (e) => updateTask(task.id, { status: e.target.value }),
|
|
839
|
-
className: "appearance-none rounded-full border bg-background pl-5 pr-6 py-1 text-xs font-medium cursor-pointer hover:border-primary/40 transition-colors",
|
|
840
|
-
children: TASK_STATUSES.map((s) => /* @__PURE__ */ jsx("option", { value: s, children: t(`tasks.status.${s}`) }, s))
|
|
841
|
-
}
|
|
842
|
-
),
|
|
843
|
-
/* @__PURE__ */ jsx("span", { className: `absolute left-2 top-1/2 -translate-y-1/2 h-2 w-2 rounded-full ${STATUS_DOT[task.status]}` }),
|
|
844
|
-
/* @__PURE__ */ jsx(ChevronDown, { className: "absolute right-1.5 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
|
|
845
|
-
] }),
|
|
846
|
-
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
847
|
-
/* @__PURE__ */ jsx(
|
|
848
|
-
"select",
|
|
849
|
-
{
|
|
850
|
-
value: task.priority,
|
|
851
|
-
onChange: (e) => updateTask(task.id, { priority: e.target.value }),
|
|
852
|
-
className: "appearance-none rounded-full border bg-background pl-2 pr-6 py-1 text-xs font-medium cursor-pointer hover:border-primary/40 transition-colors",
|
|
853
|
-
children: TASK_PRIORITIES.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: t(`tasks.priority.${p}`) }, p))
|
|
854
|
-
}
|
|
855
|
-
),
|
|
856
|
-
/* @__PURE__ */ jsx(ChevronDown, { className: "absolute right-1.5 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
|
|
857
|
-
] }),
|
|
858
|
-
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
859
|
-
/* @__PURE__ */ jsx(
|
|
860
|
-
"input",
|
|
861
|
-
{
|
|
862
|
-
type: "date",
|
|
863
|
-
value: task.dueDate ?? "",
|
|
864
|
-
onChange: (e) => updateTask(task.id, { dueDate: e.target.value || null }),
|
|
865
|
-
className: "appearance-none rounded-full border bg-background pl-6 pr-2 py-1 text-xs font-medium cursor-pointer hover:border-primary/40 transition-colors w-[120px]"
|
|
866
|
-
}
|
|
867
|
-
),
|
|
868
|
-
/* @__PURE__ */ jsx(Calendar, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
|
|
869
|
-
] }),
|
|
870
|
-
task.labels.length > 0 && /* @__PURE__ */ jsx(TaskLabelBadges, { labelIds: task.labels, max: 3 })
|
|
871
|
-
] }),
|
|
872
|
-
/* @__PURE__ */ jsx(
|
|
873
|
-
"textarea",
|
|
874
|
-
{
|
|
875
|
-
value: description,
|
|
876
|
-
onChange: (e) => setDescription(e.target.value),
|
|
877
|
-
onBlur: handleDescriptionBlur,
|
|
878
|
-
placeholder: t("tasks.detail.descriptionPlaceholder"),
|
|
879
|
-
className: "min-h-[72px] flex-1 resize-none bg-transparent px-4 py-3 text-sm leading-relaxed text-foreground outline-none placeholder:text-muted-foreground/60"
|
|
880
|
-
}
|
|
881
|
-
),
|
|
882
|
-
/* @__PURE__ */ jsx("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsxs(
|
|
883
|
-
"button",
|
|
884
|
-
{
|
|
885
|
-
type: "button",
|
|
886
|
-
onClick: () => delegate(
|
|
887
|
-
`${t("tasks.detail.chatWithAi.prompt")}
|
|
888
|
-
|
|
889
|
-
"${task.title}"${task.description ? `
|
|
890
|
-
${task.description}` : ""}${task.dueDate ? `
|
|
891
|
-
(${t("tasks.detail.dueDate")}: ${task.dueDate})` : ""}`
|
|
892
|
-
),
|
|
893
|
-
className: "group flex w-full items-center justify-center gap-2 rounded-xl bg-gradient-to-r from-primary via-primary/80 to-primary bg-[length:200%_100%] bg-left px-3 py-2 text-[13px] font-semibold text-primary-foreground shadow-sm transition-all duration-500 hover:bg-right hover:shadow-md focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
894
|
-
children: [
|
|
895
|
-
/* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4 transition-transform duration-300 group-hover:rotate-12" }),
|
|
896
|
-
t("tasks.detail.chatWithAi")
|
|
897
|
-
]
|
|
898
|
-
}
|
|
899
|
-
) }),
|
|
900
|
-
/* @__PURE__ */ jsxs("div", { className: "border-t border-border/40 px-4 py-2.5", children: [
|
|
901
|
-
/* @__PURE__ */ jsxs("p", { className: "pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60", children: [
|
|
902
|
-
t("tasks.detail.subtasks"),
|
|
903
|
-
subtasks.length > 0 ? ` \xB7 ${subtasks.length}` : ""
|
|
904
|
-
] }),
|
|
905
|
-
/* @__PURE__ */ jsx("div", { className: "-mx-3", children: /* @__PURE__ */ jsx(TaskQuickAdd, { parentId: task.id }) }),
|
|
906
|
-
subtasks.length > 0 && /* @__PURE__ */ jsx("div", { className: "divide-y divide-border/40", children: subtasks.map((st) => /* @__PURE__ */ jsx(TaskCard, { task: st }, st.id)) })
|
|
907
|
-
] }),
|
|
908
|
-
/* @__PURE__ */ jsxs("p", { className: "border-t border-border/40 px-4 py-1.5 text-[10px] text-muted-foreground/60", children: [
|
|
909
|
-
t("tasks.detail.created"),
|
|
910
|
-
" ",
|
|
911
|
-
formatDateTime(task.createdAt),
|
|
912
|
-
" \xB7 ",
|
|
913
|
-
t("tasks.detail.updated"),
|
|
914
|
-
" ",
|
|
915
|
-
formatDateTime(task.updatedAt)
|
|
916
|
-
] })
|
|
917
|
-
] })
|
|
918
|
-
);
|
|
919
|
-
}
|
|
920
|
-
var STATUS_ORDER = ["todo", "in_progress", "done", "cancelled"];
|
|
921
|
-
function TasksPanel() {
|
|
922
|
-
const t = useTranslation();
|
|
923
|
-
const selectedTaskId = useTasksStore((s) => s.selectedTaskId);
|
|
924
|
-
const tasks = useTasksStore((s) => s.tasks);
|
|
925
|
-
const tasksLoading = useTasksStore((s) => s.tasksLoading);
|
|
926
|
-
const activeFilter = useTasksStore((s) => s.activeFilter);
|
|
927
|
-
const fetchTasks = useTasksStore((s) => s.fetchTasks);
|
|
928
|
-
const fetchLabels = useTasksStore((s) => s.fetchLabels);
|
|
929
|
-
const fetchSummary = useTasksStore((s) => s.fetchSummary);
|
|
930
|
-
React4.useEffect(() => {
|
|
931
|
-
void fetchTasks();
|
|
932
|
-
void fetchLabels();
|
|
933
|
-
void fetchSummary();
|
|
934
|
-
}, [fetchTasks, fetchLabels, fetchSummary]);
|
|
935
|
-
const grouped = React4.useMemo(() => {
|
|
936
|
-
const map = /* @__PURE__ */ new Map();
|
|
937
|
-
for (const s of STATUS_ORDER) map.set(s, []);
|
|
938
|
-
for (const task of tasks) {
|
|
939
|
-
const group = map.get(task.status);
|
|
940
|
-
if (group) group.push(task);
|
|
941
|
-
}
|
|
942
|
-
return map;
|
|
943
|
-
}, [tasks]);
|
|
184
|
+
function TasksLauncher() {
|
|
185
|
+
const railMounted = useRightRailStore((s) => s.mounted);
|
|
944
186
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
945
|
-
|
|
946
|
-
!
|
|
947
|
-
/* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: selectedTaskId ? /* @__PURE__ */ jsx(TaskDetail, { taskId: selectedTaskId }) : tasksLoading && tasks.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-16", children: /* @__PURE__ */ jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent" }) }) : tasks.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-muted-foreground", children: [
|
|
948
|
-
/* @__PURE__ */ jsx(ClipboardList, { className: "mb-3 h-10 w-10 opacity-40" }),
|
|
949
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("tasks.empty") }),
|
|
950
|
-
/* @__PURE__ */ jsx("p", { className: "mt-1 text-xs", children: t("tasks.empty.description") })
|
|
951
|
-
] }) : activeFilter !== "all" ? /* @__PURE__ */ jsx("div", { className: "py-1", children: tasks.map((task) => /* @__PURE__ */ jsx("div", { className: "px-2", children: /* @__PURE__ */ jsx(TaskCard, { task }) }, task.id)) }) : /* @__PURE__ */ jsx("div", { className: "space-y-2 py-2", children: STATUS_ORDER.filter((s) => s !== "cancelled" || (grouped.get(s)?.length ?? 0) > 0).map((status) => /* @__PURE__ */ jsx(TaskStatusGroup, { status, tasks: grouped.get(status) ?? [] }, status)) }) })
|
|
187
|
+
/* @__PURE__ */ jsx(TasksLauncherRow, { railMounted }),
|
|
188
|
+
!railMounted && /* @__PURE__ */ jsx(TasksDrawer, {})
|
|
952
189
|
] });
|
|
953
190
|
}
|
|
954
|
-
function
|
|
191
|
+
function TasksLauncherRow({ railMounted }) {
|
|
955
192
|
const t = useTranslation();
|
|
193
|
+
const toggleDrawer = useTasksStore((s) => s.toggleDrawer);
|
|
194
|
+
const fetchSummary = useTasksStore((s) => s.fetchSummary);
|
|
956
195
|
const summary = useTasksStore((s) => s.summary);
|
|
957
|
-
return React4.useMemo(() => {
|
|
958
|
-
if (!summary) return "";
|
|
959
|
-
const parts = [];
|
|
960
|
-
if (summary.overdue > 0) parts.push(`${summary.overdue} ${t("tasks.summary.overdue")}`);
|
|
961
|
-
if (summary.dueToday > 0) parts.push(`${summary.dueToday} ${t("tasks.summary.dueToday")}`);
|
|
962
|
-
return parts.join(" \xB7 ");
|
|
963
|
-
}, [summary, t]);
|
|
964
|
-
}
|
|
965
|
-
function TasksDrawer() {
|
|
966
|
-
const t = useTranslation();
|
|
967
|
-
const drawerOpen = useTasksStore((s) => s.drawerOpen);
|
|
968
|
-
const closeDrawer = useTasksStore((s) => s.closeDrawer);
|
|
969
|
-
const summaryLine = useTasksSummaryLine();
|
|
970
|
-
return /* @__PURE__ */ jsx(Sheet, { open: drawerOpen, onOpenChange: (v) => {
|
|
971
|
-
if (!v) closeDrawer();
|
|
972
|
-
}, children: /* @__PURE__ */ jsxs(SheetContent, { width: "max-w-lg", overlay: "dim", children: [
|
|
973
|
-
/* @__PURE__ */ jsxs(SheetHeader, { children: [
|
|
974
|
-
/* @__PURE__ */ jsx(SheetTitle, { children: t("tasks.drawer.title") }),
|
|
975
|
-
/* @__PURE__ */ jsx(SheetDescription, { children: summaryLine || t("tasks.drawer.subtitle") })
|
|
976
|
-
] }),
|
|
977
|
-
/* @__PURE__ */ jsx(SheetBody, { className: "flex flex-col px-0 py-0", children: /* @__PURE__ */ jsx(TasksPanel, {}) })
|
|
978
|
-
] }) });
|
|
979
|
-
}
|
|
980
|
-
function TasksTopbarButton({ config }) {
|
|
981
|
-
const { tasksConfig, tasksProvider, tasksStore } = config;
|
|
982
|
-
const t = useTranslation();
|
|
983
|
-
const railMounted = useRightRailStore((s) => s.mounted);
|
|
984
|
-
const summary = useStore(tasksStore, (s) => s.summary);
|
|
985
|
-
const openCount = summary ? summary.todo + summary.inProgress : 0;
|
|
986
|
-
const Panel = React4.useMemo(() => {
|
|
987
|
-
const RailPanel = () => /* @__PURE__ */ jsx(TasksContextProvider, { config: tasksConfig, provider: tasksProvider, store: tasksStore, children: /* @__PURE__ */ jsx(TasksPanel, {}) });
|
|
988
|
-
RailPanel.displayName = "TasksRailPanel";
|
|
989
|
-
return RailPanel;
|
|
990
|
-
}, [tasksConfig, tasksProvider, tasksStore]);
|
|
991
|
-
useRightRailPanel(
|
|
992
|
-
React4.useMemo(
|
|
993
|
-
() => railMounted ? {
|
|
994
|
-
id: "tasks",
|
|
995
|
-
label: t("tasks.drawer.title") || "Tasks",
|
|
996
|
-
icon: CheckSquare,
|
|
997
|
-
order: 20,
|
|
998
|
-
badge: { count: openCount, tone: "destructive" },
|
|
999
|
-
Component: Panel
|
|
1000
|
-
} : null,
|
|
1001
|
-
[railMounted, t, Panel, openCount]
|
|
1002
|
-
)
|
|
1003
|
-
);
|
|
1004
|
-
return /* @__PURE__ */ jsxs(TasksContextProvider, { config: tasksConfig, provider: tasksProvider, store: tasksStore, children: [
|
|
1005
|
-
/* @__PURE__ */ jsx(TasksTopbarButtonInner, { store: tasksStore, railMounted }),
|
|
1006
|
-
!railMounted && /* @__PURE__ */ jsx(TasksDrawer, {})
|
|
1007
|
-
] });
|
|
1008
|
-
}
|
|
1009
|
-
function TasksTopbarButtonInner({
|
|
1010
|
-
store,
|
|
1011
|
-
railMounted
|
|
1012
|
-
}) {
|
|
1013
|
-
const toggleDrawer = useStore(store, (s) => s.toggleDrawer);
|
|
1014
|
-
const fetchSummary = useStore(store, (s) => s.fetchSummary);
|
|
1015
|
-
const summary = useStore(store, (s) => s.summary);
|
|
1016
196
|
const togglePanel = useRightRailStore((s) => s.togglePanel);
|
|
1017
197
|
useEffect(() => {
|
|
1018
198
|
fetchSummary();
|
|
1019
199
|
}, [fetchSummary]);
|
|
1020
200
|
const pendingCount = summary ? summary.todo + summary.inProgress : 0;
|
|
201
|
+
const collapsed = useLayoutStore((s) => s.sidebarCollapsed);
|
|
202
|
+
const surface = useWidgetSurface();
|
|
203
|
+
const compact = collapsed || surface === "topbar";
|
|
204
|
+
const label = t("tasks.drawer.title") || "Tasks";
|
|
205
|
+
const open = () => railMounted ? togglePanel("tasks") : toggleDrawer();
|
|
206
|
+
const railRow = useRailRow(
|
|
207
|
+
surface === "rail" ? { key: "tasks", label, badge: pendingCount > 0 ? pendingCount : void 0 } : null
|
|
208
|
+
);
|
|
209
|
+
if (compact) {
|
|
210
|
+
return /* @__PURE__ */ jsxs(
|
|
211
|
+
"button",
|
|
212
|
+
{
|
|
213
|
+
...railRow,
|
|
214
|
+
onClick: open,
|
|
215
|
+
"aria-label": label,
|
|
216
|
+
title: surface === "topbar" ? label : void 0,
|
|
217
|
+
className: cn(
|
|
218
|
+
"fz-tap relative flex shrink-0 items-center justify-center rounded-button p-2 text-sidebar-muted transition-colors hover:bg-sidebar-accent/50 hover:text-sidebar-foreground",
|
|
219
|
+
// Na trilha ele é uma linha (ocupa a coluna); na barra, um ícone
|
|
220
|
+
// entre outros, e esticar tomaria a barra inteira.
|
|
221
|
+
surface === "rail" && "w-full"
|
|
222
|
+
),
|
|
223
|
+
children: [
|
|
224
|
+
/* @__PURE__ */ jsx(CheckSquare, { className: "h-4 w-4" }),
|
|
225
|
+
pendingCount > 0 && /* @__PURE__ */ jsx("span", { className: "absolute right-1 top-1 h-2 w-2 rounded-full bg-destructive" })
|
|
226
|
+
]
|
|
227
|
+
}
|
|
228
|
+
);
|
|
229
|
+
}
|
|
1021
230
|
return /* @__PURE__ */ jsxs(
|
|
1022
|
-
|
|
231
|
+
"button",
|
|
1023
232
|
{
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
className:
|
|
1027
|
-
onClick: () => railMounted ? togglePanel("tasks") : toggleDrawer(),
|
|
1028
|
-
"aria-label": "Tasks",
|
|
233
|
+
onClick: open,
|
|
234
|
+
"aria-label": label,
|
|
235
|
+
className: "fz-tap flex w-full items-center gap-2 rounded-button px-2.5 py-1.5 text-sm text-sidebar-muted transition-colors hover:bg-sidebar-accent/50 hover:text-sidebar-foreground",
|
|
1029
236
|
children: [
|
|
1030
|
-
/* @__PURE__ */ jsx(CheckSquare, { className: "h-
|
|
237
|
+
/* @__PURE__ */ jsx(CheckSquare, { className: "h-4 w-4 shrink-0" }),
|
|
238
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-left", children: label }),
|
|
1031
239
|
pendingCount > 0 && /* @__PURE__ */ jsx(
|
|
1032
240
|
"span",
|
|
1033
241
|
{
|
|
1034
242
|
className: cn(
|
|
1035
|
-
"
|
|
1036
|
-
pendingCount > 9 ? "h-5 min-w-5 px-1
|
|
243
|
+
"flex shrink-0 items-center justify-center rounded-full bg-destructive text-[10px] text-destructive-foreground",
|
|
244
|
+
pendingCount > 9 ? "h-5 min-w-5 px-1" : "h-4 w-4"
|
|
1037
245
|
),
|
|
1038
246
|
children: pendingCount > 99 ? "99+" : pendingCount
|
|
1039
247
|
}
|
|
@@ -1123,8 +331,56 @@ DROP POLICY IF EXISTS plg_tasks_labels_delete ON public.plg_tasks_labels;
|
|
|
1123
331
|
CREATE POLICY plg_tasks_labels_delete ON public.plg_tasks_labels FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
1124
332
|
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_tasks_labels TO authenticated;
|
|
1125
333
|
`;
|
|
334
|
+
var MIGRATION_002_TASKS_TO_SPINE = `-- 002_tasks_to_spine.sql \u2014 carry this plugin's rows into the spine's tasks table.
|
|
335
|
+
--
|
|
336
|
+
-- public.tasks (db migration 047) is now the one table admin work lives in: the
|
|
337
|
+
-- shell's getting-started checklist writes it, so it has to exist in apps that
|
|
338
|
+
-- never installed this plugin. This file moves what plg_tasks_tasks already has.
|
|
339
|
+
--
|
|
340
|
+
-- WHY THE COPY IS HERE AND NOT IN 047. \`fayz db apply\` runs the spine BEFORE
|
|
341
|
+
-- plugins (cli/src/lib/migration-plan.ts). On a fresh pool 047 runs while
|
|
342
|
+
-- plg_tasks_tasks does not exist yet, and a checksum-frozen file cannot try
|
|
343
|
+
-- again later. Here, both tables are guaranteed to exist.
|
|
344
|
+
--
|
|
345
|
+
-- plg_tasks_tasks is NOT dropped. It is retired in place; a later migration
|
|
346
|
+
-- removes it once the fleet has been verified on the new table.
|
|
347
|
+
--
|
|
348
|
+
-- Idempotent.
|
|
1126
349
|
|
|
1127
|
-
|
|
350
|
+
DO $$
|
|
351
|
+
BEGIN
|
|
352
|
+
-- An app on an older @fayz-ai/db has no spine table to copy into. Say so and
|
|
353
|
+
-- leave: the plugin still works against its own table until db is upgraded.
|
|
354
|
+
IF to_regclass('public.tasks') IS NULL THEN
|
|
355
|
+
RAISE NOTICE 'plugin-tasks 002: public.tasks not found \u2014 upgrade @fayz-ai/db to move tasks to the spine';
|
|
356
|
+
RETURN;
|
|
357
|
+
END IF;
|
|
358
|
+
|
|
359
|
+
IF to_regclass('public.plg_tasks_tasks') IS NULL THEN
|
|
360
|
+
RETURN; -- fresh pool: nothing to carry
|
|
361
|
+
END IF;
|
|
362
|
+
|
|
363
|
+
-- The id is preserved deliberately. The notes bridge keys a note's todos by
|
|
364
|
+
-- task id and stores the link as a 'note:<id>' string in \`labels\`; a new id
|
|
365
|
+
-- here would orphan every note todo in the fleet.
|
|
366
|
+
--
|
|
367
|
+
-- Roots before children so the self-referencing parent_id always resolves.
|
|
368
|
+
-- auth.uid() is NULL in a migration session and both of 047's stamping
|
|
369
|
+
-- triggers COALESCE, so these explicit values win.
|
|
370
|
+
INSERT INTO public.tasks (
|
|
371
|
+
id, tenant_id, title, description, status, priority, due_date,
|
|
372
|
+
assigned_to_id, assigned_to_name, parent_id, labels, position,
|
|
373
|
+
created_by, created_by_name, owner_id, created_at, updated_at
|
|
374
|
+
)
|
|
375
|
+
SELECT
|
|
376
|
+
src.id, src.tenant_id, src.title, src.description, src.status, src.priority, src.due_date,
|
|
377
|
+
src.assigned_to_id, src.assigned_to_name, src.parent_id, src.labels, src.position,
|
|
378
|
+
src.created_by_id, src.created_by_name, src.created_by_id, src.created_at, src.updated_at
|
|
379
|
+
FROM public.plg_tasks_tasks src
|
|
380
|
+
ORDER BY src.parent_id NULLS FIRST
|
|
381
|
+
ON CONFLICT (id) DO NOTHING;
|
|
382
|
+
END $$;
|
|
383
|
+
`;
|
|
1128
384
|
var DEFAULT_LABELS = {
|
|
1129
385
|
drawerTitle: "Tasks",
|
|
1130
386
|
settingsTitle: "Tasks",
|
|
@@ -1138,11 +394,8 @@ function resolveConfig(options) {
|
|
|
1138
394
|
function createTasksPlugin(options) {
|
|
1139
395
|
const config = resolveConfig(options);
|
|
1140
396
|
registerTranslations(tasksLocales);
|
|
1141
|
-
const provider = options?.dataProvider ??
|
|
1142
|
-
|
|
1143
|
-
() => createMockTasksProvider()
|
|
1144
|
-
);
|
|
1145
|
-
const store = createTasksStore(provider);
|
|
397
|
+
const provider = options?.dataProvider ?? getTasksProvider();
|
|
398
|
+
const store = useTasksStore;
|
|
1146
399
|
registerNotesTasksAdapter({
|
|
1147
400
|
async createTodo({ title, noteId, noteTitle }) {
|
|
1148
401
|
const task = await provider.createTask({
|
|
@@ -1169,6 +422,7 @@ function createTasksPlugin(options) {
|
|
|
1169
422
|
});
|
|
1170
423
|
return {
|
|
1171
424
|
id: "tasks",
|
|
425
|
+
defaultAgentRole: "setup",
|
|
1172
426
|
name: config.labels.drawerTitle,
|
|
1173
427
|
icon: "CheckSquare",
|
|
1174
428
|
version: "1.0.0",
|
|
@@ -1180,14 +434,15 @@ function createTasksPlugin(options) {
|
|
|
1180
434
|
routes: [],
|
|
1181
435
|
widgets: [
|
|
1182
436
|
{
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
437
|
+
// No pé da trilha, não no topo: tarefas são do workspace, não da
|
|
438
|
+
// tela aberta — e no topo elas dividiam o canto com as ações da página.
|
|
439
|
+
id: "tasks-launcher",
|
|
440
|
+
zone: "shell.sidebar.footer",
|
|
441
|
+
component: TasksLauncher,
|
|
1186
442
|
order: 10,
|
|
1187
443
|
props: {
|
|
1188
444
|
tasksConfig: config,
|
|
1189
|
-
tasksProvider: provider
|
|
1190
|
-
tasksStore: store
|
|
445
|
+
tasksProvider: provider
|
|
1191
446
|
}
|
|
1192
447
|
}
|
|
1193
448
|
],
|
|
@@ -1222,15 +477,7 @@ function createTasksPlugin(options) {
|
|
|
1222
477
|
id: "tasks",
|
|
1223
478
|
label: config.labels.settingsTitle,
|
|
1224
479
|
icon: "CheckSquare",
|
|
1225
|
-
component:
|
|
1226
|
-
const Tab = () => React4.createElement(
|
|
1227
|
-
TasksContextProvider,
|
|
1228
|
-
{ config, provider, store },
|
|
1229
|
-
React4.createElement(TasksGeneralSettings)
|
|
1230
|
-
);
|
|
1231
|
-
Tab.displayName = "TasksSettingsTab";
|
|
1232
|
-
return Tab;
|
|
1233
|
-
})(),
|
|
480
|
+
component: TasksGeneralSettings,
|
|
1234
481
|
order: 14
|
|
1235
482
|
}
|
|
1236
483
|
],
|
|
@@ -1246,6 +493,12 @@ function createTasksPlugin(options) {
|
|
|
1246
493
|
version: "1.0.0",
|
|
1247
494
|
sql: MIGRATION_001_TASKS_BASE,
|
|
1248
495
|
description: "Create plg_tasks_tasks and plg_tasks_labels"
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
id: "tasks-002-to-spine",
|
|
499
|
+
version: "1.1.0",
|
|
500
|
+
sql: MIGRATION_002_TASKS_TO_SPINE,
|
|
501
|
+
description: "Copy plg_tasks_tasks rows into the spine public.tasks (ids preserved; the old table is retired in place, not dropped)"
|
|
1249
502
|
}
|
|
1250
503
|
],
|
|
1251
504
|
locales: tasksLocales
|