devices.connecting / devices.disconnecting 不在 TranslationDict type 裡, 改用已存在的 devices.status.connecting + 新增 devices.status.disconnecting。 刪掉上一個 commit 多加的頂層 devices.connecting / disconnecting key。 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
92 lines
3.6 KiB
TypeScript
92 lines
3.6 KiB
TypeScript
'use client';
|
|
|
|
import Link from 'next/link';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Loader2 } from 'lucide-react';
|
|
import { DeviceStatusBadge } from './device-status';
|
|
import { useDeviceStore } from '@/stores/device-store';
|
|
import { useDevicePreferencesStore } from '@/stores/device-preferences-store';
|
|
import { useTranslation } from '@/lib/i18n';
|
|
import type { Device } from '@/types/device';
|
|
|
|
interface DeviceCardProps {
|
|
device: Device;
|
|
isFirstCard?: boolean;
|
|
}
|
|
|
|
export function DeviceCard({ device, isFirstCard }: DeviceCardProps) {
|
|
const { t } = useTranslation();
|
|
const { connectDevice, disconnectDevice, connectingId, disconnectingId } = useDeviceStore();
|
|
const prefs = useDevicePreferencesStore((s) => s.getPreferences(device.id));
|
|
const displayName = prefs.alias || device.name;
|
|
const isConnected = device.status === 'connected' || device.status === 'flashing' || device.status === 'inferencing';
|
|
const isConnecting = connectingId === device.id;
|
|
const isDisconnecting = disconnectingId === device.id;
|
|
const isBusy = isConnecting || isDisconnecting || connectingId !== null || disconnectingId !== null;
|
|
|
|
return (
|
|
<Card>
|
|
<CardHeader className="pb-3">
|
|
<div className="flex items-start justify-between gap-3">
|
|
<div className="min-w-0">
|
|
<CardTitle className="text-base truncate">{displayName}</CardTitle>
|
|
{prefs.alias && (
|
|
<p className="text-xs text-muted-foreground">{device.name}</p>
|
|
)}
|
|
</div>
|
|
<DeviceStatusBadge status={isConnecting ? 'connecting' : device.status} />
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="space-y-3">
|
|
<div className="grid grid-cols-2 gap-2 text-sm">
|
|
<div>
|
|
<p className="text-muted-foreground">{t('devices.type')}</p>
|
|
<p className="font-medium">{device.type}</p>
|
|
</div>
|
|
<div>
|
|
<p className="text-muted-foreground">{t('devices.firmware')}</p>
|
|
<p className="font-medium">{device.firmwareVersion || t('common.na')}</p>
|
|
</div>
|
|
{device.flashedModel && (
|
|
<div className="col-span-2">
|
|
<p className="text-muted-foreground">{t('devices.flashedModel')}</p>
|
|
<p className="font-medium">{device.flashedModel}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="flex gap-2">
|
|
{isConnected ? (
|
|
<>
|
|
<Link href={`/devices/${device.id}`}>
|
|
<Button size="sm" variant="outline" disabled={isBusy} {...(isFirstCard ? { 'data-tour-id': 'manage-device-btn' } : {})}>
|
|
{t('common.manage')}
|
|
</Button>
|
|
</Link>
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
disabled={isBusy}
|
|
onClick={() => disconnectDevice(device.id)}
|
|
>
|
|
{isDisconnecting && <Loader2 className="mr-1 h-3 w-3 animate-spin" />}
|
|
{isDisconnecting ? t('devices.status.disconnecting') : t('common.disconnect')}
|
|
</Button>
|
|
</>
|
|
) : (
|
|
<Button
|
|
size="sm"
|
|
disabled={isBusy}
|
|
onClick={() => connectDevice(device.id)}
|
|
{...(isFirstCard ? { 'data-tour-id': 'connect-device-btn' } : {})}
|
|
>
|
|
{isConnecting && <Loader2 className="mr-1 h-3 w-3 animate-spin" />}
|
|
{isConnecting ? t('devices.status.connecting') : t('common.connect')}
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
}
|