Skip to content

Instantly share code, notes, and snippets.

@matsubo
Created October 17, 2024 02:40
Show Gist options
  • Save matsubo/de038bd556b24a659f800a1a98665ce2 to your computer and use it in GitHub Desktop.
Save matsubo/de038bd556b24a659f800a1a98665ce2 to your computer and use it in GitHub Desktop.
"use client"
import { useState, useEffect } from "react"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
import { Input } from "@/components/ui/input"
import { Button } from "@/components/ui/button"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
import { ArrowUpDown } from "lucide-react"
// 選手データの型定義
type Athlete = {
rank: number
number: number
name: string
gender: string
age: number
country: string
swim: string
bike: string
run: string
total: string
}
// レース種別の定義
const raceTypes = [
{ id: "sado_1", name: "佐渡タイプA", url: "https://gist.githubusercontent.com/matsubo/b81e4b71f3ea280278ef532ec6a1c781/raw/f19260686d4ab24d6b947ca1204557a9d1e572eb/sado_1.csv" },
{ id: "sado_2", name: "佐渡タイプA リレー", url: "https://gist.githubusercontent.com/matsubo/b81e4b71f3ea280278ef532ec6a1c781/raw/f19260686d4ab24d6b947ca1204557a9d1e572eb/sado_2.csv" },
{ id: "sado_3", name: "佐渡タイプB", url: "https://gist.githubusercontent.com/matsubo/b81e4b71f3ea280278ef532ec6a1c781/raw/f19260686d4ab24d6b947ca1204557a9d1e572eb/sado_3.csv" },
{ id: "sado_4", name: "佐渡タイプB リレー", url: "https://gist.githubusercontent.com/matsubo/b81e4b71f3ea280278ef532ec6a1c781/raw/f19260686d4ab24d6b947ca1204557a9d1e572eb/sado_4.csv" },
]
// CSVデータを取得し解析する関数
async function fetchAndParseCSV(url: string): Promise<Athlete[]> {
const response = await fetch(url)
const csvText = await response.text()
const lines = csvText.split('\n')
const headers = lines[0].split(',')
return lines.slice(1).map(line => {
const values = line.split(',')
return {
rank: parseInt(values[0]),
number: parseInt(values[1]),
name: values[2],
gender: values[3],
age: parseInt(values[4]),
country: values[5],
swim: values[6],
bike: values[7],
run: values[8],
total: values[9]
}
}).filter(athlete => athlete.rank) // 空の行を除外
}
export default function TriathlonResults() {
const [athletes, setAthletes] = useState<Athlete[]>([])
const [sortColumn, setSortColumn] = useState<keyof Athlete>("rank")
const [sortDirection, setSortDirection] = useState<"asc" | "desc">("asc")
const [searchTerm, setSearchTerm] = useState("")
const [selectedRaceType, setSelectedRaceType] = useState(raceTypes[0].id)
useEffect(() => {
const selectedRace = raceTypes.find(race => race.id === selectedRaceType)
if (selectedRace) {
fetchAndParseCSV(selectedRace.url).then(setAthletes)
}
}, [selectedRaceType])
// ソート関数
const sortData = (column: keyof Athlete) => {
const newDirection = column === sortColumn && sortDirection === "asc" ? "desc" : "asc"
const sortedData = [...athletes].sort((a, b) => {
if (a[column] < b[column]) return newDirection === "asc" ? -1 : 1
if (a[column] > b[column]) return newDirection === "asc" ? 1 : -1
return 0
})
setAthletes(sortedData)
setSortColumn(column)
setSortDirection(newDirection)
}
// 検索関数
const handleSearch = (event: React.ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value)
}
// フィルタリングされたデータ
const filteredAthletes = athletes.filter((athlete) =>
athlete.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
athlete.country.toLowerCase().includes(searchTerm.toLowerCase())
)
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">トライアスロン リザルト</h1>
<div className="flex flex-col md:flex-row gap-4 mb-4">
<Select value={selectedRaceType} onValueChange={setSelectedRaceType}>
<SelectTrigger className="w-full md:w-[300px]">
<SelectValue placeholder="レース種別を選択" />
</SelectTrigger>
<SelectContent>
{raceTypes.map((race) => (
<SelectItem key={race.id} value={race.id}>
{race.name}
</SelectItem>
))}
</SelectContent>
</Select>
<Input
type="search"
placeholder="選手名または国名で検索..."
className="w-full md:w-[300px]"
value={searchTerm}
onChange={handleSearch}
/>
</div>
<div className="overflow-x-auto">
<Table>
<TableHeader>
<TableRow>
{["rank", "number", "name", "gender", "age", "country", "swim", "bike", "run", "total"].map((column) => (
<TableHead key={column}>
{column.charAt(0).toUpperCase() + column.slice(1)}
<Button variant="ghost" onClick={() => sortData(column as keyof Athlete)}>
<ArrowUpDown className="h-4 w-4" />
</Button>
</TableHead>
))}
</TableRow>
</TableHeader>
<TableBody>
{filteredAthletes.map((athlete) => (
<TableRow key={athlete.number}>
{["rank", "number", "name", "gender", "age", "country", "swim", "bike", "run", "total"].map((column) => (
<TableCell key={column}>{athlete[column as keyof Athlete]}</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment