@@ -46,6 +46,7 @@ import { PageHeader } from "@/components/layout/PageHeader";
4646import { SearchModal } from "@/components/search/SearchModal" ;
4747import { QuickActionsMenu } from "@/components/action-required/QuickActionsMenu" ;
4848import { DetailPanel } from "@/components/ui/detail-panel" ;
49+ import { NewIssueDialog } from "@/components/action-required/NewIssueDialog" ;
4950
5051interface ActionItem {
5152 id : string | number ;
@@ -72,7 +73,7 @@ interface ActionItem {
7273 } ;
7374}
7475
75- const VALID_TABS = [ "assigned" , "mentions" , "stale" ] as const ;
76+ const VALID_TABS = [ "all" , " assigned", "mentions" , "stale" ] as const ;
7677type ValidTab = ( typeof VALID_TABS ) [ number ] ;
7778
7879function extractIssueNumber ( url ?: string ) : string | null {
@@ -121,46 +122,100 @@ function ActionRequiredContent() {
121122 const { selectedIssue, isOpen, closePanel } = useDetailPanelStore ( ) ;
122123 const searchParams = useSearchParams ( ) ;
123124 const router = useRouter ( ) ;
125+ const [ isNewIssueOpen , setIsNewIssueOpen ] = useState ( false ) ;
124126
125127 const tabParam = searchParams ?. get ( "tab" ) ;
126128 const currentTab : ValidTab = VALID_TABS . includes ( tabParam as ValidTab )
127129 ? ( tabParam as ValidTab )
128- : "assigned " ;
130+ : "all " ;
129131
130132 // Memoize refreshData to prevent unnecessary calls
131133 const STALE_THRESHOLD = 5 * 60 * 1000 ;
132134
133135 const refreshActiveTab = useCallback ( ( tabType : ValidTab ) => {
134- refreshData ( tabType ) . catch ( ( error ) => {
135- console . error ( `Failed to refresh ${ tabType } items:` , error ) ;
136- } ) ;
136+ if ( tabType === "all" ) {
137+ [ "assigned" , "mentions" , "stale" ] . forEach ( ( type ) => {
138+ refreshData ( type as "assigned" | "mentions" | "stale" ) . catch ( ( error ) => {
139+ console . error ( `Failed to refresh ${ type } items:` , error ) ;
140+ } ) ;
141+ } ) ;
142+ } else {
143+ refreshData ( tabType ) . catch ( ( error ) => {
144+ console . error ( `Failed to refresh ${ tabType } items:` , error ) ;
145+ } ) ;
146+ }
137147 } , [ refreshData ] ) ;
138148
139149 useEffect ( ( ) => {
140- const lastRefresh = useActionItemsStore . getState ( ) . lastRefresh [ currentTab ] ;
150+ const handleKeyDown = ( e : KeyboardEvent ) => {
151+ if ( ( e . ctrlKey || e . metaKey ) && e . key === 'n' ) {
152+ e . preventDefault ( ) ;
153+ setIsNewIssueOpen ( true ) ;
154+ }
155+ } ;
156+
157+ document . addEventListener ( 'keydown' , handleKeyDown ) ;
158+ return ( ) => document . removeEventListener ( 'keydown' , handleKeyDown ) ;
159+ } , [ ] ) ;
160+
161+ useEffect ( ( ) => {
162+ if ( currentTab === "all" ) {
163+ const lastRefreshes = useActionItemsStore . getState ( ) . lastRefresh ;
164+ const needsRefresh = [ "assigned" , "mentions" , "stale" ] . some ( ( type ) => {
165+ const lastRefresh = lastRefreshes [ type as keyof typeof lastRefreshes ] ;
166+ return ! lastRefresh || Date . now ( ) - lastRefresh > STALE_THRESHOLD ;
167+ } ) ;
168+
169+ if ( needsRefresh ) {
170+ [ "assigned" , "mentions" , "stale" ] . forEach ( ( type ) => {
171+ refreshData ( type as "assigned" | "mentions" | "stale" ) . catch ( ( error ) => {
172+ console . error ( `Failed to refresh ${ type } items:` , error ) ;
173+ } ) ;
174+ } ) ;
175+ }
176+ } else {
177+ const lastRefresh = useActionItemsStore . getState ( ) . lastRefresh [ currentTab ] ;
141178
142- if ( ! lastRefresh || Date . now ( ) - lastRefresh > STALE_THRESHOLD ) {
143- refreshActiveTab ( currentTab ) ;
179+ if ( ! lastRefresh || Date . now ( ) - lastRefresh > STALE_THRESHOLD ) {
180+ refreshActiveTab ( currentTab ) ;
181+ }
144182 }
145- } , [ currentTab , refreshActiveTab , STALE_THRESHOLD ] ) ;
183+ } , [ currentTab , refreshActiveTab , refreshData , STALE_THRESHOLD ] ) ;
146184
147185 useEffect ( ( ) => {
148186 const handleVisibilityChange = ( ) => {
149187 if ( document . visibilityState === 'visible' ) {
150- const lastRefresh = useActionItemsStore . getState ( ) . lastRefresh [ currentTab ] ;
188+ if ( currentTab === "all" ) {
189+ const lastRefreshes = useActionItemsStore . getState ( ) . lastRefresh ;
190+ const needsRefresh = [ "assigned" , "mentions" , "stale" ] . some ( ( type ) => {
191+ const lastRefresh = lastRefreshes [ type as keyof typeof lastRefreshes ] ;
192+ return ! lastRefresh || Date . now ( ) - lastRefresh > STALE_THRESHOLD ;
193+ } ) ;
194+
195+ if ( needsRefresh ) {
196+ [ "assigned" , "mentions" , "stale" ] . forEach ( ( type ) => {
197+ refreshData ( type as "assigned" | "mentions" | "stale" ) . catch ( ( error ) => {
198+ console . error ( `Failed to refresh ${ type } items:` , error ) ;
199+ } ) ;
200+ } ) ;
201+ }
202+ } else {
203+ const lastRefresh = useActionItemsStore . getState ( ) . lastRefresh [ currentTab ] ;
151204
152- if ( ! lastRefresh || Date . now ( ) - lastRefresh > STALE_THRESHOLD ) {
153- refreshActiveTab ( currentTab ) ;
205+ if ( ! lastRefresh || Date . now ( ) - lastRefresh > STALE_THRESHOLD ) {
206+ refreshActiveTab ( currentTab ) ;
207+ }
154208 }
155209 }
156210 } ;
157211
158212 document . addEventListener ( 'visibilitychange' , handleVisibilityChange ) ;
159213 return ( ) => document . removeEventListener ( 'visibilitychange' , handleVisibilityChange ) ;
160- } , [ currentTab , refreshActiveTab , STALE_THRESHOLD ] ) ;
214+ } , [ currentTab , refreshActiveTab , refreshData , STALE_THRESHOLD ] ) ;
161215
162216 const actionItemsByType = useMemo (
163217 ( ) => ( {
218+ all : [ ...assignedItems , ...mentionItems , ...staleItems ] ,
164219 assigned : assignedItems ,
165220 mentions : mentionItems ,
166221 stale : staleItems ,
@@ -170,6 +225,7 @@ function ActionRequiredContent() {
170225
171226 const itemCounts = useMemo (
172227 ( ) => ( {
228+ all : actionItemsByType . all . length ,
173229 assigned : actionItemsByType . assigned . length ,
174230 mentions : actionItemsByType . mentions . length ,
175231 stale : actionItemsByType . stale . length ,
@@ -178,7 +234,7 @@ function ActionRequiredContent() {
178234 ) ;
179235
180236 const getActionItems = useCallback (
181- ( type : "assigned" | "mentions" | "stale" ) => {
237+ ( type : "all" | " assigned" | "mentions" | "stale" ) => {
182238 return actionItemsByType [ type ] ;
183239 } ,
184240 [ actionItemsByType ]
@@ -210,16 +266,20 @@ function ActionRequiredContent() {
210266 emptyMessage,
211267 emptyDescription,
212268 } : {
213- type : "assigned" | "mentions" | "stale" ;
269+ type : "all" | " assigned" | "mentions" | "stale" ;
214270 icon : LucideIcon ;
215271 title : string ;
216272 emptyMessage : string ;
217273 emptyDescription : string ;
218274 color ?: "blue" | "green" | "yellow" | "orange" ;
219275 } ) => {
220276 const items = getActionItems ( type ) ;
221- const isLoading = loading [ type ] ;
222- const error = errors [ type ] ;
277+ const isLoading = type === "all"
278+ ? loading . assigned || loading . mentions || loading . stale
279+ : loading [ type ] ;
280+ const error = type === "all"
281+ ? errors . assigned || errors . mentions || errors . stale
282+ : errors [ type ] ;
223283
224284 const [ selectedRepo , setSelectedRepo ] = useState < string > ( "all" ) ;
225285 const [ selectedLanguage , setSelectedLanguage ] = useState < string > ( "all" ) ;
@@ -340,7 +400,19 @@ function ActionRequiredContent() {
340400 variant = "outline"
341401 size = "sm"
342402 className = "mt-3"
343- onClick = { ( ) => refreshData ( type ) }
403+ onClick = { ( ) => {
404+ if ( type === "all" ) {
405+ [ "assigned" , "mentions" , "stale" ] . forEach ( ( t ) => {
406+ refreshData ( t as "assigned" | "mentions" | "stale" ) . catch ( ( error ) => {
407+ console . error ( `Failed to refresh ${ t } items:` , error ) ;
408+ } ) ;
409+ } ) ;
410+ } else {
411+ refreshData ( type ) . catch ( ( error ) => {
412+ console . error ( `Failed to refresh ${ type } items:` , error ) ;
413+ } ) ;
414+ }
415+ } }
344416 disabled = { isLoading }
345417 >
346418 < RefreshCw
@@ -366,7 +438,6 @@ function ActionRequiredContent() {
366438 < div className = "space-y-4" >
367439 < div className = "flex items-center gap-4 flex-wrap" >
368440 < div className = "flex items-center gap-2" >
369- < label className = "text-sm font-medium text-gray-700 dark:text-gray-300" > Repository:</ label >
370441 < Select value = { selectedRepo } onValueChange = { setSelectedRepo } >
371442 < SelectTrigger className = "w-[200px]" >
372443 < SelectValue placeholder = "All Repositories" />
@@ -383,7 +454,6 @@ function ActionRequiredContent() {
383454 </ div >
384455 { languages . length > 0 && (
385456 < div className = "flex items-center gap-2" >
386- < label className = "text-sm font-medium text-gray-700 dark:text-gray-300" > Language:</ label >
387457 < Select value = { selectedLanguage } onValueChange = { setSelectedLanguage } >
388458 < SelectTrigger className = "w-[150px]" >
389459 < SelectValue placeholder = "All Languages" />
@@ -455,19 +525,20 @@ function ActionRequiredContent() {
455525 #{ extractIssueNumber ( item . url ) }
456526 </ span >
457527 ) }
458- < a
459- href = { isValidUrl ( item . url ) ? item . url : "#" }
460- target = "_blank"
461- rel = "noopener noreferrer"
462- className = "font-medium hover:text-blue-600 dark:hover:text-blue-400 truncate"
463- onClick = { ( e ) =>
464- ! isValidUrl ( item . url ?? "" ) && e . preventDefault ( )
465- }
466- >
528+ < span className = "font-medium truncate cursor-pointer hover:text-blue-600 dark:hover:text-blue-400" >
467529 { item . title }
468- </ a >
469- { item . url && (
470- < ExternalLink className = "w-3 h-3 text-gray-400 flex-shrink-0" />
530+ </ span >
531+ { item . url && isValidUrl ( item . url ) && (
532+ < a
533+ href = { item . url }
534+ target = "_blank"
535+ rel = "noopener noreferrer"
536+ onClick = { ( e ) => e . stopPropagation ( ) }
537+ className = "flex-shrink-0 text-gray-400 hover:text-blue-600 dark:hover:text-blue-400"
538+ title = "Open in GitHub"
539+ >
540+ < ExternalLink className = "w-4 h-4" />
541+ </ a >
471542 ) }
472543 { item . type === "pullRequest" && item . mergeable === "CONFLICTING" && (
473544 < span title = "Has merge conflicts" >
@@ -578,7 +649,15 @@ function ActionRequiredContent() {
578649 < TableCell >
579650 < QuickActionsMenu
580651 item = { item as StoreActionItem }
581- itemType = { type }
652+ itemType = {
653+ type === "all"
654+ ? ( "mentionType" in item || "mentionedAt" in item )
655+ ? "mentions"
656+ : ( "daysStale" in item || "lastActivity" in item )
657+ ? "stale"
658+ : "assigned"
659+ : type
660+ }
582661 />
583662 </ TableCell >
584663 </ TableRow >
@@ -603,22 +682,47 @@ function ActionRequiredContent() {
603682 Action Required
604683 </ h1 >
605684 </ div >
606- < Button
607- variant = "outline"
608- size = "sm"
609- onClick = { ( ) => refreshActiveTab ( currentTab ) }
610- disabled = { loading [ currentTab ] }
611- className = "flex items-center gap-2"
612- >
613- < RefreshCw
614- className = { `w-4 h-4 ${
615- loading [ currentTab ]
616- ? "animate-spin"
617- : ""
618- } `}
619- />
620- Refresh
621- </ Button >
685+ < div className = "flex items-center gap-2" >
686+ < Button
687+ variant = "default"
688+ size = "sm"
689+ onClick = { ( ) => setIsNewIssueOpen ( true ) }
690+ className = "flex items-center gap-2"
691+ >
692+ < Plus className = "w-4 h-4" />
693+ New Issue
694+ </ Button >
695+ < Button
696+ variant = "outline"
697+ size = "sm"
698+ onClick = { ( ) => {
699+ if ( currentTab === "all" ) {
700+ [ "assigned" , "mentions" , "stale" ] . forEach ( ( type ) => {
701+ refreshData ( type as "assigned" | "mentions" | "stale" ) . catch ( ( error ) => {
702+ console . error ( `Failed to refresh ${ type } items:` , error ) ;
703+ } ) ;
704+ } ) ;
705+ } else {
706+ refreshActiveTab ( currentTab ) ;
707+ }
708+ } }
709+ disabled = { currentTab === "all"
710+ ? loading . assigned || loading . mentions || loading . stale
711+ : loading [ currentTab ] }
712+ className = "flex items-center gap-2"
713+ >
714+ < RefreshCw
715+ className = { `w-4 h-4 ${
716+ ( currentTab === "all"
717+ ? loading . assigned || loading . mentions || loading . stale
718+ : loading [ currentTab ] )
719+ ? "animate-spin"
720+ : ""
721+ } `}
722+ />
723+ Refresh
724+ </ Button >
725+ </ div >
622726 </ div >
623727 < p className = "text-gray-600 dark:text-gray-300" >
624728 Items that need your immediate attention across your repositories
@@ -635,7 +739,14 @@ function ActionRequiredContent() {
635739 } }
636740 className = "w-full"
637741 >
638- < TabsList className = "grid w-full grid-cols-3" >
742+ < TabsList className = "grid w-full grid-cols-4" >
743+ < TabsTrigger value = "all" className = "flex items-center gap-2" >
744+ < Zap className = "w-4 h-4" />
745+ All
746+ < Badge variant = "secondary" className = "ml-1" >
747+ { itemCounts . all }
748+ </ Badge >
749+ </ TabsTrigger >
639750 < TabsTrigger value = "assigned" className = "flex items-center gap-2" >
640751 < Target className = "w-4 h-4" />
641752 Assigned
@@ -659,6 +770,29 @@ function ActionRequiredContent() {
659770 </ TabsTrigger >
660771 </ TabsList >
661772
773+ < TabsContent value = "all" className = "mt-6" >
774+ < Card >
775+ < CardHeader >
776+ < CardTitle className = "flex items-center gap-2" >
777+ < Zap className = "w-5 h-5 text-orange-500" />
778+ All Action Items
779+ < Badge variant = "outline" className = "ml-auto" >
780+ { itemCounts . all } items
781+ </ Badge >
782+ </ CardTitle >
783+ </ CardHeader >
784+ < CardContent >
785+ < ActionItemsList
786+ type = "all"
787+ icon = { Zap }
788+ title = "All Items"
789+ emptyMessage = "No action items found"
790+ emptyDescription = "All your action items will appear here"
791+ />
792+ </ CardContent >
793+ </ Card >
794+ </ TabsContent >
795+
662796 < TabsContent value = "assigned" className = "mt-6" >
663797 < Card >
664798 < CardHeader >
@@ -733,6 +867,7 @@ function ActionRequiredContent() {
733867 </ Tabs >
734868
735869 < DetailPanel issue = { selectedIssue } isOpen = { isOpen } onClose = { closePanel } />
870+ < NewIssueDialog open = { isNewIssueOpen } onOpenChange = { setIsNewIssueOpen } />
736871 </ div >
737872 ) ;
738873}
0 commit comments