Skip to content

Commit 582265b

Browse files
authored
Merge pull request #136 from ArjinAlbay/claude/sidebar-content-expansion-01ThtYD31KKdkpGq4ie9DJMi
Claude/sidebar content expansion 01 tht yd31 k kdkp gq4ie9 dj mi
2 parents 355d4a1 + d01088a commit 582265b

File tree

17 files changed

+2009
-449
lines changed

17 files changed

+2009
-449
lines changed

src/app/action-required/page.tsx

Lines changed: 185 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import { PageHeader } from "@/components/layout/PageHeader";
4646
import { SearchModal } from "@/components/search/SearchModal";
4747
import { QuickActionsMenu } from "@/components/action-required/QuickActionsMenu";
4848
import { DetailPanel } from "@/components/ui/detail-panel";
49+
import { NewIssueDialog } from "@/components/action-required/NewIssueDialog";
4950

5051
interface 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;
7677
type ValidTab = (typeof VALID_TABS)[number];
7778

7879
function 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
}

src/app/dashboard/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Layout } from "@/components/layout/Layout";
44
import { useRequireAuth } from "@/hooks/useAuth";
55
import { PageHeader } from "@/components/layout/PageHeader";
66
import { TodoDashboard } from "@/components/widget/TodoDashboard";
7-
import { QuickWinsCounters } from "@/components/widget/QuickWinsCounters";
7+
import { QuickWinsNotifier } from "@/components/widget/QuickWinsNotifier";
88

99
export default function DashboardPage() {
1010
const { isLoading } = useRequireAuth();
@@ -24,11 +24,11 @@ export default function DashboardPage() {
2424

2525
return (
2626
<Layout>
27+
<QuickWinsNotifier />
28+
2729
<div className="max-w-7xl mx-auto p-6 space-y-6">
2830
<PageHeader />
2931

30-
<QuickWinsCounters />
31-
3232
<TodoDashboard />
3333
</div>
3434
</Layout>

0 commit comments

Comments
 (0)