@fayz-ai/plugin-tasks 0.8.3 → 0.9.0-next.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/index.cjs DELETED
@@ -1,1263 +0,0 @@
1
- 'use strict';
2
-
3
- var React4 = require('react');
4
- var saas = require('@fayz-ai/saas');
5
- var core = require('@fayz-ai/core');
6
- var vanilla = require('zustand/vanilla');
7
- var jsxRuntime = require('react/jsx-runtime');
8
- var lucideReact = require('lucide-react');
9
- var ui = require('@fayz-ai/ui');
10
- var zustand = require('zustand');
11
-
12
- function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
13
-
14
- var React4__default = /*#__PURE__*/_interopDefault(React4);
15
-
16
- // src/index.ts
17
- var ctx = saas.createPluginContext("TasksContextProvider");
18
- var TasksContextProvider = ctx.ContextProvider;
19
- ctx.useConfig;
20
- ctx.useProvider;
21
- var useTasksStore = ctx.useStore;
22
-
23
- // src/data/mock.ts
24
- function uid() {
25
- return crypto.randomUUID?.() ?? String(Date.now());
26
- }
27
- function now() {
28
- return (/* @__PURE__ */ new Date()).toISOString();
29
- }
30
- function today() {
31
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
32
- }
33
- var SEED_LABELS = [
34
- { id: "lbl-bug", name: "Bug", color: "#ef4444", isActive: true, tenantId: "mock", createdAt: now() },
35
- { id: "lbl-feature", name: "Feature", color: "#3b82f6", isActive: true, tenantId: "mock", createdAt: now() },
36
- { id: "lbl-urgent", name: "Urgent", color: "#f59e0b", isActive: true, tenantId: "mock", createdAt: now() },
37
- { id: "lbl-personal", name: "Personal", color: "#8b5cf6", isActive: true, tenantId: "mock", createdAt: now() }
38
- ];
39
- function makeSeedTasks() {
40
- const base = {
41
- tenantId: "mock",
42
- description: null,
43
- assignedToId: null,
44
- assignedToName: null,
45
- parentId: null,
46
- labels: [],
47
- createdById: null,
48
- createdByName: null,
49
- createdAt: now(),
50
- updatedAt: now()
51
- };
52
- return [
53
- { ...base, id: uid(), title: "Setup payment gateway", status: "todo", priority: "high", dueDate: today(), position: 0 },
54
- { ...base, id: uid(), title: "Update menu photos", status: "todo", priority: "medium", dueDate: null, position: 1 },
55
- { ...base, id: uid(), title: "Review monthly report", status: "in_progress", priority: "medium", dueDate: null, position: 0 },
56
- { ...base, id: uid(), title: "Fix login bug", status: "in_progress", priority: "urgent", dueDate: today(), position: 1, labels: ["lbl-bug"] },
57
- { ...base, id: uid(), title: "Onboard new employee", status: "done", priority: "low", dueDate: null, position: 0 }
58
- ];
59
- }
60
- function matchesQuery(task, query) {
61
- if (query.status) {
62
- const statuses = Array.isArray(query.status) ? query.status : [query.status];
63
- if (!statuses.includes(task.status)) return false;
64
- }
65
- if (query.priority) {
66
- const priorities = Array.isArray(query.priority) ? query.priority : [query.priority];
67
- if (!priorities.includes(task.priority)) return false;
68
- }
69
- if (query.assignedToId && task.assignedToId !== query.assignedToId) return false;
70
- if (query.parentId !== void 0) {
71
- if (query.parentId === null && task.parentId !== null) return false;
72
- if (query.parentId !== null && task.parentId !== query.parentId) return false;
73
- }
74
- if (query.search) {
75
- const s = query.search.toLowerCase();
76
- if (!task.title.toLowerCase().includes(s)) return false;
77
- }
78
- if (query.overdue && task.dueDate) {
79
- if (task.dueDate >= today()) return false;
80
- }
81
- return true;
82
- }
83
- function createMockTasksProvider() {
84
- let tasks = makeSeedTasks();
85
- const labels = [...SEED_LABELS];
86
- return {
87
- async getTasks(query) {
88
- return tasks.filter((t) => matchesQuery(t, query)).sort((a, b) => a.position - b.position);
89
- },
90
- async getTaskById(id) {
91
- return tasks.find((t) => t.id === id) ?? null;
92
- },
93
- async getSubtasks(parentId) {
94
- return tasks.filter((t) => t.parentId === parentId).sort((a, b) => a.position - b.position);
95
- },
96
- async createTask(input) {
97
- const statusTasks = tasks.filter((t) => t.status === (input.status ?? "todo") && t.parentId === (input.parentId ?? null));
98
- const maxPos = statusTasks.reduce((max, t) => Math.max(max, t.position), -1);
99
- const task = {
100
- id: uid(),
101
- title: input.title,
102
- description: input.description ?? null,
103
- status: input.status ?? "todo",
104
- priority: input.priority ?? "medium",
105
- dueDate: input.dueDate ?? null,
106
- assignedToId: input.assignedToId ?? null,
107
- assignedToName: null,
108
- parentId: input.parentId ?? null,
109
- labels: input.labels ?? [],
110
- position: maxPos + 1,
111
- createdById: null,
112
- createdByName: null,
113
- tenantId: "mock",
114
- createdAt: now(),
115
- updatedAt: now()
116
- };
117
- tasks.push(task);
118
- return task;
119
- },
120
- async updateTask(id, input) {
121
- const idx = tasks.findIndex((t) => t.id === id);
122
- if (idx === -1) throw new Error("Task not found");
123
- tasks[idx] = { ...tasks[idx], ...input, updatedAt: now() };
124
- return tasks[idx];
125
- },
126
- async deleteTask(id) {
127
- tasks = tasks.filter((t) => t.id !== id && t.parentId !== id);
128
- },
129
- async reorderTask(id, newPosition, status) {
130
- const task = tasks.find((t) => t.id === id);
131
- if (!task) return;
132
- task.status = status;
133
- task.position = newPosition;
134
- task.updatedAt = now();
135
- },
136
- async getLabels() {
137
- return labels.filter((l) => l.isActive);
138
- },
139
- async getSummary() {
140
- const topLevel = tasks.filter((t) => t.parentId === null);
141
- const d = today();
142
- return {
143
- total: topLevel.length,
144
- todo: topLevel.filter((t) => t.status === "todo").length,
145
- inProgress: topLevel.filter((t) => t.status === "in_progress").length,
146
- done: topLevel.filter((t) => t.status === "done").length,
147
- overdue: topLevel.filter((t) => t.dueDate != null && t.dueDate < d && t.status !== "done" && t.status !== "cancelled").length,
148
- dueToday: topLevel.filter((t) => t.dueDate === d && t.status !== "done" && t.status !== "cancelled").length,
149
- highPriority: topLevel.filter((t) => (t.priority === "high" || t.priority === "urgent") && t.status !== "done" && t.status !== "cancelled").length
150
- };
151
- }
152
- };
153
- }
154
-
155
- // src/data/tables.ts
156
- var T = {
157
- tasks: "plg_tasks_tasks",
158
- labels: "plg_tasks_labels"
159
- };
160
-
161
- // src/data/supabase.ts
162
- function getTenantId() {
163
- return core.getActiveTenantId();
164
- }
165
- function getClient() {
166
- const supabase = core.getSupabaseClientOptional();
167
- if (!supabase) throw new Error("Supabase not initialized");
168
- return supabase;
169
- }
170
- function snakeToCamel(obj) {
171
- const result = {};
172
- for (const [key, value] of Object.entries(obj)) {
173
- result[key.replace(/_([a-z])/g, (_, c) => c.toUpperCase())] = value;
174
- }
175
- return result;
176
- }
177
- function toTask(row) {
178
- const t = snakeToCamel(row);
179
- if (!Array.isArray(t.labels)) t.labels = [];
180
- return t;
181
- }
182
- function createSupabaseTasksProvider() {
183
- return {
184
- async getTasks(query) {
185
- const tenantId = getTenantId();
186
- if (!tenantId) return [];
187
- let q = getClient().from(T.tasks).select("*").eq("tenant_id", tenantId);
188
- if (query.parentId === null) {
189
- q = q.is("parent_id", null);
190
- } else if (query.parentId) {
191
- q = q.eq("parent_id", query.parentId);
192
- }
193
- if (query.status) {
194
- const statuses = Array.isArray(query.status) ? query.status : [query.status];
195
- q = q.in("status", statuses);
196
- }
197
- if (query.priority) {
198
- const priorities = Array.isArray(query.priority) ? query.priority : [query.priority];
199
- q = q.in("priority", priorities);
200
- }
201
- if (query.assignedToId) {
202
- q = q.eq("assigned_to_id", query.assignedToId);
203
- }
204
- if (query.search) {
205
- q = q.ilike("title", `%${query.search}%`);
206
- }
207
- if (query.overdue) {
208
- q = q.lt("due_date", (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)).not("status", "in", '("done","cancelled")');
209
- }
210
- q = q.order("position", { ascending: true }).order("created_at", { ascending: false });
211
- const { data } = await q;
212
- return (data ?? []).map(toTask);
213
- },
214
- async getTaskById(id) {
215
- const { data } = await getClient().from(T.tasks).select("*").eq("id", id).maybeSingle();
216
- return data ? toTask(data) : null;
217
- },
218
- async getSubtasks(parentId) {
219
- const { data } = await getClient().from(T.tasks).select("*").eq("parent_id", parentId).order("position", { ascending: true });
220
- return (data ?? []).map(toTask);
221
- },
222
- async createTask(input) {
223
- const tenantId = getTenantId();
224
- if (!tenantId) throw new Error("No active tenant");
225
- 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);
226
- const maxPos = maxData?.[0]?.position ?? -1;
227
- const { data, error } = await getClient().from(T.tasks).insert({
228
- tenant_id: tenantId,
229
- title: input.title,
230
- description: input.description ?? null,
231
- status: input.status ?? "todo",
232
- priority: input.priority ?? "medium",
233
- due_date: input.dueDate ?? null,
234
- assigned_to_id: input.assignedToId ?? null,
235
- parent_id: input.parentId ?? null,
236
- labels: input.labels ?? [],
237
- position: maxPos + 1
238
- }).select("*").single();
239
- if (error) throw error;
240
- return toTask(data);
241
- },
242
- async updateTask(id, input) {
243
- const payload = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
244
- if (input.title !== void 0) payload.title = input.title;
245
- if (input.description !== void 0) payload.description = input.description;
246
- if (input.status !== void 0) payload.status = input.status;
247
- if (input.priority !== void 0) payload.priority = input.priority;
248
- if (input.dueDate !== void 0) payload.due_date = input.dueDate;
249
- if (input.assignedToId !== void 0) payload.assigned_to_id = input.assignedToId;
250
- if (input.labels !== void 0) payload.labels = input.labels;
251
- if (input.position !== void 0) payload.position = input.position;
252
- const { data, error } = await getClient().from(T.tasks).update(payload).eq("id", id).select("*").single();
253
- if (error) throw error;
254
- return toTask(data);
255
- },
256
- async deleteTask(id) {
257
- await getClient().from(T.tasks).delete().eq("id", id);
258
- },
259
- async reorderTask(id, newPosition, status) {
260
- await getClient().from(T.tasks).update({ position: newPosition, status, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
261
- },
262
- async getLabels() {
263
- const tenantId = getTenantId();
264
- if (!tenantId) return [];
265
- const { data } = await getClient().from(T.labels).select("*").eq("tenant_id", tenantId).eq("is_active", true).order("name");
266
- return (data ?? []).map((r) => snakeToCamel(r));
267
- },
268
- async getSummary() {
269
- const tenantId = getTenantId();
270
- if (!tenantId) return { total: 0, todo: 0, inProgress: 0, done: 0, overdue: 0, dueToday: 0, highPriority: 0 };
271
- const { data } = await getClient().from(T.tasks).select("status, priority, due_date").eq("tenant_id", tenantId).is("parent_id", null);
272
- const tasks = data ?? [];
273
- const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
274
- return {
275
- total: tasks.length,
276
- todo: tasks.filter((t) => t.status === "todo").length,
277
- inProgress: tasks.filter((t) => t.status === "in_progress").length,
278
- done: tasks.filter((t) => t.status === "done").length,
279
- overdue: tasks.filter((t) => t.due_date && t.due_date < today2 && t.status !== "done" && t.status !== "cancelled").length,
280
- dueToday: tasks.filter((t) => t.due_date === today2 && t.status !== "done" && t.status !== "cancelled").length,
281
- highPriority: tasks.filter((t) => (t.priority === "high" || t.priority === "urgent") && t.status !== "done" && t.status !== "cancelled").length
282
- };
283
- }
284
- };
285
- }
286
- function createTasksStore(provider) {
287
- return vanilla.createStore((set, get) => ({
288
- tasks: [],
289
- tasksLoading: false,
290
- labels: [],
291
- summary: null,
292
- summaryLoading: false,
293
- drawerOpen: false,
294
- selectedTaskId: null,
295
- activeFilter: "all",
296
- // ----- Data actions -----
297
- async fetchTasks(query) {
298
- return saas.dedup("tasks:list", async () => {
299
- set({ tasksLoading: true });
300
- try {
301
- const filter = get().activeFilter;
302
- const q = {
303
- parentId: null,
304
- ...query,
305
- ...filter !== "all" ? { status: filter } : {}
306
- };
307
- const tasks = await provider.getTasks(q);
308
- set({ tasks, tasksLoading: false });
309
- } catch {
310
- set({ tasksLoading: false });
311
- }
312
- });
313
- },
314
- async fetchLabels() {
315
- return saas.dedup("tasks:labels", async () => {
316
- try {
317
- const labels = await provider.getLabels();
318
- set({ labels });
319
- } catch {
320
- }
321
- });
322
- },
323
- async fetchSummary() {
324
- return saas.dedup("tasks:summary", async () => {
325
- set({ summaryLoading: true });
326
- try {
327
- const summary = await provider.getSummary();
328
- set({ summary, summaryLoading: false });
329
- } catch {
330
- set({ summaryLoading: false });
331
- }
332
- });
333
- },
334
- async createTask(input) {
335
- const task = await provider.createTask(input);
336
- await get().fetchTasks();
337
- await get().fetchSummary();
338
- return task;
339
- },
340
- async updateTask(id, input) {
341
- const task = await provider.updateTask(id, input);
342
- set((s) => ({ tasks: s.tasks.map((t) => t.id === id ? task : t) }));
343
- await get().fetchSummary();
344
- return task;
345
- },
346
- async deleteTask(id) {
347
- await provider.deleteTask(id);
348
- set((s) => ({ tasks: s.tasks.filter((t) => t.id !== id), selectedTaskId: null }));
349
- await get().fetchSummary();
350
- },
351
- async reorderTask(id, newPosition, status) {
352
- await provider.reorderTask(id, newPosition, status);
353
- await get().fetchTasks();
354
- },
355
- // ----- UI actions -----
356
- openDrawer() {
357
- set({ drawerOpen: true });
358
- get().fetchTasks();
359
- get().fetchLabels();
360
- get().fetchSummary();
361
- },
362
- closeDrawer() {
363
- set({ drawerOpen: false, selectedTaskId: null });
364
- },
365
- toggleDrawer() {
366
- const { drawerOpen } = get();
367
- if (drawerOpen) get().closeDrawer();
368
- else get().openDrawer();
369
- },
370
- selectTask(id) {
371
- set({ selectedTaskId: id });
372
- },
373
- setFilter(filter) {
374
- set({ activeFilter: filter });
375
- get().fetchTasks();
376
- }
377
- }));
378
- }
379
-
380
- // src/locales/en.ts
381
- var en = {
382
- "tasks.title": "Tasks",
383
- "tasks.drawer.title": "Tasks",
384
- "tasks.drawer.subtitle": "Manage your team tasks",
385
- "tasks.quickAdd.placeholder": "Add a task...",
386
- "tasks.filter.all": "All",
387
- "tasks.filter.todo": "To Do",
388
- "tasks.filter.inProgress": "In Progress",
389
- "tasks.filter.done": "Done",
390
- "tasks.status.todo": "To Do",
391
- "tasks.status.in_progress": "In Progress",
392
- "tasks.status.done": "Done",
393
- "tasks.status.cancelled": "Cancelled",
394
- "tasks.priority.low": "Low",
395
- "tasks.priority.medium": "Medium",
396
- "tasks.priority.high": "High",
397
- "tasks.priority.urgent": "Urgent",
398
- "tasks.detail.description": "Description",
399
- "tasks.detail.descriptionPlaceholder": "Add a description...",
400
- "tasks.detail.status": "Status",
401
- "tasks.detail.priority": "Priority",
402
- "tasks.detail.dueDate": "Due Date",
403
- "tasks.detail.assignee": "Assigned To",
404
- "tasks.detail.labels": "Labels",
405
- "tasks.detail.subtasks": "Subtasks",
406
- "tasks.detail.delete": "Delete Task",
407
- "tasks.detail.deleteConfirm": "Are you sure you want to delete this task?",
408
- "tasks.detail.created": "Created",
409
- "tasks.detail.chatWithAi": "Chat with AI",
410
- "tasks.detail.chatWithAi.prompt": "Take over this task and help me resolve it now, using your tools when needed:",
411
- "tasks.detail.updated": "Updated",
412
- "tasks.detail.addSubtask": "Add subtask...",
413
- "tasks.detail.back": "Back to list",
414
- "tasks.summary.overdue": "overdue",
415
- "tasks.summary.dueToday": "due today",
416
- "tasks.empty": "No tasks yet",
417
- "tasks.empty.description": "Add your first task using the input above",
418
- "tasks.settings.title": "Tasks",
419
- "tasks.settings.general": "General",
420
- "tasks.settings.defaultPriority": "Default Priority",
421
- "tasks.settings.autoAssign": "Auto-assign tasks to creator"
422
- };
423
-
424
- // src/locales/pt-BR.ts
425
- var ptBR = {
426
- "tasks.title": "Tarefas",
427
- "tasks.drawer.title": "Tarefas",
428
- "tasks.drawer.subtitle": "Gerencie as tarefas da equipe",
429
- "tasks.quickAdd.placeholder": "Adicionar tarefa...",
430
- "tasks.filter.all": "Todas",
431
- "tasks.filter.todo": "A Fazer",
432
- "tasks.filter.inProgress": "Em Andamento",
433
- "tasks.filter.done": "Conclu\xEDdas",
434
- "tasks.status.todo": "A Fazer",
435
- "tasks.status.in_progress": "Em Andamento",
436
- "tasks.status.done": "Conclu\xEDda",
437
- "tasks.status.cancelled": "Cancelada",
438
- "tasks.priority.low": "Baixa",
439
- "tasks.priority.medium": "M\xE9dia",
440
- "tasks.priority.high": "Alta",
441
- "tasks.priority.urgent": "Urgente",
442
- "tasks.detail.description": "Descri\xE7\xE3o",
443
- "tasks.detail.descriptionPlaceholder": "Adicionar descri\xE7\xE3o...",
444
- "tasks.detail.status": "Status",
445
- "tasks.detail.priority": "Prioridade",
446
- "tasks.detail.dueDate": "Data de Vencimento",
447
- "tasks.detail.assignee": "Respons\xE1vel",
448
- "tasks.detail.labels": "Etiquetas",
449
- "tasks.detail.subtasks": "Subtarefas",
450
- "tasks.detail.delete": "Excluir Tarefa",
451
- "tasks.detail.deleteConfirm": "Tem certeza que deseja excluir esta tarefa?",
452
- "tasks.detail.created": "Criada em",
453
- "tasks.detail.chatWithAi": "Conversar com IA",
454
- "tasks.detail.chatWithAi.prompt": "Assuma esta tarefa e me ajude a resolver agora, usando suas ferramentas quando precisar:",
455
- "tasks.detail.updated": "Atualizada em",
456
- "tasks.detail.addSubtask": "Adicionar subtarefa...",
457
- "tasks.detail.back": "Voltar \xE0 lista",
458
- "tasks.summary.overdue": "atrasadas",
459
- "tasks.summary.dueToday": "vencem hoje",
460
- "tasks.empty": "Nenhuma tarefa ainda",
461
- "tasks.empty.description": "Adicione sua primeira tarefa usando o campo acima",
462
- "tasks.settings.title": "Tarefas",
463
- "tasks.settings.general": "Geral",
464
- "tasks.settings.defaultPriority": "Prioridade Padr\xE3o",
465
- "tasks.settings.autoAssign": "Atribuir tarefas automaticamente ao criador"
466
- };
467
-
468
- // src/locales/index.ts
469
- var tasksLocales = {
470
- en,
471
- "pt-BR": ptBR
472
- };
473
-
474
- // src/registries.ts
475
- var taskLabelEntity = {
476
- name: "Task Label",
477
- namePlural: "Task Labels",
478
- icon: "Tag",
479
- displayField: "name",
480
- defaultSort: "name",
481
- fields: [
482
- { key: "name", label: "Name", type: "text", required: true, showInTable: true },
483
- { key: "color", label: "Color", type: "text", showInTable: true, defaultValue: "#6366f1" },
484
- { key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
485
- ],
486
- data: { table: T.labels, tenantScoped: true }
487
- };
488
- var tasksRegistries = [
489
- {
490
- id: "task-labels",
491
- entity: taskLabelEntity,
492
- icon: "Tag",
493
- description: "Labels for categorizing tasks",
494
- display: "table",
495
- seedData: [
496
- { id: "lbl-bug", name: "Bug", color: "#ef4444", isActive: true },
497
- { id: "lbl-feature", name: "Feature", color: "#3b82f6", isActive: true },
498
- { id: "lbl-urgent", name: "Urgent", color: "#f59e0b", isActive: true },
499
- { id: "lbl-personal", name: "Personal", color: "#8b5cf6", isActive: true }
500
- ]
501
- }
502
- ];
503
- var TASKS_DEFAULTS = {
504
- defaultPriority: "medium"
505
- };
506
- function TasksGeneralSettings() {
507
- const t = core.useTranslation();
508
- const s = saas.useTenantPluginSettings("tasks", TASKS_DEFAULTS);
509
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
510
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
511
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold mb-1", children: t("tasks.settings.general") }),
512
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground mb-3", children: t("tasks.drawer.subtitle") })
513
- ] }),
514
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
515
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium", children: t("tasks.settings.defaultPriority") }),
516
- /* @__PURE__ */ jsxRuntime.jsxs(
517
- "select",
518
- {
519
- className: "w-full max-w-xs rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-sm",
520
- value: s.get("defaultPriority"),
521
- onChange: (e) => s.set("defaultPriority", e.target.value),
522
- children: [
523
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "low", children: t("tasks.priority.low") }),
524
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "medium", children: t("tasks.priority.medium") }),
525
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "high", children: t("tasks.priority.high") }),
526
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "urgent", children: t("tasks.priority.urgent") })
527
- ]
528
- }
529
- )
530
- ] }) })
531
- ] });
532
- }
533
- var FILTERS = [
534
- { key: "all", labelKey: "tasks.filter.all" },
535
- { key: "todo", labelKey: "tasks.filter.todo" },
536
- { key: "in_progress", labelKey: "tasks.filter.inProgress" },
537
- { key: "done", labelKey: "tasks.filter.done" }
538
- ];
539
- function TasksFilterBar() {
540
- const t = core.useTranslation();
541
- const activeFilter = useTasksStore((s) => s.activeFilter);
542
- const setFilter = useTasksStore((s) => s.setFilter);
543
- const summary = useTasksStore((s) => s.summary);
544
- const getCount = (key) => {
545
- if (!summary) return void 0;
546
- switch (key) {
547
- case "all":
548
- return summary.total;
549
- case "todo":
550
- return summary.todo;
551
- case "in_progress":
552
- return summary.inProgress;
553
- case "done":
554
- return summary.done;
555
- default:
556
- return void 0;
557
- }
558
- };
559
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1 px-3 py-2 border-b overflow-x-auto", children: FILTERS.map(({ key, labelKey }) => {
560
- const active = activeFilter === key;
561
- const count = getCount(key);
562
- return /* @__PURE__ */ jsxRuntime.jsxs(
563
- "button",
564
- {
565
- type: "button",
566
- onClick: () => setFilter(key),
567
- 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"}`,
568
- children: [
569
- t(labelKey),
570
- count != null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: `text-[10px] ${active ? "text-primary-foreground/80" : "text-muted-foreground"}`, children: count })
571
- ]
572
- },
573
- key
574
- );
575
- }) });
576
- }
577
- function TaskQuickAdd({ parentId }) {
578
- const t = core.useTranslation();
579
- const createTask = useTasksStore((s) => s.createTask);
580
- const [title, setTitle] = React4.useState("");
581
- const [loading, setLoading] = React4.useState(false);
582
- const handleSubmit = React4.useCallback(async (e) => {
583
- e.preventDefault();
584
- const trimmed = title.trim();
585
- if (!trimmed || loading) return;
586
- setLoading(true);
587
- try {
588
- await createTask({
589
- title: trimmed,
590
- parentId: parentId ?? null
591
- });
592
- setTitle("");
593
- } finally {
594
- setLoading(false);
595
- }
596
- }, [title, loading, createTask, parentId]);
597
- return /* @__PURE__ */ jsxRuntime.jsxs("form", { onSubmit: handleSubmit, className: "flex items-center gap-2 px-3 py-2 border-b", children: [
598
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4 shrink-0 text-muted-foreground" }),
599
- /* @__PURE__ */ jsxRuntime.jsx(
600
- "input",
601
- {
602
- type: "text",
603
- value: title,
604
- onChange: (e) => setTitle(e.target.value),
605
- placeholder: parentId ? t("tasks.detail.addSubtask") : t("tasks.quickAdd.placeholder"),
606
- className: "flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground/60",
607
- disabled: loading
608
- }
609
- )
610
- ] });
611
- }
612
- var PRIORITY_CONFIG = {
613
- low: { color: "text-muted-foreground", bg: "bg-muted", icon: lucideReact.ArrowDown },
614
- medium: { color: "text-info-soft-foreground", bg: "bg-info-soft", icon: lucideReact.Minus },
615
- high: { color: "text-warning-soft-foreground", bg: "bg-warning-soft", icon: lucideReact.ArrowUp },
616
- urgent: { color: "text-destructive-soft-foreground", bg: "bg-destructive-soft", icon: lucideReact.AlertTriangle }
617
- };
618
- function TaskPriorityBadge({ priority, showLabel }) {
619
- const cfg = PRIORITY_CONFIG[priority];
620
- const Icon = cfg.icon;
621
- return /* @__PURE__ */ jsxRuntime.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: [
622
- /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-3 w-3" }),
623
- showLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "capitalize", children: priority })
624
- ] });
625
- }
626
- function TaskLabelBadge({ labelId }) {
627
- const labels = useTasksStore((s) => s.labels);
628
- const label = labels.find((l) => l.id === labelId);
629
- if (!label) return null;
630
- return /* @__PURE__ */ jsxRuntime.jsx(
631
- "span",
632
- {
633
- className: "inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium leading-none",
634
- style: {
635
- backgroundColor: label.color + "20",
636
- color: label.color
637
- },
638
- children: label.name
639
- }
640
- );
641
- }
642
- function TaskLabelBadges({ labelIds, max = 2 }) {
643
- if (labelIds.length === 0) return null;
644
- const visible = labelIds.slice(0, max);
645
- const overflow = labelIds.length - max;
646
- return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1", children: [
647
- visible.map((id) => /* @__PURE__ */ jsxRuntime.jsx(TaskLabelBadge, { labelId: id }, id)),
648
- overflow > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
649
- "+",
650
- overflow
651
- ] })
652
- ] });
653
- }
654
- function isOverdue(dueDate) {
655
- if (!dueDate) return false;
656
- return dueDate < (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
657
- }
658
- function isToday(dueDate) {
659
- if (!dueDate) return false;
660
- return dueDate === (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
661
- }
662
- function formatDueDate(dueDate) {
663
- const d = /* @__PURE__ */ new Date(dueDate + "T00:00:00");
664
- return d.toLocaleDateString(void 0, { month: "short", day: "numeric" });
665
- }
666
- function TaskCard({ task, subtaskCount }) {
667
- const selectTask = useTasksStore((s) => s.selectTask);
668
- const updateTask = useTasksStore((s) => s.updateTask);
669
- const isDone = task.status === "done";
670
- const overdue = !isDone && isOverdue(task.dueDate);
671
- const dueToday = !isDone && isToday(task.dueDate);
672
- const handleCheck = (e) => {
673
- e.stopPropagation();
674
- updateTask(task.id, { status: isDone ? "todo" : "done" });
675
- };
676
- return /* @__PURE__ */ jsxRuntime.jsxs(
677
- "div",
678
- {
679
- onClick: () => selectTask(task.id),
680
- className: "group flex items-start gap-2.5 rounded-lg border border-transparent px-3 py-2.5 transition-colors hover:bg-muted bg-card shadow-button active:shadow-button-inset hover:border-border cursor-pointer",
681
- children: [
682
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pt-0.5", onClick: handleCheck, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, { checked: isDone, onChange: () => {
683
- } }) }),
684
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "min-w-0 flex-1 space-y-1", children: [
685
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: `text-sm leading-snug ${isDone ? "line-through text-muted-foreground" : ""}`, children: task.title }),
686
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-1.5", children: [
687
- /* @__PURE__ */ jsxRuntime.jsx(TaskPriorityBadge, { priority: task.priority }),
688
- task.dueDate && /* @__PURE__ */ jsxRuntime.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: [
689
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "h-3 w-3" }),
690
- formatDueDate(task.dueDate)
691
- ] }),
692
- subtaskCount && subtaskCount.total > 0 && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "inline-flex items-center gap-1 text-[11px] text-muted-foreground", children: [
693
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ListChecks, { className: "h-3 w-3" }),
694
- subtaskCount.done,
695
- "/",
696
- subtaskCount.total
697
- ] }),
698
- /* @__PURE__ */ jsxRuntime.jsx(TaskLabelBadges, { labelIds: task.labels, max: 2 })
699
- ] })
700
- ] }),
701
- task.assignedToName && /* @__PURE__ */ jsxRuntime.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() })
702
- ]
703
- }
704
- );
705
- }
706
- var STATUS_COLORS = {
707
- todo: "bg-muted-foreground",
708
- in_progress: "bg-info",
709
- done: "bg-success",
710
- cancelled: "bg-muted-foreground/60"
711
- };
712
- function TaskStatusGroup({ status, tasks }) {
713
- const t = core.useTranslation();
714
- const [collapsed, setCollapsed] = React4.useState(status === "cancelled");
715
- if (tasks.length === 0) return null;
716
- const Chevron = collapsed ? lucideReact.ChevronRight : lucideReact.ChevronDown;
717
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-0.5", children: [
718
- /* @__PURE__ */ jsxRuntime.jsxs(
719
- "button",
720
- {
721
- type: "button",
722
- onClick: () => setCollapsed(!collapsed),
723
- 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",
724
- children: [
725
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `h-2 w-2 rounded-full ${STATUS_COLORS[status]}` }),
726
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: t(`tasks.status.${status}`) }),
727
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] font-normal", children: [
728
- "(",
729
- tasks.length,
730
- ")"
731
- ] }),
732
- /* @__PURE__ */ jsxRuntime.jsx(Chevron, { className: "ml-auto h-3.5 w-3.5" })
733
- ]
734
- }
735
- ),
736
- !collapsed && /* @__PURE__ */ jsxRuntime.jsx("div", { children: tasks.map((task) => /* @__PURE__ */ jsxRuntime.jsx(TaskCard, { task }, task.id)) })
737
- ] });
738
- }
739
-
740
- // src/types.ts
741
- var TASK_STATUSES = ["todo", "in_progress", "done", "cancelled"];
742
- var TASK_PRIORITIES = ["low", "medium", "high", "urgent"];
743
- var STATUS_DOT = {
744
- todo: "bg-muted-foreground",
745
- in_progress: "bg-info",
746
- done: "bg-success",
747
- cancelled: "bg-muted-foreground/60"
748
- };
749
- function formatDateTime(iso) {
750
- const d = new Date(iso);
751
- const sameDay = d.toDateString() === (/* @__PURE__ */ new Date()).toDateString();
752
- 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" });
753
- }
754
- function TaskDetail({ taskId }) {
755
- const t = core.useTranslation();
756
- const tasks = useTasksStore((s) => s.tasks);
757
- const selectTask = useTasksStore((s) => s.selectTask);
758
- const updateTask = useTasksStore((s) => s.updateTask);
759
- const deleteTask = useTasksStore((s) => s.deleteTask);
760
- const task = tasks.find((tt) => tt.id === taskId);
761
- const delegate = saas.useDelegateToAssistant();
762
- const [title, setTitle] = React4.useState("");
763
- const [description, setDescription] = React4.useState("");
764
- const [subtasks, setSubtasks] = React4.useState([]);
765
- React4.useEffect(() => {
766
- if (task) {
767
- setTitle(task.title);
768
- setDescription(task.description ?? "");
769
- }
770
- }, [task]);
771
- React4.useEffect(() => {
772
- setSubtasks([]);
773
- }, [taskId]);
774
- const handleTitleBlur = React4.useCallback(() => {
775
- if (task && title.trim() && title !== task.title) {
776
- updateTask(task.id, { title: title.trim() });
777
- }
778
- }, [task, title, updateTask]);
779
- const handleDescriptionBlur = React4.useCallback(() => {
780
- if (task && description !== (task.description ?? "")) {
781
- updateTask(task.id, { description: description || null });
782
- }
783
- }, [task, description, updateTask]);
784
- const handleDelete = React4.useCallback(() => {
785
- if (task && confirm(t("tasks.detail.deleteConfirm"))) {
786
- deleteTask(task.id);
787
- }
788
- }, [task, deleteTask, t]);
789
- if (!task) {
790
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-8 text-muted-foreground", children: [
791
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm", children: "Task not found" }),
792
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => selectTask(null), className: "mt-2 text-xs text-primary hover:underline", children: t("tasks.detail.back") })
793
- ] });
794
- }
795
- return (
796
- // Same frame as the Notes editor: header row with back + icon actions,
797
- // full-bleed px-4 content, sectioned by hairlines, thin timestamp footer.
798
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-full flex-col", children: [
799
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1 border-b border-border/40 px-2 py-1.5", children: [
800
- /* @__PURE__ */ jsxRuntime.jsxs(
801
- "button",
802
- {
803
- type: "button",
804
- onClick: () => selectTask(null),
805
- 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",
806
- children: [
807
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "h-3.5 w-3.5" }),
808
- t("tasks.drawer.title")
809
- ]
810
- }
811
- ),
812
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1" }),
813
- /* @__PURE__ */ jsxRuntime.jsx(
814
- "button",
815
- {
816
- type: "button",
817
- onClick: handleDelete,
818
- "aria-label": t("tasks.detail.delete"),
819
- title: t("tasks.detail.delete"),
820
- className: "inline-flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-destructive",
821
- children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-3.5 w-3.5" })
822
- }
823
- )
824
- ] }),
825
- /* @__PURE__ */ jsxRuntime.jsx(
826
- "input",
827
- {
828
- type: "text",
829
- value: title,
830
- onChange: (e) => setTitle(e.target.value),
831
- onBlur: handleTitleBlur,
832
- onKeyDown: (e) => {
833
- if (e.key === "Enter") e.currentTarget.blur();
834
- },
835
- className: "bg-transparent px-4 pt-3 text-sm font-semibold text-foreground outline-none"
836
- }
837
- ),
838
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-1.5 px-4 pt-2", children: [
839
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
840
- /* @__PURE__ */ jsxRuntime.jsx(
841
- "select",
842
- {
843
- value: task.status,
844
- onChange: (e) => updateTask(task.id, { status: e.target.value }),
845
- 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",
846
- children: TASK_STATUSES.map((s) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: s, children: t(`tasks.status.${s}`) }, s))
847
- }
848
- ),
849
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: `absolute left-2 top-1/2 -translate-y-1/2 h-2 w-2 rounded-full ${STATUS_DOT[task.status]}` }),
850
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "absolute right-1.5 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
851
- ] }),
852
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
853
- /* @__PURE__ */ jsxRuntime.jsx(
854
- "select",
855
- {
856
- value: task.priority,
857
- onChange: (e) => updateTask(task.id, { priority: e.target.value }),
858
- 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",
859
- children: TASK_PRIORITIES.map((p) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: p, children: t(`tasks.priority.${p}`) }, p))
860
- }
861
- ),
862
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "absolute right-1.5 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
863
- ] }),
864
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", children: [
865
- /* @__PURE__ */ jsxRuntime.jsx(
866
- "input",
867
- {
868
- type: "date",
869
- value: task.dueDate ?? "",
870
- onChange: (e) => updateTask(task.id, { dueDate: e.target.value || null }),
871
- 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]"
872
- }
873
- ),
874
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Calendar, { className: "absolute left-2 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" })
875
- ] }),
876
- task.labels.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(TaskLabelBadges, { labelIds: task.labels, max: 3 })
877
- ] }),
878
- /* @__PURE__ */ jsxRuntime.jsx(
879
- "textarea",
880
- {
881
- value: description,
882
- onChange: (e) => setDescription(e.target.value),
883
- onBlur: handleDescriptionBlur,
884
- placeholder: t("tasks.detail.descriptionPlaceholder"),
885
- 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"
886
- }
887
- ),
888
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsxs(
889
- "button",
890
- {
891
- type: "button",
892
- onClick: () => delegate(
893
- `${t("tasks.detail.chatWithAi.prompt")}
894
-
895
- "${task.title}"${task.description ? `
896
- ${task.description}` : ""}${task.dueDate ? `
897
- (${t("tasks.detail.dueDate")}: ${task.dueDate})` : ""}`
898
- ),
899
- 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",
900
- children: [
901
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-4 w-4 transition-transform duration-300 group-hover:rotate-12" }),
902
- t("tasks.detail.chatWithAi")
903
- ]
904
- }
905
- ) }),
906
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-border/40 px-4 py-2.5", children: [
907
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60", children: [
908
- t("tasks.detail.subtasks"),
909
- subtasks.length > 0 ? ` \xB7 ${subtasks.length}` : ""
910
- ] }),
911
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "-mx-3", children: /* @__PURE__ */ jsxRuntime.jsx(TaskQuickAdd, { parentId: task.id }) }),
912
- subtasks.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border/40", children: subtasks.map((st) => /* @__PURE__ */ jsxRuntime.jsx(TaskCard, { task: st }, st.id)) })
913
- ] }),
914
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "border-t border-border/40 px-4 py-1.5 text-[10px] text-muted-foreground/60", children: [
915
- t("tasks.detail.created"),
916
- " ",
917
- formatDateTime(task.createdAt),
918
- " \xB7 ",
919
- t("tasks.detail.updated"),
920
- " ",
921
- formatDateTime(task.updatedAt)
922
- ] })
923
- ] })
924
- );
925
- }
926
- var STATUS_ORDER = ["todo", "in_progress", "done", "cancelled"];
927
- function TasksPanel() {
928
- const t = core.useTranslation();
929
- const selectedTaskId = useTasksStore((s) => s.selectedTaskId);
930
- const tasks = useTasksStore((s) => s.tasks);
931
- const tasksLoading = useTasksStore((s) => s.tasksLoading);
932
- const activeFilter = useTasksStore((s) => s.activeFilter);
933
- const fetchTasks = useTasksStore((s) => s.fetchTasks);
934
- const fetchLabels = useTasksStore((s) => s.fetchLabels);
935
- const fetchSummary = useTasksStore((s) => s.fetchSummary);
936
- React4__default.default.useEffect(() => {
937
- void fetchTasks();
938
- void fetchLabels();
939
- void fetchSummary();
940
- }, [fetchTasks, fetchLabels, fetchSummary]);
941
- const grouped = React4__default.default.useMemo(() => {
942
- const map = /* @__PURE__ */ new Map();
943
- for (const s of STATUS_ORDER) map.set(s, []);
944
- for (const task of tasks) {
945
- const group = map.get(task.status);
946
- if (group) group.push(task);
947
- }
948
- return map;
949
- }, [tasks]);
950
- return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
951
- !selectedTaskId && /* @__PURE__ */ jsxRuntime.jsx(TasksFilterBar, {}),
952
- !selectedTaskId && /* @__PURE__ */ jsxRuntime.jsx(TaskQuickAdd, {}),
953
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-h-0 flex-1 overflow-y-auto", children: selectedTaskId ? /* @__PURE__ */ jsxRuntime.jsx(TaskDetail, { taskId: selectedTaskId }) : tasksLoading && tasks.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center py-16", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-primary border-t-transparent" }) }) : tasks.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-muted-foreground", children: [
954
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ClipboardList, { className: "mb-3 h-10 w-10 opacity-40" }),
955
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: t("tasks.empty") }),
956
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs", children: t("tasks.empty.description") })
957
- ] }) : activeFilter !== "all" ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "py-1", children: tasks.map((task) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-2", children: /* @__PURE__ */ jsxRuntime.jsx(TaskCard, { task }) }, task.id)) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2 py-2", children: STATUS_ORDER.filter((s) => s !== "cancelled" || (grouped.get(s)?.length ?? 0) > 0).map((status) => /* @__PURE__ */ jsxRuntime.jsx(TaskStatusGroup, { status, tasks: grouped.get(status) ?? [] }, status)) }) })
958
- ] });
959
- }
960
- function useTasksSummaryLine() {
961
- const t = core.useTranslation();
962
- const summary = useTasksStore((s) => s.summary);
963
- return React4__default.default.useMemo(() => {
964
- if (!summary) return "";
965
- const parts = [];
966
- if (summary.overdue > 0) parts.push(`${summary.overdue} ${t("tasks.summary.overdue")}`);
967
- if (summary.dueToday > 0) parts.push(`${summary.dueToday} ${t("tasks.summary.dueToday")}`);
968
- return parts.join(" \xB7 ");
969
- }, [summary, t]);
970
- }
971
- function TasksDrawer() {
972
- const t = core.useTranslation();
973
- const drawerOpen = useTasksStore((s) => s.drawerOpen);
974
- const closeDrawer = useTasksStore((s) => s.closeDrawer);
975
- const summaryLine = useTasksSummaryLine();
976
- return /* @__PURE__ */ jsxRuntime.jsx(ui.Sheet, { open: drawerOpen, onOpenChange: (v) => {
977
- if (!v) closeDrawer();
978
- }, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.SheetContent, { width: "max-w-lg", overlay: "dim", children: [
979
- /* @__PURE__ */ jsxRuntime.jsxs(ui.SheetHeader, { children: [
980
- /* @__PURE__ */ jsxRuntime.jsx(ui.SheetTitle, { children: t("tasks.drawer.title") }),
981
- /* @__PURE__ */ jsxRuntime.jsx(ui.SheetDescription, { children: summaryLine || t("tasks.drawer.subtitle") })
982
- ] }),
983
- /* @__PURE__ */ jsxRuntime.jsx(ui.SheetBody, { className: "flex flex-col px-0 py-0", children: /* @__PURE__ */ jsxRuntime.jsx(TasksPanel, {}) })
984
- ] }) });
985
- }
986
- function TasksTopbarButton({ config }) {
987
- const { tasksConfig, tasksProvider, tasksStore } = config;
988
- const t = core.useTranslation();
989
- const railMounted = saas.useRightRailStore((s) => s.mounted);
990
- const summary = zustand.useStore(tasksStore, (s) => s.summary);
991
- const openCount = summary ? summary.todo + summary.inProgress : 0;
992
- const Panel = React4__default.default.useMemo(() => {
993
- const RailPanel = () => /* @__PURE__ */ jsxRuntime.jsx(TasksContextProvider, { config: tasksConfig, provider: tasksProvider, store: tasksStore, children: /* @__PURE__ */ jsxRuntime.jsx(TasksPanel, {}) });
994
- RailPanel.displayName = "TasksRailPanel";
995
- return RailPanel;
996
- }, [tasksConfig, tasksProvider, tasksStore]);
997
- saas.useRightRailPanel(
998
- React4__default.default.useMemo(
999
- () => railMounted ? {
1000
- id: "tasks",
1001
- label: t("tasks.drawer.title") || "Tasks",
1002
- icon: lucideReact.CheckSquare,
1003
- order: 20,
1004
- badge: { count: openCount, tone: "destructive" },
1005
- Component: Panel
1006
- } : null,
1007
- [railMounted, t, Panel, openCount]
1008
- )
1009
- );
1010
- return /* @__PURE__ */ jsxRuntime.jsxs(TasksContextProvider, { config: tasksConfig, provider: tasksProvider, store: tasksStore, children: [
1011
- /* @__PURE__ */ jsxRuntime.jsx(TasksTopbarButtonInner, { store: tasksStore, railMounted }),
1012
- !railMounted && /* @__PURE__ */ jsxRuntime.jsx(TasksDrawer, {})
1013
- ] });
1014
- }
1015
- function TasksTopbarButtonInner({
1016
- store,
1017
- railMounted
1018
- }) {
1019
- const toggleDrawer = zustand.useStore(store, (s) => s.toggleDrawer);
1020
- const fetchSummary = zustand.useStore(store, (s) => s.fetchSummary);
1021
- const summary = zustand.useStore(store, (s) => s.summary);
1022
- const togglePanel = saas.useRightRailStore((s) => s.togglePanel);
1023
- React4.useEffect(() => {
1024
- fetchSummary();
1025
- }, [fetchSummary]);
1026
- const pendingCount = summary ? summary.todo + summary.inProgress : 0;
1027
- return /* @__PURE__ */ jsxRuntime.jsxs(
1028
- ui.Button,
1029
- {
1030
- variant: "ghost",
1031
- size: "icon",
1032
- className: ui.cn("relative text-sidebar-foreground/70 hover:bg-sidebar/30 hover:text-sidebar-foreground"),
1033
- onClick: () => railMounted ? togglePanel("tasks") : toggleDrawer(),
1034
- "aria-label": "Tasks",
1035
- children: [
1036
- /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckSquare, { className: "h-5 w-5" }),
1037
- pendingCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(
1038
- "span",
1039
- {
1040
- className: ui.cn(
1041
- "absolute -right-0.5 -top-0.5 flex items-center justify-center rounded-full bg-destructive text-destructive-foreground",
1042
- pendingCount > 9 ? "h-5 min-w-5 px-1 text-[10px]" : "h-4 w-4 text-[10px]"
1043
- ),
1044
- children: pendingCount > 99 ? "99+" : pendingCount
1045
- }
1046
- )
1047
- ]
1048
- }
1049
- );
1050
- }
1051
-
1052
- // src/migrations/index.ts
1053
- var MIGRATION_000_PLG_RENAME = `-- 000_plg_rename.sql \u2014 rename legacy tasks tables to plg_tasks_* for pools
1054
- -- provisioned before the industry-pool rename. Guarded: fires only when legacy
1055
- -- name exists and target does not, so fresh pools skip every branch.
1056
- DO $$
1057
- BEGIN
1058
- IF to_regclass('public.tsk_tasks') IS NOT NULL AND to_regclass('public.plg_tasks_tasks') IS NULL THEN
1059
- ALTER TABLE public.tsk_tasks RENAME TO plg_tasks_tasks;
1060
- END IF;
1061
- IF to_regclass('public.tsk_labels') IS NOT NULL AND to_regclass('public.plg_tasks_labels') IS NULL THEN
1062
- ALTER TABLE public.tsk_labels RENAME TO plg_tasks_labels;
1063
- END IF;
1064
- END $$;
1065
- `;
1066
- var MIGRATION_001_TASKS_BASE = `-- Tasks Plugin: Base Tables
1067
- -- Prefix: plg_tasks_
1068
-
1069
- -- Label definitions for categorizing tasks
1070
- CREATE TABLE IF NOT EXISTS public.plg_tasks_labels (
1071
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
1072
- tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
1073
- name text NOT NULL,
1074
- color text NOT NULL DEFAULT '#6366f1',
1075
- is_active boolean NOT NULL DEFAULT true,
1076
- created_at timestamptz NOT NULL DEFAULT now()
1077
- );
1078
- ALTER TABLE public.plg_tasks_labels ENABLE ROW LEVEL SECURITY;
1079
- CREATE INDEX IF NOT EXISTS idx_plg_tasks_labels_tenant ON public.plg_tasks_labels(tenant_id);
1080
-
1081
- -- Main tasks table (subtasks via parent_id self-reference)
1082
- CREATE TABLE IF NOT EXISTS public.plg_tasks_tasks (
1083
- id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
1084
- tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
1085
- title text NOT NULL,
1086
- description text,
1087
- status text NOT NULL DEFAULT 'todo'
1088
- CHECK (status IN ('todo', 'in_progress', 'done', 'cancelled')),
1089
- priority text NOT NULL DEFAULT 'medium'
1090
- CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
1091
- due_date date,
1092
- assigned_to_id uuid,
1093
- assigned_to_name text,
1094
- parent_id uuid REFERENCES public.plg_tasks_tasks(id) ON DELETE CASCADE,
1095
- labels jsonb NOT NULL DEFAULT '[]'::jsonb,
1096
- position integer NOT NULL DEFAULT 0,
1097
- created_by_id uuid,
1098
- created_by_name text,
1099
- created_at timestamptz NOT NULL DEFAULT now(),
1100
- updated_at timestamptz NOT NULL DEFAULT now()
1101
- );
1102
- ALTER TABLE public.plg_tasks_tasks ENABLE ROW LEVEL SECURITY;
1103
- CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_tenant ON public.plg_tasks_tasks(tenant_id);
1104
- CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_status ON public.plg_tasks_tasks(tenant_id, status);
1105
- CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_parent ON public.plg_tasks_tasks(parent_id);
1106
- CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_assigned ON public.plg_tasks_tasks(assigned_to_id);
1107
- CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_due ON public.plg_tasks_tasks(tenant_id, due_date)
1108
- WHERE due_date IS NOT NULL;
1109
-
1110
- -- RLS policies for plg_tasks_tasks (idempotent \u2014 DROP IF EXISTS before CREATE)
1111
- DROP POLICY IF EXISTS plg_tasks_tasks_select ON public.plg_tasks_tasks;
1112
- CREATE POLICY plg_tasks_tasks_select ON public.plg_tasks_tasks FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1113
- DROP POLICY IF EXISTS plg_tasks_tasks_insert ON public.plg_tasks_tasks;
1114
- CREATE POLICY plg_tasks_tasks_insert ON public.plg_tasks_tasks FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
1115
- DROP POLICY IF EXISTS plg_tasks_tasks_update ON public.plg_tasks_tasks;
1116
- CREATE POLICY plg_tasks_tasks_update ON public.plg_tasks_tasks FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1117
- DROP POLICY IF EXISTS plg_tasks_tasks_delete ON public.plg_tasks_tasks;
1118
- CREATE POLICY plg_tasks_tasks_delete ON public.plg_tasks_tasks FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1119
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_tasks_tasks TO authenticated;
1120
-
1121
- -- RLS policies for plg_tasks_labels (idempotent)
1122
- DROP POLICY IF EXISTS plg_tasks_labels_select ON public.plg_tasks_labels;
1123
- CREATE POLICY plg_tasks_labels_select ON public.plg_tasks_labels FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1124
- DROP POLICY IF EXISTS plg_tasks_labels_insert ON public.plg_tasks_labels;
1125
- CREATE POLICY plg_tasks_labels_insert ON public.plg_tasks_labels FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
1126
- DROP POLICY IF EXISTS plg_tasks_labels_update ON public.plg_tasks_labels;
1127
- CREATE POLICY plg_tasks_labels_update ON public.plg_tasks_labels FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1128
- DROP POLICY IF EXISTS plg_tasks_labels_delete ON public.plg_tasks_labels;
1129
- CREATE POLICY plg_tasks_labels_delete ON public.plg_tasks_labels FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
1130
- GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_tasks_labels TO authenticated;
1131
- `;
1132
-
1133
- // src/index.ts
1134
- var DEFAULT_LABELS = {
1135
- drawerTitle: "Tasks",
1136
- settingsTitle: "Tasks",
1137
- quickAddPlaceholder: "Add a task..."
1138
- };
1139
- function resolveConfig(options) {
1140
- return {
1141
- labels: { ...DEFAULT_LABELS, ...options?.labels }
1142
- };
1143
- }
1144
- function createTasksPlugin(options) {
1145
- const config = resolveConfig(options);
1146
- core.registerTranslations(tasksLocales);
1147
- const provider = options?.dataProvider ?? core.createSafeDataProvider(
1148
- () => createSupabaseTasksProvider(),
1149
- () => createMockTasksProvider()
1150
- );
1151
- const store = createTasksStore(provider);
1152
- saas.registerNotesTasksAdapter({
1153
- async createTodo({ title, noteId, noteTitle }) {
1154
- const task = await provider.createTask({
1155
- title,
1156
- description: noteTitle ? `Nota: ${noteTitle}` : void 0,
1157
- labels: [`${saas.NOTE_LABEL_PREFIX}${noteId}`]
1158
- });
1159
- void store.getState().fetchSummary();
1160
- return { id: task.id, title: task.title, done: task.status === "done" };
1161
- },
1162
- async listTodos(noteId) {
1163
- const tasks = await provider.getTasks({});
1164
- const label = `${saas.NOTE_LABEL_PREFIX}${noteId}`;
1165
- return tasks.filter((t) => t.labels.includes(label)).sort((a, b) => (a.status === "done" ? 1 : 0) - (b.status === "done" ? 1 : 0)).map((t) => ({ id: t.id, title: t.title, done: t.status === "done" }));
1166
- },
1167
- async toggleTodo(id, done) {
1168
- await provider.updateTask(id, { status: done ? "done" : "todo" });
1169
- void store.getState().fetchSummary();
1170
- },
1171
- async removeTodo(id) {
1172
- await provider.deleteTask(id);
1173
- void store.getState().fetchSummary();
1174
- }
1175
- });
1176
- return {
1177
- id: "tasks",
1178
- name: config.labels.drawerTitle,
1179
- icon: "CheckSquare",
1180
- version: "1.0.0",
1181
- scope: options?.scope ?? "universal",
1182
- verticalId: options?.verticalId,
1183
- defaultEnabled: true,
1184
- dependencies: [],
1185
- navigation: [],
1186
- routes: [],
1187
- widgets: [
1188
- {
1189
- id: "tasks-topbar-button",
1190
- zone: "shell.topbar.end",
1191
- component: TasksTopbarButton,
1192
- order: 10,
1193
- props: {
1194
- tasksConfig: config,
1195
- tasksProvider: provider,
1196
- tasksStore: store
1197
- }
1198
- }
1199
- ],
1200
- aiTools: [
1201
- {
1202
- id: "tasks.summary",
1203
- name: "getTasksSummary",
1204
- description: "Returns a summary of pending tasks: total, overdue, due today, by status and priority.",
1205
- icon: "CheckSquare",
1206
- mode: "read",
1207
- category: "Tasks",
1208
- parameters: {
1209
- type: "object",
1210
- properties: {
1211
- status: {
1212
- type: "string",
1213
- description: "Filter by task status",
1214
- enum: ["todo", "in_progress", "done", "cancelled", "all"]
1215
- }
1216
- }
1217
- },
1218
- suggestions: [
1219
- { label: "What tasks are overdue?" },
1220
- { label: "How many tasks are pending?" },
1221
- { label: "What's due today?" }
1222
- ]
1223
- }
1224
- ],
1225
- registries: tasksRegistries,
1226
- settings: [
1227
- {
1228
- id: "tasks",
1229
- label: config.labels.settingsTitle,
1230
- icon: "CheckSquare",
1231
- component: (() => {
1232
- const Tab = () => React4__default.default.createElement(
1233
- TasksContextProvider,
1234
- { config, provider, store },
1235
- React4__default.default.createElement(TasksGeneralSettings)
1236
- );
1237
- Tab.displayName = "TasksSettingsTab";
1238
- return Tab;
1239
- })(),
1240
- order: 14
1241
- }
1242
- ],
1243
- migrations: [
1244
- {
1245
- id: "tasks-000-plg-rename",
1246
- version: "1.0.0",
1247
- sql: MIGRATION_000_PLG_RENAME,
1248
- description: "Rename legacy tsk_* tables to plg_tasks_* in-place for pools provisioned before the industry-pool rename (guarded no-op on fresh pools)"
1249
- },
1250
- {
1251
- id: "tasks-001-base-tables",
1252
- version: "1.0.0",
1253
- sql: MIGRATION_001_TASKS_BASE,
1254
- description: "Create plg_tasks_tasks and plg_tasks_labels"
1255
- }
1256
- ],
1257
- locales: tasksLocales
1258
- };
1259
- }
1260
-
1261
- exports.createTasksPlugin = createTasksPlugin;
1262
- //# sourceMappingURL=index.cjs.map
1263
- //# sourceMappingURL=index.cjs.map