佐渡トライアスロン 2024 トラッカー 続き

調査

概要

vercelのv0が引き続き性能が上がっているとのことなので試してみました。

早速作ってみた

以前作ってみたアスリートトラッカーと同じようなものを作らせてみました。

まず、たった5分で以下のクオリティのものができました。

Triathlon Results - v0 by Vercel
Chat with v0. Generate UI with simple text prompts. Copy, paste, ship.

v3で上記の物ができたので、3回のプロンプトだけで作れています。

それ以降、デザインを調整したり機能を追加したりすると自分のやりたい方向とはズレてきたり、基本機能がリグレッションしたりするので諦めました。

たとえば、v11は以下のような感じ。

v3のときのソースコード

こちらにおいておきます。

"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>
)
}
view raw sado-2024.tsx hosted with ❤ by GitHub

コメント

タイトルとURLをコピーしました