diff --git a/frontend/src/components/IssuesSection.tsx b/frontend/src/components/IssuesSection.tsx index cb6f1f5..06ef929 100644 --- a/frontend/src/components/IssuesSection.tsx +++ b/frontend/src/components/IssuesSection.tsx @@ -13,8 +13,19 @@ import { Link, Chip, TableSortLabel, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + TextField, + FormControl, + FormLabel, + RadioGroup, + FormControlLabel, + Radio, + Badge, } from '@mui/material'; -import { Settings } from '@mui/icons-material'; +import { FilterAlt } from '@mui/icons-material'; import { Issue } from '../types'; import { formatDate } from '../utils/dateUtils'; @@ -29,6 +40,12 @@ interface IssuesSectionProps { repo: string; } +interface IssueFilters { + author: string; + status: 'all' | 'open' | 'closed'; + linkedPR: 'all' | 'with' | 'without'; +} + export const IssuesSection: React.FC = ({ issues, owner, @@ -36,6 +53,12 @@ export const IssuesSection: React.FC = ({ }) => { const [sortBy, setSortBy] = useState('created_at'); const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc'); + const [filterDialogOpen, setFilterDialogOpen] = useState(false); + const [filters, setFilters] = useState({ + author: '', + status: 'all', + linkedPR: 'all', + }); // Sort issues based on current sort criteria const sortedIssues = useMemo(() => { @@ -68,6 +91,43 @@ export const IssuesSection: React.FC = ({ }); }, [issues, sortBy, sortDirection]); + // Apply filters to sorted issues + const filteredIssues = useMemo(() => { + return sortedIssues.filter((issue) => { + // Author filter + if (filters.author && !issue.author.toLowerCase().includes(filters.author.toLowerCase())) { + return false; + } + + // Status filter + if (filters.status === 'open' && issue.is_closed) { + return false; + } + if (filters.status === 'closed' && !issue.is_closed) { + return false; + } + + // Linked PR filter + if (filters.linkedPR === 'with' && (!issue.linked_prs || issue.linked_prs.length === 0)) { + return false; + } + if (filters.linkedPR === 'without' && issue.linked_prs && issue.linked_prs.length > 0) { + return false; + } + + return true; + }); + }, [sortedIssues, filters]); + + // Count active filters + const activeFilterCount = useMemo(() => { + let count = 0; + if (filters.author) count++; + if (filters.status !== 'all') count++; + if (filters.linkedPR !== 'all') count++; + return count; + }, [filters]); + const handleSort = (column: keyof Issue) => { if (sortBy === column) { setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc'); @@ -76,6 +136,18 @@ export const IssuesSection: React.FC = ({ setSortDirection('asc'); } }; + + const handleClearFilters = () => { + setFilters({ + author: '', + status: 'all', + linkedPR: 'all', + }); + }; + + const handleApplyFilters = () => { + setFilterDialogOpen(false); + }; return ( @@ -92,12 +164,18 @@ export const IssuesSection: React.FC = ({ Issues - {issues.length} issues in this period + {filteredIssues.length} of {issues.length} issues - + + + @@ -188,7 +266,7 @@ export const IssuesSection: React.FC = ({ - {sortedIssues.map((issue) => { + {filteredIssues.map((issue) => { // Render assignees const renderAssignees = () => { if (!issue.assignees || issue.assignees.length === 0) { @@ -326,6 +404,60 @@ export const IssuesSection: React.FC = ({ })}
+ + {/* Filter Dialog */} + setFilterDialogOpen(false)} + maxWidth="sm" + fullWidth + > + Filter Issues + + + {/* Author Filter */} + setFilters({ ...filters, author: e.target.value })} + fullWidth + size="small" + /> + + {/* Status Filter */} + + Status + setFilters({ ...filters, status: e.target.value as IssueFilters['status'] })} + > + } label="All" /> + } label="Open" /> + } label="Closed" /> + + + + {/* Linked PR Filter */} + + Linked Pull Request + setFilters({ ...filters, linkedPR: e.target.value as IssueFilters['linkedPR'] })} + > + } label="All" /> + } label="Has linked PR" /> + } label="No linked PR" /> + + + + + + + + + +
);