"use client"
import {
CheckCircle2,
Clock,
Filter,
Gamepad2,
Search,
SlidersHorizontal,
Star,
Store,
User,
XCircle,
} from "lucide-react"
import Link from "next/link"
import { useRouter, useSearchParams } from "next/navigation"
import { Suspense, useEffect, useMemo, useState } from "react"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { Separator } from "@/components/ui/separator"
import {
Sheet,
SheetClose,
SheetContent,
SheetDescription,
SheetFooter,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "@/components/ui/sheet"
import { Switch } from "@/components/ui/switch"
import { listGames, listPlayers, listServices, listShops } from "@/lib/api"
import { GameIcon } from "@/lib/game-icons"
import type { Game, Player, Shop } from "@/lib/types"
import { cn } from "@/lib/utils"
function StatusBadge({ status }: { status: Player["status"] }) {
switch (status) {
case "available":
return (
可接单
)
case "busy":
return (
忙碌中
)
case "offline":
return (
离线
)
default:
return null
}
}
function PlayerCard({ player }: { player: Player }) {
const minPrice =
!player.services || player.services.length === 0
? 0
: Math.min(...player.services.map((s) => s.price))
const unit =
!player.services || player.services.length === 0
? "局"
: player.services.reduce((prev, curr) => (prev.price < curr.price ? prev : curr)).unit
return (
{player.user.nickname.slice(0, 2)}
{player.user.nickname}
{player.shopName ? (
{player.shopName}
) : (
个人
)}
{player.rating.toFixed(1)}
接单 {player.totalOrders}
{player.games.slice(0, 3).map((game) => (
{game}
))}
{player.games.length > 3 && (
+{player.games.length - 3}
)}
{player.tags.slice(0, 2).map((tag) => (
{tag}
))}
起
¥{minPrice}
/{unit}
)
}
interface ShopResultItem {
shop: Shop
minPrice: number
unit: string
games: string[]
hasAvailable: boolean
}
function ShopCard({ item }: { item: ShopResultItem }) {
return (
{item.shop.name.slice(0, 2)}
{item.shop.rating.toFixed(1)}
接单 {item.shop.totalOrders}
{item.games.slice(0, 3).map((game) => (
{game}
))}
{item.games.length > 3 && (
+{item.games.length - 3}
)}
起
¥{item.minPrice}
/{item.unit}
)
}
type SearchResult =
| {
type: "player"
id: string
rating: number
orders: number
minPrice: number
item: Player
}
| {
type: "shop"
id: string
rating: number
orders: number
minPrice: number
item: ShopResultItem
}
interface FilterProps {
games: Game[]
selectedGames: string[]
onGameChange: (game: string, checked: boolean) => void
priceRange: { min: string; max: string }
onPriceChange: (type: "min" | "max", value: string) => void
onlyOnline: boolean
onOnlineChange: (checked: boolean) => void
minRating: string
onRatingChange: (value: string) => void
className?: string
}
function FilterSection({
games,
selectedGames,
onGameChange,
priceRange,
onPriceChange,
onlyOnline,
onOnlineChange,
minRating,
onRatingChange,
className,
}: FilterProps) {
return (
游戏类型
{games.map((game) => (
onGameChange(game.name, checked as boolean)}
/>
))}
最低评分
)
}
function SearchPageContent() {
const searchParams = useSearchParams()
const router = useRouter()
const games = listGames()
const players = listPlayers()
const services = listServices()
const shops = listShops()
const [searchQuery, setSearchQuery] = useState(searchParams.get("q") || "")
const [selectedGames, setSelectedGames] = useState(() => {
const game = searchParams.get("game")
return game ? [game] : []
})
const [priceRange, setPriceRange] = useState<{ min: string; max: string }>({
min: "",
max: "",
})
const [onlyOnline, setOnlyOnline] = useState(false)
const [minRating, setMinRating] = useState("0")
const [sortBy, setSortBy] = useState("composite")
const [visibleCount, setVisibleCount] = useState(12)
useEffect(() => {
const timer = setTimeout(() => {
const params = new URLSearchParams(searchParams.toString())
if (searchQuery) {
params.set("q", searchQuery)
} else {
params.delete("q")
}
router.replace(`/search?${params.toString()}`, { scroll: false })
}, 500)
return () => clearTimeout(timer)
}, [searchQuery, router, searchParams])
const handleGameChange = (game: string, checked: boolean) => {
setSelectedGames((prev) => (checked ? [...prev, game] : prev.filter((g) => g !== game)))
}
const handlePriceChange = (type: "min" | "max", value: string) => {
setPriceRange((prev) => ({ ...prev, [type]: value }))
}
const shopResultItems = useMemo(() => {
return shops.map((shop) => {
const shopPlayers = players.filter((player) => player.shopId === shop.id)
const playerIds = new Set(shopPlayers.map((player) => player.id))
const shopServices = services.filter((service) => playerIds.has(service.playerId))
const minPrice =
shopServices.length > 0 ? Math.min(...shopServices.map((service) => service.price)) : 0
const unit =
shopServices.length > 0
? shopServices.reduce((prev, curr) => (prev.price < curr.price ? prev : curr)).unit
: "局"
const shopGames = [...new Set(shopServices.map((service) => service.gameName))]
const hasAvailable = shopPlayers.some((player) => player.status === "available")
return {
shop,
minPrice,
unit,
games: shopGames,
hasAvailable,
}
})
}, [players, services, shops])
const filteredPlayers = useMemo(() => {
return players.filter((player) => {
if (searchQuery) {
const query = searchQuery.toLowerCase()
const matchName = player.user.nickname.toLowerCase().includes(query)
const matchTags = player.tags.some((tag) => tag.toLowerCase().includes(query))
const matchGames = player.games.some((game) => game.toLowerCase().includes(query))
if (!matchName && !matchTags && !matchGames) return false
}
if (selectedGames.length > 0) {
const hasGame = player.games.some((game) => selectedGames.includes(game))
if (!hasGame) return false
}
const minP = priceRange.min ? Number(priceRange.min) : 0
const maxP = priceRange.max ? Number(priceRange.max) : Infinity
const playerMinPrice = Math.min(...player.services.map((s) => s.price))
if (playerMinPrice < minP) return false
if (priceRange.max && playerMinPrice > maxP) return false
if (onlyOnline && player.status !== "available") return false
if (player.rating < Number(minRating)) return false
return true
})
}, [minRating, onlyOnline, players, priceRange, searchQuery, selectedGames])
const filteredShops = useMemo(() => {
return shopResultItems.filter((item) => {
if (searchQuery) {
const query = searchQuery.toLowerCase()
const matchName = item.shop.name.toLowerCase().includes(query)
const matchDescription = item.shop.description.toLowerCase().includes(query)
const matchGames = item.games.some((game) => game.toLowerCase().includes(query))
if (!matchName && !matchDescription && !matchGames) return false
}
if (selectedGames.length > 0) {
const hasGame = item.games.some((game) => selectedGames.includes(game))
if (!hasGame) return false
}
const minP = priceRange.min ? Number(priceRange.min) : 0
const maxP = priceRange.max ? Number(priceRange.max) : Infinity
if (item.minPrice < minP) return false
if (priceRange.max && item.minPrice > maxP) return false
if (onlyOnline && !item.hasAvailable) return false
if (item.shop.rating < Number(minRating)) return false
return true
})
}, [shopResultItems, searchQuery, selectedGames, priceRange, onlyOnline, minRating])
const sortedResults = useMemo(() => {
const playerResults: SearchResult[] = filteredPlayers.map((player) => ({
type: "player",
id: player.id,
rating: player.rating,
orders: player.totalOrders,
minPrice: Math.min(...player.services.map((service) => service.price)),
item: player,
}))
const shopResults: SearchResult[] = filteredShops.map((item) => ({
type: "shop",
id: item.shop.id,
rating: item.shop.rating,
orders: item.shop.totalOrders,
minPrice: item.minPrice,
item,
}))
return [...playerResults, ...shopResults].sort((a, b) => {
switch (sortBy) {
case "rating":
return b.rating - a.rating
case "price_asc":
return a.minPrice - b.minPrice
case "price_desc":
return b.minPrice - a.minPrice
case "orders":
return b.orders - a.orders
case "composite": {
const scoreA = a.rating * Math.log10(a.orders + 1)
const scoreB = b.rating * Math.log10(b.orders + 1)
return scoreB - scoreA
}
default:
return 0
}
})
}, [filteredPlayers, filteredShops, sortBy])
const displayedResults = sortedResults.slice(0, visibleCount)
return (
共找到 {sortedResults.length} 位陪玩
{displayedResults.length > 0 ? (
{displayedResults.map((result) => (
{result.type === "player" ? (
) : (
)}
))}
) : (
未找到相关陪玩
尝试调整筛选条件或更换搜索关键词
)}
{sortedResults.length > visibleCount && (
)}
)
}
export default function SearchPage() {
return (
}
>
)
}