feat(task-list): add TaskListSkeleton component for improved loading state
- Introduced TaskListSkeleton to provide a visual loading state for the task list, enhancing user experience during data fetching. - Updated TaskListV2Table to utilize TaskListSkeleton instead of a generic Skeleton component, allowing for a more tailored loading interface. - The new skeleton component includes customizable column widths and multiple rows to better represent the task list structure while loading.
This commit is contained in:
@@ -63,6 +63,7 @@ import OptimizedBulkActionBar from '@/components/task-management/optimized-bulk-
|
||||
import CustomColumnModal from '@/pages/projects/projectView/taskList/task-list-table/custom-columns/custom-column-modal/custom-column-modal';
|
||||
import AddTaskRow from './components/AddTaskRow';
|
||||
import { AddCustomColumnButton, CustomColumnHeader } from './components/CustomColumnComponents';
|
||||
import TaskListSkeleton from './components/TaskListSkeleton';
|
||||
|
||||
// Hooks and utilities
|
||||
import { useTaskSocketHandlers } from '@/hooks/useTaskSocketHandlers';
|
||||
@@ -597,7 +598,9 @@ const TaskListV2Section: React.FC = () => {
|
||||
);
|
||||
|
||||
// Loading and error states
|
||||
if (loading || loadingColumns) return <Skeleton style={{ marginTop: 8 }} active />;
|
||||
if (loading || loadingColumns) {
|
||||
return <TaskListSkeleton visibleColumns={visibleColumns} />;
|
||||
}
|
||||
if (error)
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import React from 'react';
|
||||
import { Skeleton } from 'antd';
|
||||
import ImprovedTaskFilters from '@/components/task-management/improved-task-filters';
|
||||
|
||||
interface TaskListSkeletonProps {
|
||||
visibleColumns?: Array<{
|
||||
id: string;
|
||||
width: string;
|
||||
isSticky?: boolean;
|
||||
}>;
|
||||
}
|
||||
|
||||
const TaskListSkeleton: React.FC<TaskListSkeletonProps> = ({ visibleColumns }) => {
|
||||
// Default columns if none provided
|
||||
const defaultColumns = [
|
||||
{ id: 'dragHandle', width: '40px' },
|
||||
{ id: 'checkbox', width: '40px' },
|
||||
{ id: 'taskKey', width: '100px' },
|
||||
{ id: 'title', width: '300px' },
|
||||
{ id: 'assignees', width: '120px' },
|
||||
{ id: 'status', width: '120px' },
|
||||
{ id: 'priority', width: '100px' },
|
||||
{ id: 'dueDate', width: '120px' },
|
||||
];
|
||||
|
||||
const columns = visibleColumns || defaultColumns;
|
||||
|
||||
// Generate multiple skeleton rows
|
||||
const skeletonRows = Array.from({ length: 8 }, (_, index) => (
|
||||
<div key={index} className="flex items-center min-w-max px-1 py-3 border-b border-gray-100 dark:border-gray-800">
|
||||
{columns.map((column, colIndex) => {
|
||||
const columnStyle = {
|
||||
width: column.width,
|
||||
flexShrink: 0,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`${index}-${column.id}`}
|
||||
className="border-r border-gray-200 dark:border-gray-700 flex items-center px-2"
|
||||
style={columnStyle}
|
||||
>
|
||||
{column.id === 'dragHandle' || column.id === 'checkbox' ? (
|
||||
<Skeleton.Button size="small" shape="circle" active />
|
||||
) : column.id === 'title' ? (
|
||||
<div className="w-full">
|
||||
<Skeleton.Input size="small" active style={{ width: '80%' }} />
|
||||
</div>
|
||||
) : column.id === 'assignees' ? (
|
||||
<div className="flex items-center gap-1">
|
||||
<Skeleton.Avatar size="small" active />
|
||||
<Skeleton.Avatar size="small" active />
|
||||
</div>
|
||||
) : (
|
||||
<Skeleton.Button size="small" active style={{ width: '70%' }} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
));
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col bg-white dark:bg-gray-900 h-full overflow-hidden">
|
||||
{/* Table Container */}
|
||||
<div
|
||||
className="border border-gray-200 dark:border-gray-700 rounded-lg"
|
||||
style={{
|
||||
height: 'calc(100vh - 240px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
{/* Skeleton Content */}
|
||||
<div
|
||||
className="flex-1 bg-white dark:bg-gray-900 relative"
|
||||
style={{
|
||||
overflowX: 'auto',
|
||||
overflowY: 'auto',
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
{/* Skeleton Column Headers */}
|
||||
<div
|
||||
className="sticky top-0 z-30 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"
|
||||
style={{ width: '100%', minWidth: 'max-content' }}
|
||||
>
|
||||
<div
|
||||
className="flex items-center px-1 py-3 w-full"
|
||||
style={{ minWidth: 'max-content', height: '44px' }}
|
||||
>
|
||||
{columns.map((column, index) => {
|
||||
const columnStyle = {
|
||||
width: column.width,
|
||||
flexShrink: 0,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
key={`header-${column.id}`}
|
||||
className="border-r border-gray-200 dark:border-gray-700 flex items-center px-2"
|
||||
style={columnStyle}
|
||||
>
|
||||
{column.id === 'dragHandle' || column.id === 'checkbox' ? (
|
||||
<span></span>
|
||||
) : (
|
||||
<Skeleton.Button size="small" active style={{ width: '60%' }} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{/* Add Custom Column Button Skeleton */}
|
||||
<div
|
||||
className="flex items-center justify-center px-2 border-r border-gray-200 dark:border-gray-700"
|
||||
style={{ width: '50px', flexShrink: 0 }}
|
||||
>
|
||||
<Skeleton.Button size="small" shape="circle" active />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Skeleton Group Headers and Rows */}
|
||||
<div style={{ minWidth: 'max-content' }}>
|
||||
{/* First Group */}
|
||||
<div className="mt-2">
|
||||
{/* Group Header Skeleton */}
|
||||
<div className="flex items-center px-4 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||
<Skeleton.Button size="small" shape="circle" active />
|
||||
<div className="ml-3 flex-1">
|
||||
<Skeleton.Input size="small" active style={{ width: '150px' }} />
|
||||
</div>
|
||||
<Skeleton.Button size="small" active style={{ width: '30px' }} />
|
||||
</div>
|
||||
|
||||
{/* Group Tasks Skeleton */}
|
||||
{skeletonRows.slice(0, 3)}
|
||||
</div>
|
||||
|
||||
{/* Second Group */}
|
||||
<div className="mt-2">
|
||||
{/* Group Header Skeleton */}
|
||||
<div className="flex items-center px-4 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||
<Skeleton.Button size="small" shape="circle" active />
|
||||
<div className="ml-3 flex-1">
|
||||
<Skeleton.Input size="small" active style={{ width: '150px' }} />
|
||||
</div>
|
||||
<Skeleton.Button size="small" active style={{ width: '30px' }} />
|
||||
</div>
|
||||
|
||||
{/* Group Tasks Skeleton */}
|
||||
{skeletonRows.slice(3, 6)}
|
||||
</div>
|
||||
|
||||
{/* Third Group */}
|
||||
<div className="mt-2">
|
||||
{/* Group Header Skeleton */}
|
||||
<div className="flex items-center px-4 py-2 bg-gray-50 dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700">
|
||||
<Skeleton.Button size="small" shape="circle" active />
|
||||
<div className="ml-3 flex-1">
|
||||
<Skeleton.Input size="small" active style={{ width: '150px' }} />
|
||||
</div>
|
||||
<Skeleton.Button size="small" active style={{ width: '30px' }} />
|
||||
</div>
|
||||
|
||||
{/* Group Tasks Skeleton */}
|
||||
{skeletonRows.slice(6, 8)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TaskListSkeleton;
|
||||
Reference in New Issue
Block a user