- Replaced `next/navigation` with `@/lib/i18n/routing` across components, pages, and tests. - Removed redundant `locale` props from `ProjectWizard` and related pages. - Updated navigation to exclude explicit `locale` in paths. - Refactored tests to use mocks from `next-intl/navigation`.
146 lines
4.5 KiB
TypeScript
146 lines
4.5 KiB
TypeScript
/**
|
|
* Projects List Page
|
|
*
|
|
* Displays all projects with filtering, sorting, and search.
|
|
* Supports grid and list view modes.
|
|
*
|
|
* @see Issue #54
|
|
*/
|
|
|
|
'use client';
|
|
|
|
import { useState, useCallback, useMemo } from 'react';
|
|
import { useRouter } from '@/lib/i18n/routing';
|
|
import { toast } from 'sonner';
|
|
import { Plus } from 'lucide-react';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Link } from '@/lib/i18n/routing';
|
|
import { ProjectFilters, ProjectsGrid } from '@/components/projects';
|
|
import type { ViewMode, SortBy, SortOrder, Complexity } from '@/components/projects';
|
|
import type { ProjectStatus } from '@/components/projects/types';
|
|
import { useProjects, type ProjectListItem } from '@/lib/api/hooks/useProjects';
|
|
import { useDebounce } from '@/lib/hooks/useDebounce';
|
|
|
|
export default function ProjectsPage() {
|
|
const router = useRouter();
|
|
|
|
// Filter state
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [statusFilter, setStatusFilter] = useState<ProjectStatus | 'all'>('all');
|
|
const [complexityFilter, setComplexityFilter] = useState<Complexity>('all');
|
|
const [sortBy, setSortBy] = useState<SortBy>('recent');
|
|
const [sortOrder, setSortOrder] = useState<SortOrder>('desc');
|
|
const [viewMode, setViewMode] = useState<ViewMode>('grid');
|
|
|
|
// Debounce search for API calls
|
|
const debouncedSearch = useDebounce(searchQuery, 300);
|
|
|
|
// Fetch projects
|
|
const { data, isLoading, error } = useProjects({
|
|
search: debouncedSearch || undefined,
|
|
status: statusFilter,
|
|
complexity: complexityFilter !== 'all' ? complexityFilter : undefined,
|
|
sortBy,
|
|
sortOrder,
|
|
page: 1,
|
|
limit: 50,
|
|
});
|
|
|
|
// Check if any filters are active (for empty state message)
|
|
const hasFilters = useMemo(() => {
|
|
return searchQuery !== '' || statusFilter !== 'all' || complexityFilter !== 'all';
|
|
}, [searchQuery, statusFilter, complexityFilter]);
|
|
|
|
// Handle project card click
|
|
const handleProjectClick = useCallback(
|
|
(project: ProjectListItem) => {
|
|
router.push(`/projects/${project.id}`);
|
|
},
|
|
[router]
|
|
);
|
|
|
|
// Handle project action
|
|
const handleProjectAction = useCallback(
|
|
(project: ProjectListItem, action: 'archive' | 'pause' | 'resume' | 'delete') => {
|
|
// TODO: Implement actual API calls
|
|
switch (action) {
|
|
case 'archive':
|
|
toast.success(`Archived: ${project.name}`);
|
|
break;
|
|
case 'pause':
|
|
toast.info(`Paused: ${project.name}`);
|
|
break;
|
|
case 'resume':
|
|
toast.success(`Resumed: ${project.name}`);
|
|
break;
|
|
case 'delete':
|
|
toast.error(`Deleted: ${project.name}`);
|
|
break;
|
|
}
|
|
},
|
|
[]
|
|
);
|
|
|
|
// Show error toast if fetch fails
|
|
if (error) {
|
|
toast.error('Failed to load projects', {
|
|
description: 'Please try again later',
|
|
});
|
|
}
|
|
|
|
return (
|
|
<div className="container mx-auto px-4 py-6">
|
|
{/* Header */}
|
|
<div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
|
<div>
|
|
<h1 className="text-3xl font-bold">Projects</h1>
|
|
<p className="text-muted-foreground">Manage and monitor your projects</p>
|
|
</div>
|
|
<Button asChild>
|
|
<Link href="/projects/new">
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
Create Project
|
|
</Link>
|
|
</Button>
|
|
</div>
|
|
|
|
{/* Filters */}
|
|
<ProjectFilters
|
|
searchQuery={searchQuery}
|
|
onSearchChange={setSearchQuery}
|
|
statusFilter={statusFilter}
|
|
onStatusFilterChange={setStatusFilter}
|
|
complexityFilter={complexityFilter}
|
|
onComplexityFilterChange={setComplexityFilter}
|
|
sortBy={sortBy}
|
|
onSortByChange={setSortBy}
|
|
sortOrder={sortOrder}
|
|
onSortOrderChange={setSortOrder}
|
|
viewMode={viewMode}
|
|
onViewModeChange={setViewMode}
|
|
className="mb-6"
|
|
/>
|
|
|
|
{/* Projects Grid */}
|
|
<ProjectsGrid
|
|
projects={data?.data ?? []}
|
|
isLoading={isLoading}
|
|
viewMode={viewMode}
|
|
onProjectClick={handleProjectClick}
|
|
onProjectAction={handleProjectAction}
|
|
hasFilters={hasFilters}
|
|
/>
|
|
|
|
{/* Pagination - TODO: Add when more than 50 projects */}
|
|
{data && data.pagination.totalPages > 1 && (
|
|
<div className="mt-6 flex items-center justify-between text-sm text-muted-foreground">
|
|
<span>
|
|
Showing {data.data.length} of {data.pagination.total} projects
|
|
</span>
|
|
{/* TODO: Add pagination controls */}
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|