"use client"; /** * 裝置詳情 Client — /devices/[id] * * 對齊 pages.md §7 + flow-offline-handling.md §5。 * * F6 範圍: * - 頂部:返回 + 裝置名稱 + RemoteDeviceBadge + 操作按鈕 * - 離線 banner(remoteStatus=offline 時顯示) * - 裝置資訊 + 模型狀態 兩欄 Card * - 離線降級:燒錄 / 工作區按鈕 disabled * * F6 不做(保留 stub 或隱藏): * - FlashDialog(雲端版 flash 走 tunnel forward;F8 補完整流程) * - DeviceHealthCard / DeviceConnectionLog(Phase 1 完整化) * - DeviceSettingsCard(alias / notes — Phase 1 接後端) */ import { useEffect } from "react"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { AlertTriangle, ArrowLeft, Trash2 } from "lucide-react"; import { toast } from "sonner"; import { RemoteDeviceBadge } from "@/components/cloud/remote-device-badge"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Separator } from "@/components/ui/separator"; import { Skeleton } from "@/components/ui/skeleton"; import { Spinner } from "@/components/ui/spinner"; import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip"; import { useT } from "@/lib/i18n/context"; import { useDeviceStore } from "@/stores/device-store"; interface DeviceDetailClientProps { id: string; } export function DeviceDetailClient({ id }: DeviceDetailClientProps) { const t = useT(); const router = useRouter(); const selectedDevice = useDeviceStore((s) => s.selectedDevice); const isLoading = useDeviceStore((s) => s.isLoading); const fetchDevice = useDeviceStore((s) => s.fetchDevice); const unpairDevice = useDeviceStore((s) => s.unpairDevice); const isUnpairing = useDeviceStore((s) => s.unpairingId === id); useEffect(() => { if (id) void fetchDevice(id); }, [id, fetchDevice]); async function handleRemove() { const result = await unpairDevice(id); if (result.ok) { toast.success(t("devices.remove.toast.success")); router.push("/devices"); } else { // 用 backend code 對應 i18n;找不到對應 key 時退化到 unknown(對齊 model download toast)。 const key = `devices.remove.error.${result.code}`; const desc = t(key); toast.error(t("devices.remove.error.title"), { description: desc === key ? t("devices.remove.error.unknown") : desc, }); } } if (isLoading && !selectedDevice) { return (
); } // 404 / 未載入(雛形:selectedDevice 為 null 時顯示占位) if (!selectedDevice) { return (

{t("common.loading")}

); } const displayName = selectedDevice.alias || selectedDevice.name; const isOnline = selectedDevice.remoteStatus === "online"; const isOffline = selectedDevice.remoteStatus === "offline"; return (
{/* 離線 banner(flow-offline-handling §5.1) F6 Review Minor #1(F7 修):改走 --warning token(globals.css §1.3 新增), 與 PrototypeBanner / 其他警示 UI 使用相同色系,避免散落 amber-* 硬編碼。 */} {isOffline && (
)}

{displayName}

{selectedDevice.alias && (

{selectedDevice.name}

)}
{isOnline && selectedDevice.flashedModel && ( )} {!isOnline && ( {t("devices.detail.offlineBanner.title")} )} {/* 移除裝置(破壞性操作):destructive 樣式 + 二次確認對話框。 對齊 model 詳細頁刪除 UX(AlertDialog / loading 態 / 成功導頁 / 失敗 toast)。 不論裝置在線與否都可移除——移除是帳號層級的解除配對、與即時連線無關。 */} {t("devices.remove.confirm.title")} {t("devices.remove.confirm.description").replace( "{name}", displayName, )} {t("common.cancel")} {t("devices.remove.confirm.action")}
{t("devices.detail.deviceInfo")} {selectedDevice.id}} /> {selectedDevice.hostName && ( )} {selectedDevice.pairedAt && ( )} {t("devices.detail.modelStatus")} {selectedDevice.flashedModel ? (
{selectedDevice.flashedModel}} />

{t("devices.detail.readyForInference")}

) : (

{t("devices.detail.noModelFlashed")}

)}
); } function InfoRow({ label, value, }: { label: string; value: React.ReactNode; }) { return (
{label} {value}
); }