"use client"; import { useCallback, useEffect, useMemo, useState } from "react"; import Link from "next/link"; import { useNodes } from "@/lib/useNodes"; import { c2api } from "@/lib/c2api"; import type { UserRecord } from "@/lib/types"; import { StatusBadge } from "@/components/StatusBadge"; import { Card } from "@/components/ui/Card"; import { ErrorBanner } from "@/components/ui/EmptyState"; import { SkeletonRow } from "@/components/ui/Skeleton"; const UNASSIGNED = "__unassigned__"; export default function NodeOwnershipSettingsPage() { const { nodes, loading: nodesLoading } = useNodes(); const [users, setUsers] = useState([]); const [loadingUsers, setLoadingUsers] = useState(true); const [error, setError] = useState(null); const [savingNodeId, setSavingNodeId] = useState(null); useEffect(() => { c2api.listUsers() .then(setUsers) .catch((e) => setError(e instanceof Error ? e.message : String(e))) .finally(() => setLoadingUsers(false)); }, []); // Ownership (owned_node_ids) is only meaningful for operators elsewhere in the // app (see Admin → Users); admins already have full access regardless. const assignable = useMemo(() => users.filter((u) => u.role === "operator"), [users]); const ownerByNode = useMemo(() => { const map = new Map(); for (const u of users) { if (u.role !== "operator") continue; for (const nodeId of u.owned_node_ids) map.set(nodeId, u); } return map; }, [users]); const reassign = useCallback(async (nodeId: string, newUid: string) => { setSavingNodeId(nodeId); setError(null); try { const prevOwner = ownerByNode.get(nodeId); // Remove from previous owner, if any and different from the new one. if (prevOwner && prevOwner.uid !== newUid) { const next = prevOwner.owned_node_ids.filter((id) => id !== nodeId); await c2api.updateUser(prevOwner.uid, { owned_node_ids: next }); setUsers((all) => all.map((u) => (u.uid === prevOwner.uid ? { ...u, owned_node_ids: next } : u))); } // Add to new owner, if one was selected. if (newUid !== UNASSIGNED) { const newOwner = users.find((u) => u.uid === newUid); if (newOwner && !newOwner.owned_node_ids.includes(nodeId)) { const next = [...newOwner.owned_node_ids, nodeId]; await c2api.updateUser(newOwner.uid, { owned_node_ids: next }); setUsers((all) => all.map((u) => (u.uid === newOwner.uid ? { ...u, owned_node_ids: next } : u))); } } } catch (e) { setError(e instanceof Error ? e.message : String(e)); } finally { setSavingNodeId(null); } }, [ownerByNode, users]); const loading = nodesLoading || loadingUsers; return (

Assign each node to the operator responsible for it. Operators only see and manage the nodes assigned to them here.

{error && } {loading ? ( Array.from({ length: 3 }).map((_, i) => ) ) : nodes.length === 0 ? ( ) : ( nodes.map((n) => { const owner = ownerByNode.get(n.node_id); return ( ); }) )}
Node Status Owner
No nodes registered yet.
{n.name}

{n.node_id}

); }