@fayz-ai/plugin-tasks 0.9.0 → 0.9.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.
Files changed (42) hide show
  1. package/README.md +4 -4
  2. package/dist/components/TasksTopbarButton.d.ts +1 -1
  3. package/dist/components/TasksTopbarButton.d.ts.map +1 -1
  4. package/dist/{TasksContext.d.ts → context.d.ts} +1 -1
  5. package/dist/context.d.ts.map +1 -0
  6. package/dist/data/registries.d.ts.map +1 -0
  7. package/dist/index.d.ts +2 -2
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +2 -2
  10. package/dist/index.js.map +1 -1
  11. package/package.json +5 -7
  12. package/dist/TasksContext.d.ts.map +0 -1
  13. package/dist/registries.d.ts.map +0 -1
  14. package/src/TasksContext.tsx +0 -24
  15. package/src/components/TaskCard.tsx +0 -81
  16. package/src/components/TaskDetail.tsx +0 -209
  17. package/src/components/TaskLabelBadge.tsx +0 -37
  18. package/src/components/TaskPriorityBadge.tsx +0 -21
  19. package/src/components/TaskQuickAdd.tsx +0 -42
  20. package/src/components/TaskStatusGroup.tsx +0 -44
  21. package/src/components/TasksDrawer.tsx +0 -32
  22. package/src/components/TasksFilterBar.tsx +0 -57
  23. package/src/components/TasksGeneralSettings.tsx +0 -41
  24. package/src/components/TasksPanel.tsx +0 -94
  25. package/src/components/TasksTopbarButton.tsx +0 -109
  26. package/src/data/capability.test.ts +0 -43
  27. package/src/data/mock.ts +0 -143
  28. package/src/data/supabase.ts +0 -194
  29. package/src/data/tables.ts +0 -6
  30. package/src/data/types.ts +0 -16
  31. package/src/index.ts +0 -202
  32. package/src/lib/tenant.ts +0 -13
  33. package/src/locales/en.ts +0 -42
  34. package/src/locales/index.ts +0 -7
  35. package/src/locales/pt-BR.ts +0 -42
  36. package/src/migrations/000_plg_rename.sql +0 -12
  37. package/src/migrations/001_tasks_base.sql +0 -65
  38. package/src/migrations/index.ts +0 -89
  39. package/src/registries.ts +0 -33
  40. package/src/store.ts +0 -148
  41. package/src/types.ts +0 -94
  42. /package/dist/{registries.d.ts → data/registries.d.ts} +0 -0
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "fayz": {
4
4
  "status": "beta"
5
5
  },
6
- "version": "0.9.0",
6
+ "version": "0.9.1",
7
7
  "description": "Fayz SDK — plugin-tasks plugin",
8
8
  "type": "module",
9
9
  "sideEffects": false,
@@ -12,14 +12,12 @@
12
12
  "types": "./dist/index.d.ts",
13
13
  "exports": {
14
14
  ".": {
15
- "source": "./src/index.ts",
16
15
  "types": "./dist/index.d.ts",
17
16
  "import": "./dist/index.js"
18
17
  }
19
18
  },
20
19
  "files": [
21
- "dist",
22
- "src"
20
+ "dist"
23
21
  ],
24
22
  "peerDependencies": {
25
23
  "react": "^18.0.0 || ^19.0.0",
@@ -28,9 +26,9 @@
28
26
  "lucide-react": ">=0.400.0 <1.0.0"
29
27
  },
30
28
  "dependencies": {
31
- "@fayz-ai/core": "^0.9.0",
32
- "@fayz-ai/saas": "^0.9.0",
33
- "@fayz-ai/ui": "^0.9.0"
29
+ "@fayz-ai/core": "^0.10.0",
30
+ "@fayz-ai/admin": "^0.10.0",
31
+ "@fayz-ai/ui": "^0.10.0"
34
32
  },
35
33
  "devDependencies": {
36
34
  "@types/react": "^18.3.0",
@@ -1 +0,0 @@
1
- {"version":3,"file":"TasksContext.d.ts","sourceRoot":"","sources":["../src/TasksContext.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAM3C,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAA;IACnB,aAAa,EAAE,MAAM,CAAA;IACrB,mBAAmB,EAAE,MAAM,CAAA;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,iBAAiB,CAAA;CAC1B;AAID,eAAO,MAAM,oBAAoB;;;;;EAAsB,CAAA;AACvD,eAAO,MAAM,cAAc,2BAAgB,CAAA;AAC3C,eAAO,MAAM,gBAAgB,yBAAkB,CAAA;AAC/C,eAAO,MAAM,aAAa,gDAAe,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"registries.d.ts","sourceRoot":"","sources":["../src/registries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAkBtD,eAAO,MAAM,eAAe,EAAE,iBAAiB,EAc9C,CAAA"}
@@ -1,24 +0,0 @@
1
- import { createPluginContext } from '@fayz-ai/saas'
2
- import type { TasksDataProvider } from './data/types'
3
- import type { TasksUIState } from './store'
4
-
5
- // ---------------------------------------------------------------------------
6
- // Config
7
- // ---------------------------------------------------------------------------
8
-
9
- export interface TasksPluginLabels {
10
- drawerTitle: string
11
- settingsTitle: string
12
- quickAddPlaceholder: string
13
- }
14
-
15
- export interface ResolvedTasksConfig {
16
- labels: TasksPluginLabels
17
- }
18
-
19
- const ctx = createPluginContext<ResolvedTasksConfig, TasksDataProvider, TasksUIState>('TasksContextProvider')
20
-
21
- export const TasksContextProvider = ctx.ContextProvider
22
- export const useTasksConfig = ctx.useConfig
23
- export const useTasksProvider = ctx.useProvider
24
- export const useTasksStore = ctx.useStore
@@ -1,81 +0,0 @@
1
- import React from 'react'
2
- import { Calendar, ListChecks } from 'lucide-react'
3
- import { Checkbox } from '@fayz-ai/ui'
4
- import { useTasksStore } from '../TasksContext'
5
- import { TaskPriorityBadge } from './TaskPriorityBadge'
6
- import { TaskLabelBadges } from './TaskLabelBadge'
7
- import type { Task } from '../types'
8
-
9
- function isOverdue(dueDate: string | null): boolean {
10
- if (!dueDate) return false
11
- return dueDate < new Date().toISOString().slice(0, 10)
12
- }
13
-
14
- function isToday(dueDate: string | null): boolean {
15
- if (!dueDate) return false
16
- return dueDate === new Date().toISOString().slice(0, 10)
17
- }
18
-
19
- function formatDueDate(dueDate: string): string {
20
- const d = new Date(dueDate + 'T00:00:00')
21
- return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
22
- }
23
-
24
- export function TaskCard({ task, subtaskCount }: { task: Task; subtaskCount?: { done: number; total: number } }) {
25
- const selectTask = useTasksStore((s) => s.selectTask)
26
- const updateTask = useTasksStore((s) => s.updateTask)
27
-
28
- const isDone = task.status === 'done'
29
- const overdue = !isDone && isOverdue(task.dueDate)
30
- const dueToday = !isDone && isToday(task.dueDate)
31
-
32
- const handleCheck = (e: React.MouseEvent) => {
33
- e.stopPropagation()
34
- updateTask(task.id, { status: isDone ? 'todo' : 'done' })
35
- }
36
-
37
- return (
38
- <div
39
- onClick={() => selectTask(task.id)}
40
- className="group flex items-start gap-2.5 rounded-lg border border-transparent px-3 py-2.5 transition-colors hover:bg-muted bg-card shadow-button active:shadow-button-inset hover:border-border cursor-pointer"
41
- >
42
- <div className="pt-0.5" onClick={handleCheck}>
43
- <Checkbox checked={isDone} onChange={() => {}} />
44
- </div>
45
-
46
- <div className="min-w-0 flex-1 space-y-1">
47
- <p className={`text-sm leading-snug ${isDone ? 'line-through text-muted-foreground' : ''}`}>
48
- {task.title}
49
- </p>
50
-
51
- <div className="flex flex-wrap items-center gap-1.5">
52
- <TaskPriorityBadge priority={task.priority} />
53
-
54
- {task.dueDate && (
55
- <span className={`inline-flex items-center gap-1 text-[11px] ${
56
- overdue ? 'text-destructive font-medium' : dueToday ? 'text-warning font-medium' : 'text-muted-foreground'
57
- }`}>
58
- <Calendar className="h-3 w-3" />
59
- {formatDueDate(task.dueDate)}
60
- </span>
61
- )}
62
-
63
- {subtaskCount && subtaskCount.total > 0 && (
64
- <span className="inline-flex items-center gap-1 text-[11px] text-muted-foreground">
65
- <ListChecks className="h-3 w-3" />
66
- {subtaskCount.done}/{subtaskCount.total}
67
- </span>
68
- )}
69
-
70
- <TaskLabelBadges labelIds={task.labels} max={2} />
71
- </div>
72
- </div>
73
-
74
- {task.assignedToName && (
75
- <div className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary/10 text-[10px] font-medium text-primary" title={task.assignedToName}>
76
- {task.assignedToName.split(' ').map((n) => n[0]).join('').slice(0, 2).toUpperCase()}
77
- </div>
78
- )}
79
- </div>
80
- )
81
- }
@@ -1,209 +0,0 @@
1
- import React, { useState, useEffect, useCallback } from 'react'
2
- import { ArrowLeft, Trash2, Calendar, ChevronDown, Sparkles } from 'lucide-react'
3
- import { useTasksStore } from '../TasksContext'
4
- import { useTranslation } from '@fayz-ai/core'
5
- import { useDelegateToAssistant } from '@fayz-ai/saas'
6
- import { TaskPriorityBadge } from './TaskPriorityBadge'
7
- import { TaskLabelBadges } from './TaskLabelBadge'
8
- import { TaskQuickAdd } from './TaskQuickAdd'
9
- import { TaskCard } from './TaskCard'
10
- import type { Task, TaskStatus, TaskPriority } from '../types'
11
- import { TASK_STATUSES, TASK_PRIORITIES } from '../types'
12
-
13
- const STATUS_DOT: Record<TaskStatus, string> = {
14
- todo: 'bg-muted-foreground',
15
- in_progress: 'bg-info',
16
- done: 'bg-success',
17
- cancelled: 'bg-muted-foreground/60',
18
- }
19
-
20
- /** Same footer format as the Notes tab: time when today, short date otherwise. */
21
- function formatDateTime(iso: string): string {
22
- const d = new Date(iso)
23
- const sameDay = d.toDateString() === new Date().toDateString()
24
- return sameDay
25
- ? d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' })
26
- : d.toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: '2-digit' })
27
- }
28
-
29
- export function TaskDetail({ taskId }: { taskId: string }) {
30
- const t = useTranslation()
31
- const tasks = useTasksStore((s) => s.tasks)
32
- const selectTask = useTasksStore((s) => s.selectTask)
33
- const updateTask = useTasksStore((s) => s.updateTask)
34
- const deleteTask = useTasksStore((s) => s.deleteTask)
35
-
36
- const task = tasks.find((tt) => tt.id === taskId)
37
- const delegate = useDelegateToAssistant()
38
-
39
- const [title, setTitle] = useState('')
40
- const [description, setDescription] = useState('')
41
- const [subtasks, setSubtasks] = useState<Task[]>([])
42
-
43
- useEffect(() => {
44
- if (task) {
45
- setTitle(task.title)
46
- setDescription(task.description ?? '')
47
- }
48
- }, [task])
49
-
50
- useEffect(() => { setSubtasks([]) }, [taskId])
51
-
52
- const handleTitleBlur = useCallback(() => {
53
- if (task && title.trim() && title !== task.title) {
54
- updateTask(task.id, { title: title.trim() })
55
- }
56
- }, [task, title, updateTask])
57
-
58
- const handleDescriptionBlur = useCallback(() => {
59
- if (task && description !== (task.description ?? '')) {
60
- updateTask(task.id, { description: description || null })
61
- }
62
- }, [task, description, updateTask])
63
-
64
- const handleDelete = useCallback(() => {
65
- if (task && confirm(t('tasks.detail.deleteConfirm'))) {
66
- deleteTask(task.id)
67
- }
68
- }, [task, deleteTask, t])
69
-
70
- if (!task) {
71
- return (
72
- <div className="flex flex-col items-center justify-center py-8 text-muted-foreground">
73
- <p className="text-sm">Task not found</p>
74
- <button type="button" onClick={() => selectTask(null)} className="mt-2 text-xs text-primary hover:underline">
75
- {t('tasks.detail.back')}
76
- </button>
77
- </div>
78
- )
79
- }
80
-
81
- return (
82
- // Same frame as the Notes editor: header row with back + icon actions,
83
- // full-bleed px-4 content, sectioned by hairlines, thin timestamp footer.
84
- <div className="flex min-h-full flex-col">
85
- <div className="flex items-center gap-1 border-b border-border/40 px-2 py-1.5">
86
- <button
87
- type="button"
88
- onClick={() => selectTask(null)}
89
- 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"
90
- >
91
- <ArrowLeft className="h-3.5 w-3.5" />
92
- {t('tasks.drawer.title')}
93
- </button>
94
- <span className="flex-1" />
95
- <button
96
- type="button"
97
- onClick={handleDelete}
98
- aria-label={t('tasks.detail.delete')}
99
- title={t('tasks.detail.delete')}
100
- className="inline-flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-destructive"
101
- >
102
- <Trash2 className="h-3.5 w-3.5" />
103
- </button>
104
- </div>
105
-
106
- <input
107
- type="text"
108
- value={title}
109
- onChange={(e) => setTitle(e.target.value)}
110
- onBlur={handleTitleBlur}
111
- onKeyDown={(e) => { if (e.key === 'Enter') e.currentTarget.blur() }}
112
- className="bg-transparent px-4 pt-3 text-sm font-semibold text-foreground outline-none"
113
- />
114
-
115
- {/* Inline property chips */}
116
- <div className="flex flex-wrap items-center gap-1.5 px-4 pt-2">
117
- {/* Status chip */}
118
- <div className="relative">
119
- <select
120
- value={task.status}
121
- onChange={(e) => updateTask(task.id, { status: e.target.value as TaskStatus })}
122
- 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"
123
- >
124
- {TASK_STATUSES.map((s) => (
125
- <option key={s} value={s}>{t(`tasks.status.${s}`)}</option>
126
- ))}
127
- </select>
128
- <span className={`absolute left-2 top-1/2 -translate-y-1/2 h-2 w-2 rounded-full ${STATUS_DOT[task.status]}`} />
129
- <ChevronDown className="absolute right-1.5 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" />
130
- </div>
131
-
132
- {/* Priority chip */}
133
- <div className="relative">
134
- <select
135
- value={task.priority}
136
- onChange={(e) => updateTask(task.id, { priority: e.target.value as TaskPriority })}
137
- 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"
138
- >
139
- {TASK_PRIORITIES.map((p) => (
140
- <option key={p} value={p}>{t(`tasks.priority.${p}`)}</option>
141
- ))}
142
- </select>
143
- <ChevronDown className="absolute right-1.5 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" />
144
- </div>
145
-
146
- {/* Due date chip */}
147
- <div className="relative">
148
- <input
149
- type="date"
150
- value={task.dueDate ?? ''}
151
- onChange={(e) => updateTask(task.id, { dueDate: e.target.value || null })}
152
- 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]"
153
- />
154
- <Calendar className="absolute left-2 top-1/2 -translate-y-1/2 h-3 w-3 text-muted-foreground pointer-events-none" />
155
- </div>
156
-
157
- {/* Labels */}
158
- {task.labels.length > 0 && <TaskLabelBadges labelIds={task.labels} max={3} />}
159
- </div>
160
-
161
- {/* Description — borderless, fills the middle like the note body */}
162
- <textarea
163
- value={description}
164
- onChange={(e) => setDescription(e.target.value)}
165
- onBlur={handleDescriptionBlur}
166
- placeholder={t('tasks.detail.descriptionPlaceholder')}
167
- 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"
168
- />
169
-
170
- {/* Agentic hand-off — same CTA pattern as the Notes tab */}
171
- <div className="px-4 pb-2">
172
- <button
173
- type="button"
174
- onClick={() =>
175
- delegate(
176
- `${t('tasks.detail.chatWithAi.prompt')}\n\n"${task.title}"${task.description ? `\n${task.description}` : ''}${task.dueDate ? `\n(${t('tasks.detail.dueDate')}: ${task.dueDate})` : ''}`,
177
- )
178
- }
179
- 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"
180
- >
181
- <Sparkles className="h-4 w-4 transition-transform duration-300 group-hover:rotate-12" />
182
- {t('tasks.detail.chatWithAi')}
183
- </button>
184
- </div>
185
-
186
- {/* Subtasks — same section pattern as the note's todos */}
187
- <div className="border-t border-border/40 px-4 py-2.5">
188
- <p className="pb-1.5 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground/60">
189
- {t('tasks.detail.subtasks')}{subtasks.length > 0 ? ` · ${subtasks.length}` : ''}
190
- </p>
191
- <div className="-mx-3">
192
- <TaskQuickAdd parentId={task.id} />
193
- </div>
194
- {subtasks.length > 0 && (
195
- <div className="divide-y divide-border/40">
196
- {subtasks.map((st) => (
197
- <TaskCard key={st.id} task={st} />
198
- ))}
199
- </div>
200
- )}
201
- </div>
202
-
203
- {/* Footer: timestamps only — delete lives in the header, like a note */}
204
- <p className="border-t border-border/40 px-4 py-1.5 text-[10px] text-muted-foreground/60">
205
- {t('tasks.detail.created')} {formatDateTime(task.createdAt)} · {t('tasks.detail.updated')} {formatDateTime(task.updatedAt)}
206
- </p>
207
- </div>
208
- )
209
- }
@@ -1,37 +0,0 @@
1
- import React from 'react'
2
- import { useTasksStore } from '../TasksContext'
3
-
4
- export function TaskLabelBadge({ labelId }: { labelId: string }) {
5
- const labels = useTasksStore((s) => s.labels)
6
- const label = labels.find((l) => l.id === labelId)
7
- if (!label) return null
8
-
9
- return (
10
- <span
11
- className="inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium leading-none"
12
- style={{
13
- backgroundColor: label.color + '20',
14
- color: label.color,
15
- }}
16
- >
17
- {label.name}
18
- </span>
19
- )
20
- }
21
-
22
- export function TaskLabelBadges({ labelIds, max = 2 }: { labelIds: string[]; max?: number }) {
23
- if (labelIds.length === 0) return null
24
- const visible = labelIds.slice(0, max)
25
- const overflow = labelIds.length - max
26
-
27
- return (
28
- <span className="inline-flex items-center gap-1">
29
- {visible.map((id) => (
30
- <TaskLabelBadge key={id} labelId={id} />
31
- ))}
32
- {overflow > 0 && (
33
- <span className="text-[10px] text-muted-foreground">+{overflow}</span>
34
- )}
35
- </span>
36
- )
37
- }
@@ -1,21 +0,0 @@
1
- import React from 'react'
2
- import { AlertTriangle, ArrowUp, ArrowDown, Minus } from 'lucide-react'
3
- import type { TaskPriority } from '../types'
4
-
5
- const PRIORITY_CONFIG: Record<TaskPriority, { color: string; bg: string; icon: React.ComponentType<{ className?: string }> }> = {
6
- low: { color: 'text-muted-foreground', bg: 'bg-muted', icon: ArrowDown },
7
- medium: { color: 'text-info-soft-foreground', bg: 'bg-info-soft', icon: Minus },
8
- high: { color: 'text-warning-soft-foreground', bg: 'bg-warning-soft', icon: ArrowUp },
9
- urgent: { color: 'text-destructive-soft-foreground', bg: 'bg-destructive-soft', icon: AlertTriangle },
10
- }
11
-
12
- export function TaskPriorityBadge({ priority, showLabel }: { priority: TaskPriority; showLabel?: boolean }) {
13
- const cfg = PRIORITY_CONFIG[priority]
14
- const Icon = cfg.icon
15
- return (
16
- <span className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-medium ${cfg.color} ${cfg.bg}`}>
17
- <Icon className="h-3 w-3" />
18
- {showLabel && <span className="capitalize">{priority}</span>}
19
- </span>
20
- )
21
- }
@@ -1,42 +0,0 @@
1
- import React, { useState, useCallback } from 'react'
2
- import { Plus } from 'lucide-react'
3
- import { useTasksStore, useTasksConfig } from '../TasksContext'
4
- import { useTranslation } from '@fayz-ai/core'
5
- import type { TaskPriority } from '../types'
6
-
7
- export function TaskQuickAdd({ parentId }: { parentId?: string | null }) {
8
- const t = useTranslation()
9
- const createTask = useTasksStore((s) => s.createTask)
10
- const [title, setTitle] = useState('')
11
- const [loading, setLoading] = useState(false)
12
-
13
- const handleSubmit = useCallback(async (e: React.FormEvent) => {
14
- e.preventDefault()
15
- const trimmed = title.trim()
16
- if (!trimmed || loading) return
17
- setLoading(true)
18
- try {
19
- await createTask({
20
- title: trimmed,
21
- parentId: parentId ?? null,
22
- })
23
- setTitle('')
24
- } finally {
25
- setLoading(false)
26
- }
27
- }, [title, loading, createTask, parentId])
28
-
29
- return (
30
- <form onSubmit={handleSubmit} className="flex items-center gap-2 px-3 py-2 border-b">
31
- <Plus className="h-4 w-4 shrink-0 text-muted-foreground" />
32
- <input
33
- type="text"
34
- value={title}
35
- onChange={(e) => setTitle(e.target.value)}
36
- placeholder={parentId ? t('tasks.detail.addSubtask') : t('tasks.quickAdd.placeholder')}
37
- className="flex-1 bg-transparent text-sm outline-none placeholder:text-muted-foreground/60"
38
- disabled={loading}
39
- />
40
- </form>
41
- )
42
- }
@@ -1,44 +0,0 @@
1
- import React, { useState } from 'react'
2
- import { ChevronDown, ChevronRight } from 'lucide-react'
3
- import { useTranslation } from '@fayz-ai/core'
4
- import { TaskCard } from './TaskCard'
5
- import type { Task, TaskStatus } from '../types'
6
-
7
- const STATUS_COLORS: Record<TaskStatus, string> = {
8
- todo: 'bg-muted-foreground',
9
- in_progress: 'bg-info',
10
- done: 'bg-success',
11
- cancelled: 'bg-muted-foreground/60',
12
- }
13
-
14
- export function TaskStatusGroup({ status, tasks }: { status: TaskStatus; tasks: Task[] }) {
15
- const t = useTranslation()
16
- const [collapsed, setCollapsed] = useState(status === 'cancelled')
17
-
18
- if (tasks.length === 0) return null
19
-
20
- const Chevron = collapsed ? ChevronRight : ChevronDown
21
-
22
- return (
23
- <div className="space-y-0.5">
24
- <button
25
- type="button"
26
- onClick={() => setCollapsed(!collapsed)}
27
- className="flex w-full items-center gap-2 px-3 py-1.5 text-xs font-semibold text-muted-foreground uppercase tracking-wider hover:text-foreground transition-colors"
28
- >
29
- <span className={`h-2 w-2 rounded-full ${STATUS_COLORS[status]}`} />
30
- <span>{t(`tasks.status.${status}`)}</span>
31
- <span className="text-[10px] font-normal">({tasks.length})</span>
32
- <Chevron className="ml-auto h-3.5 w-3.5" />
33
- </button>
34
-
35
- {!collapsed && (
36
- <div>
37
- {tasks.map((task) => (
38
- <TaskCard key={task.id} task={task} />
39
- ))}
40
- </div>
41
- )}
42
- </div>
43
- )
44
- }
@@ -1,32 +0,0 @@
1
- import React from 'react'
2
- import {
3
- Sheet, SheetContent, SheetHeader, SheetBody, SheetTitle, SheetDescription,
4
- } from '@fayz-ai/ui'
5
- import { useTasksStore } from '../TasksContext'
6
- import { useTranslation } from '@fayz-ai/core'
7
- import { TasksPanel, useTasksSummaryLine } from './TasksPanel'
8
-
9
- /** Tasks as a sheet — the fallback where the shell has no right rail. */
10
- export function TasksDrawer() {
11
- const t = useTranslation()
12
- const drawerOpen = useTasksStore((s) => s.drawerOpen)
13
- const closeDrawer = useTasksStore((s) => s.closeDrawer)
14
- const summaryLine = useTasksSummaryLine()
15
-
16
- return (
17
- <Sheet open={drawerOpen} onOpenChange={(v) => { if (!v) closeDrawer() }}>
18
- <SheetContent width="max-w-lg" overlay="dim">
19
- <SheetHeader>
20
- <SheetTitle>{t('tasks.drawer.title')}</SheetTitle>
21
- <SheetDescription>
22
- {summaryLine || t('tasks.drawer.subtitle')}
23
- </SheetDescription>
24
- </SheetHeader>
25
-
26
- <SheetBody className="flex flex-col px-0 py-0">
27
- <TasksPanel />
28
- </SheetBody>
29
- </SheetContent>
30
- </Sheet>
31
- )
32
- }
@@ -1,57 +0,0 @@
1
- import React from 'react'
2
- import { useTasksStore } from '../TasksContext'
3
- import { useTranslation } from '@fayz-ai/core'
4
- import type { TaskStatus } from '../types'
5
-
6
- const FILTERS: Array<{ key: TaskStatus | 'all'; labelKey: string }> = [
7
- { key: 'all', labelKey: 'tasks.filter.all' },
8
- { key: 'todo', labelKey: 'tasks.filter.todo' },
9
- { key: 'in_progress', labelKey: 'tasks.filter.inProgress' },
10
- { key: 'done', labelKey: 'tasks.filter.done' },
11
- ]
12
-
13
- export function TasksFilterBar() {
14
- const t = useTranslation()
15
- const activeFilter = useTasksStore((s) => s.activeFilter)
16
- const setFilter = useTasksStore((s) => s.setFilter)
17
- const summary = useTasksStore((s) => s.summary)
18
-
19
- const getCount = (key: TaskStatus | 'all'): number | undefined => {
20
- if (!summary) return undefined
21
- switch (key) {
22
- case 'all': return summary.total
23
- case 'todo': return summary.todo
24
- case 'in_progress': return summary.inProgress
25
- case 'done': return summary.done
26
- default: return undefined
27
- }
28
- }
29
-
30
- return (
31
- <div className="flex items-center gap-1 px-3 py-2 border-b overflow-x-auto">
32
- {FILTERS.map(({ key, labelKey }) => {
33
- const active = activeFilter === key
34
- const count = getCount(key)
35
- return (
36
- <button
37
- key={key}
38
- type="button"
39
- onClick={() => setFilter(key)}
40
- className={`inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-xs font-medium transition-colors whitespace-nowrap ${
41
- active
42
- ? 'bg-primary text-primary-foreground'
43
- : 'text-muted-foreground hover:bg-muted hover:text-foreground'
44
- }`}
45
- >
46
- {t(labelKey)}
47
- {count != null && (
48
- <span className={`text-[10px] ${active ? 'text-primary-foreground/80' : 'text-muted-foreground'}`}>
49
- {count}
50
- </span>
51
- )}
52
- </button>
53
- )
54
- })}
55
- </div>
56
- )
57
- }
@@ -1,41 +0,0 @@
1
- import React from 'react'
2
- import { useTranslation } from '@fayz-ai/core'
3
- import { useTenantPluginSettings } from '@fayz-ai/saas'
4
-
5
- // Per-tenant defaults = the value that used to be the hardcoded `selected` option.
6
- // Persisted + re-hydrated per tenant; behavioural consumption is a follow-up.
7
- const TASKS_DEFAULTS = {
8
- defaultPriority: 'medium',
9
- }
10
-
11
- export function TasksGeneralSettings() {
12
- const t = useTranslation()
13
- const s = useTenantPluginSettings('tasks', TASKS_DEFAULTS)
14
-
15
- return (
16
- <div className="space-y-6">
17
- <div>
18
- <h3 className="text-sm font-semibold mb-1">{t('tasks.settings.general')}</h3>
19
- <p className="text-xs text-muted-foreground mb-3">
20
- {t('tasks.drawer.subtitle')}
21
- </p>
22
- </div>
23
-
24
- <div className="space-y-4">
25
- <div className="space-y-1.5">
26
- <label className="text-sm font-medium">{t('tasks.settings.defaultPriority')}</label>
27
- <select
28
- 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"
29
- value={s.get('defaultPriority')}
30
- onChange={(e) => s.set('defaultPriority', e.target.value)}
31
- >
32
- <option value="low">{t('tasks.priority.low')}</option>
33
- <option value="medium">{t('tasks.priority.medium')}</option>
34
- <option value="high">{t('tasks.priority.high')}</option>
35
- <option value="urgent">{t('tasks.priority.urgent')}</option>
36
- </select>
37
- </div>
38
- </div>
39
- </div>
40
- )
41
- }