1. Added original favicon - Copied favicon.svg from original frontend - Added favicon reference to layout metadata - Professional icon with brand colors 2. Updated logo in navigation - Replaced emoji with proper SVG logo from original design - Uses circular target design with crosshairs - Matches brand identity with coral accent color 3. Removed emoji icons throughout app for professional look - Removed 📍 (location pin) from school locations - Removed 🏫 (school building) from school types - Removed 🔢 from URN labels and section headings - Kept meaningful symbols (✓, +) in buttons only - Updated map popup button color to brand coral (#e07256) Components updated: - Navigation: Professional SVG logo - HomeView: Clean location banner - SchoolDetailView: No decorative emojis in metadata - ComparisonView: Text-only school information - SchoolSearchModal: Clean school listings - LeafletMapInner: Professional map popups Result: More polished, professional appearance suitable for educational data platform Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
322 lines
13 KiB
TypeScript
322 lines
13 KiB
TypeScript
/**
|
||
* SchoolDetailView Component
|
||
* Displays comprehensive school information with performance charts
|
||
*/
|
||
|
||
'use client';
|
||
|
||
import { useComparison } from '@/hooks/useComparison';
|
||
import { PerformanceChart } from './PerformanceChart';
|
||
import { SchoolMap } from './SchoolMap';
|
||
import type { School, SchoolResult, AbsenceData } from '@/lib/types';
|
||
import { formatPercentage, formatProgress, calculateTrend } from '@/lib/utils';
|
||
import styles from './SchoolDetailView.module.css';
|
||
|
||
interface SchoolDetailViewProps {
|
||
schoolInfo: School;
|
||
yearlyData: SchoolResult[];
|
||
absenceData: AbsenceData | null;
|
||
}
|
||
|
||
export function SchoolDetailView({ schoolInfo, yearlyData, absenceData }: SchoolDetailViewProps) {
|
||
const { addSchool, removeSchool, isSelected } = useComparison();
|
||
const isInComparison = isSelected(schoolInfo.urn);
|
||
|
||
// Get latest results
|
||
const latestResults = yearlyData.length > 0 ? yearlyData[yearlyData.length - 1] : null;
|
||
|
||
// Handle add/remove from comparison
|
||
const handleComparisonToggle = () => {
|
||
if (isInComparison) {
|
||
removeSchool(schoolInfo.urn);
|
||
} else {
|
||
addSchool(schoolInfo);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className={styles.container}>
|
||
{/* Header Section */}
|
||
<header className={styles.header}>
|
||
<div className={styles.headerContent}>
|
||
<div className={styles.titleSection}>
|
||
<h1 className={styles.schoolName}>{schoolInfo.school_name}</h1>
|
||
<div className={styles.meta}>
|
||
{schoolInfo.local_authority && (
|
||
<span className={styles.metaItem}>
|
||
{schoolInfo.local_authority}
|
||
</span>
|
||
)}
|
||
{schoolInfo.school_type && (
|
||
<span className={styles.metaItem}>
|
||
{schoolInfo.school_type}
|
||
</span>
|
||
)}
|
||
<span className={styles.metaItem}>
|
||
URN: {schoolInfo.urn}
|
||
</span>
|
||
</div>
|
||
{schoolInfo.address && (
|
||
<p className={styles.address}>
|
||
{schoolInfo.address}
|
||
{schoolInfo.postcode && `, ${schoolInfo.postcode}`}
|
||
</p>
|
||
)}
|
||
</div>
|
||
<div className={styles.actions}>
|
||
<button
|
||
onClick={handleComparisonToggle}
|
||
className={isInComparison ? styles.btnRemove : styles.btnAdd}
|
||
>
|
||
{isInComparison ? '✓ In Comparison' : '+ Add to Compare'}
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
{/* Latest Results Summary */}
|
||
{latestResults && (
|
||
<section className={styles.summary}>
|
||
<h2 className={styles.sectionTitle}>
|
||
Latest Results ({latestResults.year})
|
||
</h2>
|
||
<div className={styles.metricsGrid}>
|
||
{latestResults.rwm_expected_pct !== null && (
|
||
<div className={styles.metricCard}>
|
||
<div className={styles.metricLabel}>
|
||
RWM Expected Standard
|
||
</div>
|
||
<div className={styles.metricValue}>
|
||
{formatPercentage(latestResults.rwm_expected_pct)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{latestResults.rwm_high_pct !== null && (
|
||
<div className={styles.metricCard}>
|
||
<div className={styles.metricLabel}>
|
||
RWM Higher Standard
|
||
</div>
|
||
<div className={styles.metricValue}>
|
||
{formatPercentage(latestResults.rwm_high_pct)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{latestResults.reading_progress !== null && (
|
||
<div className={styles.metricCard}>
|
||
<div className={styles.metricLabel}>
|
||
Reading Progress
|
||
</div>
|
||
<div className={styles.metricValue}>
|
||
{formatProgress(latestResults.reading_progress)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{latestResults.writing_progress !== null && (
|
||
<div className={styles.metricCard}>
|
||
<div className={styles.metricLabel}>
|
||
Writing Progress
|
||
</div>
|
||
<div className={styles.metricValue}>
|
||
{formatProgress(latestResults.writing_progress)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{latestResults.maths_progress !== null && (
|
||
<div className={styles.metricCard}>
|
||
<div className={styles.metricLabel}>
|
||
Maths Progress
|
||
</div>
|
||
<div className={styles.metricValue}>
|
||
{formatProgress(latestResults.maths_progress)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{/* Performance Over Time */}
|
||
{yearlyData.length > 0 && (
|
||
<section className={styles.chartsSection}>
|
||
<h2 className={styles.sectionTitle}>Performance Over Time</h2>
|
||
<div className={styles.chartContainer}>
|
||
<PerformanceChart
|
||
data={yearlyData}
|
||
schoolName={schoolInfo.school_name}
|
||
/>
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{/* Detailed Metrics */}
|
||
{latestResults && (
|
||
<section className={styles.detailedMetrics}>
|
||
<h2 className={styles.sectionTitle}>Detailed Metrics</h2>
|
||
|
||
{/* Reading Metrics */}
|
||
<div className={styles.metricGroup}>
|
||
<h3 className={styles.metricGroupTitle}>📖 Reading</h3>
|
||
<div className={styles.metricTable}>
|
||
{latestResults.reading_expected_pct !== null && (
|
||
<div className={styles.metricRow}>
|
||
<span className={styles.metricName}>Expected Standard</span>
|
||
<span className={styles.metricValue}>{formatPercentage(latestResults.reading_expected_pct)}</span>
|
||
</div>
|
||
)}
|
||
{latestResults.reading_high_pct !== null && (
|
||
<div className={styles.metricRow}>
|
||
<span className={styles.metricName}>Higher Standard</span>
|
||
<span className={styles.metricValue}>{formatPercentage(latestResults.reading_high_pct)}</span>
|
||
</div>
|
||
)}
|
||
{latestResults.reading_progress !== null && (
|
||
<div className={styles.metricRow}>
|
||
<span className={styles.metricName}>Progress Score</span>
|
||
<span className={styles.metricValue}>{formatProgress(latestResults.reading_progress)}</span>
|
||
</div>
|
||
)}
|
||
{latestResults.reading_avg_score !== null && (
|
||
<div className={styles.metricRow}>
|
||
<span className={styles.metricName}>Average Scaled Score</span>
|
||
<span className={styles.metricValue}>{latestResults.reading_avg_score.toFixed(1)}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Writing Metrics */}
|
||
<div className={styles.metricGroup}>
|
||
<h3 className={styles.metricGroupTitle}>✍️ Writing</h3>
|
||
<div className={styles.metricTable}>
|
||
{latestResults.writing_expected_pct !== null && (
|
||
<div className={styles.metricRow}>
|
||
<span className={styles.metricName}>Expected Standard</span>
|
||
<span className={styles.metricValue}>{formatPercentage(latestResults.writing_expected_pct)}</span>
|
||
</div>
|
||
)}
|
||
{latestResults.writing_high_pct !== null && (
|
||
<div className={styles.metricRow}>
|
||
<span className={styles.metricName}>Higher Standard</span>
|
||
<span className={styles.metricValue}>{formatPercentage(latestResults.writing_high_pct)}</span>
|
||
</div>
|
||
)}
|
||
{latestResults.writing_progress !== null && (
|
||
<div className={styles.metricRow}>
|
||
<span className={styles.metricName}>Progress Score</span>
|
||
<span className={styles.metricValue}>{formatProgress(latestResults.writing_progress)}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Maths Metrics */}
|
||
<div className={styles.metricGroup}>
|
||
<h3 className={styles.metricGroupTitle}>Mathematics</h3>
|
||
<div className={styles.metricTable}>
|
||
{latestResults.maths_expected_pct !== null && (
|
||
<div className={styles.metricRow}>
|
||
<span className={styles.metricName}>Expected Standard</span>
|
||
<span className={styles.metricValue}>{formatPercentage(latestResults.maths_expected_pct)}</span>
|
||
</div>
|
||
)}
|
||
{latestResults.maths_high_pct !== null && (
|
||
<div className={styles.metricRow}>
|
||
<span className={styles.metricName}>Higher Standard</span>
|
||
<span className={styles.metricValue}>{formatPercentage(latestResults.maths_high_pct)}</span>
|
||
</div>
|
||
)}
|
||
{latestResults.maths_progress !== null && (
|
||
<div className={styles.metricRow}>
|
||
<span className={styles.metricName}>Progress Score</span>
|
||
<span className={styles.metricValue}>{formatProgress(latestResults.maths_progress)}</span>
|
||
</div>
|
||
)}
|
||
{latestResults.maths_avg_score !== null && (
|
||
<div className={styles.metricRow}>
|
||
<span className={styles.metricName}>Average Scaled Score</span>
|
||
<span className={styles.metricValue}>{latestResults.maths_avg_score.toFixed(1)}</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{/* Absence Data */}
|
||
{absenceData && (
|
||
<section className={styles.absenceSection}>
|
||
<h2 className={styles.sectionTitle}>Absence Data</h2>
|
||
<div className={styles.absenceGrid}>
|
||
{absenceData.overall_absence_rate !== null && (
|
||
<div className={styles.absenceCard}>
|
||
<div className={styles.absenceLabel}>Overall Absence Rate</div>
|
||
<div className={styles.absenceValue}>{formatPercentage(absenceData.overall_absence_rate)}</div>
|
||
</div>
|
||
)}
|
||
{absenceData.persistent_absence_rate !== null && (
|
||
<div className={styles.absenceCard}>
|
||
<div className={styles.absenceLabel}>Persistent Absence</div>
|
||
<div className={styles.absenceValue}>{formatPercentage(absenceData.persistent_absence_rate)}</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{/* Map */}
|
||
{schoolInfo.latitude && schoolInfo.longitude && (
|
||
<section className={styles.mapSection}>
|
||
<h2 className={styles.sectionTitle}>Location</h2>
|
||
<div className={styles.mapContainer}>
|
||
<SchoolMap
|
||
schools={[schoolInfo]}
|
||
center={[schoolInfo.latitude, schoolInfo.longitude]}
|
||
zoom={15}
|
||
/>
|
||
</div>
|
||
</section>
|
||
)}
|
||
|
||
{/* All Years Data Table */}
|
||
{yearlyData.length > 0 && (
|
||
<section className={styles.historySection}>
|
||
<h2 className={styles.sectionTitle}>Historical Data</h2>
|
||
<div className={styles.tableWrapper}>
|
||
<table className={styles.dataTable}>
|
||
<thead>
|
||
<tr>
|
||
<th>Year</th>
|
||
<th>RWM Expected</th>
|
||
<th>RWM Higher</th>
|
||
<th>Reading Progress</th>
|
||
<th>Writing Progress</th>
|
||
<th>Maths Progress</th>
|
||
<th>Avg. Scaled Score</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{yearlyData.map((result) => (
|
||
<tr key={result.year}>
|
||
<td className={styles.yearCell}>{result.year}</td>
|
||
<td>{result.rwm_expected_pct !== null ? formatPercentage(result.rwm_expected_pct) : '-'}</td>
|
||
<td>{result.rwm_high_pct !== null ? formatPercentage(result.rwm_high_pct) : '-'}</td>
|
||
<td>{result.reading_progress !== null ? formatProgress(result.reading_progress) : '-'}</td>
|
||
<td>{result.writing_progress !== null ? formatProgress(result.writing_progress) : '-'}</td>
|
||
<td>{result.maths_progress !== null ? formatProgress(result.maths_progress) : '-'}</td>
|
||
<td>-</td>
|
||
</tr>
|
||
))}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</section>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|