@fayz-ai/plugin-tasks 0.9.0 → 0.9.2
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 +4 -4
- package/dist/components/TasksTopbarButton.d.ts +1 -1
- package/dist/components/TasksTopbarButton.d.ts.map +1 -1
- package/dist/{TasksContext.d.ts → context.d.ts} +1 -1
- package/dist/context.d.ts.map +1 -0
- package/dist/data/registries.d.ts.map +1 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +5 -7
- package/dist/TasksContext.d.ts.map +0 -1
- package/dist/registries.d.ts.map +0 -1
- package/src/TasksContext.tsx +0 -24
- package/src/components/TaskCard.tsx +0 -81
- package/src/components/TaskDetail.tsx +0 -209
- package/src/components/TaskLabelBadge.tsx +0 -37
- package/src/components/TaskPriorityBadge.tsx +0 -21
- package/src/components/TaskQuickAdd.tsx +0 -42
- package/src/components/TaskStatusGroup.tsx +0 -44
- package/src/components/TasksDrawer.tsx +0 -32
- package/src/components/TasksFilterBar.tsx +0 -57
- package/src/components/TasksGeneralSettings.tsx +0 -41
- package/src/components/TasksPanel.tsx +0 -94
- package/src/components/TasksTopbarButton.tsx +0 -109
- package/src/data/capability.test.ts +0 -43
- package/src/data/mock.ts +0 -143
- package/src/data/supabase.ts +0 -194
- package/src/data/tables.ts +0 -6
- package/src/data/types.ts +0 -16
- package/src/index.ts +0 -202
- package/src/lib/tenant.ts +0 -13
- package/src/locales/en.ts +0 -42
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -42
- package/src/migrations/000_plg_rename.sql +0 -12
- package/src/migrations/001_tasks_base.sql +0 -65
- package/src/migrations/index.ts +0 -89
- package/src/registries.ts +0 -33
- package/src/store.ts +0 -148
- package/src/types.ts +0 -94
- /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
package/src/index.ts
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import type { PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core'
|
|
3
|
-
import { TasksContextProvider, type ResolvedTasksConfig, type TasksPluginLabels } from './TasksContext'
|
|
4
|
-
import type { TasksDataProvider } from './data/types'
|
|
5
|
-
import type { TaskPriority } from './types'
|
|
6
|
-
import { createMockTasksProvider } from './data/mock'
|
|
7
|
-
import { createSafeDataProvider, registerTranslations } from '@fayz-ai/core'
|
|
8
|
-
import { createSupabaseTasksProvider } from './data/supabase'
|
|
9
|
-
import { createTasksStore } from './store'
|
|
10
|
-
import { tasksLocales } from './locales'
|
|
11
|
-
import { tasksRegistries } from './registries'
|
|
12
|
-
import { TasksGeneralSettings } from './components/TasksGeneralSettings'
|
|
13
|
-
import { TasksTopbarButton } from './components/TasksTopbarButton'
|
|
14
|
-
import { registerNotesTasksAdapter, NOTE_LABEL_PREFIX } from '@fayz-ai/saas'
|
|
15
|
-
import { MIGRATION_000_PLG_RENAME, MIGRATION_001_TASKS_BASE } from './migrations'
|
|
16
|
-
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Public types
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
export interface TasksPluginOptions {
|
|
22
|
-
/** Label overrides */
|
|
23
|
-
labels?: Partial<TasksPluginLabels>
|
|
24
|
-
/** Plugin scope */
|
|
25
|
-
scope?: PluginScope
|
|
26
|
-
/** Vertical ID */
|
|
27
|
-
verticalId?: VerticalId
|
|
28
|
-
/** Data provider override (defaults to safe auto-selection) */
|
|
29
|
-
dataProvider?: TasksDataProvider
|
|
30
|
-
/** Default priority for new tasks (default: 'medium') */
|
|
31
|
-
defaultPriority?: TaskPriority
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ---------------------------------------------------------------------------
|
|
35
|
-
// Defaults
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
|
|
38
|
-
const DEFAULT_LABELS: TasksPluginLabels = {
|
|
39
|
-
drawerTitle: 'Tasks',
|
|
40
|
-
settingsTitle: 'Tasks',
|
|
41
|
-
quickAddPlaceholder: 'Add a task...',
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
// Config resolver
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
|
|
48
|
-
function resolveConfig(options?: TasksPluginOptions): ResolvedTasksConfig {
|
|
49
|
-
return {
|
|
50
|
-
labels: { ...DEFAULT_LABELS, ...options?.labels },
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
// ---------------------------------------------------------------------------
|
|
56
|
-
// Factory
|
|
57
|
-
// ---------------------------------------------------------------------------
|
|
58
|
-
|
|
59
|
-
export function createTasksPlugin(options?: TasksPluginOptions): PluginManifest {
|
|
60
|
-
const config = resolveConfig(options)
|
|
61
|
-
// Register locales globally so the plugin's translations resolve even when the
|
|
62
|
-
// host shell does not mount @fayz-ai/core's I18nProvider (incremental de-bridge).
|
|
63
|
-
registerTranslations(tasksLocales)
|
|
64
|
-
const provider = options?.dataProvider ?? createSafeDataProvider(
|
|
65
|
-
() => createSupabaseTasksProvider(),
|
|
66
|
-
() => createMockTasksProvider(),
|
|
67
|
-
)
|
|
68
|
-
const store = createTasksStore(provider)
|
|
69
|
-
|
|
70
|
-
// Notes ↔ Tasks bridge: note todos are real tasks, linked by a `note:<id>`
|
|
71
|
-
// label. The shell's Notes tab consumes this; the Tarefas tab (and its
|
|
72
|
-
// summary badge) stays the single source of truth.
|
|
73
|
-
registerNotesTasksAdapter({
|
|
74
|
-
async createTodo({ title, noteId, noteTitle }) {
|
|
75
|
-
const task = await provider.createTask({
|
|
76
|
-
title,
|
|
77
|
-
description: noteTitle ? `Nota: ${noteTitle}` : undefined,
|
|
78
|
-
labels: [`${NOTE_LABEL_PREFIX}${noteId}`],
|
|
79
|
-
})
|
|
80
|
-
void store.getState().fetchSummary()
|
|
81
|
-
return { id: task.id, title: task.title, done: task.status === 'done' }
|
|
82
|
-
},
|
|
83
|
-
async listTodos(noteId) {
|
|
84
|
-
const tasks = await provider.getTasks({})
|
|
85
|
-
const label = `${NOTE_LABEL_PREFIX}${noteId}`
|
|
86
|
-
return tasks
|
|
87
|
-
.filter((t) => t.labels.includes(label))
|
|
88
|
-
.sort((a, b) => (a.status === 'done' ? 1 : 0) - (b.status === 'done' ? 1 : 0))
|
|
89
|
-
.map((t) => ({ id: t.id, title: t.title, done: t.status === 'done' }))
|
|
90
|
-
},
|
|
91
|
-
async toggleTodo(id: string, done: boolean) {
|
|
92
|
-
await provider.updateTask(id, { status: done ? 'done' : 'todo' })
|
|
93
|
-
void store.getState().fetchSummary()
|
|
94
|
-
},
|
|
95
|
-
async removeTodo(id: string) {
|
|
96
|
-
await provider.deleteTask(id)
|
|
97
|
-
void store.getState().fetchSummary()
|
|
98
|
-
},
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
return {
|
|
102
|
-
id: 'tasks',
|
|
103
|
-
name: config.labels.drawerTitle,
|
|
104
|
-
icon: 'CheckSquare',
|
|
105
|
-
version: '1.0.0',
|
|
106
|
-
scope: options?.scope ?? 'universal',
|
|
107
|
-
verticalId: options?.verticalId,
|
|
108
|
-
defaultEnabled: true,
|
|
109
|
-
dependencies: [],
|
|
110
|
-
|
|
111
|
-
navigation: [],
|
|
112
|
-
|
|
113
|
-
routes: [],
|
|
114
|
-
|
|
115
|
-
widgets: [
|
|
116
|
-
{
|
|
117
|
-
id: 'tasks-topbar-button',
|
|
118
|
-
zone: 'shell.topbar.end',
|
|
119
|
-
component: TasksTopbarButton as unknown as React.ComponentType<unknown>,
|
|
120
|
-
order: 10,
|
|
121
|
-
props: {
|
|
122
|
-
tasksConfig: config,
|
|
123
|
-
tasksProvider: provider,
|
|
124
|
-
tasksStore: store,
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
|
|
129
|
-
aiTools: [
|
|
130
|
-
{
|
|
131
|
-
id: 'tasks.summary',
|
|
132
|
-
name: 'getTasksSummary',
|
|
133
|
-
description: 'Returns a summary of pending tasks: total, overdue, due today, by status and priority.',
|
|
134
|
-
icon: 'CheckSquare',
|
|
135
|
-
mode: 'read' as const,
|
|
136
|
-
category: 'Tasks',
|
|
137
|
-
parameters: {
|
|
138
|
-
type: 'object' as const,
|
|
139
|
-
properties: {
|
|
140
|
-
status: {
|
|
141
|
-
type: 'string' as const,
|
|
142
|
-
description: 'Filter by task status',
|
|
143
|
-
enum: ['todo', 'in_progress', 'done', 'cancelled', 'all'],
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
suggestions: [
|
|
148
|
-
{ label: 'What tasks are overdue?' },
|
|
149
|
-
{ label: 'How many tasks are pending?' },
|
|
150
|
-
{ label: "What's due today?" },
|
|
151
|
-
],
|
|
152
|
-
},
|
|
153
|
-
],
|
|
154
|
-
|
|
155
|
-
registries: tasksRegistries,
|
|
156
|
-
|
|
157
|
-
settings: [
|
|
158
|
-
{
|
|
159
|
-
id: 'tasks',
|
|
160
|
-
label: config.labels.settingsTitle,
|
|
161
|
-
icon: 'CheckSquare',
|
|
162
|
-
component: (() => {
|
|
163
|
-
const Tab: React.FC = () =>
|
|
164
|
-
React.createElement(TasksContextProvider, { config, provider, store },
|
|
165
|
-
React.createElement(TasksGeneralSettings),
|
|
166
|
-
)
|
|
167
|
-
Tab.displayName = 'TasksSettingsTab'
|
|
168
|
-
return Tab as unknown as React.ComponentType<unknown>
|
|
169
|
-
})(),
|
|
170
|
-
order: 14,
|
|
171
|
-
},
|
|
172
|
-
],
|
|
173
|
-
|
|
174
|
-
migrations: [
|
|
175
|
-
{
|
|
176
|
-
id: 'tasks-000-plg-rename',
|
|
177
|
-
version: '1.0.0',
|
|
178
|
-
sql: MIGRATION_000_PLG_RENAME,
|
|
179
|
-
description: 'Rename legacy tsk_* tables to plg_tasks_* in-place for pools provisioned before the industry-pool rename (guarded no-op on fresh pools)',
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
id: 'tasks-001-base-tables',
|
|
183
|
-
version: '1.0.0',
|
|
184
|
-
sql: MIGRATION_001_TASKS_BASE,
|
|
185
|
-
description: 'Create plg_tasks_tasks and plg_tasks_labels',
|
|
186
|
-
},
|
|
187
|
-
],
|
|
188
|
-
|
|
189
|
-
locales: tasksLocales,
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ---------------------------------------------------------------------------
|
|
194
|
-
// Re-exports
|
|
195
|
-
// ---------------------------------------------------------------------------
|
|
196
|
-
|
|
197
|
-
export type { TasksDataProvider } from './data/types'
|
|
198
|
-
export type { ResolvedTasksConfig, TasksPluginLabels } from './TasksContext'
|
|
199
|
-
export type {
|
|
200
|
-
Task, TaskLabel, TaskStatus, TaskPriority, TasksSummary,
|
|
201
|
-
CreateTaskInput, UpdateTaskInput, TaskQuery,
|
|
202
|
-
} from './types'
|
package/src/lib/tenant.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// Current tenant id holder. The app/runtime sets this (runtime DI), decoupling
|
|
2
|
-
// the plugin from any specific org store — previously this read saas-core's
|
|
3
|
-
// organization.store. Defaults to undefined, in which case Supabase RLS scopes
|
|
4
|
-
// queries by the authenticated session.
|
|
5
|
-
let currentTenantId: string | undefined
|
|
6
|
-
|
|
7
|
-
export function setTasksTenantId(id: string | undefined): void {
|
|
8
|
-
currentTenantId = id
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function getTasksTenantId(): string | undefined {
|
|
12
|
-
return currentTenantId
|
|
13
|
-
}
|
package/src/locales/en.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export const en: Record<string, string> = {
|
|
2
|
-
'tasks.title': 'Tasks',
|
|
3
|
-
'tasks.drawer.title': 'Tasks',
|
|
4
|
-
'tasks.drawer.subtitle': 'Manage your team tasks',
|
|
5
|
-
'tasks.quickAdd.placeholder': 'Add a task...',
|
|
6
|
-
'tasks.filter.all': 'All',
|
|
7
|
-
'tasks.filter.todo': 'To Do',
|
|
8
|
-
'tasks.filter.inProgress': 'In Progress',
|
|
9
|
-
'tasks.filter.done': 'Done',
|
|
10
|
-
'tasks.status.todo': 'To Do',
|
|
11
|
-
'tasks.status.in_progress': 'In Progress',
|
|
12
|
-
'tasks.status.done': 'Done',
|
|
13
|
-
'tasks.status.cancelled': 'Cancelled',
|
|
14
|
-
'tasks.priority.low': 'Low',
|
|
15
|
-
'tasks.priority.medium': 'Medium',
|
|
16
|
-
'tasks.priority.high': 'High',
|
|
17
|
-
'tasks.priority.urgent': 'Urgent',
|
|
18
|
-
'tasks.detail.description': 'Description',
|
|
19
|
-
'tasks.detail.descriptionPlaceholder': 'Add a description...',
|
|
20
|
-
'tasks.detail.status': 'Status',
|
|
21
|
-
'tasks.detail.priority': 'Priority',
|
|
22
|
-
'tasks.detail.dueDate': 'Due Date',
|
|
23
|
-
'tasks.detail.assignee': 'Assigned To',
|
|
24
|
-
'tasks.detail.labels': 'Labels',
|
|
25
|
-
'tasks.detail.subtasks': 'Subtasks',
|
|
26
|
-
'tasks.detail.delete': 'Delete Task',
|
|
27
|
-
'tasks.detail.deleteConfirm': 'Are you sure you want to delete this task?',
|
|
28
|
-
'tasks.detail.created': 'Created',
|
|
29
|
-
'tasks.detail.chatWithAi': 'Chat with AI',
|
|
30
|
-
'tasks.detail.chatWithAi.prompt': 'Take over this task and help me resolve it now, using your tools when needed:',
|
|
31
|
-
'tasks.detail.updated': 'Updated',
|
|
32
|
-
'tasks.detail.addSubtask': 'Add subtask...',
|
|
33
|
-
'tasks.detail.back': 'Back to list',
|
|
34
|
-
'tasks.summary.overdue': 'overdue',
|
|
35
|
-
'tasks.summary.dueToday': 'due today',
|
|
36
|
-
'tasks.empty': 'No tasks yet',
|
|
37
|
-
'tasks.empty.description': 'Add your first task using the input above',
|
|
38
|
-
'tasks.settings.title': 'Tasks',
|
|
39
|
-
'tasks.settings.general': 'General',
|
|
40
|
-
'tasks.settings.defaultPriority': 'Default Priority',
|
|
41
|
-
'tasks.settings.autoAssign': 'Auto-assign tasks to creator',
|
|
42
|
-
}
|
package/src/locales/index.ts
DELETED
package/src/locales/pt-BR.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
export const ptBR: Record<string, string> = {
|
|
2
|
-
'tasks.title': 'Tarefas',
|
|
3
|
-
'tasks.drawer.title': 'Tarefas',
|
|
4
|
-
'tasks.drawer.subtitle': 'Gerencie as tarefas da equipe',
|
|
5
|
-
'tasks.quickAdd.placeholder': 'Adicionar tarefa...',
|
|
6
|
-
'tasks.filter.all': 'Todas',
|
|
7
|
-
'tasks.filter.todo': 'A Fazer',
|
|
8
|
-
'tasks.filter.inProgress': 'Em Andamento',
|
|
9
|
-
'tasks.filter.done': 'Concluídas',
|
|
10
|
-
'tasks.status.todo': 'A Fazer',
|
|
11
|
-
'tasks.status.in_progress': 'Em Andamento',
|
|
12
|
-
'tasks.status.done': 'Concluída',
|
|
13
|
-
'tasks.status.cancelled': 'Cancelada',
|
|
14
|
-
'tasks.priority.low': 'Baixa',
|
|
15
|
-
'tasks.priority.medium': 'Média',
|
|
16
|
-
'tasks.priority.high': 'Alta',
|
|
17
|
-
'tasks.priority.urgent': 'Urgente',
|
|
18
|
-
'tasks.detail.description': 'Descrição',
|
|
19
|
-
'tasks.detail.descriptionPlaceholder': 'Adicionar descrição...',
|
|
20
|
-
'tasks.detail.status': 'Status',
|
|
21
|
-
'tasks.detail.priority': 'Prioridade',
|
|
22
|
-
'tasks.detail.dueDate': 'Data de Vencimento',
|
|
23
|
-
'tasks.detail.assignee': 'Responsável',
|
|
24
|
-
'tasks.detail.labels': 'Etiquetas',
|
|
25
|
-
'tasks.detail.subtasks': 'Subtarefas',
|
|
26
|
-
'tasks.detail.delete': 'Excluir Tarefa',
|
|
27
|
-
'tasks.detail.deleteConfirm': 'Tem certeza que deseja excluir esta tarefa?',
|
|
28
|
-
'tasks.detail.created': 'Criada em',
|
|
29
|
-
'tasks.detail.chatWithAi': 'Conversar com IA',
|
|
30
|
-
'tasks.detail.chatWithAi.prompt': 'Assuma esta tarefa e me ajude a resolver agora, usando suas ferramentas quando precisar:',
|
|
31
|
-
'tasks.detail.updated': 'Atualizada em',
|
|
32
|
-
'tasks.detail.addSubtask': 'Adicionar subtarefa...',
|
|
33
|
-
'tasks.detail.back': 'Voltar à lista',
|
|
34
|
-
'tasks.summary.overdue': 'atrasadas',
|
|
35
|
-
'tasks.summary.dueToday': 'vencem hoje',
|
|
36
|
-
'tasks.empty': 'Nenhuma tarefa ainda',
|
|
37
|
-
'tasks.empty.description': 'Adicione sua primeira tarefa usando o campo acima',
|
|
38
|
-
'tasks.settings.title': 'Tarefas',
|
|
39
|
-
'tasks.settings.general': 'Geral',
|
|
40
|
-
'tasks.settings.defaultPriority': 'Prioridade Padrão',
|
|
41
|
-
'tasks.settings.autoAssign': 'Atribuir tarefas automaticamente ao criador',
|
|
42
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
-- 000_plg_rename.sql — rename legacy tasks tables to plg_tasks_* for pools
|
|
2
|
-
-- provisioned before the industry-pool rename. Guarded: fires only when legacy
|
|
3
|
-
-- name exists and target does not, so fresh pools skip every branch.
|
|
4
|
-
DO $$
|
|
5
|
-
BEGIN
|
|
6
|
-
IF to_regclass('public.tsk_tasks') IS NOT NULL AND to_regclass('public.plg_tasks_tasks') IS NULL THEN
|
|
7
|
-
ALTER TABLE public.tsk_tasks RENAME TO plg_tasks_tasks;
|
|
8
|
-
END IF;
|
|
9
|
-
IF to_regclass('public.tsk_labels') IS NOT NULL AND to_regclass('public.plg_tasks_labels') IS NULL THEN
|
|
10
|
-
ALTER TABLE public.tsk_labels RENAME TO plg_tasks_labels;
|
|
11
|
-
END IF;
|
|
12
|
-
END $$;
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
-- Tasks Plugin: Base Tables
|
|
2
|
-
-- Prefix: plg_tasks_
|
|
3
|
-
|
|
4
|
-
-- Label definitions for categorizing tasks
|
|
5
|
-
CREATE TABLE IF NOT EXISTS public.plg_tasks_labels (
|
|
6
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
8
|
-
name text NOT NULL,
|
|
9
|
-
color text NOT NULL DEFAULT '#6366f1',
|
|
10
|
-
is_active boolean NOT NULL DEFAULT true,
|
|
11
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
12
|
-
);
|
|
13
|
-
ALTER TABLE public.plg_tasks_labels ENABLE ROW LEVEL SECURITY;
|
|
14
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_labels_tenant ON public.plg_tasks_labels(tenant_id);
|
|
15
|
-
|
|
16
|
-
-- Main tasks table (subtasks via parent_id self-reference)
|
|
17
|
-
CREATE TABLE IF NOT EXISTS public.plg_tasks_tasks (
|
|
18
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
19
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
20
|
-
title text NOT NULL,
|
|
21
|
-
description text,
|
|
22
|
-
status text NOT NULL DEFAULT 'todo'
|
|
23
|
-
CHECK (status IN ('todo', 'in_progress', 'done', 'cancelled')),
|
|
24
|
-
priority text NOT NULL DEFAULT 'medium'
|
|
25
|
-
CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
|
|
26
|
-
due_date date,
|
|
27
|
-
assigned_to_id uuid,
|
|
28
|
-
assigned_to_name text,
|
|
29
|
-
parent_id uuid REFERENCES public.plg_tasks_tasks(id) ON DELETE CASCADE,
|
|
30
|
-
labels jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
31
|
-
position integer NOT NULL DEFAULT 0,
|
|
32
|
-
created_by_id uuid,
|
|
33
|
-
created_by_name text,
|
|
34
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
35
|
-
updated_at timestamptz NOT NULL DEFAULT now()
|
|
36
|
-
);
|
|
37
|
-
ALTER TABLE public.plg_tasks_tasks ENABLE ROW LEVEL SECURITY;
|
|
38
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_tenant ON public.plg_tasks_tasks(tenant_id);
|
|
39
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_status ON public.plg_tasks_tasks(tenant_id, status);
|
|
40
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_parent ON public.plg_tasks_tasks(parent_id);
|
|
41
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_assigned ON public.plg_tasks_tasks(assigned_to_id);
|
|
42
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_due ON public.plg_tasks_tasks(tenant_id, due_date)
|
|
43
|
-
WHERE due_date IS NOT NULL;
|
|
44
|
-
|
|
45
|
-
-- RLS policies for plg_tasks_tasks (idempotent — DROP IF EXISTS before CREATE)
|
|
46
|
-
DROP POLICY IF EXISTS plg_tasks_tasks_select ON public.plg_tasks_tasks;
|
|
47
|
-
CREATE POLICY plg_tasks_tasks_select ON public.plg_tasks_tasks FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
48
|
-
DROP POLICY IF EXISTS plg_tasks_tasks_insert ON public.plg_tasks_tasks;
|
|
49
|
-
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()));
|
|
50
|
-
DROP POLICY IF EXISTS plg_tasks_tasks_update ON public.plg_tasks_tasks;
|
|
51
|
-
CREATE POLICY plg_tasks_tasks_update ON public.plg_tasks_tasks FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
52
|
-
DROP POLICY IF EXISTS plg_tasks_tasks_delete ON public.plg_tasks_tasks;
|
|
53
|
-
CREATE POLICY plg_tasks_tasks_delete ON public.plg_tasks_tasks FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
54
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_tasks_tasks TO authenticated;
|
|
55
|
-
|
|
56
|
-
-- RLS policies for plg_tasks_labels (idempotent)
|
|
57
|
-
DROP POLICY IF EXISTS plg_tasks_labels_select ON public.plg_tasks_labels;
|
|
58
|
-
CREATE POLICY plg_tasks_labels_select ON public.plg_tasks_labels FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
59
|
-
DROP POLICY IF EXISTS plg_tasks_labels_insert ON public.plg_tasks_labels;
|
|
60
|
-
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()));
|
|
61
|
-
DROP POLICY IF EXISTS plg_tasks_labels_update ON public.plg_tasks_labels;
|
|
62
|
-
CREATE POLICY plg_tasks_labels_update ON public.plg_tasks_labels FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
63
|
-
DROP POLICY IF EXISTS plg_tasks_labels_delete ON public.plg_tasks_labels;
|
|
64
|
-
CREATE POLICY plg_tasks_labels_delete ON public.plg_tasks_labels FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
65
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_tasks_labels TO authenticated;
|
package/src/migrations/index.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
// AUTO-GENERATED from 000_plg_rename.sql, 001_tasks_base.sql — regenerate with scripts/embed-migrations.mjs
|
|
2
|
-
// SQL files are the source of truth; this inline copy lets the manifest declare
|
|
3
|
-
// migrations as data. Do not edit by hand — run the embed script instead.
|
|
4
|
-
|
|
5
|
-
export const MIGRATION_000_PLG_RENAME = `-- 000_plg_rename.sql — rename legacy tasks tables to plg_tasks_* for pools
|
|
6
|
-
-- provisioned before the industry-pool rename. Guarded: fires only when legacy
|
|
7
|
-
-- name exists and target does not, so fresh pools skip every branch.
|
|
8
|
-
DO $$
|
|
9
|
-
BEGIN
|
|
10
|
-
IF to_regclass('public.tsk_tasks') IS NOT NULL AND to_regclass('public.plg_tasks_tasks') IS NULL THEN
|
|
11
|
-
ALTER TABLE public.tsk_tasks RENAME TO plg_tasks_tasks;
|
|
12
|
-
END IF;
|
|
13
|
-
IF to_regclass('public.tsk_labels') IS NOT NULL AND to_regclass('public.plg_tasks_labels') IS NULL THEN
|
|
14
|
-
ALTER TABLE public.tsk_labels RENAME TO plg_tasks_labels;
|
|
15
|
-
END IF;
|
|
16
|
-
END $$;
|
|
17
|
-
`
|
|
18
|
-
|
|
19
|
-
export const MIGRATION_001_TASKS_BASE = `-- Tasks Plugin: Base Tables
|
|
20
|
-
-- Prefix: plg_tasks_
|
|
21
|
-
|
|
22
|
-
-- Label definitions for categorizing tasks
|
|
23
|
-
CREATE TABLE IF NOT EXISTS public.plg_tasks_labels (
|
|
24
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
25
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
26
|
-
name text NOT NULL,
|
|
27
|
-
color text NOT NULL DEFAULT '#6366f1',
|
|
28
|
-
is_active boolean NOT NULL DEFAULT true,
|
|
29
|
-
created_at timestamptz NOT NULL DEFAULT now()
|
|
30
|
-
);
|
|
31
|
-
ALTER TABLE public.plg_tasks_labels ENABLE ROW LEVEL SECURITY;
|
|
32
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_labels_tenant ON public.plg_tasks_labels(tenant_id);
|
|
33
|
-
|
|
34
|
-
-- Main tasks table (subtasks via parent_id self-reference)
|
|
35
|
-
CREATE TABLE IF NOT EXISTS public.plg_tasks_tasks (
|
|
36
|
-
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
37
|
-
tenant_id uuid NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
|
|
38
|
-
title text NOT NULL,
|
|
39
|
-
description text,
|
|
40
|
-
status text NOT NULL DEFAULT 'todo'
|
|
41
|
-
CHECK (status IN ('todo', 'in_progress', 'done', 'cancelled')),
|
|
42
|
-
priority text NOT NULL DEFAULT 'medium'
|
|
43
|
-
CHECK (priority IN ('low', 'medium', 'high', 'urgent')),
|
|
44
|
-
due_date date,
|
|
45
|
-
assigned_to_id uuid,
|
|
46
|
-
assigned_to_name text,
|
|
47
|
-
parent_id uuid REFERENCES public.plg_tasks_tasks(id) ON DELETE CASCADE,
|
|
48
|
-
labels jsonb NOT NULL DEFAULT '[]'::jsonb,
|
|
49
|
-
position integer NOT NULL DEFAULT 0,
|
|
50
|
-
created_by_id uuid,
|
|
51
|
-
created_by_name text,
|
|
52
|
-
created_at timestamptz NOT NULL DEFAULT now(),
|
|
53
|
-
updated_at timestamptz NOT NULL DEFAULT now()
|
|
54
|
-
);
|
|
55
|
-
ALTER TABLE public.plg_tasks_tasks ENABLE ROW LEVEL SECURITY;
|
|
56
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_tenant ON public.plg_tasks_tasks(tenant_id);
|
|
57
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_status ON public.plg_tasks_tasks(tenant_id, status);
|
|
58
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_parent ON public.plg_tasks_tasks(parent_id);
|
|
59
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_assigned ON public.plg_tasks_tasks(assigned_to_id);
|
|
60
|
-
CREATE INDEX IF NOT EXISTS idx_plg_tasks_tasks_due ON public.plg_tasks_tasks(tenant_id, due_date)
|
|
61
|
-
WHERE due_date IS NOT NULL;
|
|
62
|
-
|
|
63
|
-
-- RLS policies for plg_tasks_tasks (idempotent — DROP IF EXISTS before CREATE)
|
|
64
|
-
DROP POLICY IF EXISTS plg_tasks_tasks_select ON public.plg_tasks_tasks;
|
|
65
|
-
CREATE POLICY plg_tasks_tasks_select ON public.plg_tasks_tasks FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
66
|
-
DROP POLICY IF EXISTS plg_tasks_tasks_insert ON public.plg_tasks_tasks;
|
|
67
|
-
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()));
|
|
68
|
-
DROP POLICY IF EXISTS plg_tasks_tasks_update ON public.plg_tasks_tasks;
|
|
69
|
-
CREATE POLICY plg_tasks_tasks_update ON public.plg_tasks_tasks FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
70
|
-
DROP POLICY IF EXISTS plg_tasks_tasks_delete ON public.plg_tasks_tasks;
|
|
71
|
-
CREATE POLICY plg_tasks_tasks_delete ON public.plg_tasks_tasks FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
72
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_tasks_tasks TO authenticated;
|
|
73
|
-
|
|
74
|
-
-- RLS policies for plg_tasks_labels (idempotent)
|
|
75
|
-
DROP POLICY IF EXISTS plg_tasks_labels_select ON public.plg_tasks_labels;
|
|
76
|
-
CREATE POLICY plg_tasks_labels_select ON public.plg_tasks_labels FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
77
|
-
DROP POLICY IF EXISTS plg_tasks_labels_insert ON public.plg_tasks_labels;
|
|
78
|
-
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()));
|
|
79
|
-
DROP POLICY IF EXISTS plg_tasks_labels_update ON public.plg_tasks_labels;
|
|
80
|
-
CREATE POLICY plg_tasks_labels_update ON public.plg_tasks_labels FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
81
|
-
DROP POLICY IF EXISTS plg_tasks_labels_delete ON public.plg_tasks_labels;
|
|
82
|
-
CREATE POLICY plg_tasks_labels_delete ON public.plg_tasks_labels FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
83
|
-
GRANT SELECT, INSERT, UPDATE, DELETE ON public.plg_tasks_labels TO authenticated;
|
|
84
|
-
`
|
|
85
|
-
|
|
86
|
-
export const MIGRATIONS: Array<{ id: string; sql: string }> = [
|
|
87
|
-
{ id: "000_plg_rename", sql: MIGRATION_000_PLG_RENAME },
|
|
88
|
-
{ id: "001_tasks_base", sql: MIGRATION_001_TASKS_BASE },
|
|
89
|
-
]
|
package/src/registries.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import type { PluginRegistryDef } from '@fayz-ai/core'
|
|
2
|
-
import type { EntityDef } from '@fayz-ai/core'
|
|
3
|
-
import { T } from './data/tables'
|
|
4
|
-
|
|
5
|
-
const taskLabelEntity: EntityDef = {
|
|
6
|
-
name: 'Task Label',
|
|
7
|
-
namePlural: 'Task Labels',
|
|
8
|
-
icon: 'Tag',
|
|
9
|
-
displayField: 'name',
|
|
10
|
-
defaultSort: 'name',
|
|
11
|
-
fields: [
|
|
12
|
-
{ key: 'name', label: 'Name', type: 'text', required: true, showInTable: true },
|
|
13
|
-
{ key: 'color', label: 'Color', type: 'text', showInTable: true, defaultValue: '#6366f1' },
|
|
14
|
-
{ key: 'isActive', label: 'Active', type: 'boolean', showInTable: true, defaultValue: true },
|
|
15
|
-
],
|
|
16
|
-
data: { table: T.labels, tenantScoped: true },
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export const tasksRegistries: PluginRegistryDef[] = [
|
|
20
|
-
{
|
|
21
|
-
id: 'task-labels',
|
|
22
|
-
entity: taskLabelEntity,
|
|
23
|
-
icon: 'Tag',
|
|
24
|
-
description: 'Labels for categorizing tasks',
|
|
25
|
-
display: 'table',
|
|
26
|
-
seedData: [
|
|
27
|
-
{ id: 'lbl-bug', name: 'Bug', color: '#ef4444', isActive: true },
|
|
28
|
-
{ id: 'lbl-feature', name: 'Feature', color: '#3b82f6', isActive: true },
|
|
29
|
-
{ id: 'lbl-urgent', name: 'Urgent', color: '#f59e0b', isActive: true },
|
|
30
|
-
{ id: 'lbl-personal', name: 'Personal', color: '#8b5cf6', isActive: true },
|
|
31
|
-
],
|
|
32
|
-
},
|
|
33
|
-
]
|
package/src/store.ts
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { createStore, type StoreApi } from 'zustand/vanilla'
|
|
2
|
-
import { dedup } from '@fayz-ai/saas'
|
|
3
|
-
import type { TasksDataProvider } from './data/types'
|
|
4
|
-
import type {
|
|
5
|
-
Task, TaskLabel, TasksSummary, TaskQuery,
|
|
6
|
-
CreateTaskInput, UpdateTaskInput, TaskStatus,
|
|
7
|
-
} from './types'
|
|
8
|
-
|
|
9
|
-
export interface TasksUIState {
|
|
10
|
-
// Data
|
|
11
|
-
tasks: Task[]
|
|
12
|
-
tasksLoading: boolean
|
|
13
|
-
labels: TaskLabel[]
|
|
14
|
-
summary: TasksSummary | null
|
|
15
|
-
summaryLoading: boolean
|
|
16
|
-
|
|
17
|
-
// Drawer UI
|
|
18
|
-
drawerOpen: boolean
|
|
19
|
-
selectedTaskId: string | null
|
|
20
|
-
activeFilter: TaskStatus | 'all'
|
|
21
|
-
|
|
22
|
-
// Actions — data
|
|
23
|
-
fetchTasks(query?: TaskQuery): Promise<void>
|
|
24
|
-
fetchLabels(): Promise<void>
|
|
25
|
-
fetchSummary(): Promise<void>
|
|
26
|
-
createTask(input: CreateTaskInput): Promise<Task>
|
|
27
|
-
updateTask(id: string, input: UpdateTaskInput): Promise<Task>
|
|
28
|
-
deleteTask(id: string): Promise<void>
|
|
29
|
-
reorderTask(id: string, newPosition: number, status: TaskStatus): Promise<void>
|
|
30
|
-
|
|
31
|
-
// Actions — UI
|
|
32
|
-
openDrawer(): void
|
|
33
|
-
closeDrawer(): void
|
|
34
|
-
toggleDrawer(): void
|
|
35
|
-
selectTask(id: string | null): void
|
|
36
|
-
setFilter(filter: TaskStatus | 'all'): void
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function createTasksStore(provider: TasksDataProvider): StoreApi<TasksUIState> {
|
|
40
|
-
return createStore<TasksUIState>((set, get) => ({
|
|
41
|
-
tasks: [],
|
|
42
|
-
tasksLoading: false,
|
|
43
|
-
labels: [],
|
|
44
|
-
summary: null,
|
|
45
|
-
summaryLoading: false,
|
|
46
|
-
|
|
47
|
-
drawerOpen: false,
|
|
48
|
-
selectedTaskId: null,
|
|
49
|
-
activeFilter: 'all',
|
|
50
|
-
|
|
51
|
-
// ----- Data actions -----
|
|
52
|
-
|
|
53
|
-
async fetchTasks(query?: TaskQuery) {
|
|
54
|
-
return dedup('tasks:list', async () => {
|
|
55
|
-
set({ tasksLoading: true })
|
|
56
|
-
try {
|
|
57
|
-
const filter = get().activeFilter
|
|
58
|
-
const q: TaskQuery = {
|
|
59
|
-
parentId: null,
|
|
60
|
-
...query,
|
|
61
|
-
...(filter !== 'all' ? { status: filter } : {}),
|
|
62
|
-
}
|
|
63
|
-
const tasks = await provider.getTasks(q)
|
|
64
|
-
set({ tasks, tasksLoading: false })
|
|
65
|
-
} catch {
|
|
66
|
-
set({ tasksLoading: false })
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
},
|
|
70
|
-
|
|
71
|
-
async fetchLabels() {
|
|
72
|
-
return dedup('tasks:labels', async () => {
|
|
73
|
-
try {
|
|
74
|
-
const labels = await provider.getLabels()
|
|
75
|
-
set({ labels })
|
|
76
|
-
} catch { /* silent */ }
|
|
77
|
-
})
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
async fetchSummary() {
|
|
81
|
-
return dedup('tasks:summary', async () => {
|
|
82
|
-
set({ summaryLoading: true })
|
|
83
|
-
try {
|
|
84
|
-
const summary = await provider.getSummary()
|
|
85
|
-
set({ summary, summaryLoading: false })
|
|
86
|
-
} catch {
|
|
87
|
-
set({ summaryLoading: false })
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
},
|
|
91
|
-
|
|
92
|
-
async createTask(input) {
|
|
93
|
-
const task = await provider.createTask(input)
|
|
94
|
-
// Re-fetch to get correct ordering
|
|
95
|
-
await get().fetchTasks()
|
|
96
|
-
await get().fetchSummary()
|
|
97
|
-
return task
|
|
98
|
-
},
|
|
99
|
-
|
|
100
|
-
async updateTask(id, input) {
|
|
101
|
-
const task = await provider.updateTask(id, input)
|
|
102
|
-
// Update in-place for snappy UI, then re-fetch
|
|
103
|
-
set((s) => ({ tasks: s.tasks.map((t) => (t.id === id ? task : t)) }))
|
|
104
|
-
await get().fetchSummary()
|
|
105
|
-
return task
|
|
106
|
-
},
|
|
107
|
-
|
|
108
|
-
async deleteTask(id) {
|
|
109
|
-
await provider.deleteTask(id)
|
|
110
|
-
set((s) => ({ tasks: s.tasks.filter((t) => t.id !== id), selectedTaskId: null }))
|
|
111
|
-
await get().fetchSummary()
|
|
112
|
-
},
|
|
113
|
-
|
|
114
|
-
async reorderTask(id, newPosition, status) {
|
|
115
|
-
await provider.reorderTask(id, newPosition, status)
|
|
116
|
-
await get().fetchTasks()
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
// ----- UI actions -----
|
|
120
|
-
|
|
121
|
-
openDrawer() {
|
|
122
|
-
set({ drawerOpen: true })
|
|
123
|
-
// Refresh data when opening
|
|
124
|
-
get().fetchTasks()
|
|
125
|
-
get().fetchLabels()
|
|
126
|
-
get().fetchSummary()
|
|
127
|
-
},
|
|
128
|
-
|
|
129
|
-
closeDrawer() {
|
|
130
|
-
set({ drawerOpen: false, selectedTaskId: null })
|
|
131
|
-
},
|
|
132
|
-
|
|
133
|
-
toggleDrawer() {
|
|
134
|
-
const { drawerOpen } = get()
|
|
135
|
-
if (drawerOpen) get().closeDrawer()
|
|
136
|
-
else get().openDrawer()
|
|
137
|
-
},
|
|
138
|
-
|
|
139
|
-
selectTask(id) {
|
|
140
|
-
set({ selectedTaskId: id })
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
setFilter(filter) {
|
|
144
|
-
set({ activeFilter: filter })
|
|
145
|
-
get().fetchTasks()
|
|
146
|
-
},
|
|
147
|
-
}))
|
|
148
|
-
}
|