@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.cjs
CHANGED
|
@@ -152,6 +152,12 @@ function createMockTasksProvider() {
|
|
|
152
152
|
};
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
// src/data/tables.ts
|
|
156
|
+
var T = {
|
|
157
|
+
tasks: "plg_tasks_tasks",
|
|
158
|
+
labels: "plg_tasks_labels"
|
|
159
|
+
};
|
|
160
|
+
|
|
155
161
|
// src/data/supabase.ts
|
|
156
162
|
function getTenantId() {
|
|
157
163
|
return core.getActiveTenantId();
|
|
@@ -178,7 +184,7 @@ function createSupabaseTasksProvider() {
|
|
|
178
184
|
async getTasks(query) {
|
|
179
185
|
const tenantId = getTenantId();
|
|
180
186
|
if (!tenantId) return [];
|
|
181
|
-
let q = getClient().from(
|
|
187
|
+
let q = getClient().from(T.tasks).select("*").eq("tenant_id", tenantId);
|
|
182
188
|
if (query.parentId === null) {
|
|
183
189
|
q = q.is("parent_id", null);
|
|
184
190
|
} else if (query.parentId) {
|
|
@@ -206,19 +212,19 @@ function createSupabaseTasksProvider() {
|
|
|
206
212
|
return (data ?? []).map(toTask);
|
|
207
213
|
},
|
|
208
214
|
async getTaskById(id) {
|
|
209
|
-
const { data } = await getClient().from(
|
|
215
|
+
const { data } = await getClient().from(T.tasks).select("*").eq("id", id).maybeSingle();
|
|
210
216
|
return data ? toTask(data) : null;
|
|
211
217
|
},
|
|
212
218
|
async getSubtasks(parentId) {
|
|
213
|
-
const { data } = await getClient().from(
|
|
219
|
+
const { data } = await getClient().from(T.tasks).select("*").eq("parent_id", parentId).order("position", { ascending: true });
|
|
214
220
|
return (data ?? []).map(toTask);
|
|
215
221
|
},
|
|
216
222
|
async createTask(input) {
|
|
217
223
|
const tenantId = getTenantId();
|
|
218
224
|
if (!tenantId) throw new Error("No active tenant");
|
|
219
|
-
const { data: maxData } = await getClient().from(
|
|
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);
|
|
220
226
|
const maxPos = maxData?.[0]?.position ?? -1;
|
|
221
|
-
const { data, error } = await getClient().from(
|
|
227
|
+
const { data, error } = await getClient().from(T.tasks).insert({
|
|
222
228
|
tenant_id: tenantId,
|
|
223
229
|
title: input.title,
|
|
224
230
|
description: input.description ?? null,
|
|
@@ -243,26 +249,26 @@ function createSupabaseTasksProvider() {
|
|
|
243
249
|
if (input.assignedToId !== void 0) payload.assigned_to_id = input.assignedToId;
|
|
244
250
|
if (input.labels !== void 0) payload.labels = input.labels;
|
|
245
251
|
if (input.position !== void 0) payload.position = input.position;
|
|
246
|
-
const { data, error } = await getClient().from(
|
|
252
|
+
const { data, error } = await getClient().from(T.tasks).update(payload).eq("id", id).select("*").single();
|
|
247
253
|
if (error) throw error;
|
|
248
254
|
return toTask(data);
|
|
249
255
|
},
|
|
250
256
|
async deleteTask(id) {
|
|
251
|
-
await getClient().from(
|
|
257
|
+
await getClient().from(T.tasks).delete().eq("id", id);
|
|
252
258
|
},
|
|
253
259
|
async reorderTask(id, newPosition, status) {
|
|
254
|
-
await getClient().from(
|
|
260
|
+
await getClient().from(T.tasks).update({ position: newPosition, status, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id);
|
|
255
261
|
},
|
|
256
262
|
async getLabels() {
|
|
257
263
|
const tenantId = getTenantId();
|
|
258
264
|
if (!tenantId) return [];
|
|
259
|
-
const { data } = await getClient().from(
|
|
265
|
+
const { data } = await getClient().from(T.labels).select("*").eq("tenant_id", tenantId).eq("is_active", true).order("name");
|
|
260
266
|
return (data ?? []).map((r) => snakeToCamel(r));
|
|
261
267
|
},
|
|
262
268
|
async getSummary() {
|
|
263
269
|
const tenantId = getTenantId();
|
|
264
270
|
if (!tenantId) return { total: 0, todo: 0, inProgress: 0, done: 0, overdue: 0, dueToday: 0, highPriority: 0 };
|
|
265
|
-
const { data } = await getClient().from(
|
|
271
|
+
const { data } = await getClient().from(T.tasks).select("status, priority, due_date").eq("tenant_id", tenantId).is("parent_id", null);
|
|
266
272
|
const tasks = data ?? [];
|
|
267
273
|
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
268
274
|
return {
|
|
@@ -400,6 +406,8 @@ var en = {
|
|
|
400
406
|
"tasks.detail.delete": "Delete Task",
|
|
401
407
|
"tasks.detail.deleteConfirm": "Are you sure you want to delete this task?",
|
|
402
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:",
|
|
403
411
|
"tasks.detail.updated": "Updated",
|
|
404
412
|
"tasks.detail.addSubtask": "Add subtask...",
|
|
405
413
|
"tasks.detail.back": "Back to list",
|
|
@@ -442,6 +450,8 @@ var ptBR = {
|
|
|
442
450
|
"tasks.detail.delete": "Excluir Tarefa",
|
|
443
451
|
"tasks.detail.deleteConfirm": "Tem certeza que deseja excluir esta tarefa?",
|
|
444
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:",
|
|
445
455
|
"tasks.detail.updated": "Atualizada em",
|
|
446
456
|
"tasks.detail.addSubtask": "Adicionar subtarefa...",
|
|
447
457
|
"tasks.detail.back": "Voltar \xE0 lista",
|
|
@@ -473,7 +483,7 @@ var taskLabelEntity = {
|
|
|
473
483
|
{ key: "color", label: "Color", type: "text", showInTable: true, defaultValue: "#6366f1" },
|
|
474
484
|
{ key: "isActive", label: "Active", type: "boolean", showInTable: true, defaultValue: true }
|
|
475
485
|
],
|
|
476
|
-
data: { table:
|
|
486
|
+
data: { table: T.labels, tenantScoped: true }
|
|
477
487
|
};
|
|
478
488
|
var tasksRegistries = [
|
|
479
489
|
{
|
|
@@ -490,8 +500,12 @@ var tasksRegistries = [
|
|
|
490
500
|
]
|
|
491
501
|
}
|
|
492
502
|
];
|
|
503
|
+
var TASKS_DEFAULTS = {
|
|
504
|
+
defaultPriority: "medium"
|
|
505
|
+
};
|
|
493
506
|
function TasksGeneralSettings() {
|
|
494
507
|
const t = core.useTranslation();
|
|
508
|
+
const s = saas.useTenantPluginSettings("tasks", TASKS_DEFAULTS);
|
|
495
509
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
|
|
496
510
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
497
511
|
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-semibold mb-1", children: t("tasks.settings.general") }),
|
|
@@ -499,12 +513,20 @@ function TasksGeneralSettings() {
|
|
|
499
513
|
] }),
|
|
500
514
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
|
|
501
515
|
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium", children: t("tasks.settings.defaultPriority") }),
|
|
502
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
+
)
|
|
508
530
|
] }) })
|
|
509
531
|
] });
|
|
510
532
|
}
|
|
@@ -724,8 +746,10 @@ var STATUS_DOT = {
|
|
|
724
746
|
done: "bg-success",
|
|
725
747
|
cancelled: "bg-muted-foreground/60"
|
|
726
748
|
};
|
|
727
|
-
function
|
|
728
|
-
|
|
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" });
|
|
729
753
|
}
|
|
730
754
|
function TaskDetail({ taskId }) {
|
|
731
755
|
const t = core.useTranslation();
|
|
@@ -734,6 +758,7 @@ function TaskDetail({ taskId }) {
|
|
|
734
758
|
const updateTask = useTasksStore((s) => s.updateTask);
|
|
735
759
|
const deleteTask = useTasksStore((s) => s.deleteTask);
|
|
736
760
|
const task = tasks.find((tt) => tt.id === taskId);
|
|
761
|
+
const delegate = saas.useDelegateToAssistant();
|
|
737
762
|
const [title, setTitle] = React4.useState("");
|
|
738
763
|
const [description, setDescription] = React4.useState("");
|
|
739
764
|
const [subtasks, setSubtasks] = React4.useState([]);
|
|
@@ -767,17 +792,36 @@ function TaskDetail({ taskId }) {
|
|
|
767
792
|
/* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => selectTask(null), className: "mt-2 text-xs text-primary hover:underline", children: t("tasks.detail.back") })
|
|
768
793
|
] });
|
|
769
794
|
}
|
|
770
|
-
return
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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
|
+
] }),
|
|
781
825
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
782
826
|
"input",
|
|
783
827
|
{
|
|
@@ -788,101 +832,112 @@ function TaskDetail({ taskId }) {
|
|
|
788
832
|
onKeyDown: (e) => {
|
|
789
833
|
if (e.key === "Enter") e.currentTarget.blur();
|
|
790
834
|
},
|
|
791
|
-
className: "
|
|
835
|
+
className: "bg-transparent px-4 pt-3 text-sm font-semibold text-foreground outline-none"
|
|
792
836
|
}
|
|
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
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
task.labels.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(TaskLabelBadges, { labelIds: task.labels, max: 3 })
|
|
834
|
-
] }),
|
|
835
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
836
|
-
"textarea",
|
|
837
|
-
{
|
|
838
|
-
value: description,
|
|
839
|
-
onChange: (e) => setDescription(e.target.value),
|
|
840
|
-
onBlur: handleDescriptionBlur,
|
|
841
|
-
placeholder: t("tasks.detail.descriptionPlaceholder"),
|
|
842
|
-
rows: 3,
|
|
843
|
-
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"
|
|
844
|
-
}
|
|
845
|
-
),
|
|
846
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
847
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1", children: t("tasks.detail.subtasks") }),
|
|
848
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-md border overflow-hidden", children: [
|
|
849
|
-
/* @__PURE__ */ jsxRuntime.jsx(TaskQuickAdd, { parentId: task.id }),
|
|
850
|
-
subtasks.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y", children: subtasks.map((st) => /* @__PURE__ */ jsxRuntime.jsx(TaskCard, { task: st }, st.id)) })
|
|
851
|
-
] })
|
|
852
|
-
] }),
|
|
853
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between pt-2 border-t", children: [
|
|
854
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
|
|
855
|
-
formatDate(task.createdAt),
|
|
856
|
-
" \xB7 ",
|
|
857
|
-
t("tasks.detail.updated"),
|
|
858
|
-
" ",
|
|
859
|
-
formatDate(task.updatedAt)
|
|
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 })
|
|
860
877
|
] }),
|
|
861
|
-
/* @__PURE__ */ jsxRuntime.
|
|
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(
|
|
862
889
|
"button",
|
|
863
890
|
{
|
|
864
891
|
type: "button",
|
|
865
|
-
onClick:
|
|
866
|
-
|
|
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",
|
|
867
900
|
children: [
|
|
868
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.
|
|
869
|
-
t("tasks.detail.
|
|
901
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "h-4 w-4 transition-transform duration-300 group-hover:rotate-12" }),
|
|
902
|
+
t("tasks.detail.chatWithAi")
|
|
870
903
|
]
|
|
871
904
|
}
|
|
872
|
-
)
|
|
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
|
+
] })
|
|
873
923
|
] })
|
|
874
|
-
|
|
924
|
+
);
|
|
875
925
|
}
|
|
876
926
|
var STATUS_ORDER = ["todo", "in_progress", "done", "cancelled"];
|
|
877
|
-
function
|
|
927
|
+
function TasksPanel() {
|
|
878
928
|
const t = core.useTranslation();
|
|
879
|
-
const drawerOpen = useTasksStore((s) => s.drawerOpen);
|
|
880
|
-
const closeDrawer = useTasksStore((s) => s.closeDrawer);
|
|
881
929
|
const selectedTaskId = useTasksStore((s) => s.selectedTaskId);
|
|
882
930
|
const tasks = useTasksStore((s) => s.tasks);
|
|
883
931
|
const tasksLoading = useTasksStore((s) => s.tasksLoading);
|
|
884
|
-
const summary = useTasksStore((s) => s.summary);
|
|
885
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]);
|
|
886
941
|
const grouped = React4__default.default.useMemo(() => {
|
|
887
942
|
const map = /* @__PURE__ */ new Map();
|
|
888
943
|
for (const s of STATUS_ORDER) map.set(s, []);
|
|
@@ -892,13 +947,32 @@ function TasksDrawer() {
|
|
|
892
947
|
}
|
|
893
948
|
return map;
|
|
894
949
|
}, [tasks]);
|
|
895
|
-
|
|
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(() => {
|
|
896
964
|
if (!summary) return "";
|
|
897
965
|
const parts = [];
|
|
898
966
|
if (summary.overdue > 0) parts.push(`${summary.overdue} ${t("tasks.summary.overdue")}`);
|
|
899
967
|
if (summary.dueToday > 0) parts.push(`${summary.dueToday} ${t("tasks.summary.dueToday")}`);
|
|
900
968
|
return parts.join(" \xB7 ");
|
|
901
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();
|
|
902
976
|
return /* @__PURE__ */ jsxRuntime.jsx(ui.Sheet, { open: drawerOpen, onOpenChange: (v) => {
|
|
903
977
|
if (!v) closeDrawer();
|
|
904
978
|
}, children: /* @__PURE__ */ jsxRuntime.jsxs(ui.SheetContent, { width: "max-w-lg", overlay: "dim", children: [
|
|
@@ -906,26 +980,46 @@ function TasksDrawer() {
|
|
|
906
980
|
/* @__PURE__ */ jsxRuntime.jsx(ui.SheetTitle, { children: t("tasks.drawer.title") }),
|
|
907
981
|
/* @__PURE__ */ jsxRuntime.jsx(ui.SheetDescription, { children: summaryLine || t("tasks.drawer.subtitle") })
|
|
908
982
|
] }),
|
|
909
|
-
|
|
910
|
-
!selectedTaskId && /* @__PURE__ */ jsxRuntime.jsx(TaskQuickAdd, {}),
|
|
911
|
-
/* @__PURE__ */ jsxRuntime.jsx(ui.SheetBody, { className: "px-0 py-0", children: selectedTaskId ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-5 py-4", children: /* @__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: [
|
|
912
|
-
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.ClipboardList, { className: "h-10 w-10 mb-3 opacity-40" }),
|
|
913
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium", children: t("tasks.empty") }),
|
|
914
|
-
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs mt-1", children: t("tasks.empty.description") })
|
|
915
|
-
] }) : 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: "py-2 space-y-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)) }) })
|
|
983
|
+
/* @__PURE__ */ jsxRuntime.jsx(ui.SheetBody, { className: "flex flex-col px-0 py-0", children: /* @__PURE__ */ jsxRuntime.jsx(TasksPanel, {}) })
|
|
916
984
|
] }) });
|
|
917
985
|
}
|
|
918
986
|
function TasksTopbarButton({ config }) {
|
|
919
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
|
+
);
|
|
920
1010
|
return /* @__PURE__ */ jsxRuntime.jsxs(TasksContextProvider, { config: tasksConfig, provider: tasksProvider, store: tasksStore, children: [
|
|
921
|
-
/* @__PURE__ */ jsxRuntime.jsx(TasksTopbarButtonInner, { store: tasksStore }),
|
|
922
|
-
/* @__PURE__ */ jsxRuntime.jsx(TasksDrawer, {})
|
|
1011
|
+
/* @__PURE__ */ jsxRuntime.jsx(TasksTopbarButtonInner, { store: tasksStore, railMounted }),
|
|
1012
|
+
!railMounted && /* @__PURE__ */ jsxRuntime.jsx(TasksDrawer, {})
|
|
923
1013
|
] });
|
|
924
1014
|
}
|
|
925
|
-
function TasksTopbarButtonInner({
|
|
1015
|
+
function TasksTopbarButtonInner({
|
|
1016
|
+
store,
|
|
1017
|
+
railMounted
|
|
1018
|
+
}) {
|
|
926
1019
|
const toggleDrawer = zustand.useStore(store, (s) => s.toggleDrawer);
|
|
927
1020
|
const fetchSummary = zustand.useStore(store, (s) => s.fetchSummary);
|
|
928
1021
|
const summary = zustand.useStore(store, (s) => s.summary);
|
|
1022
|
+
const togglePanel = saas.useRightRailStore((s) => s.togglePanel);
|
|
929
1023
|
React4.useEffect(() => {
|
|
930
1024
|
fetchSummary();
|
|
931
1025
|
}, [fetchSummary]);
|
|
@@ -936,7 +1030,7 @@ function TasksTopbarButtonInner({ store }) {
|
|
|
936
1030
|
variant: "ghost",
|
|
937
1031
|
size: "icon",
|
|
938
1032
|
className: ui.cn("relative text-sidebar-foreground/70 hover:bg-sidebar/30 hover:text-sidebar-foreground"),
|
|
939
|
-
onClick: toggleDrawer,
|
|
1033
|
+
onClick: () => railMounted ? togglePanel("tasks") : toggleDrawer(),
|
|
940
1034
|
"aria-label": "Tasks",
|
|
941
1035
|
children: [
|
|
942
1036
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.CheckSquare, { className: "h-5 w-5" }),
|
|
@@ -955,40 +1049,49 @@ function TasksTopbarButtonInner({ store }) {
|
|
|
955
1049
|
);
|
|
956
1050
|
}
|
|
957
1051
|
|
|
958
|
-
// src/index.ts
|
|
959
|
-
var
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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 (
|
|
971
1071
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
972
|
-
tenant_id uuid NOT NULL REFERENCES
|
|
1072
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
973
1073
|
name text NOT NULL,
|
|
974
1074
|
color text NOT NULL DEFAULT '#6366f1',
|
|
975
1075
|
is_active boolean NOT NULL DEFAULT true,
|
|
976
1076
|
created_at timestamptz NOT NULL DEFAULT now()
|
|
977
1077
|
);
|
|
978
|
-
ALTER TABLE public.
|
|
979
|
-
CREATE INDEX IF NOT EXISTS
|
|
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);
|
|
980
1080
|
|
|
981
|
-
|
|
1081
|
+
-- Main tasks table (subtasks via parent_id self-reference)
|
|
1082
|
+
CREATE TABLE IF NOT EXISTS public.plg_tasks_tasks (
|
|
982
1083
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
983
|
-
tenant_id uuid NOT NULL REFERENCES
|
|
1084
|
+
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
984
1085
|
title text NOT NULL,
|
|
985
1086
|
description text,
|
|
986
|
-
status text NOT NULL DEFAULT 'todo'
|
|
987
|
-
|
|
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')),
|
|
988
1091
|
due_date date,
|
|
989
1092
|
assigned_to_id uuid,
|
|
990
1093
|
assigned_to_name text,
|
|
991
|
-
parent_id uuid REFERENCES public.
|
|
1094
|
+
parent_id uuid REFERENCES public.plg_tasks_tasks(id) ON DELETE CASCADE,
|
|
992
1095
|
labels jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
993
1096
|
position integer NOT NULL DEFAULT 0,
|
|
994
1097
|
created_by_id uuid,
|
|
@@ -996,26 +1099,48 @@ CREATE TABLE IF NOT EXISTS public.tsk_tasks (
|
|
|
996
1099
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
997
1100
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
998
1101
|
);
|
|
999
|
-
ALTER TABLE public.
|
|
1000
|
-
CREATE INDEX IF NOT EXISTS
|
|
1001
|
-
CREATE INDEX IF NOT EXISTS
|
|
1002
|
-
CREATE INDEX IF NOT EXISTS
|
|
1003
|
-
CREATE INDEX IF NOT EXISTS
|
|
1004
|
-
CREATE INDEX IF NOT EXISTS
|
|
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;
|
|
1005
1120
|
|
|
1006
|
-
-- RLS policies for
|
|
1007
|
-
|
|
1008
|
-
CREATE POLICY
|
|
1009
|
-
|
|
1010
|
-
CREATE POLICY
|
|
1011
|
-
|
|
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
|
+
`;
|
|
1012
1132
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
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
|
+
}
|
|
1019
1144
|
function createTasksPlugin(options) {
|
|
1020
1145
|
const config = resolveConfig(options);
|
|
1021
1146
|
core.registerTranslations(tasksLocales);
|
|
@@ -1024,6 +1149,30 @@ function createTasksPlugin(options) {
|
|
|
1024
1149
|
() => createMockTasksProvider()
|
|
1025
1150
|
);
|
|
1026
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
|
+
});
|
|
1027
1176
|
return {
|
|
1028
1177
|
id: "tasks",
|
|
1029
1178
|
name: config.labels.drawerTitle,
|
|
@@ -1092,11 +1241,17 @@ function createTasksPlugin(options) {
|
|
|
1092
1241
|
}
|
|
1093
1242
|
],
|
|
1094
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
|
+
},
|
|
1095
1250
|
{
|
|
1096
1251
|
id: "tasks-001-base-tables",
|
|
1097
1252
|
version: "1.0.0",
|
|
1098
|
-
sql:
|
|
1099
|
-
description: "Create
|
|
1253
|
+
sql: MIGRATION_001_TASKS_BASE,
|
|
1254
|
+
description: "Create plg_tasks_tasks and plg_tasks_labels"
|
|
1100
1255
|
}
|
|
1101
1256
|
],
|
|
1102
1257
|
locales: tasksLocales
|