import React from "react"; import SHA1 from "crypto-js/sha1"; import { Link } from "react-router"; import type {Score, ScoreDisplayProps} from "../../types/game"; import { globalSkipKeys } from "../../types/constants"; const NostalgiaScoreDisplay: React.FC = ({ scores, viewMode, sortField, sortDirection, onSort, onDelete, showUsername = false, hideTitleArtist = false, }) => { // Key mappings for better display names. Hit or miss const keyDisplayNames: Record = { title: "Title", artist: "Artist", score: "Score", difficulty: "Difficulty", lamp: "Lamp", timestamp: "Date", judgements: "Judgements", maxCombo: "Max Combo", perfect: "Perfect", fast: "Fast", slow: "Slow", good: "Good", just: "Just", miss: "Miss", near: "Near", marvelous: "Marvelous", level: "Level", notes: "Notes", date: "Date", username: "Username", }; const mainStatKeys = [ "score", "difficulty", "lamp", "rank", "diff_lamp", "percent", "rating", "grade", "level" ]; const expandableKeys = ["judgements", "optional"]; const gameParam = new URLSearchParams(window.location.search).get("game") || "nostalgia"; // eslint-disable-next-line @typescript-eslint/no-explicit-any const formatValue = (value: any, key: string): string => { if (value === null || value === undefined) return "N/A"; // Handle timestamps if (key === "timestamp" || key === "date") { if(value === 0) return "N/A"; const date = new Date(typeof value === "number" ? value : value); return date.toLocaleDateString(); } if (typeof value === "number") { if (key === "score" || key === "maxCombo" || key === "combo") { return value.toLocaleString(); } return value.toString(); } if (typeof value === "boolean") { return value ? "Yes" : "No"; } if (Array.isArray(value)) { return value.join(", "); } return String(value); }; const getDisplayName = (key: string): string => { return keyDisplayNames[key] || key.charAt(0).toUpperCase() + key.slice(1); }; const renderValue = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any value: any, key: string, compact: boolean = false, ): React.ReactElement => { if (value === null || value === undefined) return N/A; // Handle judgements specially if (key === "judgements" && typeof value === "object") { const judgementEntries = Object.entries(value); if (compact) { return (
{judgementEntries.map(([jKey, jValue]) => (
{getDisplayName(jKey)}: {formatValue(jValue, jKey)}
))}
); } return (
{judgementEntries.map(([jKey, jValue]) => ( {getDisplayName(jKey)}:{" "} {formatValue(jValue, jKey)} ))}
); } if (typeof value === "object" && !Array.isArray(value)) { return (
{Object.entries(value).map(([subKey, subValue]) => (
{getDisplayName(subKey)}: {formatValue(subValue, subKey)}
))}
); } return {formatValue(value, key)}; }; const getScoreEntries = (score: Score) => { const entries = Object.entries(score).filter( ([key]) => !globalSkipKeys.includes(key), ); const mainStats = entries.filter(([key]) => mainStatKeys.includes(key)); const expandable = entries.filter(([key]) => expandableKeys.includes(key)); const others = entries.filter( ([key]) => !mainStatKeys.includes(key) && !expandableKeys.includes(key) && key !== "timestamp" && key !== "title" && key !== "artist", ); return { mainStats, expandable, others, timestamp: score.timestamp, }; }; const SortIcon = ({ field }: { field: string }) => { if (sortField !== field) { return ; } return sortDirection === "asc" ? ( ) : ( ); }; const sortedScores = [...scores].sort((a, b) => { const aVal = a[sortField]; const bVal = b[sortField]; if (aVal === undefined || aVal === null) return 1; if (bVal === undefined || bVal === null) return -1; let comparison = 0; if (typeof aVal === "string" && typeof bVal === "string") { comparison = aVal.localeCompare(bVal); } else if (typeof aVal === "number" && typeof bVal === "number") { comparison = aVal - bVal; } else if (aVal instanceof Date && bVal instanceof Date) { comparison = aVal.getTime() - bVal.getTime(); } else { comparison = String(aVal).localeCompare(String(bVal)); } return sortDirection === "asc" ? comparison : -comparison; }); // Get all possible keys for table headers const allKeys = Array.from( new Set(scores.flatMap((score) => Object.keys(score))), ).filter((key) => !globalSkipKeys.includes(key)); // Prioritize important keys for table display const tableKeys = [ ...(hideTitleArtist ? [] : ["title", "song", "artist"]), ...(showUsername ? ["username"] : []), "score", "difficulty", "lamp", "diff_lamp", "rating", "percent", "grade", "judgements", "maxCombo", "combo", "timestamp", ].filter((key) => allKeys.includes(key)); // Add actions column if delete function is provided const showActions = onDelete && viewMode === "table"; if (scores.length === 0) { return (
🎵

No scores found

Import some score data to get started!

); } if (viewMode === "cards") { return (
{sortedScores.map((score, index) => { const chartIdHash = SHA1(`${gameParam}${score.title}${score.artist}`).toString(); const { mainStats, expandable, others, timestamp } = getScoreEntries(score); return (
{/* Primary Info */}
{!hideTitleArtist && (

{score.title || score.song || "Unknown Title"}

{score.artist && (

{score.artist}

)} )} {showUsername && score.username && (

by {score.username}

)}
{/* Main Stats */} {mainStats.length > 0 && (
{mainStats.slice(0, 4).map(([key, value]) => (

{getDisplayName(key)}

{renderValue(value, key)}

))}
)} {/* Expandable sections (judgements, optional) */} {expandable.map(([key, value]) => (

{getDisplayName(key)}

{renderValue(value, key)}
))} {/* Other fields */} {others.length > 0 && (
{others.map(([key, value]) => (
{getDisplayName(key)}: {renderValue(value, key)}
))}
)} {/* Timestamp */}

{timestamp === 0 ? "N/A" : ( <> {new Date( typeof timestamp === "number" ? timestamp : timestamp, ).toLocaleDateString()}{" "} •{" "} {new Date( typeof timestamp === "number" ? timestamp : timestamp, ).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", })} )}

); })}
); } return (
{tableKeys.map((key) => ( ))} {showActions && ( )} {sortedScores.map((score, index) => ( {tableKeys.map((key) => ( ))} {showActions && ( )} ))}
{key === "judgements" ? ( {getDisplayName(key)} ) : ( )} Actions
{key === "lamp" || key === "diff_lamp" ? (
{score[key] || "No Clear"}
) : key === "judgements" ? (
{renderValue(score[key], key, true)}
) : key === "timestamp" ? ( {new Date( typeof score[key] === "number" ? score[key] : score[key], ).toLocaleDateString()} ) : key === "username" ? ( {score[key] || "Unknown"} ) : ( {renderValue(score[key], key)} )}
); }; export default NostalgiaScoreDisplay;