Files
syndarix/frontend/src/app/[locale]/(authenticated)/projects/[id]/issues/page.tsx
Felipe Cardoso 5b1e2852ea feat(frontend): implement main dashboard page (#48)
Implement the main dashboard / projects list page for Syndarix as the landing
page after login. The implementation includes:

Dashboard Components:
- QuickStats: Overview cards showing active projects, agents, issues, approvals
- ProjectsSection: Grid/list view with filtering and sorting controls
- ProjectCardGrid: Rich project cards for grid view
- ProjectRowList: Compact rows for list view
- ActivityFeed: Real-time activity sidebar with connection status
- PerformanceCard: Performance metrics display
- EmptyState: Call-to-action for new users
- ProjectStatusBadge: Status indicator with icons
- ComplexityIndicator: Visual complexity dots
- ProgressBar: Accessible progress bar component

Features:
- Projects grid/list view with view mode toggle
- Filter by status (all, active, paused, completed, archived)
- Sort by recent, name, progress, or issues
- Quick stats overview with counts
- Real-time activity feed sidebar with live/reconnecting status
- Performance metrics card
- Create project button linking to wizard
- Responsive layout for mobile/desktop
- Loading skeleton states
- Empty state for new users

API Integration:
- useProjects hook for fetching projects (mock data until backend ready)
- useDashboardStats hook for statistics
- TanStack Query for caching and data fetching

Testing:
- 37 unit tests covering all dashboard components
- E2E test suite for dashboard functionality
- Accessibility tests (keyboard nav, aria attributes, heading hierarchy)

Technical:
- TypeScript strict mode compliance
- ESLint passing
- WCAG AA accessibility compliance
- Mobile-first responsive design
- Dark mode support via semantic tokens
- Follows design system guidelines

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-30 23:46:50 +01:00

202 lines
5.7 KiB
TypeScript

'use client';
/**
* Project Issues List Page
*
* Displays filterable, sortable list of issues for a project.
* Supports bulk actions and sync with external trackers.
*
* @module app/[locale]/(authenticated)/projects/[id]/issues/page
*/
import { useState, use } from 'react';
import { useRouter } from 'next/navigation';
import { Plus, Upload } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Skeleton } from '@/components/ui/skeleton';
import {
IssueFilters,
IssueTable,
BulkActions,
useIssues,
} from '@/features/issues';
import type { IssueFiltersType, IssueSort } from '@/features/issues';
interface ProjectIssuesPageProps {
params: Promise<{
locale: string;
id: string;
}>;
}
export default function ProjectIssuesPage({ params }: ProjectIssuesPageProps) {
const { locale, id: projectId } = use(params);
const router = useRouter();
// Filter state
const [filters, setFilters] = useState<IssueFiltersType>({
status: 'all',
priority: 'all',
sprint: 'all',
assignee: 'all',
});
// Sort state
const [sort, setSort] = useState<IssueSort>({
field: 'updated_at',
direction: 'desc',
});
// Selection state
const [selectedIssues, setSelectedIssues] = useState<string[]>([]);
// Fetch issues
const { data, isLoading, error } = useIssues(projectId, filters, sort);
const handleIssueClick = (issueId: string) => {
router.push(`/${locale}/projects/${projectId}/issues/${issueId}`);
};
const handleBulkChangeStatus = () => {
// TODO: Open status change dialog
console.log('Change status for:', selectedIssues);
};
const handleBulkAssign = () => {
// TODO: Open assign dialog
console.log('Assign:', selectedIssues);
};
const handleBulkAddLabels = () => {
// TODO: Open labels dialog
console.log('Add labels to:', selectedIssues);
};
const handleBulkDelete = () => {
// TODO: Confirm and delete
console.log('Delete:', selectedIssues);
};
const handleSync = () => {
// TODO: Sync all issues
console.log('Sync issues');
};
const handleNewIssue = () => {
// TODO: Navigate to new issue page or open dialog
console.log('Create new issue');
};
if (error) {
return (
<div className="container mx-auto px-4 py-8">
<div className="rounded-lg border border-destructive bg-destructive/10 p-6 text-center">
<h2 className="text-lg font-semibold text-destructive">Error Loading Issues</h2>
<p className="mt-2 text-sm text-muted-foreground">
Failed to load issues. Please try again later.
</p>
<Button
variant="outline"
className="mt-4"
onClick={() => window.location.reload()}
>
Retry
</Button>
</div>
</div>
);
}
return (
<div className="container mx-auto px-4 py-6">
<div className="space-y-6">
{/* Header */}
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 className="text-3xl font-bold">Issues</h1>
<p className="text-muted-foreground">
{isLoading ? (
<Skeleton className="h-4 w-24" />
) : (
`${data?.pagination.total || 0} issues found`
)}
</p>
</div>
<div className="flex gap-2">
<Button variant="outline" size="sm" onClick={handleSync}>
<Upload className="mr-2 h-4 w-4" aria-hidden="true" />
Sync
</Button>
<Button size="sm" onClick={handleNewIssue}>
<Plus className="mr-2 h-4 w-4" aria-hidden="true" />
New Issue
</Button>
</div>
</div>
{/* Filters */}
<IssueFilters filters={filters} onFiltersChange={setFilters} />
{/* Bulk Actions */}
<BulkActions
selectedCount={selectedIssues.length}
onChangeStatus={handleBulkChangeStatus}
onAssign={handleBulkAssign}
onAddLabels={handleBulkAddLabels}
onDelete={handleBulkDelete}
/>
{/* Issue Table */}
{isLoading ? (
<div className="space-y-2">
{[...Array(5)].map((_, i) => (
<Skeleton key={i} className="h-16 w-full" />
))}
</div>
) : (
<IssueTable
issues={data?.data || []}
selectedIssues={selectedIssues}
onSelectionChange={setSelectedIssues}
onIssueClick={handleIssueClick}
sort={sort}
onSortChange={setSort}
/>
)}
{/* Pagination info */}
{data && data.pagination.total > 0 && (
<div className="flex items-center justify-between text-sm text-muted-foreground">
<span>
Showing {(data.pagination.page - 1) * data.pagination.page_size + 1} to{' '}
{Math.min(
data.pagination.page * data.pagination.page_size,
data.pagination.total
)}{' '}
of {data.pagination.total} issues
</span>
{data.pagination.total_pages > 1 && (
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
disabled={!data.pagination.has_prev}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
disabled={!data.pagination.has_next}
>
Next
</Button>
</div>
)}
</div>
)}
</div>
</div>
);
}