@fayz-ai/plugin-tasks 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/TasksContext.d.ts +20 -0
- package/dist/TasksContext.d.ts.map +1 -0
- package/dist/components/TaskCard.d.ts +10 -0
- package/dist/components/TaskCard.d.ts.map +1 -0
- package/dist/components/TaskDetail.d.ts +5 -0
- package/dist/components/TaskDetail.d.ts.map +1 -0
- package/dist/components/TaskLabelBadge.d.ts +9 -0
- package/dist/components/TaskLabelBadge.d.ts.map +1 -0
- package/dist/components/TaskPriorityBadge.d.ts +7 -0
- package/dist/components/TaskPriorityBadge.d.ts.map +1 -0
- package/dist/components/TaskQuickAdd.d.ts +5 -0
- package/dist/components/TaskQuickAdd.d.ts.map +1 -0
- package/dist/components/TaskStatusGroup.d.ts +7 -0
- package/dist/components/TaskStatusGroup.d.ts.map +1 -0
- package/dist/components/TasksDrawer.d.ts +3 -0
- package/dist/components/TasksDrawer.d.ts.map +1 -0
- package/dist/components/TasksFilterBar.d.ts +3 -0
- package/dist/components/TasksFilterBar.d.ts.map +1 -0
- package/dist/components/TasksGeneralSettings.d.ts +3 -0
- package/dist/components/TasksGeneralSettings.d.ts.map +1 -0
- package/dist/components/TasksTopbarButton.d.ts +15 -0
- package/dist/components/TasksTopbarButton.d.ts.map +1 -0
- package/dist/data/capability.test.d.ts +2 -0
- package/dist/data/capability.test.d.ts.map +1 -0
- package/dist/data/mock.d.ts +3 -0
- package/dist/data/mock.d.ts.map +1 -0
- package/dist/data/supabase.d.ts +3 -0
- package/dist/data/supabase.d.ts.map +1 -0
- package/dist/data/types.d.ts +13 -0
- package/dist/data/types.d.ts.map +1 -0
- package/dist/index.cjs +1108 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1102 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/tenant.d.ts +3 -0
- package/dist/lib/tenant.d.ts.map +1 -0
- package/dist/locales/en.d.ts +2 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/index.d.ts +2 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/pt-BR.d.ts +2 -0
- package/dist/locales/pt-BR.d.ts.map +1 -0
- package/dist/registries.d.ts +3 -0
- package/dist/registries.d.ts.map +1 -0
- package/dist/store.d.ts +27 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +55 -0
- package/src/TasksContext.tsx +24 -0
- package/src/components/TaskCard.tsx +81 -0
- package/src/components/TaskDetail.tsx +184 -0
- package/src/components/TaskLabelBadge.tsx +37 -0
- package/src/components/TaskPriorityBadge.tsx +21 -0
- package/src/components/TaskQuickAdd.tsx +42 -0
- package/src/components/TaskStatusGroup.tsx +44 -0
- package/src/components/TasksDrawer.tsx +94 -0
- package/src/components/TasksFilterBar.tsx +57 -0
- package/src/components/TasksGeneralSettings.tsx +29 -0
- package/src/components/TasksTopbarButton.tsx +64 -0
- package/src/data/capability.test.ts +43 -0
- package/src/data/mock.ts +143 -0
- package/src/data/supabase.ts +193 -0
- package/src/data/types.ts +16 -0
- package/src/index.ts +217 -0
- package/src/lib/tenant.ts +13 -0
- package/src/locales/en.ts +40 -0
- package/src/locales/index.ts +7 -0
- package/src/locales/pt-BR.ts +40 -0
- package/src/migrations/001_tasks_base.sql +57 -0
- package/src/registries.ts +32 -0
- package/src/store.ts +148 -0
- package/src/types.ts +94 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
-- Tasks Plugin: Base Tables
|
|
2
|
+
-- Prefix: tsk_
|
|
3
|
+
|
|
4
|
+
-- Label definitions for categorizing tasks
|
|
5
|
+
CREATE TABLE IF NOT EXISTS public.tsk_labels (
|
|
6
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
7
|
+
tenant_id uuid NOT NULL REFERENCES saas_core.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.tsk_labels ENABLE ROW LEVEL SECURITY;
|
|
14
|
+
CREATE INDEX IF NOT EXISTS idx_tsk_labels_tenant ON public.tsk_labels(tenant_id);
|
|
15
|
+
|
|
16
|
+
-- Main tasks table (subtasks via parent_id self-reference)
|
|
17
|
+
CREATE TABLE IF NOT EXISTS public.tsk_tasks (
|
|
18
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
19
|
+
tenant_id uuid NOT NULL REFERENCES saas_core.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.tsk_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.tsk_tasks ENABLE ROW LEVEL SECURITY;
|
|
38
|
+
CREATE INDEX IF NOT EXISTS idx_tsk_tasks_tenant ON public.tsk_tasks(tenant_id);
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_tsk_tasks_status ON public.tsk_tasks(tenant_id, status);
|
|
40
|
+
CREATE INDEX IF NOT EXISTS idx_tsk_tasks_parent ON public.tsk_tasks(parent_id);
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_tsk_tasks_assigned ON public.tsk_tasks(assigned_to_id);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_tsk_tasks_due ON public.tsk_tasks(tenant_id, due_date)
|
|
43
|
+
WHERE due_date IS NOT NULL;
|
|
44
|
+
|
|
45
|
+
-- RLS policies for tsk_tasks
|
|
46
|
+
CREATE POLICY tsk_tasks_select ON public.tsk_tasks FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
47
|
+
CREATE POLICY tsk_tasks_insert ON public.tsk_tasks FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
48
|
+
CREATE POLICY tsk_tasks_update ON public.tsk_tasks FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
49
|
+
CREATE POLICY tsk_tasks_delete ON public.tsk_tasks FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
50
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.tsk_tasks TO authenticated;
|
|
51
|
+
|
|
52
|
+
-- RLS policies for tsk_labels
|
|
53
|
+
CREATE POLICY tsk_labels_select ON public.tsk_labels FOR SELECT TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
54
|
+
CREATE POLICY tsk_labels_insert ON public.tsk_labels FOR INSERT TO authenticated WITH CHECK (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
55
|
+
CREATE POLICY tsk_labels_update ON public.tsk_labels FOR UPDATE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
56
|
+
CREATE POLICY tsk_labels_delete ON public.tsk_labels FOR DELETE TO authenticated USING (tenant_id IN (SELECT public.user_tenant_ids()));
|
|
57
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.tsk_labels TO authenticated;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { PluginRegistryDef } from '@fayz-ai/core'
|
|
2
|
+
import type { EntityDef } from '@fayz-ai/core'
|
|
3
|
+
|
|
4
|
+
const taskLabelEntity: EntityDef = {
|
|
5
|
+
name: 'Task Label',
|
|
6
|
+
namePlural: 'Task Labels',
|
|
7
|
+
icon: 'Tag',
|
|
8
|
+
displayField: 'name',
|
|
9
|
+
defaultSort: 'name',
|
|
10
|
+
fields: [
|
|
11
|
+
{ key: 'name', label: 'Name', type: 'text', required: true, showInTable: true },
|
|
12
|
+
{ key: 'color', label: 'Color', type: 'text', showInTable: true, defaultValue: '#6366f1' },
|
|
13
|
+
{ key: 'isActive', label: 'Active', type: 'boolean', showInTable: true, defaultValue: true },
|
|
14
|
+
],
|
|
15
|
+
data: { table: 'tsk_labels', tenantScoped: true },
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const tasksRegistries: PluginRegistryDef[] = [
|
|
19
|
+
{
|
|
20
|
+
id: 'task-labels',
|
|
21
|
+
entity: taskLabelEntity,
|
|
22
|
+
icon: 'Tag',
|
|
23
|
+
description: 'Labels for categorizing tasks',
|
|
24
|
+
display: 'table',
|
|
25
|
+
seedData: [
|
|
26
|
+
{ id: 'lbl-bug', name: 'Bug', color: '#ef4444', isActive: true },
|
|
27
|
+
{ id: 'lbl-feature', name: 'Feature', color: '#3b82f6', isActive: true },
|
|
28
|
+
{ id: 'lbl-urgent', name: 'Urgent', color: '#f59e0b', isActive: true },
|
|
29
|
+
{ id: 'lbl-personal', name: 'Personal', color: '#8b5cf6', isActive: true },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
]
|
package/src/store.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
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
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Tasks Plugin — Domain Types
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
|
|
5
|
+
export type TaskStatus = 'todo' | 'in_progress' | 'done' | 'cancelled'
|
|
6
|
+
export type TaskPriority = 'low' | 'medium' | 'high' | 'urgent'
|
|
7
|
+
|
|
8
|
+
export const TASK_STATUSES: TaskStatus[] = ['todo', 'in_progress', 'done', 'cancelled']
|
|
9
|
+
export const TASK_PRIORITIES: TaskPriority[] = ['low', 'medium', 'high', 'urgent']
|
|
10
|
+
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Core entities
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
|
|
15
|
+
export interface Task {
|
|
16
|
+
id: string
|
|
17
|
+
title: string
|
|
18
|
+
description: string | null
|
|
19
|
+
status: TaskStatus
|
|
20
|
+
priority: TaskPriority
|
|
21
|
+
dueDate: string | null
|
|
22
|
+
assignedToId: string | null
|
|
23
|
+
assignedToName: string | null
|
|
24
|
+
parentId: string | null
|
|
25
|
+
labels: string[]
|
|
26
|
+
position: number
|
|
27
|
+
createdById: string | null
|
|
28
|
+
createdByName: string | null
|
|
29
|
+
tenantId: string
|
|
30
|
+
createdAt: string
|
|
31
|
+
updatedAt: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface TaskLabel {
|
|
35
|
+
id: string
|
|
36
|
+
name: string
|
|
37
|
+
color: string
|
|
38
|
+
isActive: boolean
|
|
39
|
+
tenantId: string
|
|
40
|
+
createdAt: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// Input types
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
|
|
47
|
+
export interface CreateTaskInput {
|
|
48
|
+
title: string
|
|
49
|
+
description?: string
|
|
50
|
+
status?: TaskStatus
|
|
51
|
+
priority?: TaskPriority
|
|
52
|
+
dueDate?: string | null
|
|
53
|
+
assignedToId?: string | null
|
|
54
|
+
parentId?: string | null
|
|
55
|
+
labels?: string[]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface UpdateTaskInput {
|
|
59
|
+
title?: string
|
|
60
|
+
description?: string | null
|
|
61
|
+
status?: TaskStatus
|
|
62
|
+
priority?: TaskPriority
|
|
63
|
+
dueDate?: string | null
|
|
64
|
+
assignedToId?: string | null
|
|
65
|
+
labels?: string[]
|
|
66
|
+
position?: number
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Query types
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
export interface TaskQuery {
|
|
74
|
+
status?: TaskStatus | TaskStatus[]
|
|
75
|
+
priority?: TaskPriority | TaskPriority[]
|
|
76
|
+
assignedToId?: string
|
|
77
|
+
parentId?: string | null
|
|
78
|
+
search?: string
|
|
79
|
+
overdue?: boolean
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Aggregation
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
|
|
86
|
+
export interface TasksSummary {
|
|
87
|
+
total: number
|
|
88
|
+
todo: number
|
|
89
|
+
inProgress: number
|
|
90
|
+
done: number
|
|
91
|
+
overdue: number
|
|
92
|
+
dueToday: number
|
|
93
|
+
highPriority: number
|
|
94
|
+
}
|