@fayz-ai/plugin-tasks 0.1.7 → 0.8.3
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/README.md +2 -0
- package/dist/components/TaskDetail.d.ts.map +1 -1
- package/dist/components/TasksDrawer.d.ts +1 -0
- package/dist/components/TasksDrawer.d.ts.map +1 -1
- package/dist/components/TasksGeneralSettings.d.ts.map +1 -1
- package/dist/components/TasksPanel.d.ts +7 -0
- package/dist/components/TasksPanel.d.ts.map +1 -0
- package/dist/components/TasksTopbarButton.d.ts.map +1 -1
- package/dist/data/supabase.d.ts.map +1 -1
- package/dist/data/tables.d.ts +5 -0
- package/dist/data/tables.d.ts.map +1 -0
- package/dist/index.cjs +317 -162
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +320 -165
- package/dist/index.js.map +1 -1
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/migrations/index.d.ts +7 -0
- package/dist/migrations/index.d.ts.map +1 -0
- package/dist/registries.d.ts.map +1 -1
- package/package.json +10 -4
- package/src/components/TaskDetail.tsx +71 -46
- package/src/components/TasksDrawer.tsx +5 -67
- package/src/components/TasksGeneralSettings.tsx +14 -2
- package/src/components/TasksPanel.tsx +94 -0
- package/src/components/TasksTopbarButton.tsx +49 -4
- package/src/data/capability.test.ts +1 -1
- package/src/data/supabase.ts +11 -10
- package/src/data/tables.ts +6 -0
- package/src/index.ts +41 -56
- package/src/locales/en.ts +2 -0
- package/src/locales/pt-BR.ts +2 -0
- package/src/migrations/000_plg_rename.sql +12 -0
- package/src/migrations/001_tasks_base.sql +34 -34
- package/src/migrations/index.ts +89 -0
- package/src/registries.ts +2 -1
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import React4, { useEffect, useState, useCallback } from 'react';
|
|
2
|
-
import { createPluginContext, dedup } from '@fayz-ai/saas';
|
|
2
|
+
import { createPluginContext, registerNotesTasksAdapter, NOTE_LABEL_PREFIX, dedup, useRightRailStore, useRightRailPanel, useTenantPluginSettings, useDelegateToAssistant } from '@fayz-ai/saas';
|
|
3
3
|
import { registerTranslations, createSafeDataProvider, useTranslation, getActiveTenantId, getSupabaseClientOptional } from '@fayz-ai/core';
|
|
4
4
|
import { createStore } from 'zustand/vanilla';
|
|
5
|
-
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
-
import { CheckSquare, ClipboardList, Plus, ArrowLeft, ChevronDown, Calendar,
|
|
5
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
6
|
+
import { CheckSquare, ClipboardList, Plus, ArrowLeft, Trash2, ChevronDown, Calendar, Sparkles, ListChecks, ChevronRight, AlertTriangle, ArrowUp, Minus, ArrowDown } from 'lucide-react';
|
|
7
7
|
import { Button, cn, Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, SheetBody, Checkbox } from '@fayz-ai/ui';
|
|
8
8
|
import { useStore } from 'zustand';
|
|
9
9
|
|
|
@@ -146,6 +146,12 @@ function createMockTasksProvider() {
|
|
|
146
146
|
};
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
+
// src/data/tables.ts
|
|
150
|
+
var T = {
|
|
151
|
+
tasks: "plg_tasks_tasks",
|
|
152
|
+
labels: "plg_tasks_labels"
|
|
153
|
+
};
|
|
154
|
+
|
|
149
155
|
// src/data/supabase.ts
|
|
150
156
|
function getTenantId() {
|
|
151
157
|
return getActiveTenantId();
|
|
@@ -172,7 +178,7 @@ function createSupabaseTasksProvider() {
|
|
|
172
178
|
async getTasks(query) {
|
|
173
179
|
const tenantId = getTenantId();
|
|
174
180
|
if (!tenantId) return [];
|
|
175
|
-
let q = getClient().from(
|
|
181
|
+
let q = getClient().from(T.tasks).select("*").eq("tenant_id", tenantId);
|
|
176
182
|
if (query.parentId === null) {
|
|
177
183
|
q = q.is("parent_id", null);
|
|
178
184
|
} else if (query.parentId) {
|
|
@@ -200,19 +206,19 @@ function createSupabaseTasksProvider() {
|
|
|
200
206
|
return (data ?? []).map(toTask);
|
|
201
207
|
},
|
|
202
208
|
async getTaskById(id) {
|
|
203
|
-
const { data } = await getClient().from(
|
|
209
|
+
const { data } = await getClient().from(T.tasks).select("*").eq("id", id).maybeSingle();
|
|
204
210
|
return data ? toTask(data) : null;
|
|
205
211
|
},
|
|
206
212
|
async getSubtasks(parentId) {
|
|
207
|
-
const { data } = await getClient().from(
|
|
213
|
+
const { data } = await getClient().from(T.tasks).select("*").eq("parent_id", parentId).order("position", { ascending: true });
|
|
208
214
|
return (data ?? []).map(toTask);
|
|
209
215
|
},
|
|
210
216
|
async createTask(input) {
|
|
211
217
|
const tenantId = getTenantId();
|
|
212
218
|
if (!tenantId) throw new Error("No active tenant");
|
|
213
|
-
const { data: maxData } = await getClient().from(
|
|
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);
|
|
214
220
|
const maxPos = maxData?.[0]?.position ?? -1;
|
|
215
|
-
const { data, error } = await getClient().from(
|
|
221
|
+
const { data, error } = await getClient().from(T.tasks).insert({
|
|
216
222
|
tenant_id: tenantId,
|
|
217
223
|
title: input.title,
|
|
218
224
|
description: input.description ?? null,
|
|
@@ -237,26 +243,26 @@ function createSupabaseTasksProvider() {
|
|
|
237
243
|
if (input.assignedToId !== void 0) payload.assigned_to_id = input.assignedToId;
|
|
238
244
|
if (input.labels !== void 0) payload.labels = input.labels;
|
|
239
245
|
if (input.position !== void 0) payload.position = input.position;
|
|
240
|
-
const { data, error } = await getClient().from(
|
|
246
|
+
const { data, error } = await getClient().from(T.tasks).update(payload).eq("id", id).select("*").single();
|
|
241
247
|
if (error) throw error;
|
|
242
248
|
return toTask(data);
|
|
243
249
|
},
|
|
244
250
|
async deleteTask(id) {
|
|
245
|
-
await getClient().from(
|
|
251
|
+
await getClient().from(T.tasks).delete().eq("id", id);
|
|
246
252
|
},
|
|
247
253
|
async reorderTask(id, newPosition, status) {
|
|
248
|
-
await getClient().from(
|
|
254
|
+
await getClient().from(T.tasks).update({ position: newPosition, status, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
|
|
249
255
|
},
|
|
250
256
|
async getLabels() {
|
|
251
257
|
const tenantId = getTenantId();
|
|
252
258
|
if (!tenantId) return [];
|
|
253
|
-
const { data } = await getClient().from(
|
|
259
|
+
const { data } = await getClient().from(T.labels).select("*").eq("tenant_id", tenantId).eq("is_active", true).order("name");
|
|
254
260
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
255
261
|
},
|
|
256
262
|
async getSummary() {
|
|
257
263
|
const tenantId = getTenantId();
|
|
258
264
|
if (!tenantId) return { total: 0, todo: 0, inProgress: 0, done: 0, overdue: 0, dueToday: 0, highPriority: 0 };
|
|
259
|
-
const { data } = await getClient().from(
|
|
265
|
+
const { data } = await getClient().from(T.tasks).select("status, priority, due_date").eq("tenant_id", tenantId).is("parent_id", null);
|
|
260
266
|
const tasks = data ?? [];
|
|
261
267
|
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
262
268
|
return {
|
|
@@ -394,6 +400,8 @@ var en = {
|
|
|
394
400
|
"tasks.detail.delete": "Delete Task",
|
|
395
401
|
"tasks.detail.deleteConfirm": "Are you sure you want to delete this task?",
|
|
396
402
|
"tasks.detail.created": "Created",
|
|
403
|
+
"tasks.detail.chatWithAi": "Chat with AI",
|
|
404
|
+
"tasks.detail.chatWithAi.prompt": "Take over this task and help me resolve it now, using your tools when needed:",
|
|
397
405
|
"tasks.detail.updated": "Updated",
|
|
398
406
|
"tasks.detail.addSubtask": "Add subtask...",
|
|
399
407
|
"tasks.detail.back": "Back to list",
|
|
@@ -436,6 +444,8 @@ var ptBR = {
|
|
|
436
444
|
"tasks.detail.delete": "Excluir Tarefa",
|
|
437
445
|
"tasks.detail.deleteConfirm": "Tem certeza que deseja excluir esta tarefa?",
|
|
438
446
|
"tasks.detail.created": "Criada em",
|
|
447
|
+
"tasks.detail.chatWithAi": "Conversar com IA",
|
|
448
|
+
"tasks.detail.chatWithAi.prompt": "Assuma esta tarefa e me ajude a resolver agora, usando suas ferramentas quando precisar:",
|
|
439
449
|
"tasks.detail.updated": "Atualizada em",
|
|
440
450
|
"tasks.detail.addSubtask": "Adicionar subtarefa...",
|
|
441
451
|
"tasks.detail.back": "Voltar \xE0 lista",
|
|
@@ -467,7 +477,7 @@ var taskLabelEntity = {
|
|
|
467
477
|
{ key: "color", label: "Color", type: "text", showInTable: true, defaultValue: "#6366f1" },
|
|
468
478
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
469
479
|
],
|
|
470
|
-
data: { table:
|
|
480
|
+
data: { table: T.labels, tenantScoped: true }
|
|
471
481
|
};
|
|
472
482
|
var tasksRegistries = [
|
|
473
483
|
{
|
|
@@ -484,8 +494,12 @@ var tasksRegistries = [
|
|
|
484
494
|
]
|
|
485
495
|
}
|
|
486
496
|
];
|
|
497
|
+
var TASKS_DEFAULTS = {
|
|
498
|
+
defaultPriority: "medium"
|
|
499
|
+
};
|
|
487
500
|
function TasksGeneralSettings() {
|
|
488
501
|
const t = useTranslation();
|
|
502
|
+
const s = useTenantPluginSettings("tasks", TASKS_DEFAULTS);
|
|
489
503
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
490
504
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
491
505
|
/* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold mb-1", children: t("tasks.settings.general") }),
|
|
@@ -493,12 +507,20 @@ function TasksGeneralSettings() {
|
|
|
493
507
|
] }),
|
|
494
508
|
/* @__PURE__ */ jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
495
509
|
/* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: t("tasks.settings.defaultPriority") }),
|
|
496
|
-
/* @__PURE__ */ jsxs(
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
510
|
+
/* @__PURE__ */ jsxs(
|
|
511
|
+
"select",
|
|
512
|
+
{
|
|
513
|
+
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",
|
|
514
|
+
value: s.get("defaultPriority"),
|
|
515
|
+
onChange: (e) => s.set("defaultPriority", e.target.value),
|
|
516
|
+
children: [
|
|
517
|
+
/* @__PURE__ */ jsx("option", { value: "low", children: t("tasks.priority.low") }),
|
|
518
|
+
/* @__PURE__ */ jsx("option", { value: "medium", children: t("tasks.priority.medium") }),
|
|
519
|
+
/* @__PURE__ */ jsx("option", { value: "high", children: t("tasks.priority.high") }),
|
|
520
|
+
/* @__PURE__ */ jsx("option", { value: "urgent", children: t("tasks.priority.urgent") })
|
|
521
|
+
]
|
|
522
|
+
}
|
|
523
|
+
)
|
|
502
524
|
] }) })
|
|
503
525
|
] });
|
|
504
526
|
}
|
|
@@ -718,8 +740,10 @@ var STATUS_DOT = {
|
|
|
718
740
|
done: "bg-success",
|
|
719
741
|
cancelled: "bg-muted-foreground/60"
|
|
720
742
|
};
|
|
721
|
-
function
|
|
722
|
-
|
|
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" });
|
|
723
747
|
}
|
|
724
748
|
function TaskDetail({ taskId }) {
|
|
725
749
|
const t = useTranslation();
|
|
@@ -728,6 +752,7 @@ function TaskDetail({ taskId }) {
|
|
|
728
752
|
const updateTask = useTasksStore((s) => s.updateTask);
|
|
729
753
|
const deleteTask = useTasksStore((s) => s.deleteTask);
|
|
730
754
|
const task = tasks.find((tt) => tt.id === taskId);
|
|
755
|
+
const delegate = useDelegateToAssistant();
|
|
731
756
|
const [title, setTitle] = useState("");
|
|
732
757
|
const [description, setDescription] = useState("");
|
|
733
758
|
const [subtasks, setSubtasks] = useState([]);
|
|
@@ -761,17 +786,36 @@ function TaskDetail({ taskId }) {
|
|
|
761
786
|
/* @__PURE__ */ jsx("button", { type: "button", onClick: () => selectTask(null), className: "mt-2 text-xs text-primary hover:underline", children: t("tasks.detail.back") })
|
|
762
787
|
] });
|
|
763
788
|
}
|
|
764
|
-
return
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
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
|
+
] }),
|
|
775
819
|
/* @__PURE__ */ jsx(
|
|
776
820
|
"input",
|
|
777
821
|
{
|
|
@@ -782,101 +826,112 @@ function TaskDetail({ taskId }) {
|
|
|
782
826
|
onKeyDown: (e) => {
|
|
783
827
|
if (e.key === "Enter") e.currentTarget.blur();
|
|
784
828
|
},
|
|
785
|
-
className: "
|
|
829
|
+
className: "bg-transparent px-4 pt-3 text-sm font-semibold text-foreground outline-none"
|
|
786
830
|
}
|
|
787
|
-
)
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
task.labels.length > 0 && /* @__PURE__ */ jsx(TaskLabelBadges, { labelIds: task.labels, max: 3 })
|
|
828
|
-
] }),
|
|
829
|
-
/* @__PURE__ */ jsx(
|
|
830
|
-
"textarea",
|
|
831
|
-
{
|
|
832
|
-
value: description,
|
|
833
|
-
onChange: (e) => setDescription(e.target.value),
|
|
834
|
-
onBlur: handleDescriptionBlur,
|
|
835
|
-
placeholder: t("tasks.detail.descriptionPlaceholder"),
|
|
836
|
-
rows: 3,
|
|
837
|
-
className: "w-full rounded-input border border-input bg-card shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] px-3 py-2 text-xs outline-none resize-none placeholder:text-muted-foreground/50 leading-relaxed"
|
|
838
|
-
}
|
|
839
|
-
),
|
|
840
|
-
/* @__PURE__ */ jsxs("div", { children: [
|
|
841
|
-
/* @__PURE__ */ jsx("p", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1", children: t("tasks.detail.subtasks") }),
|
|
842
|
-
/* @__PURE__ */ jsxs("div", { className: "rounded-md border overflow-hidden", children: [
|
|
843
|
-
/* @__PURE__ */ jsx(TaskQuickAdd, { parentId: task.id }),
|
|
844
|
-
subtasks.length > 0 && /* @__PURE__ */ jsx("div", { className: "divide-y", children: subtasks.map((st) => /* @__PURE__ */ jsx(TaskCard, { task: st }, st.id)) })
|
|
845
|
-
] })
|
|
846
|
-
] }),
|
|
847
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-2 border-t", children: [
|
|
848
|
-
/* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
|
|
849
|
-
formatDate(task.createdAt),
|
|
850
|
-
" \xB7 ",
|
|
851
|
-
t("tasks.detail.updated"),
|
|
852
|
-
" ",
|
|
853
|
-
formatDate(task.updatedAt)
|
|
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 })
|
|
854
871
|
] }),
|
|
855
|
-
/* @__PURE__ */
|
|
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(
|
|
856
883
|
"button",
|
|
857
884
|
{
|
|
858
885
|
type: "button",
|
|
859
|
-
onClick:
|
|
860
|
-
|
|
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",
|
|
861
894
|
children: [
|
|
862
|
-
/* @__PURE__ */ jsx(
|
|
863
|
-
t("tasks.detail.
|
|
895
|
+
/* @__PURE__ */ jsx(Sparkles, { className: "h-4 w-4 transition-transform duration-300 group-hover:rotate-12" }),
|
|
896
|
+
t("tasks.detail.chatWithAi")
|
|
864
897
|
]
|
|
865
898
|
}
|
|
866
|
-
)
|
|
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
|
+
] })
|
|
867
917
|
] })
|
|
868
|
-
|
|
918
|
+
);
|
|
869
919
|
}
|
|
870
920
|
var STATUS_ORDER = ["todo", "in_progress", "done", "cancelled"];
|
|
871
|
-
function
|
|
921
|
+
function TasksPanel() {
|
|
872
922
|
const t = useTranslation();
|
|
873
|
-
const drawerOpen = useTasksStore((s) => s.drawerOpen);
|
|
874
|
-
const closeDrawer = useTasksStore((s) => s.closeDrawer);
|
|
875
923
|
const selectedTaskId = useTasksStore((s) => s.selectedTaskId);
|
|
876
924
|
const tasks = useTasksStore((s) => s.tasks);
|
|
877
925
|
const tasksLoading = useTasksStore((s) => s.tasksLoading);
|
|
878
|
-
const summary = useTasksStore((s) => s.summary);
|
|
879
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]);
|
|
880
935
|
const grouped = React4.useMemo(() => {
|
|
881
936
|
const map = /* @__PURE__ */ new Map();
|
|
882
937
|
for (const s of STATUS_ORDER) map.set(s, []);
|
|
@@ -886,13 +941,32 @@ function TasksDrawer() {
|
|
|
886
941
|
}
|
|
887
942
|
return map;
|
|
888
943
|
}, [tasks]);
|
|
889
|
-
|
|
944
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
945
|
+
!selectedTaskId && /* @__PURE__ */ jsx(TasksFilterBar, {}),
|
|
946
|
+
!selectedTaskId && /* @__PURE__ */ jsx(TaskQuickAdd, {}),
|
|
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)) }) })
|
|
952
|
+
] });
|
|
953
|
+
}
|
|
954
|
+
function useTasksSummaryLine() {
|
|
955
|
+
const t = useTranslation();
|
|
956
|
+
const summary = useTasksStore((s) => s.summary);
|
|
957
|
+
return React4.useMemo(() => {
|
|
890
958
|
if (!summary) return "";
|
|
891
959
|
const parts = [];
|
|
892
960
|
if (summary.overdue > 0) parts.push(`${summary.overdue} ${t("tasks.summary.overdue")}`);
|
|
893
961
|
if (summary.dueToday > 0) parts.push(`${summary.dueToday} ${t("tasks.summary.dueToday")}`);
|
|
894
962
|
return parts.join(" \xB7 ");
|
|
895
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();
|
|
896
970
|
return /* @__PURE__ */ jsx(Sheet, { open: drawerOpen, onOpenChange: (v) => {
|
|
897
971
|
if (!v) closeDrawer();
|
|
898
972
|
}, children: /* @__PURE__ */ jsxs(SheetContent, { width: "max-w-lg", overlay: "dim", children: [
|
|
@@ -900,26 +974,46 @@ function TasksDrawer() {
|
|
|
900
974
|
/* @__PURE__ */ jsx(SheetTitle, { children: t("tasks.drawer.title") }),
|
|
901
975
|
/* @__PURE__ */ jsx(SheetDescription, { children: summaryLine || t("tasks.drawer.subtitle") })
|
|
902
976
|
] }),
|
|
903
|
-
|
|
904
|
-
!selectedTaskId && /* @__PURE__ */ jsx(TaskQuickAdd, {}),
|
|
905
|
-
/* @__PURE__ */ jsx(SheetBody, { className: "px-0 py-0", children: selectedTaskId ? /* @__PURE__ */ jsx("div", { className: "px-5 py-4", children: /* @__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: [
|
|
906
|
-
/* @__PURE__ */ jsx(ClipboardList, { className: "h-10 w-10 mb-3 opacity-40" }),
|
|
907
|
-
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium", children: t("tasks.empty") }),
|
|
908
|
-
/* @__PURE__ */ jsx("p", { className: "text-xs mt-1", children: t("tasks.empty.description") })
|
|
909
|
-
] }) : 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: "py-2 space-y-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)) }) })
|
|
977
|
+
/* @__PURE__ */ jsx(SheetBody, { className: "flex flex-col px-0 py-0", children: /* @__PURE__ */ jsx(TasksPanel, {}) })
|
|
910
978
|
] }) });
|
|
911
979
|
}
|
|
912
980
|
function TasksTopbarButton({ config }) {
|
|
913
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
|
+
);
|
|
914
1004
|
return /* @__PURE__ */ jsxs(TasksContextProvider, { config: tasksConfig, provider: tasksProvider, store: tasksStore, children: [
|
|
915
|
-
/* @__PURE__ */ jsx(TasksTopbarButtonInner, { store: tasksStore }),
|
|
916
|
-
/* @__PURE__ */ jsx(TasksDrawer, {})
|
|
1005
|
+
/* @__PURE__ */ jsx(TasksTopbarButtonInner, { store: tasksStore, railMounted }),
|
|
1006
|
+
!railMounted && /* @__PURE__ */ jsx(TasksDrawer, {})
|
|
917
1007
|
] });
|
|
918
1008
|
}
|
|
919
|
-
function TasksTopbarButtonInner({
|
|
1009
|
+
function TasksTopbarButtonInner({
|
|
1010
|
+
store,
|
|
1011
|
+
railMounted
|
|
1012
|
+
}) {
|
|
920
1013
|
const toggleDrawer = useStore(store, (s) => s.toggleDrawer);
|
|
921
1014
|
const fetchSummary = useStore(store, (s) => s.fetchSummary);
|
|
922
1015
|
const summary = useStore(store, (s) => s.summary);
|
|
1016
|
+
const togglePanel = useRightRailStore((s) => s.togglePanel);
|
|
923
1017
|
useEffect(() => {
|
|
924
1018
|
fetchSummary();
|
|
925
1019
|
}, [fetchSummary]);
|
|
@@ -930,7 +1024,7 @@ function TasksTopbarButtonInner({ store }) {
|
|
|
930
1024
|
variant: "ghost",
|
|
931
1025
|
size: "icon",
|
|
932
1026
|
className: cn("relative text-sidebar-foreground/70 hover:bg-sidebar/30 hover:text-sidebar-foreground"),
|
|
933
|
-
onClick: toggleDrawer,
|
|
1027
|
+
onClick: () => railMounted ? togglePanel("tasks") : toggleDrawer(),
|
|
934
1028
|
"aria-label": "Tasks",
|
|
935
1029
|
children: [
|
|
936
1030
|
/* @__PURE__ */ jsx(CheckSquare, { className: "h-5 w-5" }),
|
|
@@ -949,40 +1043,49 @@ function TasksTopbarButtonInner({ store }) {
|
|
|
949
1043
|
);
|
|
950
1044
|
}
|
|
951
1045
|
|
|
952
|
-
// src/index.ts
|
|
953
|
-
var
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1046
|
+
// src/migrations/index.ts
|
|
1047
|
+
var MIGRATION_000_PLG_RENAME = `-- 000_plg_rename.sql \u2014 rename legacy tasks tables to plg_tasks_* for pools
|
|
1048
|
+
-- provisioned before the industry-pool rename. Guarded: fires only when legacy
|
|
1049
|
+
-- name exists and target does not, so fresh pools skip every branch.
|
|
1050
|
+
DO $$
|
|
1051
|
+
BEGIN
|
|
1052
|
+
IF to_regclass('public.tsk_tasks') IS NOT NULL AND to_regclass('public.plg_tasks_tasks') IS NULL THEN
|
|
1053
|
+
ALTER TABLE public.tsk_tasks RENAME TO plg_tasks_tasks;
|
|
1054
|
+
END IF;
|
|
1055
|
+
IF to_regclass('public.tsk_labels') IS NOT NULL AND to_regclass('public.plg_tasks_labels') IS NULL THEN
|
|
1056
|
+
ALTER TABLE public.tsk_labels RENAME TO plg_tasks_labels;
|
|
1057
|
+
END IF;
|
|
1058
|
+
END $$;
|
|
1059
|
+
`;
|
|
1060
|
+
var MIGRATION_001_TASKS_BASE = `-- Tasks Plugin: Base Tables
|
|
1061
|
+
-- Prefix: plg_tasks_
|
|
1062
|
+
|
|
1063
|
+
-- Label definitions for categorizing tasks
|
|
1064
|
+
CREATE TABLE IF NOT EXISTS public.plg_tasks_labels (
|
|
965
1065
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
966
|
-
tenant_id uuid NOT NULL REFERENCES
|
|
1066
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
967
1067
|
name text NOT NULL,
|
|
968
1068
|
color text NOT NULL DEFAULT '#6366f1',
|
|
969
1069
|
is_active boolean NOT NULL DEFAULT true,
|
|
970
1070
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
971
1071
|
);
|
|
972
|
-
ALTER TABLE public.
|
|
973
|
-
CREATE INDEX IF NOT EXISTS
|
|
1072
|
+
ALTER TABLE public.plg_tasks_labels ENABLE ROW LEVEL SECURITY;
|
|
1073
|
+
CREATE INDEX IF NOT EXISTS idx_plg_tasks_labels_tenant ON public.plg_tasks_labels(tenant_id);
|
|
974
1074
|
|
|
975
|
-
|
|
1075
|
+
-- Main tasks table (subtasks via parent_id self-reference)
|
|
1076
|
+
CREATE TABLE IF NOT EXISTS public.plg_tasks_tasks (
|
|
976
1077
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
977
|
-
tenant_id uuid NOT NULL REFERENCES
|
|
1078
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
978
1079
|
title text NOT NULL,
|
|
979
1080
|
description text,
|
|
980
|
-
status text NOT NULL DEFAULT 'todo'
|
|
981
|
-
|
|
1081
|
+
status text NOT NULL DEFAULT 'todo'
|
|
1082
|
+
CHECK (status IN ('todo', 'in_progress', 'done', 'cancelled')),
|
|
1083
|
+
priority text NOT NULL DEFAULT 'medium'
|
|
1084
|
+
CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
|
|
982
1085
|
due_date date,
|
|
983
1086
|
assigned_to_id uuid,
|
|
984
1087
|
assigned_to_name text,
|
|
985
|
-
parent_id uuid REFERENCES public.
|
|
1088
|
+
parent_id uuid REFERENCES public.plg_tasks_tasks(id) ON DELETE CASCADE,
|
|
986
1089
|
labels jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
987
1090
|
position integer NOT NULL DEFAULT 0,
|
|
988
1091
|
created_by_id uuid,
|
|
@@ -990,26 +1093,48 @@ CREATE TABLE IF NOT EXISTS public.tsk_tasks (
|
|
|
990
1093
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
991
1094
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
992
1095
|
);
|
|
993
|
-
ALTER TABLE public.
|
|
994
|
-
CREATE INDEX IF NOT EXISTS
|
|
995
|
-
CREATE INDEX IF NOT EXISTS
|
|
996
|
-
CREATE INDEX IF NOT EXISTS
|
|
997
|
-
CREATE INDEX IF NOT EXISTS
|
|
998
|
-
CREATE INDEX IF NOT EXISTS
|
|
1096
|
+
ALTER TABLE public.plg_tasks_tasks ENABLE ROW LEVEL SECURITY;
|
|
1097
|
+
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_tenant ON public.plg_tasks_tasks(tenant_id);
|
|
1098
|
+
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_status ON public.plg_tasks_tasks(tenant_id, status);
|
|
1099
|
+
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_parent ON public.plg_tasks_tasks(parent_id);
|
|
1100
|
+
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_assigned ON public.plg_tasks_tasks(assigned_to_id);
|
|
1101
|
+
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_due ON public.plg_tasks_tasks(tenant_id, due_date)
|
|
1102
|
+
WHERE due_date IS NOT NULL;
|
|
1103
|
+
|
|
1104
|
+
-- RLS policies for plg_tasks_tasks (idempotent \u2014 DROP IF EXISTS before CREATE)
|
|
1105
|
+
DROP POLICY IF EXISTS plg_tasks_tasks_select ON public.plg_tasks_tasks;
|
|
1106
|
+
CREATE POLICY plg_tasks_tasks_select ON public.plg_tasks_tasks FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
1107
|
+
DROP POLICY IF EXISTS plg_tasks_tasks_insert ON public.plg_tasks_tasks;
|
|
1108
|
+
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()));
|
|
1109
|
+
DROP POLICY IF EXISTS plg_tasks_tasks_update ON public.plg_tasks_tasks;
|
|
1110
|
+
CREATE POLICY plg_tasks_tasks_update ON public.plg_tasks_tasks FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
1111
|
+
DROP POLICY IF EXISTS plg_tasks_tasks_delete ON public.plg_tasks_tasks;
|
|
1112
|
+
CREATE POLICY plg_tasks_tasks_delete ON public.plg_tasks_tasks FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
1113
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_tasks_tasks TO authenticated;
|
|
999
1114
|
|
|
1000
|
-
-- RLS policies for
|
|
1001
|
-
|
|
1002
|
-
CREATE POLICY
|
|
1003
|
-
|
|
1004
|
-
CREATE POLICY
|
|
1005
|
-
|
|
1115
|
+
-- RLS policies for plg_tasks_labels (idempotent)
|
|
1116
|
+
DROP POLICY IF EXISTS plg_tasks_labels_select ON public.plg_tasks_labels;
|
|
1117
|
+
CREATE POLICY plg_tasks_labels_select ON public.plg_tasks_labels FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
1118
|
+
DROP POLICY IF EXISTS plg_tasks_labels_insert ON public.plg_tasks_labels;
|
|
1119
|
+
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()));
|
|
1120
|
+
DROP POLICY IF EXISTS plg_tasks_labels_update ON public.plg_tasks_labels;
|
|
1121
|
+
CREATE POLICY plg_tasks_labels_update ON public.plg_tasks_labels FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
1122
|
+
DROP POLICY IF EXISTS plg_tasks_labels_delete ON public.plg_tasks_labels;
|
|
1123
|
+
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
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_tasks_labels TO authenticated;
|
|
1125
|
+
`;
|
|
1006
1126
|
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1127
|
+
// src/index.ts
|
|
1128
|
+
var DEFAULT_LABELS = {
|
|
1129
|
+
drawerTitle: "Tasks",
|
|
1130
|
+
settingsTitle: "Tasks",
|
|
1131
|
+
quickAddPlaceholder: "Add a task..."
|
|
1132
|
+
};
|
|
1133
|
+
function resolveConfig(options) {
|
|
1134
|
+
return {
|
|
1135
|
+
labels: { ...DEFAULT_LABELS, ...options?.labels }
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1013
1138
|
function createTasksPlugin(options) {
|
|
1014
1139
|
const config = resolveConfig(options);
|
|
1015
1140
|
registerTranslations(tasksLocales);
|
|
@@ -1018,6 +1143,30 @@ function createTasksPlugin(options) {
|
|
|
1018
1143
|
() => createMockTasksProvider()
|
|
1019
1144
|
);
|
|
1020
1145
|
const store = createTasksStore(provider);
|
|
1146
|
+
registerNotesTasksAdapter({
|
|
1147
|
+
async createTodo({ title, noteId, noteTitle }) {
|
|
1148
|
+
const task = await provider.createTask({
|
|
1149
|
+
title,
|
|
1150
|
+
description: noteTitle ? `Nota: ${noteTitle}` : void 0,
|
|
1151
|
+
labels: [`${NOTE_LABEL_PREFIX}${noteId}`]
|
|
1152
|
+
});
|
|
1153
|
+
void store.getState().fetchSummary();
|
|
1154
|
+
return { id: task.id, title: task.title, done: task.status === "done" };
|
|
1155
|
+
},
|
|
1156
|
+
async listTodos(noteId) {
|
|
1157
|
+
const tasks = await provider.getTasks({});
|
|
1158
|
+
const label = `${NOTE_LABEL_PREFIX}${noteId}`;
|
|
1159
|
+
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" }));
|
|
1160
|
+
},
|
|
1161
|
+
async toggleTodo(id, done) {
|
|
1162
|
+
await provider.updateTask(id, { status: done ? "done" : "todo" });
|
|
1163
|
+
void store.getState().fetchSummary();
|
|
1164
|
+
},
|
|
1165
|
+
async removeTodo(id) {
|
|
1166
|
+
await provider.deleteTask(id);
|
|
1167
|
+
void store.getState().fetchSummary();
|
|
1168
|
+
}
|
|
1169
|
+
});
|
|
1021
1170
|
return {
|
|
1022
1171
|
id: "tasks",
|
|
1023
1172
|
name: config.labels.drawerTitle,
|
|
@@ -1086,11 +1235,17 @@ function createTasksPlugin(options) {
|
|
|
1086
1235
|
}
|
|
1087
1236
|
],
|
|
1088
1237
|
migrations: [
|
|
1238
|
+
{
|
|
1239
|
+
id: "tasks-000-plg-rename",
|
|
1240
|
+
version: "1.0.0",
|
|
1241
|
+
sql: MIGRATION_000_PLG_RENAME,
|
|
1242
|
+
description: "Rename legacy tsk_* tables to plg_tasks_* in-place for pools provisioned before the industry-pool rename (guarded no-op on fresh pools)"
|
|
1243
|
+
},
|
|
1089
1244
|
{
|
|
1090
1245
|
id: "tasks-001-base-tables",
|
|
1091
1246
|
version: "1.0.0",
|
|
1092
|
-
sql:
|
|
1093
|
-
description: "Create
|
|
1247
|
+
sql: MIGRATION_001_TASKS_BASE,
|
|
1248
|
+
description: "Create plg_tasks_tasks and plg_tasks_labels"
|
|
1094
1249
|
}
|
|
1095
1250
|
],
|
|
1096
1251
|
locales: tasksLocales
|