diff --git a/src/app/[interval]/[[...date]]/components/IssuesListModalContent.tsx b/src/app/[interval]/[[...date]]/components/IssuesListModalContent.tsx
index 0b448a351..937a851a9 100644
--- a/src/app/[interval]/[[...date]]/components/IssuesListModalContent.tsx
+++ b/src/app/[interval]/[[...date]]/components/IssuesListModalContent.tsx
@@ -22,6 +22,7 @@ export default function IssuesListModalContent({
author={issue.author}
number={issue.number}
href={`https://github.com/${issue.repository}/issues/${issue.number}`}
+ allowTitleWrap={true}
icon={
issue.state === "closed" || issue.closedAt ? (
diff --git a/src/app/[interval]/[[...date]]/components/LeftSidebar.tsx b/src/app/[interval]/[[...date]]/components/LeftSidebar.tsx
new file mode 100644
index 000000000..745dd7813
--- /dev/null
+++ b/src/app/[interval]/[[...date]]/components/LeftSidebar.tsx
@@ -0,0 +1,280 @@
+"use client";
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import { Badge } from "@/components/ui/badge";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import type { IntervalMetrics } from "@/app/[interval]/[[...date]]/queries";
+import {
+ Users,
+ Star,
+ TrendingUp,
+ CircleDot,
+ GitMerge,
+ CheckCircle,
+} from "lucide-react";
+import { CounterWithIcon } from "@/components/counter-with-icon";
+import Link from "next/link";
+import PullRequestsListModalContent from "./PullRequestsListModalContent";
+import IssuesListModalContent from "./IssuesListModalContent";
+import ContributorsListModalContent from "./ContributorsListModalContent";
+import { formatTimeframeTitle } from "@/lib/date-utils";
+
+interface LeftSidebarProps {
+ metrics: IntervalMetrics;
+}
+
+interface Contributor {
+ username: string;
+ totalScore: number;
+ summary?: string | null;
+}
+
+export function LeftSidebar({ metrics }: LeftSidebarProps) {
+ const timeframeTitle = formatTimeframeTitle(
+ metrics.interval.intervalStart,
+ metrics.interval.intervalType,
+ );
+
+ return (
+ <>
+ {/* Top Contributors Card */}
+
+
+ {/* Pull Requests Card */}
+
+
+ {/* Issues Card */}
+
+ >
+ );
+}
diff --git a/src/app/[interval]/[[...date]]/components/MainContent.tsx b/src/app/[interval]/[[...date]]/components/MainContent.tsx
new file mode 100644
index 000000000..6d7cddf84
--- /dev/null
+++ b/src/app/[interval]/[[...date]]/components/MainContent.tsx
@@ -0,0 +1,144 @@
+import { StatCard } from "@/components/stat-card";
+import { CounterWithIcon } from "@/components/counter-with-icon";
+import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
+import type { IntervalMetrics } from "@/app/[interval]/[[...date]]/queries";
+import {
+ Users,
+ GitPullRequest,
+ MessageCircleWarning,
+ CircleDot,
+ GitMerge,
+ CheckCircle,
+} from "lucide-react";
+import { formatTimeframeTitle } from "@/lib/date-utils";
+import { SummaryContent } from "./SummaryContent";
+import ContributorsListModalContent from "./ContributorsListModalContent";
+import PullRequestsListModalContent from "./PullRequestsListModalContent";
+import IssuesListModalContent from "./IssuesListModalContent";
+
+interface MainContentProps {
+ metrics: IntervalMetrics;
+ summaryContent: string | null;
+}
+
+interface Contributor {
+ username: string;
+ totalScore: number;
+}
+
+export function MainContent({ metrics, summaryContent }: MainContentProps) {
+ const timeframeTitle = formatTimeframeTitle(
+ metrics.interval.intervalStart,
+ metrics.interval.intervalType,
+ );
+
+ return (
+
+ {/* Main Statistics Cards */}
+
+
+ }
+ >
+
+
+ {metrics.activeContributors}
+
+
+ {metrics.topContributors
+ .slice(0, 3)
+ .map((contributor: Contributor) => (
+
+
+
+ {contributor.username[0].toUpperCase()}
+
+
+ ))}
+ {metrics.topContributors.length > 3 && (
+
+ +{metrics.topContributors.length - 3}
+
+ )}
+
+
+
+
+
+ }
+ >
+
+
+ {metrics.pullRequests.total}
+
+
+
+
+
+
+
+
+
}
+ >
+
+
{metrics.issues.total}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/[interval]/[[...date]]/components/PullRequestsListModalContent.tsx b/src/app/[interval]/[[...date]]/components/PullRequestsListModalContent.tsx
index 9f3902b8a..76dd51e42 100644
--- a/src/app/[interval]/[[...date]]/components/PullRequestsListModalContent.tsx
+++ b/src/app/[interval]/[[...date]]/components/PullRequestsListModalContent.tsx
@@ -22,6 +22,7 @@ export default function PullRequestsListModalContent({
className="px-4"
number={pr.number}
href={`https://github.com/${pr.repository}/pull/${pr.number}`}
+ allowTitleWrap={true}
icon={
pr.mergedAt ? (
diff --git a/src/app/[interval]/[[...date]]/components/RightSidebar.tsx b/src/app/[interval]/[[...date]]/components/RightSidebar.tsx
new file mode 100644
index 000000000..102562f3d
--- /dev/null
+++ b/src/app/[interval]/[[...date]]/components/RightSidebar.tsx
@@ -0,0 +1,155 @@
+"use client";
+
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Badge } from "@/components/ui/badge";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { Progress } from "@/components/ui/progress";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import type { IntervalMetrics } from "@/app/[interval]/[[...date]]/queries";
+import { GitCommitVertical, FileCode, Target } from "lucide-react";
+
+interface RightSidebarProps {
+ metrics: IntervalMetrics;
+}
+
+export function RightSidebar({ metrics }: RightSidebarProps) {
+ const { codeChanges, focusAreas } = metrics;
+
+ // Calculate percentage for the commit activity bar
+ const totalChanges = codeChanges.additions + codeChanges.deletions;
+ const additionPercentage =
+ totalChanges > 0 ? (codeChanges.additions / totalChanges) * 100 : 50;
+
+ return (
+ <>
+
+
+
+
+ Code Activity
+
+
+
+ {/* Commit Volume Visualization */}
+
+
+ Volume
+
+ {codeChanges.commitCount} commits
+
+
+
+
+ +{codeChanges.additions.toLocaleString()}
+ -{codeChanges.deletions.toLocaleString()}
+
+
+
+
+
+
+
+ {/* Focus Areas */}
+ {focusAreas && focusAreas.length > 0 && (
+
+
+
+
+ Focus Areas
+
+
+
+
+ {focusAreas.slice(0, 10).map((focusArea, index) => (
+
+ {focusArea.area}
+
+ ))}
+ {focusAreas.length > 10 && (
+
+ +{focusAreas.length - 10} more
+
+ )}
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/app/[interval]/[[...date]]/components/SummaryContent.skeleton.tsx b/src/app/[interval]/[[...date]]/components/SummaryContent.skeleton.tsx
index 85d796959..9b421697f 100644
--- a/src/app/[interval]/[[...date]]/components/SummaryContent.skeleton.tsx
+++ b/src/app/[interval]/[[...date]]/components/SummaryContent.skeleton.tsx
@@ -5,7 +5,7 @@ export function SummaryContentSkeleton({ className }: { className?: string }) {
return (
diff --git a/src/app/[interval]/[[...date]]/components/SummaryContent.tsx b/src/app/[interval]/[[...date]]/components/SummaryContent.tsx
index aa87d569c..3673ecc25 100644
--- a/src/app/[interval]/[[...date]]/components/SummaryContent.tsx
+++ b/src/app/[interval]/[[...date]]/components/SummaryContent.tsx
@@ -29,7 +29,7 @@ const remarkRemoveFirstH1 = () => {
// Custom H2 component to apply primary color
const CustomH2 = (props: HTMLProps
) => {
- return ;
+ return ;
};
interface SummaryContentProps {
@@ -48,7 +48,7 @@ export function SummaryContent({
return (
diff --git a/src/app/[interval]/[[...date]]/page.tsx b/src/app/[interval]/[[...date]]/page.tsx
index ae51a81a4..34ab14f67 100644
--- a/src/app/[interval]/[[...date]]/page.tsx
+++ b/src/app/[interval]/[[...date]]/page.tsx
@@ -16,11 +16,11 @@ import {
import { UTCDate } from "@date-fns/utc";
import { addDays } from "date-fns";
import { DateNavigation } from "./components/DateNavigation";
-import { SummaryContent } from "./components/SummaryContent";
-import { StatCardsDisplay } from "./components/StatCardsDisplay";
-import { CodeChangesDisplay } from "./components/CodeChangesDisplay";
import { LlmCopyButton } from "@/components/ui/llm-copy-button";
import { IntervalSelector } from "./components/IntervalSelector";
+import { LeftSidebar } from "./components/LeftSidebar";
+import { MainContent } from "./components/MainContent";
+import { RightSidebar } from "./components/RightSidebar";
interface PageProps {
params: Promise<{
@@ -124,27 +124,50 @@ export default async function IntervalSummaryPage({ params }: PageProps) {
);
return (
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ {/* LEFT SIDEBAR */}
+
+
+
+
+ {/* MAIN CONTENT */}
+
+
+
+
+ {/* RIGHT SIDEBAR - Hidden on md, shown on lg+ */}
+
+
+
+
+
+ {/* RIGHT SIDEBAR CONTENT FOR TABLET (md) - Shown below main content */}
+
-
-
-
-
-
-
-
);
diff --git a/src/app/[interval]/[[...date]]/queries.ts b/src/app/[interval]/[[...date]]/queries.ts
index 58e1d2bc1..ac7a47f51 100644
--- a/src/app/[interval]/[[...date]]/queries.ts
+++ b/src/app/[interval]/[[...date]]/queries.ts
@@ -180,6 +180,7 @@ export async function getMetricsForInterval(
codeChanges: repoMetrics.codeChanges,
topContributors: repoMetrics.topContributors,
focusAreas: repoMetrics.focusAreas,
+ topFilesChanged: repoMetrics.topFilesChanged,
topIssues,
topPullRequests,
detailedContributorSummaries, // Add the new field here
diff --git a/src/components/activity-item.tsx b/src/components/activity-item.tsx
index 846b07881..1d9d1ca07 100644
--- a/src/components/activity-item.tsx
+++ b/src/components/activity-item.tsx
@@ -12,6 +12,7 @@ interface ActivityItemProps {
icon: React.ReactNode;
metadata?: React.ReactNode;
className?: string;
+ allowTitleWrap?: boolean;
}
export function ActivityItem({
@@ -23,6 +24,7 @@ export function ActivityItem({
icon,
metadata,
className,
+ allowTitleWrap = false,
}: ActivityItemProps) {
return (
-
{title}
+
+ {title}
+
diff --git a/src/lib/pipelines/export/queries.ts b/src/lib/pipelines/export/queries.ts
index 478d1931e..4701300a7 100644
--- a/src/lib/pipelines/export/queries.ts
+++ b/src/lib/pipelines/export/queries.ts
@@ -283,6 +283,17 @@ export async function getProjectMetrics(params: QueryParams = {}) {
.sort((a, b) => b.count - a.count)
.slice(0, 10);
+ // Get all files changed (sorted by total changes: additions + deletions)
+ const topFilesChanged = prFiles
+ .map((file) => ({
+ path: file.path,
+ additions: file.additions || 0,
+ deletions: file.deletions || 0,
+ totalChanges: (file.additions || 0) + (file.deletions || 0),
+ }))
+ .filter((file) => file.totalChanges > 0)
+ .sort((a, b) => b.totalChanges - a.totalChanges);
+
// Get completed items (PRs merged in this period)
const completedItems = mergedPRsThisPeriod.map((pr) => ({
title: pr.title,
@@ -299,6 +310,7 @@ export async function getProjectMetrics(params: QueryParams = {}) {
topContributors,
codeChanges,
focusAreas,
+ topFilesChanged,
completedItems,
};
}