Files
cunkebao_v3/Cunkebao/app/workspace/auto-group/components/device-selection.tsx
笔记本里的永平 5ff15472f5 feat: 本次提交更新内容如下
场景获客列表搞定
2025-07-07 17:08:27 +08:00

281 lines
9.4 KiB
TypeScript

"use client"
import { useState, useEffect } from "react"
import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Badge } from "@/components/ui/badge"
import { Search, Filter, Smartphone, Loader2, CheckCircle2 } from "lucide-react"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { AlertCircle } from "lucide-react"
import { useMobile } from "@/hooks/use-mobile"
interface Device {
id: string
name: string
status: "online" | "offline" | "busy"
lastActive: string
type: "android" | "ios"
number?: string
}
// 模拟设备数据
const mockDevices: Device[] = [
{
id: "device-1",
name: "设备1",
status: "online",
lastActive: "2023-05-15 14:30",
type: "android",
number: "13812345678",
},
{
id: "device-2",
name: "设备2",
status: "offline",
lastActive: "2023-05-14 09:15",
type: "ios",
number: "13987654321",
},
{
id: "device-3",
name: "设备3",
status: "busy",
lastActive: "2023-05-15 11:45",
type: "android",
number: "15612345678",
},
{
id: "device-4",
name: "设备4",
status: "online",
lastActive: "2023-05-15 13:20",
type: "android",
number: "18712345678",
},
{
id: "device-5",
name: "设备5",
status: "online",
lastActive: "2023-05-15 10:05",
type: "ios",
number: "13612345678",
},
]
interface DeviceSelectionProps {
onNext: () => void
onPrevious: () => void
initialSelectedDevices?: string[]
onDevicesChange: (deviceIds: string[]) => void
}
export function DeviceSelection({
onNext,
onPrevious,
initialSelectedDevices = [],
onDevicesChange,
}: DeviceSelectionProps) {
const isMobile = useMobile()
const [searchQuery, setSearchQuery] = useState("")
const [selectedDevices, setSelectedDevices] = useState<string[]>(initialSelectedDevices)
const [devices, setDevices] = useState<Device[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [statusFilter, setStatusFilter] = useState<"all" | "online" | "offline" | "busy">("all")
useEffect(() => {
const fetchDevices = async () => {
setLoading(true)
try {
// 模拟API调用
await new Promise((resolve) => setTimeout(resolve, 1000))
setDevices(mockDevices)
} catch (error) {
console.error("获取设备失败:", error)
setError("获取设备列表失败,请稍后重试")
} finally {
setLoading(false)
}
}
fetchDevices()
}, [])
useEffect(() => {
// 只在设备选择变化时通知父组件,而不是每次渲染
const currentSelection = JSON.stringify(selectedDevices)
const prevSelection = JSON.stringify(initialSelectedDevices)
if (currentSelection !== prevSelection) {
onDevicesChange(selectedDevices)
}
}, [selectedDevices, initialSelectedDevices, onDevicesChange])
const handleDeviceToggle = (deviceId: string) => {
setSelectedDevices((prev) => (prev.includes(deviceId) ? prev.filter((id) => id !== deviceId) : [...prev, deviceId]))
}
const handleSelectAll = () => {
if (selectedDevices.length === filteredDevices.length) {
setSelectedDevices([])
} else {
setSelectedDevices(filteredDevices.map((device) => device.id))
}
}
const filteredDevices = devices.filter((device) => {
const matchesSearch =
device.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
(device.number && device.number.includes(searchQuery))
const matchesStatus = statusFilter === "all" || device.status === statusFilter
return matchesSearch && matchesStatus
})
const getStatusColor = (status: Device["status"]) => {
switch (status) {
case "online":
return "bg-green-500/10 text-green-500"
case "offline":
return "bg-gray-500/10 text-gray-500"
case "busy":
return "bg-yellow-500/10 text-yellow-500"
default:
return "bg-gray-500/10 text-gray-500"
}
}
const getStatusText = (status: Device["status"]) => {
switch (status) {
case "online":
return "在线"
case "offline":
return "离线"
case "busy":
return "忙碌"
default:
return status
}
}
return (
<div className="space-y-6">
<Card>
<CardContent className="pt-6">
<div className="space-y-4">
<div className="flex items-center justify-between">
<Label className="text-base font-medium"></Label>
<div className="flex items-center space-x-2">
<Button variant="outline" size="sm" onClick={handleSelectAll} disabled={filteredDevices.length === 0}>
{selectedDevices.length === filteredDevices.length && filteredDevices.length > 0
? "取消全选"
: "全选"}
</Button>
</div>
</div>
<div className="flex flex-col md:flex-row gap-4">
<div className="relative flex-1">
<Search className="absolute left-3 top-2.5 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索设备名称或手机号"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<div className="flex items-center space-x-2">
<Filter className="h-4 w-4 text-gray-400" />
<select
className="border rounded px-2 py-1 text-sm"
value={statusFilter}
onChange={(e) => setStatusFilter(e.target.value as any)}
>
<option value="all"></option>
<option value="online">线</option>
<option value="offline">线</option>
<option value="busy"></option>
</select>
</div>
</div>
{loading ? (
<div className="flex items-center justify-center py-8">
<Loader2 className="h-6 w-6 animate-spin text-blue-500 mr-2" />
<span>...</span>
</div>
) : error ? (
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertDescription>{error}</AlertDescription>
</Alert>
) : filteredDevices.length === 0 ? (
<div className="text-center py-8 text-gray-500"></div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-3 mt-4">
{filteredDevices.map((device) => (
<div
key={device.id}
className={`border rounded-lg p-3 cursor-pointer transition-colors ${
selectedDevices.includes(device.id)
? "border-blue-500 bg-blue-50"
: "border-gray-200 hover:border-blue-300 hover:bg-blue-50/50"
}`}
onClick={() => handleDeviceToggle(device.id)}
>
<div className="flex items-start justify-between">
<div className="flex items-start space-x-3">
<div className="mt-0.5">
{selectedDevices.includes(device.id) ? (
<CheckCircle2 className="h-5 w-5 text-blue-500" />
) : (
<div className="h-5 w-5 rounded-full border-2 border-gray-300" />
)}
</div>
<div>
<div className="font-medium flex items-center">
{device.name}
<Badge variant="outline" className={`ml-2 ${getStatusColor(device.status)}`}>
{getStatusText(device.status)}
</Badge>
</div>
<div className="text-sm text-gray-500 mt-1">
{device.number && <div>: {device.number}</div>}
<div>: {device.lastActive}</div>
</div>
</div>
</div>
<div className="flex items-center">
<Smartphone
className={`h-5 w-5 ${device.status === "online" ? "text-green-500" : "text-gray-400"}`}
/>
</div>
</div>
</div>
))}
</div>
)}
<div className="flex items-center justify-between pt-2">
<div className="text-sm text-gray-500">
{selectedDevices.length} / {filteredDevices.length}
</div>
</div>
</div>
</CardContent>
</Card>
<div className="flex justify-between">
<Button variant="outline" onClick={onPrevious}>
</Button>
<Button onClick={onNext} className="bg-blue-500 hover:bg-blue-600" disabled={selectedDevices.length === 0}>
</Button>
</div>
</div>
)
}