diff --git a/src/App.css b/src/App.css index 3215c8f..7681516 100644 --- a/src/App.css +++ b/src/App.css @@ -1324,6 +1324,78 @@ textarea:focus-visible { background: linear-gradient(90deg, var(--accent-color), color-mix(in srgb, var(--accent-color) 70%, #22c55e)); } +.dashboard-progress .progress-stack { + display: grid; + gap: 16px; +} + +.progress-row { + display: grid; + gap: 10px; +} + +.progress-label { + display: flex; + justify-content: space-between; + gap: 12px; + align-items: center; + color: var(--text-color); + font-weight: 700; +} + +.progress-bar { + height: 12px; + border-radius: 999px; + background: color-mix(in srgb, var(--accent-color) 14%, var(--card-bg)); + overflow: hidden; +} + +.progress-bar span { + display: block; + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, var(--accent-color), color-mix(in srgb, var(--accent-color) 70%, #22c55e)); +} + +.tag-chip-list { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 16px; +} + +.tag-chip { + display: inline-flex; + align-items: center; + gap: 8px; + padding: 10px 14px; + border-radius: 999px; + border: 1px solid color-mix(in srgb, var(--accent-color) 16%, transparent); + background: color-mix(in srgb, var(--accent-color) 8%, var(--card-bg)); + color: var(--text-color); + font-size: 0.92rem; +} + +.tag-chip strong { + font-weight: 700; +} + +.career-list { + display: grid; + gap: 12px; + padding-left: 0; + margin: 18px 0 0; + list-style: none; +} + +.career-list li { + color: var(--text-secondary); + padding: 14px 16px; + border-radius: 16px; + background: color-mix(in srgb, var(--card-bg) 92%, transparent); + border: 1px solid color-mix(in srgb, var(--accent-color) 12%, transparent); +} + .timeline-list { display: grid; gap: 18px; margin-bottom: 28px; } .timeline-item { display: grid; diff --git a/src/dashboard/DashboardPage.jsx b/src/dashboard/DashboardPage.jsx index 5983c87..a3aafad 100644 --- a/src/dashboard/DashboardPage.jsx +++ b/src/dashboard/DashboardPage.jsx @@ -5,6 +5,9 @@ import { getJournalProblems, getJournalProjects, getJournalStats, + getProblemLanguages, + toPlatformSegment, + toProjectSlug, sumNumber, uniqueValues, } from "../lib/codingJournal"; @@ -31,6 +34,26 @@ const languageOrder = [ const difficultyOrder = ["Easy", "Medium", "Hard"]; const trackedTags = ["Array", "Hash Map", "String", "Tree", "Graph", "DP"]; +const technologyAliases = { + js: "JavaScript", + javascript: "JavaScript", + ts: "TypeScript", + typescript: "TypeScript", + react: "React", + reactjs: "React", + node: "Node.js", + "node.js": "Node.js", + nodejs: "Node.js", + "c lang": "C", + c: "C", +}; + +function normalizeTechnology(value) { + if (!value || typeof value !== "string") return ""; + const normalized = value.trim().toLowerCase(); + return technologyAliases[normalized] || value.trim(); +} + function percent(value, total) { if (!total) return "0%"; return `${Math.round((value / total) * 100)}%`; @@ -88,6 +111,10 @@ export default function DashboardPage() { const totalProjects = stats?.totalProjects ?? projects.length; const totalStars = stats?.totalStars ?? sumNumber(projects, "stars"); const totalForks = stats?.totalForks ?? sumNumber(projects, "forks"); + const languagesUsed = uniqueValues([ + ...projects.map((project) => project.language), + ...problems.map((problem) => problem.language), + ]).length; const byPlatform = problems.reduce((acc, problem) => { const key = problem.platform || "Custom"; @@ -130,6 +157,7 @@ export default function DashboardPage() { const difficultyCards = difficultyOrder.map((name) => ({ name, count: byDifficulty[name] || 0, + percentage: percent(byDifficulty[name] || 0, totalProblems), })); const byTag = problems.reduce((acc, problem) => { @@ -139,10 +167,126 @@ export default function DashboardPage() { return acc; }, {}); - const tagCards = trackedTags.map((name) => ({ - name, - count: byTag[name] || 0, - })); + const tagCards = Object.entries(byTag) + .sort(([, countA], [, countB]) => countB - countA) + .slice(0, 6) + .map(([name, count]) => ({ name, count })); + + const technologyCandidates = []; + + projects.forEach((project) => { + const projectLanguage = normalizeTechnology(project.language); + if (projectLanguage) technologyCandidates.push(projectLanguage); + + (project.topics || []).forEach((topic) => { + const normalizedTopic = normalizeTechnology(topic); + if (normalizedTopic) technologyCandidates.push(normalizedTopic); + }); + }); + + problems.forEach((problem) => { + const solutionLanguages = Array.isArray(problem.solutions) + ? problem.solutions.map((solution) => normalizeTechnology(solution.language)).filter(Boolean) + : []; + + if (solutionLanguages.length) { + technologyCandidates.push(...solutionLanguages); + } else { + const fallbackLanguage = normalizeTechnology(problem.language); + if (fallbackLanguage) technologyCandidates.push(fallbackLanguage); + } + }); + + const technologyCounts = technologyCandidates.reduce((acc, tech) => { + if (!tech) return acc; + acc[tech] = (acc[tech] || 0) + 1; + return acc; + }, {}); + + const technologyCards = Object.entries(technologyCounts) + .sort(([, countA], [, countB]) => countB - countA) + .slice(0, 10) + .map(([name, count]) => ({ + name, + count, + percentage: percent(count, technologyCandidates.length), + })); + + const recentVerifiedProblems = [...problems] + .filter((problem) => problem.verified) + .sort((a, b) => { + const dateA = new Date(a.solvedAt || a.updatedAt || a.createdAt || 0).getTime(); + const dateB = new Date(b.solvedAt || b.updatedAt || b.createdAt || 0).getTime(); + const hasDateA = Boolean(dateA); + const hasDateB = Boolean(dateB); + + if (hasDateA && hasDateB) return dateB - dateA; + if (hasDateA) return -1; + if (hasDateB) return 1; + return 0; + }) + .slice(0, 6) + .map((problem) => ({ + ...problem, + languageCount: getProblemLanguages(problem).length, + platformSegment: toPlatformSegment(problem.platform), + })); + + const recentProjects = [...projects] + .sort((a, b) => { + const dateA = new Date(a.updatedAt || 0).getTime(); + const dateB = new Date(b.updatedAt || 0).getTime(); + const hasDateA = Boolean(dateA); + const hasDateB = Boolean(dateB); + + if (hasDateA && hasDateB) return dateB - dateA; + if (hasDateA) return -1; + if (hasDateB) return 1; + return 0; + }) + .slice(0, 6) + .map((project) => ({ + ...project, + slug: toProjectSlug(project.name), + })); + + const timelineEvents = [ + ...projects + .filter((project) => project.updatedAt) + .map((project) => ({ + type: "Project Updated", + title: project.name, + date: new Date(project.updatedAt), + description: project.description + ? `Updated project ${project.name} on coding-journal.` + : `Updated project ${project.name}.`, + link: `/projects/${toProjectSlug(project.name)}`, + badge: project.language || "Project", + })), + ...problems + .filter((problem) => problem.solvedAt || problem.updatedAt) + .map((problem) => ({ + type: "Problem Solved", + title: problem.title, + date: new Date(problem.solvedAt || problem.updatedAt), + description: `Solved on ${problem.platform || "a platform"} with ${problem.difficulty || "unknown"} difficulty.`, + link: `/problems/${toPlatformSegment(problem.platform)}/${problem.slug}`, + badge: problem.platform || "Problem", + })), + ...problems + .filter((problem) => problem.verified && (problem.solvedAt || problem.updatedAt || problem.createdAt)) + .map((problem) => ({ + type: "Solution Verified", + title: problem.title, + date: new Date(problem.solvedAt || problem.updatedAt || problem.createdAt), + description: `Verified solution for ${problem.title} on ${problem.platform || "a platform"}.`, + link: `/problems/${toPlatformSegment(problem.platform)}/${problem.slug}`, + badge: "Verified", + })), + ] + .filter((event) => !Number.isNaN(event.date.getTime())) + .sort((a, b) => b.date.getTime() - a.date.getTime()) + .slice(0, 8); const problemsWithDate = problems.filter( (problem) => problem.addedAt || problem.createdAt || problem.updatedAt || problem.solvedAt @@ -207,13 +351,17 @@ export default function DashboardPage() { { label: "Total Problems", value: totalProblems }, { label: "Verified Problems", value: verifiedProblems }, { label: "Total Projects", value: totalProjects }, - { label: "Total Stars", value: totalStars }, - { label: "Total Forks", value: totalForks }, + { label: "Languages Used", value: languagesUsed }, + { label: "GitHub Stars", value: totalStars }, + { label: "GitHub Forks", value: totalForks }, ], platformCards, languageCards, difficultyCards, tagCards, + technologyCards, + recentVerifiedProblems, + recentProjects, timelineCards, projectInsights: { byStars, @@ -234,6 +382,228 @@ export default function DashboardPage() { align="left" /> + {!loading && !error ? ( + <> + +
+ {analytics.overview.map((metric) => ( +
+ {metric.label} +

{metric.value}

+
+ ))} +
+
+ + +
+
+

Difficulty Progress

+
+ {analytics.difficultyCards.map((item) => ( +
+
+ {item.name} + {item.count} +
+
+ +
+
+ ))} +
+
+ +
+

Platform Breakdown

+
+ {analytics.platformCards.map((item) => ( +
+
+ {item.name} + {item.count} +
+
+ +
+
+ ))} +
+
+ +
+

Top Topics

+
+ {analytics.tagCards.map((item) => ( + + {item.name} + {item.count} + + ))} +
+
+
+
+ + +
+
+

Top Technologies

+
+ {analytics.technologyCards.map((item) => ( +
+
+ {item.name} + {item.count} +
+
+ +
+
+ ))} +
+
+
+
+ + +
+
+

Current Focus

+
    +
  • MCA @ Bangalore University
  • +
  • React Development
  • +
  • Node.js APIs
  • +
  • DSA Problem Solving
  • +
  • Full Stack Projects
  • +
  • coding-journal
  • +
+
+ +
+

Open To Work

+
    +
  • Remote Internship
  • +
  • Part-Time Developer Roles
  • +
  • Freelance Projects
  • +
  • React Development
  • +
  • Full Stack Development
  • +
+
+ + Contact Me + + + View Projects + +
+
+
+
+ + +
+ {analytics.recentVerifiedProblems.map((problem) => ( +
+
+ {problem.platform} + {problem.difficulty || "Unknown"} +
+

{problem.title}

+

{problem.languageCount} solution language{problem.languageCount === 1 ? "" : "s"}

+
+ Verified + + View Problem + +
+
+ ))} +
+
+ + +
+ {analytics.recentProjects.map((project) => ( +
+
+ {project.language || "Unknown"} + ⭐ {project.stars || 0} +
+

{project.name}

+

{project.forks || 0} forks • Updated {formatDate(project.updatedAt) || "Unknown"}

+ + View Project + +
+ ))} +
+
+ + +
+ {(analytics.timelineEvents || []).length ? ( + (analytics.timelineEvents || []).map((event) => ( +
+ {formatDate(event.date) || "Unknown"} +
+
+

{event.title}

+ {event.type} +
+

{event.description}

+ + View Details + +
+
+ )) + ) : ( +
+

No public build events available

+

+ There are currently not enough dated project or problem events in the live + coding-journal feed to build a timeline. +

+
+ )} +
+
+ ) : null} + {loading ? (