enhanced ui

This commit is contained in:
Desktop
2025-11-12 21:37:41 +05:00
parent 9692a5ed2e
commit fa47cfa7ff
12 changed files with 2341 additions and 428 deletions

View File

@@ -29,6 +29,8 @@ import { TaskIcon, PlusIcon, DownloadIcon } from '../../icons';
import { createTasksPageConfig } from '../../config/pages/tasks.config';
import { useSectorStore } from '../../store/sectorStore';
import { usePageSizeStore } from '../../store/pageSizeStore';
import ViewToggle, { ViewType } from '../../components/common/ViewToggle';
import { KanbanBoard, TaskList, Task as KanbanTask } from '../../components/tasks';
export default function Tasks() {
const toast = useToast();
@@ -58,6 +60,9 @@ export default function Tasks() {
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [showContent, setShowContent] = useState(false);
// View state
const [currentView, setCurrentView] = useState<ViewType>('table');
// Modal state
const [isModalOpen, setIsModalOpen] = useState(false);
const [isEditMode, setIsEditMode] = useState(false);
@@ -183,6 +188,43 @@ export default function Tasks() {
setCurrentPage(1);
}, [pageSize]);
// Map API Task to KanbanBoard Task
const mapTaskToKanbanTask = (task: Task): KanbanTask => {
// Map status from API to Kanban status
const statusMap: Record<string, "todo" | "in_progress" | "completed"> = {
'queued': 'todo',
'in_progress': 'in_progress',
'generating': 'in_progress',
'completed': 'completed',
'published': 'completed',
'draft': 'todo',
'review': 'in_progress',
};
const kanbanStatus = statusMap[task.status] || 'todo';
// Format due date if available (you might need to add this field to Task)
const dueDate = task.created_at ? new Date(task.created_at).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric'
}) : undefined;
return {
id: String(task.id),
title: task.title || 'Untitled Task',
status: kanbanStatus,
dueDate,
commentsCount: 0, // Add if you have this data
attachmentsCount: 0, // Add if you have this data
tags: task.cluster_name ? [{ label: task.cluster_name, color: 'brand' as const }] : undefined,
description: task.description || undefined,
assignee: undefined, // Add if you have assignee data
};
};
const kanbanTasks = useMemo(() => tasks.map(mapTaskToKanbanTask), [tasks]);
// Debounced search
useEffect(() => {
const timer = setTimeout(() => {
@@ -545,111 +587,233 @@ export default function Tasks() {
}
};
const handleTaskClick = (kanbanTask: KanbanTask) => {
const apiTask = tasks.find(t => String(t.id) === kanbanTask.id);
if (apiTask) {
setEditingTask(apiTask);
setFormData({
title: apiTask.title || '',
description: apiTask.description || '',
keywords: apiTask.keywords || '',
cluster_id: apiTask.cluster_id || null,
content_structure: apiTask.content_structure || 'blog_post',
content_type: apiTask.content_type || 'blog_post',
status: apiTask.status || 'queued',
word_count: apiTask.word_count || 0,
});
setIsEditMode(true);
setIsModalOpen(true);
}
};
const handleTaskMove = async (taskId: string, newStatus: "todo" | "in_progress" | "completed") => {
const apiTask = tasks.find(t => String(t.id) === taskId);
if (!apiTask) return;
// Map Kanban status back to API status
const statusMap: Record<string, string> = {
'todo': 'queued',
'in_progress': 'in_progress',
'completed': 'completed',
};
try {
await updateTask(apiTask.id, { status: statusMap[newStatus] || 'queued' });
toast.success('Task status updated');
loadTasks();
} catch (error: any) {
toast.error(`Failed to update task: ${error.message}`);
}
};
return (
<>
<TablePageTemplate
title="Tasks"
titleIcon={<TaskIcon className="text-brand-500 size-5" />}
subtitle="Manage content generation queue and tasks"
columns={pageConfig.columns}
data={tasks}
loading={loading}
showContent={showContent}
filters={pageConfig.filters}
filterValues={{
search: searchTerm,
status: statusFilter,
cluster_id: clusterFilter,
content_structure: structureFilter,
content_type: typeFilter,
}}
onFilterChange={(key, value) => {
const stringValue = value === null || value === undefined ? '' : String(value);
if (key === 'search') {
setSearchTerm(stringValue);
} else if (key === 'status') {
setStatusFilter(stringValue);
} else if (key === 'cluster_id') {
setClusterFilter(stringValue);
} else if (key === 'content_structure') {
setStructureFilter(stringValue);
} else if (key === 'content_type') {
setTypeFilter(stringValue);
}
setCurrentPage(1);
}}
onEdit={(row) => {
setEditingTask(row);
setFormData({
title: row.title || '',
description: row.description || '',
keywords: row.keywords || '',
cluster_id: row.cluster_id || null,
content_structure: row.content_structure || 'blog_post',
content_type: row.content_type || 'blog_post',
status: row.status || 'queued',
word_count: row.word_count || 0,
});
setIsEditMode(true);
setIsModalOpen(true);
}}
onCreate={() => {
resetForm();
setIsModalOpen(true);
}}
createLabel="Add Task"
onCreateIcon={<PlusIcon />}
onDelete={async (id: number) => {
await deleteTask(id);
loadTasks();
}}
onBulkDelete={async (ids: number[]) => {
const result = await bulkDeleteTasks(ids);
// Clear selection first
setSelectedIds([]);
// Reset to page 1 if we deleted all items on current page
if (currentPage > 1 && tasks.length <= ids.length) {
{/* View Toggle - Only show for Kanban/List views */}
{currentView !== 'table' && (
<div className="mb-4 flex items-center justify-between">
<div className="flex items-center gap-4">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white flex items-center gap-2">
<TaskIcon className="text-brand-500 size-6" />
Tasks
</h1>
<p className="text-sm text-gray-500 dark:text-gray-400 hidden sm:block">
Manage content generation queue and tasks
</p>
</div>
<ViewToggle currentView={currentView} onViewChange={setCurrentView} />
</div>
)}
{/* Table View */}
{currentView === 'table' && (
<div className="mb-4 flex justify-end">
<ViewToggle currentView={currentView} onViewChange={setCurrentView} />
</div>
<TablePageTemplate
title="Tasks"
titleIcon={<TaskIcon className="text-brand-500 size-5" />}
subtitle="Manage content generation queue and tasks"
columns={pageConfig.columns}
data={tasks}
loading={loading}
showContent={showContent}
filters={pageConfig.filters}
filterValues={{
search: searchTerm,
status: statusFilter,
cluster_id: clusterFilter,
content_structure: structureFilter,
content_type: typeFilter,
}}
onFilterChange={(key, value) => {
const stringValue = value === null || value === undefined ? '' : String(value);
if (key === 'search') {
setSearchTerm(stringValue);
} else if (key === 'status') {
setStatusFilter(stringValue);
} else if (key === 'cluster_id') {
setClusterFilter(stringValue);
} else if (key === 'content_structure') {
setStructureFilter(stringValue);
} else if (key === 'content_type') {
setTypeFilter(stringValue);
}
setCurrentPage(1);
}
// Always reload data to refresh the table
await loadTasks();
return result;
}}
onBulkExport={handleBulkExport}
onBulkUpdateStatus={handleBulkUpdateStatus}
onBulkAction={handleBulkAction}
onRowAction={handleRowAction}
getItemDisplayName={(row: Task) => row.title}
onExport={async () => {
toast.info('Export functionality coming soon');
}}
onExportIcon={<DownloadIcon />}
selectionLabel="task"
pagination={{
currentPage,
totalPages,
totalCount,
onPageChange: setCurrentPage,
}}
selection={{
selectedIds,
onSelectionChange: setSelectedIds,
}}
sorting={{
sortBy,
sortDirection,
onSort: handleSort,
}}
headerMetrics={headerMetrics}
onFilterReset={() => {
setSearchTerm('');
setStatusFilter('');
setClusterFilter('');
setStructureFilter('');
setTypeFilter('');
setCurrentPage(1);
}}
/>
}}
onEdit={(row) => {
setEditingTask(row);
setFormData({
title: row.title || '',
description: row.description || '',
keywords: row.keywords || '',
cluster_id: row.cluster_id || null,
content_structure: row.content_structure || 'blog_post',
content_type: row.content_type || 'blog_post',
status: row.status || 'queued',
word_count: row.word_count || 0,
});
setIsEditMode(true);
setIsModalOpen(true);
}}
onCreate={() => {
resetForm();
setIsModalOpen(true);
}}
createLabel="Add Task"
onCreateIcon={<PlusIcon />}
onDelete={async (id: number) => {
await deleteTask(id);
loadTasks();
}}
onBulkDelete={async (ids: number[]) => {
const result = await bulkDeleteTasks(ids);
// Clear selection first
setSelectedIds([]);
// Reset to page 1 if we deleted all items on current page
if (currentPage > 1 && tasks.length <= ids.length) {
setCurrentPage(1);
}
// Always reload data to refresh the table
await loadTasks();
return result;
}}
onBulkExport={handleBulkExport}
onBulkUpdateStatus={handleBulkUpdateStatus}
onBulkAction={handleBulkAction}
onRowAction={handleRowAction}
getItemDisplayName={(row: Task) => row.title}
onExport={async () => {
toast.info('Export functionality coming soon');
}}
onExportIcon={<DownloadIcon />}
selectionLabel="task"
pagination={{
currentPage,
totalPages,
totalCount,
onPageChange: setCurrentPage,
}}
selection={{
selectedIds,
onSelectionChange: setSelectedIds,
}}
sorting={{
sortBy,
sortDirection,
onSort: handleSort,
}}
headerMetrics={headerMetrics}
onFilterReset={() => {
setSearchTerm('');
setStatusFilter('');
setClusterFilter('');
setStructureFilter('');
setTypeFilter('');
setCurrentPage(1);
}}
/>
)}
{/* Kanban View */}
{currentView === 'kanban' && (
<KanbanBoard
tasks={kanbanTasks}
onTaskClick={handleTaskClick}
onTaskMove={handleTaskMove}
onAddTask={() => {
resetForm();
setIsModalOpen(true);
}}
onFilterChange={(filter) => {
// Map filter to status filter
const statusMap: Record<string, string> = {
'All': '',
'Todo': 'queued',
'InProgress': 'in_progress',
'Completed': 'completed',
};
setStatusFilter(statusMap[filter] || '');
setCurrentPage(1);
loadTasks();
}}
/>
)}
{/* List View */}
{currentView === 'list' && (
<TaskList
tasks={kanbanTasks}
onTaskClick={handleTaskClick}
onTaskToggle={async (taskId, completed) => {
const apiTask = tasks.find(t => String(t.id) === taskId);
if (apiTask) {
try {
await updateTask(apiTask.id, { status: completed ? 'completed' : 'queued' });
toast.success('Task updated');
loadTasks();
} catch (error: any) {
toast.error(`Failed to update task: ${error.message}`);
}
}
}}
onAddTask={() => {
resetForm();
setIsModalOpen(true);
}}
onFilterChange={(filter) => {
// Map filter to status filter
const statusMap: Record<string, string> = {
'All': '',
'Todo': 'queued',
'InProgress': 'in_progress',
'Completed': 'completed',
};
setStatusFilter(statusMap[filter] || '');
setCurrentPage(1);
loadTasks();
}}
/>
)}
{/* Progress Modal for AI Functions */}
<ProgressModal