generated from namidapoo/next15-shadcn-use-bun
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #12 from namidapoo/feat/recap
feat: 統計情報を表示
- Loading branch information
Showing
30 changed files
with
2,319 additions
and
259 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,38 @@ | ||
import type { Metadata } from "next"; | ||
import { Geist, Geist_Mono } from "next/font/google"; | ||
import "./globals.css"; | ||
import { cn } from "@/lib/utils"; | ||
|
||
const geistSans = Geist({ | ||
variable: "--font-geist-sans", | ||
subsets: ["latin"], | ||
}); | ||
|
||
const geistMono = Geist_Mono({ | ||
variable: "--font-geist-mono", | ||
subsets: ["latin"], | ||
}); | ||
import type { FC } from "react"; | ||
import { Header } from "./recap/components/header/header"; | ||
import { ThemeProvider } from "./recap/components/theme-toggle/theme-provider"; | ||
|
||
export const metadata: Metadata = { | ||
title: "Create Next App", | ||
description: "Generated by create next app", | ||
title: "Dev Recap 2024", | ||
description: "A recap of the latest in web development", | ||
}; | ||
|
||
export default function RootLayout({ | ||
children, | ||
}: Readonly<{ | ||
type Props = { | ||
children: React.ReactNode; | ||
}>) { | ||
}; | ||
|
||
const RootLayout: FC<Readonly<Props>> = ({ children }) => { | ||
return ( | ||
<html lang="en"> | ||
<body | ||
className={cn( | ||
geistSans.variable, | ||
geistMono.variable, | ||
"antialiased text-gray-900", | ||
)} | ||
> | ||
{children} | ||
<html lang="ja" suppressHydrationWarning> | ||
<body className="antialiased text-gray-900"> | ||
<ThemeProvider | ||
attribute="class" | ||
defaultTheme="system" | ||
enableSystem | ||
disableTransitionOnChange | ||
> | ||
<div className="flex flex-col h-dvh"> | ||
<Header /> | ||
<div className="flex-grow p-4 md:px-6 overflow-auto"> | ||
{children} | ||
</div> | ||
</div> | ||
</ThemeProvider> | ||
</body> | ||
</html> | ||
); | ||
} | ||
}; | ||
|
||
export default RootLayout; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
"use client"; | ||
import type { Stats } from "@/app/recap/fetchGitHubStats"; | ||
import { | ||
Card, | ||
CardContent, | ||
CardDescription, | ||
CardHeader, | ||
CardTitle, | ||
} from "@/components/ui/card"; | ||
import { | ||
type ChartConfig, | ||
ChartContainer, | ||
ChartTooltip, | ||
ChartTooltipContent, | ||
} from "@/components/ui/chart"; | ||
import { useMemo } from "react"; | ||
import type { FC } from "react"; | ||
import { Label, Pie, PieChart } from "recharts"; | ||
|
||
type Props = { | ||
data: Stats["languagesByCommitCount"]; | ||
}; | ||
|
||
export const LanguagesUsageGraph: FC<Props> = ({ data }) => { | ||
const limitedData = useMemo(() => { | ||
if (data.length <= 6) { | ||
return data; | ||
} | ||
const mainItems = data.slice(0, 5); | ||
const others = data.slice(5); | ||
|
||
const othersCommitSum = others.reduce( | ||
(acc, item) => acc + item.commitCount, | ||
0, | ||
); | ||
|
||
return [ | ||
...mainItems, | ||
{ | ||
language: "Others", | ||
commitCount: othersCommitSum, | ||
}, | ||
]; | ||
}, [data]); | ||
|
||
const colorPalette = useMemo( | ||
() => [ | ||
"hsl(var(--chart-1))", | ||
"hsl(var(--chart-2))", | ||
"hsl(var(--chart-3))", | ||
"hsl(var(--chart-4))", | ||
"hsl(var(--chart-5))", | ||
"hsl(var(--chart-6))", | ||
], | ||
[], | ||
); | ||
|
||
const totalCommits = useMemo(() => { | ||
return limitedData.reduce((acc, curr) => acc + curr.commitCount, 0); | ||
}, [limitedData]); | ||
|
||
const chartData = useMemo(() => { | ||
if (totalCommits === 0) { | ||
return limitedData.map((item, index) => ({ | ||
language: item.language, | ||
share: 0, | ||
fill: colorPalette[index % colorPalette.length], | ||
})); | ||
} | ||
return limitedData.map((item, index) => ({ | ||
language: item.language, | ||
share: (item.commitCount / totalCommits) * 100, | ||
fill: colorPalette[index % colorPalette.length], | ||
})); | ||
}, [limitedData, totalCommits, colorPalette]); | ||
|
||
const chartConfig = useMemo<ChartConfig>(() => { | ||
const dynamicLangConfig = limitedData.reduce((acc, item, index) => { | ||
acc[item.language] = { | ||
label: item.language, | ||
color: colorPalette[index % colorPalette.length], | ||
}; | ||
return acc; | ||
}, {} as ChartConfig); | ||
|
||
return { | ||
visitors: { | ||
label: "Share", | ||
}, | ||
...dynamicLangConfig, | ||
}; | ||
}, [limitedData, colorPalette]); | ||
|
||
return ( | ||
<Card className="flex flex-col py-[0.42rem]"> | ||
<CardHeader className="items-start pb-0"> | ||
<CardTitle>言語の使用率</CardTitle> | ||
<CardDescription> | ||
あなたが最も使用した言語は <b>{limitedData[0].language}</b> です。 | ||
</CardDescription> | ||
</CardHeader> | ||
<CardContent className="flex-1 pb-0"> | ||
<ChartContainer | ||
config={chartConfig} | ||
className="mx-auto aspect-square max-h-[360px]" | ||
> | ||
<PieChart> | ||
<ChartTooltip | ||
cursor={false} | ||
content={<ChartTooltipContent hideLabel />} | ||
/> | ||
<Pie | ||
data={chartData} | ||
dataKey="share" | ||
nameKey="language" | ||
innerRadius={60} | ||
strokeWidth={5} | ||
> | ||
<Label | ||
content={({ viewBox }) => { | ||
if (viewBox && "cx" in viewBox && "cy" in viewBox) { | ||
return ( | ||
<text | ||
x={viewBox.cx} | ||
y={viewBox.cy} | ||
textAnchor="middle" | ||
dominantBaseline="middle" | ||
> | ||
<tspan | ||
x={viewBox.cx} | ||
y={viewBox.cy} | ||
className="fill-foreground text-3xl font-bold" | ||
> | ||
100 | ||
</tspan> | ||
<tspan | ||
x={viewBox.cx} | ||
y={(viewBox.cy || 0) + 24} | ||
className="fill-muted-foreground text-sm" | ||
> | ||
% | ||
</tspan> | ||
</text> | ||
); | ||
} | ||
return null; | ||
}} | ||
/> | ||
</Pie> | ||
</PieChart> | ||
</ChartContainer> | ||
</CardContent> | ||
</Card> | ||
); | ||
}; |
Oops, something went wrong.