Files
syndarix/frontend/src/app/[locale]/(authenticated)/projects/[id]/issues/page.tsx
Felipe Cardoso da5affd613 fix(frontend): remove locale-dependent routing and migrate to centralized locale-aware router
- 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`.
2026-01-03 01:34:53 +01:00

182 lines
5.4 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 '@/lib/i18n/routing';
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 { 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(`/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>
);
}