feat: setup frontend project with bun and dynamic data fetching
- Create new frontend project directory with Next.js 15 + React 19 - Migrate from pnpm to bun for faster package management - Add TanStack Query + Axios for dynamic data fetching - Create comprehensive Makefile with development commands - Setup API layer with query hooks and error handling - Configure environment variables and bun settings - Add TypeScript type checking and project documentation - Update CLAUDE.md with bun-specific development workflow
This commit is contained in:
81
frontend/components/timeline-stats.tsx
Normal file
81
frontend/components/timeline-stats.tsx
Normal file
@ -0,0 +1,81 @@
|
||||
"use client"
|
||||
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Camera, MapPin, Calendar, Hash } from "lucide-react"
|
||||
|
||||
interface Photo {
|
||||
id: number
|
||||
src: string
|
||||
title: string
|
||||
description: string
|
||||
category: string
|
||||
tags: string[]
|
||||
date: string
|
||||
exif: {
|
||||
camera: string
|
||||
lens: string
|
||||
settings: string
|
||||
location: string
|
||||
}
|
||||
}
|
||||
|
||||
interface TimelineStatsProps {
|
||||
photos: Photo[]
|
||||
}
|
||||
|
||||
export function TimelineStats({ photos }: TimelineStatsProps) {
|
||||
// Calculate statistics
|
||||
const totalPhotos = photos.length
|
||||
const uniqueLocations = new Set(photos.map((p) => p.exif.location)).size
|
||||
const uniqueCameras = new Set(photos.map((p) => p.exif.camera)).size
|
||||
const dateRange =
|
||||
photos.length > 0
|
||||
? {
|
||||
start: new Date(Math.min(...photos.map((p) => new Date(p.date).getTime()))),
|
||||
end: new Date(Math.max(...photos.map((p) => new Date(p.date).getTime()))),
|
||||
}
|
||||
: null
|
||||
|
||||
const stats = [
|
||||
{
|
||||
icon: Camera,
|
||||
label: "总作品数",
|
||||
value: totalPhotos,
|
||||
color: "text-blue-600",
|
||||
},
|
||||
{
|
||||
icon: MapPin,
|
||||
label: "拍摄地点",
|
||||
value: uniqueLocations,
|
||||
color: "text-green-600",
|
||||
},
|
||||
{
|
||||
icon: Hash,
|
||||
label: "使用设备",
|
||||
value: uniqueCameras,
|
||||
color: "text-purple-600",
|
||||
},
|
||||
{
|
||||
icon: Calendar,
|
||||
label: "创作时间",
|
||||
value: dateRange ? `${dateRange.start.getFullYear()}-${dateRange.end.getFullYear()}` : "N/A",
|
||||
color: "text-orange-600",
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<div className="mb-12">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{stats.map((stat, index) => (
|
||||
<Card key={index} className="border-0 shadow-sm">
|
||||
<CardContent className="p-4 text-center">
|
||||
<stat.icon className={`h-6 w-6 mx-auto mb-2 ${stat.color}`} />
|
||||
<div className="text-2xl font-light text-gray-900 mb-1">{stat.value}</div>
|
||||
<div className="text-sm text-gray-500">{stat.label}</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user