Skip to content

feat: init stats page refresh (WIP/ON HOLD) #547

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 60 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
87e33ed
feat: init stats page refresh!
Logannford Mar 24, 2025
b3727f0
feat: work on stats page hero component
Logannford Mar 24, 2025
7f54d04
feat: stats hero chart tooltip work.
Logannford Mar 24, 2025
72a8e78
feat: stats page layout issues, minor changes to hero chart
Logannford Mar 24, 2025
77962e6
trying to fix chart svg height
Logannford Mar 24, 2025
0fa1973
feat: some minor progress
Logannford Mar 25, 2025
16c7dbc
feat: adds difficulty radial chart story
Logannford Mar 25, 2025
90667a4
feat: work getting RSC's working with storybook
Logannford Mar 25, 2025
d188bc4
feat: correctly rendering rsc's in storybook
Logannford Mar 25, 2025
15f3171
feat: function abstraction, stories created.
Logannford Mar 25, 2025
b22308d
feat: messing around with different styling for question history block
Logannford Mar 25, 2025
9413370
minor work
Logannford Mar 25, 2025
917a3aa
feat: styling work with question history component
Logannford Mar 26, 2025
8b433da
progress with question history block
Logannford Mar 26, 2025
e1143c5
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 26, 2025
31f17be
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 26, 2025
7ca3910
feat: adds new DifficultyRadialChart to homepage
Logannford Mar 26, 2025
c09a40e
minor changes to story and revert to rsc
Logannford Mar 26, 2025
17aee2d
chore: adds tremor area chart & new directory for charts
Logannford Mar 26, 2025
515b949
feat: adds story for total-question-chart
Logannford Mar 26, 2025
cb97c95
chore: minor cleanup
Logannford Mar 26, 2025
a866d8a
feat: some story work, adds new color to chart utils & custom tooltip
Logannford Mar 27, 2025
e8a52d0
progress with stories.
Logannford Mar 27, 2025
55240df
minor style changes
Logannford Mar 27, 2025
d4354f4
feat: adding spark charts (will be used elsewhere in the app)
Logannford Mar 27, 2025
29ba732
feat: tooltip style changes & total question chart style changes
Logannford Mar 27, 2025
72f1eb4
more work with total questions chart.
Logannford Mar 27, 2025
c5401c5
style amends
Logannford Mar 27, 2025
6f9f46f
feat: adds bar-list and story added.
Logannford Mar 27, 2025
b542d5d
feat: adding separators to stats charts
Logannford Mar 27, 2025
66f430f
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 27, 2025
b797a80
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 28, 2025
6042d90
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 28, 2025
7394872
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 29, 2025
bf8819a
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 29, 2025
1fc6ce7
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 30, 2025
436b3ad
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 30, 2025
eec0ca1
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 30, 2025
e2df550
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 31, 2025
961178a
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Mar 31, 2025
9b5b931
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 1, 2025
2fae587
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 2, 2025
b7fccbb
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 2, 2025
cfbf9d5
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 3, 2025
f796cce
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 4, 2025
be7a4fd
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 5, 2025
2732d94
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 5, 2025
2a330eb
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 6, 2025
c203d41
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 8, 2025
b381f69
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 9, 2025
5275e0d
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 9, 2025
441abb2
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 9, 2025
e59086e
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 10, 2025
2ec35bc
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 12, 2025
09dd2f2
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 18, 2025
74941d1
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 19, 2025
c16c723
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 20, 2025
3ec1695
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 20, 2025
7019c72
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 22, 2025
f7604d7
Merge branch 'main' into improvement/stats-page-ui-improvements
Logannford Apr 23, 2025
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
22 changes: 10 additions & 12 deletions src/app/(app)/(default_layout)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@ import UserXp from '@/components/ui/user-xp';

export default function StatisticsLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<div className="text-white flex flex-col gap-y-4 relative h-full">
<div className="container">
<div className="flex w-full items-center container">
<div className="flex-1">
<SidebarLayoutTrigger />
</div>
<div className="flex items-center gap-x-3">
<CurrentStreak />
<UserXp />
<UpgradeModal />
</div>
<div className="text-white flex flex-col gap-y-4 relative h-full container">
<div className="flex w-full items-center">
<div className="flex-1">
<SidebarLayoutTrigger />
</div>
<div className="flex items-center gap-x-3">
<CurrentStreak />
<UserXp />
<UpgradeModal />
</div>
</div>
<div className="container">{children}</div>
{children}
</div>
);
}
67 changes: 38 additions & 29 deletions src/app/(app)/(default_layout)/statistics/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import StatsRangePicker from '@/components/app/statistics/range-picker';
import QuestionChart from '@/components/app/statistics/total-question-chart';
import DifficultyRadialChart from '@/components/app/statistics/difficulty-radial-chart';

import { useUserServer } from '@/hooks/use-user-server';
import { StatsSteps } from '@/types/Stats';
Expand All @@ -12,11 +13,22 @@ import SuggestedQuestions from '@/components/app/statistics/suggested-questions'
import StatisticsReport from '@/components/app/statistics/statistics-report';
import StatisticsOverviewMenu from '@/components/app/statistics/statistics-overview-menu';
import QuestionTracker from '@/components/app/statistics/question-tracker';
import { createMetadata } from '@/utils/seo';
import { getUserDisplayName } from '@/utils/user';

export const metadata = {
title: 'Statistics | techblitz',
description: 'View your coding statistics and progress',
};
export async function generateMetadata() {
return createMetadata({
title: 'Statistics | TechBlitz',
description:
'Dive into your current coding journey, track your progress, and gain insight on how to improve your skills.',
image: {
text: 'Statistics | TechBlitz',
bgColor: '#000',
textColor: '#fff',
},
canonicalUrl: '/statistics',
});
}

export default async function StatisticsPage({
searchParams,
Expand All @@ -33,36 +45,33 @@ export default async function StatisticsPage({
const range = (searchParams.range as StatsSteps) || '7d';
const { step } = STATISTICS[range];

// Prefetch data
const { stats } = await getData({
userUid: user.uid,
from: range,
to: new Date().toISOString(),
step,
});
// Prefetch data - get both time-grouped and overall stats
const [timeGroupedStats, overallStats] = await Promise.all([
getData({
userUid: user.uid,
from: range,
to: new Date().toISOString(),
step,
includeDifficultyData: true,
}),
getData({
userUid: user.uid,
from: 'all',
to: new Date().toISOString(),
includeDifficultyData: true,
}),
]);

return (
<div>
<div className="flex flex-col gap-3 md:flex-row w-full justify-between md:items-center">
<div className="space-y-6">
<div className="flex flex-col gap-3 md:flex-row w-full justify-between">
<Hero
heading="Coding Journey"
heading={`${getUserDisplayName(user)}'s Statistics`}
container={false}
subheading="An overview of your coding journey on TechBlitz."
subheading="Dive into your coding journey, track your progress, and gain insight on how to improve your skills."
gridPosition="top-right"
/>
<div className="flex gap-3">
<StatsRangePicker selectedRange={STATISTICS[range].label} />
<StatisticsOverviewMenu user={user} />
</div>
</div>

<div className="grid grid-cols-12 gap-y-4 gap-x-8 mt-8 md:mt-0">
<div className="max-h-[28rem] col-span-12 mb-4">
{stats && <QuestionChart questionData={stats} step={step} />}
</div>
{stats && <QuestionTracker className="mb-4" stats={stats} step={step} range={range} />}
{/** suggested q's and analysis blocks TODO: CHANGE SUGGESTED QUESTIONS TO STREAK DATA (I THINK) */}
<SuggestedQuestions />
<StatisticsReport />
{overallStats.stats && <DifficultyRadialChart questionData={overallStats.stats} />}
</div>
</div>
);
Expand Down
144 changes: 144 additions & 0 deletions src/components/app/statistics/difficulty-radial-chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
'use client';

import { useMemo } from 'react';
import { RadialBarChart, RadialBar, Legend, ResponsiveContainer, Tooltip } from 'recharts';
import { StatsChartData } from '@/types/Stats';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';

// Define colors for each difficulty
// Use the same difficulty colors as defined in the app's utility functions
const DIFFICULTY_COLORS = {
BEGINNER: '#3b82f6', // blue-500
EASY: '#22c55e', // green-500
MEDIUM: '#eab308', // yellow-500
HARD: '#ef4444', // red-500
};

// Map difficulty to friendly names
const DIFFICULTY_LABELS = {
BEGINNER: 'Beginner',
EASY: 'Easy',
MEDIUM: 'Medium',
HARD: 'Hard',
};

export default function DifficultyRadialChart({ questionData }: { questionData: StatsChartData }) {
// Calculate total questions by difficulty
const difficultyData = useMemo(() => {
// Create object to store totals by difficulty
const totalsByDifficulty: Record<string, number> = {};
let grandTotal = 0;

// Sum up all question counts by difficulty across all time periods
Object.values(questionData).forEach((data) => {
// Only process entries that have difficulties data
if (data.difficulties) {
Object.entries(data.difficulties).forEach(([difficulty, count]) => {
// Ensure count is treated as a number
const countValue = count ? Number(count) : 0;
totalsByDifficulty[difficulty] = (totalsByDifficulty[difficulty] || 0) + countValue;
grandTotal += countValue;
});
}
});

// Convert to array format for radial chart
// Sort from highest to lowest count for better visualization
const chartData = Object.entries(totalsByDifficulty)
.filter(([_, count]) => count > 0) // Only include non-zero counts
.sort((a, b) => b[1] - a[1]) // Sort by count (descending)
.map(([difficulty, count], index) => {
// Calculate the angle for the radial chart
return {
name: DIFFICULTY_LABELS[difficulty as keyof typeof DIFFICULTY_LABELS] || difficulty,
value: count,
difficulty,
fill: DIFFICULTY_COLORS[difficulty as keyof typeof DIFFICULTY_COLORS] || '#888',
percentage: grandTotal > 0 ? ((count / grandTotal) * 100).toFixed(1) : '0',
};
});

return { chartData, grandTotal };
}, [questionData]);

// Generate a legend that shows both counts and percentages
const LegendContent = () => {
return (
<div className="flex flex-wrap justify-center gap-4 mt-4">
{difficultyData.chartData.map((entry, index) => (
<div key={index} className="flex items-center">
<div
className="w-3 h-3 rounded-full mr-2"
style={{ backgroundColor: entry.fill }}
></div>
<div className="flex flex-col">
<span className="text-sm font-medium">{entry.name}</span>
<span className="text-xs text-gray-400">
{entry.value} ({entry.percentage}%)
</span>
</div>
</div>
))}
</div>
);
};

// Custom tooltip component
const CustomTooltip = ({ active, payload }: any) => {
if (active && payload && payload.length) {
const data = payload[0].payload;
return (
<div className="bg-[#090909] border border-black-50 rounded-md shadow-lg">
<p className="text-white px-3 py-2 font-onest text-sm font-medium">{data.name}</p>
<Separator className="bg-black-50" />
<div className="flex items-center gap-2 px-3 py-2">
<div className="size-2 rounded-[2px]" style={{ backgroundColor: data.fill }} />
<p className="text-gray-300 text-sm">Questions: {data.value}</p>
</div>
</div>
);
}
return null;
};

return (
<>
{difficultyData.grandTotal > 0 ? (
<div className="w-full h-[350px]">
<ResponsiveContainer width="100%" height="100%">
<RadialBarChart
cx="50%"
cy="50%"
innerRadius="10%"
outerRadius="80%"
barSize={17}
data={difficultyData.chartData}
>
<RadialBar
background={{ fill: '#090909' }}
label={{
position: 'insideStart',
fill: '#fff',
fontSize: 12,
formatter: (value: number) => value,
}}
dataKey="value"
startAngle={45}
endAngle={450}
/>
<Tooltip content={<CustomTooltip />} animationDuration={500} />
</RadialBarChart>
</ResponsiveContainer>
</div>
) : (
<div className="flex flex-col items-center justify-center gap-4 h-[300px]">
<p className="text-lg text-muted-foreground text-center">
No difficulty data available due to lack of questions answered
</p>
<Button href="/questions">Start answering now</Button>
</div>
)}
</>
);
}
12 changes: 10 additions & 2 deletions src/components/shared/hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@ export default function Hero(opts: {
children?: React.ReactNode;
container?: boolean;
chip?: React.ReactNode;
gridPosition?: 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
}) {
const { heading, subheading, children, container = true, chip } = opts;
const {
heading,
subheading,
children,
container = true,
chip,
gridPosition = 'bottom-left',
} = opts;

return (
<section className="w-full pt-14 pb-8 group relative">
Expand All @@ -30,7 +38,7 @@ export default function Hero(opts: {
)}
{children}
</div>
<Grid size={25} position="bottom-left" />
<Grid size={25} position={gridPosition} />

{/* Fade-out gradient overlay */}
<div className="absolute inset-x-0 bottom-0 h-10 bg-gradient-to-t from-[#000000] to-transparent pointer-events-none"></div>
Expand Down
8 changes: 7 additions & 1 deletion src/types/Stats.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { STEPS } from '@/utils/constants';
import { StatisticsReport } from '@prisma/client';
import { StatisticsReport, QuestionDifficulty } from '@prisma/client';
import { Question } from '@/types/Questions';

// Record of difficulty types with their counts
export type DifficultyRecord = {
[K in QuestionDifficulty]?: number;
};

export type StatsChartData = {
[key: string]: {
totalQuestions: number;
tagCounts: Record<string, number>;
tags: string[];
difficulties?: DifficultyRecord;
};
};

Expand Down
Loading