feat: auto-expand single node and filter-matched nodes
- When only one node exists, expand its list by default - When filtering, auto-expand all nodes with matching results - Use useMemo for filter/grouping performance Backlog: mark Search/Filter as done Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useMemo } from 'react';
|
||||
import StatusBadge from './StatusBadge';
|
||||
|
||||
/**
|
||||
@@ -14,18 +14,25 @@ export default function LXCList({ lxc, onPowerAction, onOpenConsole }) {
|
||||
return <p className="text-text-secondary text-sm">No LXCs found.</p>;
|
||||
}
|
||||
|
||||
const filtered = filter
|
||||
? lxc.filter((item) =>
|
||||
[item.name, item.node, item.status].some((v) => v.toLowerCase().includes(filter.toLowerCase())
|
||||
))
|
||||
: lxc;
|
||||
const filtered = useMemo(() => {
|
||||
if (!filter) return lxc;
|
||||
const q = filter.toLowerCase();
|
||||
return lxc.filter((item) =>
|
||||
[item.name, item.node, item.status].some((v) => v.toLowerCase().includes(q)),
|
||||
);
|
||||
}, [lxc, filter]);
|
||||
|
||||
const groups = Object.groupBy(filtered, (item) => item.node);
|
||||
const groups = useMemo(() => Object.groupBy(filtered, (item) => item.node), [filtered]);
|
||||
const sortedNodes = Object.keys(groups).sort();
|
||||
const autoExpand = sortedNodes.length === 1;
|
||||
|
||||
const total = filtered.length;
|
||||
const running = filtered.filter((item) => item.status === 'running').length;
|
||||
|
||||
function toggleNode(node) {
|
||||
setExpandedNodes((prev) => ({ ...prev, [node]: !prev[node] }));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{filter && (
|
||||
@@ -47,12 +54,12 @@ export default function LXCList({ lxc, onPowerAction, onOpenConsole }) {
|
||||
{sortedNodes.map((node) => {
|
||||
const running = groups[node].filter((item) => item.status === 'running');
|
||||
const stopped = groups[node].filter((item) => item.status !== 'running');
|
||||
const expanded = expandedNodes[node] ?? false;
|
||||
const expanded = autoExpand || expandedNodes[node] ?? false;
|
||||
|
||||
return (
|
||||
<div key={node} className="rounded-xl border border-border-card bg-bg-card">
|
||||
<button
|
||||
onClick={() => setExpandedNodes((prev) => ({ ...prev, [node]: !prev[node] }))}
|
||||
onClick={() => toggleNode(node)}
|
||||
className="flex w-full items-center justify-between rounded-t-xl px-4 py-2.5 text-left"
|
||||
>
|
||||
<span className="text-sm font-medium text-text-primary">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useState, useMemo } from 'react';
|
||||
import StatusBadge from './StatusBadge';
|
||||
|
||||
/**
|
||||
@@ -14,17 +14,25 @@ export default function VMList({ vms, onPowerAction, onOpenConsole }) {
|
||||
return <p className="text-text-secondary text-sm">No VMs found.</p>;
|
||||
}
|
||||
|
||||
const filtered = filter
|
||||
? vms.filter((vm) =>
|
||||
[vm.name, vm.node, vm.status].some((v) => v.toLowerCase().includes(filter.toLowerCase())))
|
||||
: vms;
|
||||
const filtered = useMemo(() => {
|
||||
if (!filter) return vms;
|
||||
const q = filter.toLowerCase();
|
||||
return vms.filter((vm) =>
|
||||
[vm.name, vm.node, vm.status].some((v) => v.toLowerCase().includes(q)),
|
||||
);
|
||||
}, [vms, filter]);
|
||||
|
||||
const groups = Object.groupBy(filtered, (vm) => vm.node);
|
||||
const groups = useMemo(() => Object.groupBy(filtered, (vm) => vm.node), [filtered]);
|
||||
const sortedNodes = Object.keys(groups).sort();
|
||||
const autoExpand = sortedNodes.length === 1;
|
||||
|
||||
const total = filtered.length;
|
||||
const running = filtered.filter((vm) => vm.status === 'running').length;
|
||||
|
||||
function toggleNode(node) {
|
||||
setExpandedNodes((prev) => ({ ...prev, [node]: !prev[node] }));
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{filter && (
|
||||
@@ -46,12 +54,12 @@ export default function VMList({ vms, onPowerAction, onOpenConsole }) {
|
||||
{sortedNodes.map((node) => {
|
||||
const running = groups[node].filter((vm) => vm.status === 'running');
|
||||
const stopped = groups[node].filter((vm) => vm.status !== 'running');
|
||||
const expanded = expandedNodes[node] ?? false;
|
||||
const expanded = autoExpand || expandedNodes[node] ?? false;
|
||||
|
||||
return (
|
||||
<div key={node} className="rounded-xl border border-border-card bg-bg-card">
|
||||
<button
|
||||
onClick={() => setExpandedNodes((prev) => ({ ...prev, [node]: !prev[node] }))}
|
||||
onClick={() => toggleNode(node)}
|
||||
className="flex w-full items-center justify-between rounded-t-xl px-4 py-2.5 text-left"
|
||||
>
|
||||
<span className="text-sm font-medium text-text-primary">
|
||||
|
||||
Reference in New Issue
Block a user