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

- 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:
2026-02-20 21:45:01 +01:00
parent 77cbc64b33
commit 8125ca6567
24 changed files with 3412 additions and 3401 deletions

View File

@@ -1,8 +1,8 @@
'use client'
import { ResponsiveLine } from '@nivo/line'
import { AreaChart } from '@tremor/react'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { nivoTheme, BRAND_DARK_BLUE } from './chart-theme'
import { BRAND_DARK_BLUE, BRAND_TEAL } from './chart-theme'
interface TimelineDataPoint {
date: string
@@ -17,26 +17,17 @@ interface EvaluationTimelineProps {
export function EvaluationTimelineChart({ data }: EvaluationTimelineProps) {
if (!data?.length) return null
const formattedData = data.map((d) => ({
...d,
dateFormatted: new Date(d.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
}),
}))
const totalEvaluations =
data.length > 0 ? data[data.length - 1].cumulative : 0
const lineData = [
{
id: 'Cumulative Evaluations',
data: formattedData.map((d) => ({
x: d.dateFormatted,
y: d.cumulative,
})),
},
]
const chartData = data.map((d) => ({
date: new Date(d.date).toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
}),
Cumulative: d.cumulative,
Daily: d.daily,
}))
return (
<Card>
@@ -49,57 +40,16 @@ export function EvaluationTimelineChart({ data }: EvaluationTimelineProps) {
</CardTitle>
</CardHeader>
<CardContent>
<div style={{ height: '300px' }}>
<ResponsiveLine
data={lineData}
theme={nivoTheme}
colors={[BRAND_DARK_BLUE]}
enableArea={true}
areaOpacity={0.1}
areaBaselineValue={0}
curve="monotoneX"
pointSize={6}
pointColor={BRAND_DARK_BLUE}
pointBorderWidth={2}
pointBorderColor="#ffffff"
useMesh={true}
enableSlices={formattedData.length >= 2 ? 'x' : false}
sliceTooltip={({ slice }) => {
const point = slice.points[0]
if (!point) return null
const dataItem = formattedData.find(
(d) => d.dateFormatted === point.data.xFormatted
)
return (
<div
style={{
background: '#fff',
padding: '8px 12px',
border: '1px solid #e5e7eb',
borderRadius: '8px',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
}}
>
<strong>{point.data.xFormatted}</strong>
<div>Cumulative: {point.data.yFormatted}</div>
{dataItem && <div>Daily: {dataItem.daily}</div>}
</div>
)
}}
margin={{ top: 20, right: 20, bottom: 50, left: 60 }}
axisBottom={{
tickRotation: -45,
legend: '',
legendOffset: 36,
}}
axisLeft={{
legend: 'Evaluations',
legendOffset: -50,
legendPosition: 'middle',
}}
yScale={{ type: 'linear', min: 0, max: 'auto' }}
/>
</div>
<AreaChart
data={chartData}
index="date"
categories={['Cumulative', 'Daily']}
colors={[BRAND_DARK_BLUE, BRAND_TEAL] as string[]}
curveType="monotone"
showGradient={true}
yAxisWidth={50}
className="h-[300px]"
/>
</CardContent>
</Card>
)