11"use client" ;
22
3- import { useEffect , Suspense , useCallback , useMemo } from "react" ;
3+ import { useEffect , Suspense , useCallback , useMemo , useState } from "react" ;
44import { useSearchParams , useRouter } from "next/navigation" ;
55import { Layout } from "@/components/layout/Layout" ;
66import { Card , CardContent , CardHeader , CardTitle } from "@/components/ui/card" ;
77import { Button } from "@/components/ui/button" ;
88import { Badge } from "@/components/ui/badge" ;
9+ import {
10+ Select ,
11+ SelectContent ,
12+ SelectItem ,
13+ SelectTrigger ,
14+ SelectValue ,
15+ } from "@/components/ui/select" ;
16+ import { Checkbox } from "@/components/ui/checkbox" ;
917import { Tabs , TabsContent , TabsList , TabsTrigger } from "@/components/ui/tabs" ;
1018import { Avatar , AvatarImage , AvatarFallback } from "@/components/ui/avatar" ;
1119import {
@@ -25,8 +33,14 @@ import {
2533 ExternalLink ,
2634 RefreshCw ,
2735 LucideIcon ,
36+ GitMerge ,
37+ AlertCircle ,
38+ CheckCircle2 ,
39+ XCircle ,
40+ Loader2 ,
41+ Plus ,
2842} from "lucide-react" ;
29- import { useActionItemsStore } from "@/stores" ;
43+ import { useActionItemsStore , useKanbanStore } from "@/stores" ;
3044import type { ActionItem as StoreActionItem } from "@/stores/actionItems" ;
3145import { PageHeader } from "@/components/layout/PageHeader" ;
3246import { SearchModal } from "@/components/search/SearchModal" ;
@@ -48,6 +62,13 @@ interface ActionItem {
4862 updatedAt : string ;
4963 comments ?: number ;
5064 stars ?: number ;
65+ additions ?: number ;
66+ deletions ?: number ;
67+ language ?: string ;
68+ mergeable ?: "MERGEABLE" | "CONFLICTING" | "UNKNOWN" ;
69+ statusCheckRollup ?: {
70+ state : "SUCCESS" | "FAILURE" | "PENDING" | "EXPECTED" ;
71+ } ;
5172}
5273
5374const VALID_TABS = [ "assigned" , "mentions" , "stale" ] as const ;
@@ -198,6 +219,61 @@ function ActionRequiredContent() {
198219 const isLoading = loading [ type ] ;
199220 const error = errors [ type ] ;
200221
222+ const [ selectedRepo , setSelectedRepo ] = useState < string > ( "all" ) ;
223+ const [ selectedLanguage , setSelectedLanguage ] = useState < string > ( "all" ) ;
224+ const [ selectedItems , setSelectedItems ] = useState < Set < string > > ( new Set ( ) ) ;
225+ const { addTaskFromActionItem } = useKanbanStore ( ) ;
226+
227+ const repositories = useMemo ( ( ) => {
228+ const repos = new Set ( items . map ( ( item : ActionItem ) => item . repo ) ) ;
229+ return Array . from ( repos ) . sort ( ) ;
230+ } , [ items ] ) ;
231+
232+ const languages = useMemo ( ( ) => {
233+ const langs = new Set (
234+ items
235+ . map ( ( item : ActionItem ) => item . language )
236+ . filter ( ( lang ) : lang is string => ! ! lang )
237+ ) ;
238+ return Array . from ( langs ) . sort ( ) ;
239+ } , [ items ] ) ;
240+
241+ const filteredItems = useMemo ( ( ) => {
242+ return items . filter ( ( item : ActionItem ) => {
243+ if ( selectedRepo !== "all" && item . repo !== selectedRepo ) return false ;
244+ if ( selectedLanguage !== "all" && item . language !== selectedLanguage ) return false ;
245+ return true ;
246+ } ) ;
247+ } , [ items , selectedRepo , selectedLanguage ] ) ;
248+
249+ const toggleSelectAll = ( ) => {
250+ if ( selectedItems . size === filteredItems . length ) {
251+ setSelectedItems ( new Set ( ) ) ;
252+ } else {
253+ setSelectedItems ( new Set ( filteredItems . map ( ( item : ActionItem ) => item . id . toString ( ) ) ) ) ;
254+ }
255+ } ;
256+
257+ const toggleSelectItem = ( itemId : string ) => {
258+ const newSelected = new Set ( selectedItems ) ;
259+ if ( newSelected . has ( itemId ) ) {
260+ newSelected . delete ( itemId ) ;
261+ } else {
262+ newSelected . add ( itemId ) ;
263+ }
264+ setSelectedItems ( newSelected ) ;
265+ } ;
266+
267+ const handleBulkAddToKanban = ( ) => {
268+ const itemsToAdd = filteredItems . filter ( ( item : ActionItem ) =>
269+ selectedItems . has ( item . id . toString ( ) )
270+ ) ;
271+ itemsToAdd . forEach ( ( item : ActionItem ) => {
272+ addTaskFromActionItem ( item as StoreActionItem , "" , "todo" ) ;
273+ } ) ;
274+ setSelectedItems ( new Set ( ) ) ;
275+ } ;
276+
201277 if ( isLoading ) {
202278 return (
203279 < div className = "rounded-md border" >
@@ -284,25 +360,84 @@ function ActionRequiredContent() {
284360 }
285361
286362 return (
287- < div className = "rounded-md border" >
288- < Table >
289- < TableHeader >
290- < TableRow >
291- < TableHead className = "w-[30%]" > Title / Repository</ TableHead >
292- < TableHead className = "w-[10%]" > Author</ TableHead >
293- < TableHead className = "w-[18%]" > Labels</ TableHead >
294- < TableHead className = "w-[10%]" > Priority</ TableHead >
295- < TableHead className = "w-[8%]" > Activity</ TableHead >
296- < TableHead className = "w-[10%]" > Updated</ TableHead >
297- < TableHead className = "w-[14%]" > Actions</ TableHead >
298- </ TableRow >
299- </ TableHeader >
300- < TableBody >
301- { items . map ( ( item : ActionItem ) => (
363+ < div className = "space-y-4" >
364+ < div className = "flex items-center gap-4 flex-wrap" >
365+ < div className = "flex items-center gap-2" >
366+ < label className = "text-sm font-medium text-gray-700 dark:text-gray-300" > Repository:</ label >
367+ < Select value = { selectedRepo } onValueChange = { setSelectedRepo } >
368+ < SelectTrigger className = "w-[200px]" >
369+ < SelectValue placeholder = "All Repositories" />
370+ </ SelectTrigger >
371+ < SelectContent >
372+ < SelectItem value = "all" > All Repositories</ SelectItem >
373+ { repositories . map ( ( repo ) => (
374+ < SelectItem key = { repo } value = { repo } >
375+ { repo }
376+ </ SelectItem >
377+ ) ) }
378+ </ SelectContent >
379+ </ Select >
380+ </ div >
381+ { languages . length > 0 && (
382+ < div className = "flex items-center gap-2" >
383+ < label className = "text-sm font-medium text-gray-700 dark:text-gray-300" > Language:</ label >
384+ < Select value = { selectedLanguage } onValueChange = { setSelectedLanguage } >
385+ < SelectTrigger className = "w-[150px]" >
386+ < SelectValue placeholder = "All Languages" />
387+ </ SelectTrigger >
388+ < SelectContent >
389+ < SelectItem value = "all" > All Languages</ SelectItem >
390+ { languages . map ( ( lang ) => (
391+ < SelectItem key = { lang } value = { lang } >
392+ { lang }
393+ </ SelectItem >
394+ ) ) }
395+ </ SelectContent >
396+ </ Select >
397+ </ div >
398+ ) }
399+ { selectedItems . size > 0 && (
400+ < div className = "flex items-center gap-2 ml-auto" >
401+ < Badge variant = "secondary" > { selectedItems . size } selected</ Badge >
402+ < Button size = "sm" onClick = { handleBulkAddToKanban } >
403+ < Plus className = "w-4 h-4 mr-2" />
404+ Bulk Add to Kanban
405+ </ Button >
406+ </ div >
407+ ) }
408+ </ div >
409+ < div className = "rounded-md border" >
410+ < Table >
411+ < TableHeader >
412+ < TableRow >
413+ < TableHead className = "w-[3%]" >
414+ < Checkbox
415+ checked = { selectedItems . size === filteredItems . length && filteredItems . length > 0 }
416+ onCheckedChange = { toggleSelectAll }
417+ />
418+ </ TableHead >
419+ < TableHead className = "w-[27%]" > Title / Repository</ TableHead >
420+ < TableHead className = "w-[10%]" > Author</ TableHead >
421+ < TableHead className = "w-[15%]" > Labels</ TableHead >
422+ < TableHead className = "w-[8%]" > Priority</ TableHead >
423+ < TableHead className = "w-[8%]" > Size</ TableHead >
424+ < TableHead className = "w-[6%]" > Activity</ TableHead >
425+ < TableHead className = "w-[9%]" > Updated</ TableHead >
426+ < TableHead className = "w-[14%]" > Actions</ TableHead >
427+ </ TableRow >
428+ </ TableHeader >
429+ < TableBody >
430+ { filteredItems . map ( ( item : ActionItem ) => (
302431 < TableRow
303432 key = { item . id }
304- className = { type === "stale" ? getStaleRowClassName ( item . daysOld ) : "" }
433+ className = { getStaleRowClassName ( item . daysOld ) }
305434 >
435+ < TableCell >
436+ < Checkbox
437+ checked = { selectedItems . has ( item . id . toString ( ) ) }
438+ onCheckedChange = { ( ) => toggleSelectItem ( item . id . toString ( ) ) }
439+ />
440+ </ TableCell >
306441 < TableCell >
307442 < div className = "flex items-center gap-2" >
308443 < div className = "min-w-0 flex-1" >
@@ -326,6 +461,26 @@ function ActionRequiredContent() {
326461 { item . url && (
327462 < ExternalLink className = "w-3 h-3 text-gray-400 flex-shrink-0" />
328463 ) }
464+ { item . type === "pullRequest" && item . mergeable === "CONFLICTING" && (
465+ < span title = "Has merge conflicts" >
466+ < AlertCircle className = "w-4 h-4 text-red-500 flex-shrink-0" />
467+ </ span >
468+ ) }
469+ { item . type === "pullRequest" && item . statusCheckRollup ?. state === "SUCCESS" && (
470+ < span title = "All checks passed" >
471+ < CheckCircle2 className = "w-4 h-4 text-green-500 flex-shrink-0" />
472+ </ span >
473+ ) }
474+ { item . type === "pullRequest" && item . statusCheckRollup ?. state === "FAILURE" && (
475+ < span title = "Checks failed" >
476+ < XCircle className = "w-4 h-4 text-red-500 flex-shrink-0" />
477+ </ span >
478+ ) }
479+ { item . type === "pullRequest" && item . statusCheckRollup ?. state === "PENDING" && (
480+ < span title = "Checks pending" >
481+ < Loader2 className = "w-4 h-4 text-yellow-500 flex-shrink-0 animate-spin" />
482+ </ span >
483+ ) }
329484 </ div >
330485 < p className = "text-sm text-gray-500 dark:text-gray-400 truncate" >
331486 { item . repo }
@@ -386,6 +541,16 @@ function ActionRequiredContent() {
386541 { item . priority }
387542 </ Badge >
388543 </ TableCell >
544+ < TableCell >
545+ { item . type === "pullRequest" && ( item . additions !== undefined || item . deletions !== undefined ) ? (
546+ < div className = "flex flex-col text-xs" >
547+ < span className = "text-green-600 dark:text-green-400" > +{ item . additions || 0 } </ span >
548+ < span className = "text-red-600 dark:text-red-400" > -{ item . deletions || 0 } </ span >
549+ </ div >
550+ ) : (
551+ < span className = "text-gray-400 text-xs" > N/A</ span >
552+ ) }
553+ </ TableCell >
389554 < TableCell >
390555 < div className = "flex items-center gap-1 text-gray-600 dark:text-gray-300" >
391556 < MessageSquare className = "w-4 h-4" />
@@ -404,9 +569,10 @@ function ActionRequiredContent() {
404569 />
405570 </ TableCell >
406571 </ TableRow >
407- ) ) }
408- </ TableBody >
409- </ Table >
572+ ) ) }
573+ </ TableBody >
574+ </ Table >
575+ </ div >
410576 </ div >
411577 ) ;
412578 } ;
0 commit comments