Files
school_compare/nextjs-app/components/SchoolCard.tsx
Tudor 18964a34a2
All checks were successful
Build and Push Docker Images / Build Backend (FastAPI) (push) Successful in 34s
Build and Push Docker Images / Build Frontend (Next.js) (push) Successful in 1m15s
Build and Push Docker Images / Trigger Portainer Update (push) Successful in 1s
Add visual polish and micro-interactions for editorial feel
Phase 1 - Critical Fixes:
- EmptyState: warm palette, coral button, Playfair Display title
- Pagination: design system colors, coral active state
- LoadingSkeleton: warm shimmer with coral tint

Phase 2 - Signature Patterns:
- Navigation: sliding underline hover effect on links
- globals.css: increased noise texture opacity for paper feel
- RankingsView: alternating row backgrounds
- HomeView: decorative coral bar under section headings

Phase 3 - Polish:
- SchoolCard: SVG trend icons replacing unicode arrows
- RankingsView: styled metallic rank badges replacing emoji medals

Phase 4 - Micro-interactions:
- Navigation badge: pop animation when count changes
- HomeView grid: staggered entry animation for cards

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 14:12:48 +00:00

127 lines
4.5 KiB
TypeScript

/**
* SchoolCard Component
* Displays school information with metrics and actions
*/
import Link from 'next/link';
import type { School } from '@/lib/types';
import { formatPercentage, formatProgress, calculateTrend, getTrendColor } from '@/lib/utils';
import styles from './SchoolCard.module.css';
interface SchoolCardProps {
school: School;
onAddToCompare?: (school: School) => void;
showDistance?: boolean;
distance?: number;
}
export function SchoolCard({ school, onAddToCompare, showDistance, distance }: SchoolCardProps) {
const trend = calculateTrend(school.rwm_expected_pct, school.prev_rwm_expected_pct);
const trendColor = getTrendColor(trend);
return (
<div className={styles.card}>
<div className={styles.header}>
<h3 className={styles.title}>
<Link href={`/school/${school.urn}`}>
{school.school_name}
</Link>
</h3>
{showDistance && distance !== undefined && (
<span className={styles.distance}>
{distance.toFixed(1)} km away
</span>
)}
</div>
<div className={styles.meta}>
{school.local_authority && (
<span className={styles.metaItem}>{school.local_authority}</span>
)}
{school.school_type && (
<span className={styles.metaItem}>{school.school_type}</span>
)}
{school.religious_denomination && school.religious_denomination !== 'Does not apply' && (
<span className={styles.metaItem}>{school.religious_denomination}</span>
)}
</div>
{(school.rwm_expected_pct !== null || school.reading_progress !== null) && (
<div className={styles.metrics}>
{school.rwm_expected_pct !== null && (
<div className={styles.metric}>
<span className={styles.metricLabel}>RWM Expected</span>
<div className={styles.metricValue}>
<strong>{formatPercentage(school.rwm_expected_pct)}</strong>
{school.prev_rwm_expected_pct !== null && (
<span
className={`${styles.trend} ${styles[`trend${trend.charAt(0).toUpperCase() + trend.slice(1)}`]}`}
title={`Previous: ${formatPercentage(school.prev_rwm_expected_pct)}`}
>
{trend === 'up' && (
<svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon}>
<path
d="M8 3L14 10H2L8 3Z"
fill="currentColor"
/>
</svg>
)}
{trend === 'down' && (
<svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon}>
<path
d="M8 13L2 6H14L8 13Z"
fill="currentColor"
/>
</svg>
)}
{trend === 'stable' && (
<svg viewBox="0 0 16 16" fill="none" className={styles.trendIcon}>
<rect x="2" y="7" width="12" height="2" rx="1" fill="currentColor" />
</svg>
)}
</span>
)}
</div>
</div>
)}
{school.reading_progress !== null && (
<div className={styles.metric}>
<span className={styles.metricLabel}>Reading Progress</span>
<strong>{formatProgress(school.reading_progress)}</strong>
</div>
)}
{school.writing_progress !== null && (
<div className={styles.metric}>
<span className={styles.metricLabel}>Writing Progress</span>
<strong>{formatProgress(school.writing_progress)}</strong>
</div>
)}
{school.maths_progress !== null && (
<div className={styles.metric}>
<span className={styles.metricLabel}>Maths Progress</span>
<strong>{formatProgress(school.maths_progress)}</strong>
</div>
)}
</div>
)}
<div className={styles.actions}>
<Link href={`/school/${school.urn}`} className={styles.btnSecondary}>
View Details
</Link>
{onAddToCompare && (
<button
onClick={() => onAddToCompare(school)}
className={styles.btnPrimary}
>
Add to Compare
</button>
)}
</div>
</div>
);
}