'use client' import { useEffect, useMemo, useState } from 'react' import Image from 'next/image' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { WxUserDetailResponse } from '@/types' interface WxUserDetailDialogProps { open: boolean onOpenChange: (open: boolean) => void user: WxUserDetailResponse | null | undefined isSaving?: boolean onSave: (levelIds: string[]) => Promise onClearAll: () => Promise } export function WxUserDetailDialog({ open, onOpenChange, user, isSaving = false, onSave, onClearAll, }: WxUserDetailDialogProps) { const [selectedLevelIds, setSelectedLevelIds] = useState>(new Set()) const [keyword, setKeyword] = useState('') useEffect(() => { if (!open || !user) { setSelectedLevelIds(new Set()) setKeyword('') return } setSelectedLevelIds( new Set( user.assignedLevels .filter((level) => level.completed) .map((level) => level.id) ) ) }, [open, user]) const filteredLevels = useMemo(() => { if (!user) return [] const search = keyword.trim().toLowerCase() if (!search) return user.assignedLevels return user.assignedLevels.filter((level) => { return ( String(level.sortOrder).includes(search) || level.answer.toLowerCase().includes(search) || level.id.toLowerCase().includes(search) ) }) }, [keyword, user]) const selectedCount = selectedLevelIds.size const totalLevels = user?.assignedLevels.length || 0 const completedCount = user?.assignedLevels.filter((level) => level.completed).length || 0 const hasChanges = useMemo(() => { if (!user) return false const original = new Set( user.assignedLevels.filter((level) => level.completed).map((level) => level.id) ) if (original.size !== selectedLevelIds.size) return true for (const id of Array.from(selectedLevelIds)) { if (!original.has(id)) return true } return false }, [selectedLevelIds, user]) if (!user) return null const toggleLevel = (levelId: string, checked: boolean) => { const next = new Set(selectedLevelIds) if (checked) { next.add(levelId) } else { next.delete(levelId) } setSelectedLevelIds(next) } const handleSelectAllVisible = () => { const next = new Set(selectedLevelIds) for (const level of filteredLevels) { next.add(level.id) } setSelectedLevelIds(next) } const handleClearVisible = () => { const next = new Set(selectedLevelIds) for (const level of filteredLevels) { next.delete(level.id) } setSelectedLevelIds(next) } const handleSave = async () => { await onSave(Array.from(selectedLevelIds)) } return (
用户通关配置 查看用户信息,并直接维护已经通关的关卡集合。
{user.avatarUrl ? ( {user.nickname ) : (
{user.nickname?.[0]?.toUpperCase() || 'U'}
)}

{user.nickname || '匿名用户'}

注册于 {new Date(user.createdAt).toLocaleString('zh-CN')}

已通关

{completedCount}

当前选中

{selectedCount}

OpenID

{user.openid}

体力

{user.stamina}

关卡总数

{totalLevels}

关卡列表

勾选后保存,即可定义该用户已通关哪些关卡。

setKeyword(e.target.value)} placeholder="搜索关卡序号、答案或 ID" className="sm:max-w-xs" />
{filteredLevels.length === 0 ? (
没有匹配的关卡
) : ( {filteredLevels.map((level) => { const checked = selectedLevelIds.has(level.id) return ( ) })}
选择 序号 答案 状态 通关时间
toggleLevel(level.id, e.target.checked)} disabled={isSaving} className="h-4 w-4 rounded border-slate-300" /> #{level.sortOrder}
{level.answer}
{level.id}
{checked ? '已通关' : '未通关'} {level.completedAt ? new Date(level.completedAt).toLocaleString('zh-CN') : '-'}
)}
) }