Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 27 additions & 15 deletions src/components/PRBreakdownChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ interface PRBreakdown {
merged: number;
closed: number;
}
interface PRBreakdownChartProps {
filter?: "all" | "merged" | "open" | "closed";
}

const SLICES: { key: keyof PRBreakdown; label: string; color: string }[] = [
{ key: "open", label: "Open", color: "var(--accent)" },
Expand All @@ -18,7 +21,7 @@ const SLICES: { key: keyof PRBreakdown; label: string; color: string }[] = [
{ key: "draft", label: "Draft", color: "var(--muted-foreground)" },
];

export default function PRBreakdownChart() {
export default function PRBreakdownChart({ filter = "all" }: PRBreakdownChartProps) {
const { selectedAccount } = useAccount();
const [breakdown, setBreakdown] = useState<PRBreakdown | null>(null);
const [loading, setLoading] = useState(true);
Expand Down Expand Up @@ -89,11 +92,16 @@ export default function PRBreakdownChart() {
}

const total = breakdown ? SLICES.reduce((sum, s) => sum + (breakdown[s.key] ?? 0), 0) : 0;
const chartData = breakdown
? SLICES.map((s) => ({ name: s.label, value: breakdown[s.key] ?? 0, color: s.color })).filter(
(d) => d.value > 0
)
: [];
const chartData = breakdown
? SLICES.map((s) => ({
name: s.label,
value:
filter === "all" || s.key === filter
? (breakdown[s.key] ?? 0)
: 0,
color: s.color,
})).filter((d) => d.value > 0)
: [];

return (
<div className="rounded-xl border border-[var(--border)] bg-[var(--card)] p-6 shadow-sm transition-all duration-300 hover:shadow-md hover:-translate-y-1">
Expand Down Expand Up @@ -136,15 +144,19 @@ export default function PRBreakdownChart() {
</PieChart>
</ResponsiveContainer>
<div className="mt-3 flex flex-wrap justify-center gap-4">
{SLICES.map((s) => (
<div
key={s.key}
className="flex items-center gap-1.5 text-xs text-[var(--muted-foreground)]"
>
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: s.color }} />
{s.label}: {breakdown?.[s.key] ?? 0}
</div>
))}
{SLICES.filter((s) => filter === "all" || s.key === filter).map((s) => (
<div
key={s.key}
className={`flex items-center gap-1.5 text-xs transition-opacity ${
filter === "all" || s.key === filter
? "text-[var(--muted-foreground)]"
: "opacity-30 text-[var(--muted-foreground)]"
}`}
>
<span className="h-2.5 w-2.5 rounded-full" style={{ backgroundColor: s.color }} />
{s.label}: {breakdown?.[s.key] ?? 0}
</div>
))}
</div>
</>
)}
Expand Down
56 changes: 36 additions & 20 deletions src/components/PRMetrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useDashboardWidgetA11y } from "@/components/dashboard/DashboardWidgetA1
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts";
import PRStatusDonutChart from "./PRStatusDonutChart";
import MiniPRTrendChart from "./MiniPRTrendChart";
import PRBreakdownChart from "./PRBreakdownChart";

interface PRMetricsSummary {
open: number;
Expand Down Expand Up @@ -54,7 +55,7 @@ export default function PRMetrics() {
const [minutesAgo, setMinutesAgo] = useState(0);
const [error, setError] = useState<string | null>(null);
const [activeTab, setActiveTab] = useState<"authored" | "reviews">("authored");
const [prFilter, setPrFilter] = useState<"all" | "merged" | "open">("all");
const [prFilter, setPrFilter] = useState<"all" | "merged" | "open" | "closed">("all");
const [range, setRange] = useState<"7d" | "30d" | "90d">("30d");
const [staleThresholdDays, setStaleThresholdDays] = useState(14);

Expand Down Expand Up @@ -274,16 +275,28 @@ export default function PRMetrics() {
<div className="flex flex-wrap items-center justify-between mb-4">
<p className="text-sm font-medium text-[var(--muted-foreground)]">GitHub PRs</p>
<div className="flex items-center gap-2">
{(["all", "merged", "open"] as const).map((filter) => (
<button
key={filter}
onClick={() => setPrFilter(filter)}
className={`rounded-full px-4 py-1.5 text-xs font-semibold capitalize transition-colors ${prFilter === filter ? "bg-[var(--accent)] text-white" : "bg-[var(--control)] text-[var(--muted-foreground)]"
}`}
>
{filter}
</button>
))}
{(["all", "merged", "open", "closed"] as const).map((filter) => {
const colors: Record<string, string> = {
all: "bg-[var(--accent)] text-white",
merged: "bg-green-600 text-white",
open: "bg-blue-600 text-white",
closed: "bg-orange-500 text-white",
};
return (
<button
key={filter}
onClick={() => setPrFilter(filter)}
aria-pressed={prFilter === filter}
className={`rounded-full px-4 py-1.5 text-xs font-semibold capitalize transition-colors ${
prFilter === filter
? colors[filter]
: "bg-[var(--control)] text-[var(--muted-foreground)] hover:bg-[var(--card-muted)]"
}`}
>
{filter}
</button>
);
})}
</div>
</div>
<div className="mt-3 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
Expand All @@ -302,15 +315,18 @@ export default function PRMetrics() {

{/* PR Status Donut Chart */}
{metrics && (
<div>
<p className="mb-2 text-sm font-medium text-[var(--muted-foreground)]">PR Status Distribution</p>
<PRStatusDonutChart
open={prFilter === "merged" ? 0 : (metrics.open || 0)}
merged={prFilter === "open" ? 0 : (metrics.merged || 0)}
closed={prFilter === "all" ? (metrics.closed || 0) : 0}
/>
</div>
)}
<div>
<p className="mb-2 text-sm font-medium text-[var(--muted-foreground)]">PR Status Distribution</p>
<PRStatusDonutChart
open={prFilter === "merged" ? 0 : (metrics.open || 0)}
merged={prFilter === "open" ? 0 : (metrics.merged || 0)}
closed={prFilter === "all" ? (metrics.closed || 0) : 0}
/>
<div className="mt-4">
<PRBreakdownChart filter={prFilter} />
</div>
</div>
)}

{/* Cycle Time Features */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
Expand Down
Loading