Observer platform redesign Phase 4: migrate charts to Tremor, redesign all pages
Some checks failed
Build and Push Docker Image / build (push) Failing after 23s
Some checks failed
Build and Push Docker Image / build (push) Failing after 23s
- Migrate 9 chart components from Nivo to @tremor/react (BarChart, AreaChart, DonutChart, ScatterChart) - Remove @nivo/*, @react-spring/web dependencies (45 packages removed) - Redesign dashboard: 6 stat tiles, competition pipeline, score distribution, juror workload, activity feed - Add new /observer/projects page with search, filters, sorting, pagination, CSV export - Restructure reports page from 5 tabs to 3 (Progress, Jurors, Scores & Analytics) with per-tab CSV export - Redesign project detail: breadcrumb nav, score card header, 3-tab layout (Overview/Evaluations/Files) - Update loading skeletons to match new layouts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
|
||||
import { ResponsiveBar } from '@nivo/bar'
|
||||
import { BarChart } from '@tremor/react'
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { nivoTheme, scoreGradient } from './chart-theme'
|
||||
import { BRAND_TEAL } from './chart-theme'
|
||||
|
||||
interface ProjectRankingData {
|
||||
id: string
|
||||
@@ -18,14 +18,6 @@ interface ProjectRankingsProps {
|
||||
limit?: number
|
||||
}
|
||||
|
||||
type RankingBarDatum = {
|
||||
project: string
|
||||
score: number
|
||||
fullTitle: string
|
||||
teamName: string
|
||||
evaluationCount: number
|
||||
}
|
||||
|
||||
export function ProjectRankingsChart({
|
||||
data,
|
||||
limit = 20,
|
||||
@@ -37,21 +29,12 @@ export function ProjectRankingsChart({
|
||||
|
||||
if (!scoredData.length) return null
|
||||
|
||||
const averageScore =
|
||||
scoredData.length > 0
|
||||
? scoredData.reduce((sum, d) => sum + d.averageScore, 0) /
|
||||
scoredData.length
|
||||
: 0
|
||||
|
||||
const displayData = scoredData.slice(0, limit)
|
||||
|
||||
const chartData: RankingBarDatum[] = displayData.map((d) => ({
|
||||
const chartData = displayData.map((d) => ({
|
||||
project:
|
||||
d.title.length > 30 ? d.title.substring(0, 30) + '...' : d.title,
|
||||
score: d.averageScore,
|
||||
fullTitle: d.title,
|
||||
teamName: d.teamName ?? '',
|
||||
evaluationCount: d.evaluationCount,
|
||||
Score: parseFloat(d.averageScore.toFixed(2)),
|
||||
}))
|
||||
|
||||
return (
|
||||
@@ -65,75 +48,18 @@ export function ProjectRankingsChart({
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div
|
||||
style={{
|
||||
height: `${Math.max(400, displayData.length * 30)}px`,
|
||||
}}
|
||||
>
|
||||
<ResponsiveBar
|
||||
data={chartData}
|
||||
keys={['score']}
|
||||
indexBy="project"
|
||||
layout="horizontal"
|
||||
theme={nivoTheme}
|
||||
colors={(bar) => scoreGradient(bar.data.score as number)}
|
||||
valueScale={{ type: 'linear', max: 10 }}
|
||||
borderRadius={4}
|
||||
enableLabel={true}
|
||||
label={(d) => {
|
||||
const v = d.value
|
||||
return v != null ? Number(v).toFixed(1) : ''
|
||||
}}
|
||||
labelSkipWidth={30}
|
||||
labelTextColor="#ffffff"
|
||||
margin={{ top: 10, right: 30, bottom: 30, left: 200 }}
|
||||
padding={0.2}
|
||||
markers={[
|
||||
{
|
||||
axis: 'x',
|
||||
value: averageScore,
|
||||
lineStyle: {
|
||||
stroke: '#6b7280',
|
||||
strokeWidth: 2,
|
||||
strokeDasharray: '6 4',
|
||||
},
|
||||
legend: `Avg: ${averageScore.toFixed(1)}`,
|
||||
legendPosition: 'top',
|
||||
textStyle: {
|
||||
fill: '#6b7280',
|
||||
fontSize: 11,
|
||||
},
|
||||
},
|
||||
]}
|
||||
tooltip={({ data: rowData }) => (
|
||||
<div
|
||||
style={{
|
||||
background: '#ffffff',
|
||||
padding: '8px 12px',
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
||||
border: '1px solid #e5e7eb',
|
||||
fontSize: 12,
|
||||
}}
|
||||
>
|
||||
<strong>{rowData.fullTitle}</strong>
|
||||
{rowData.teamName && (
|
||||
<>
|
||||
<br />
|
||||
<span style={{ color: '#6b7280' }}>
|
||||
{rowData.teamName}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
<br />
|
||||
Score: {Number(rowData.score).toFixed(2)}
|
||||
<br />
|
||||
Evaluations: {rowData.evaluationCount}
|
||||
</div>
|
||||
)}
|
||||
animate={true}
|
||||
/>
|
||||
</div>
|
||||
<BarChart
|
||||
data={chartData}
|
||||
index="project"
|
||||
categories={['Score']}
|
||||
colors={[BRAND_TEAL] as string[]}
|
||||
layout="horizontal"
|
||||
yAxisWidth={200}
|
||||
maxValue={10}
|
||||
showLegend={false}
|
||||
className={`h-[${Math.max(400, displayData.length * 30)}px]`}
|
||||
style={{ height: `${Math.max(400, displayData.length * 30)}px` }}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user