Files
crank/apps/ui/src/pages/operation-list/page.tsx
T
2026-03-25 19:27:31 +03:00

90 lines
2.9 KiB
TypeScript

import { useDeferredValue, useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { Link } from "react-router-dom";
import { listOperations } from "../../entities/operation/api";
import { PageSection } from "../../shared/ui/page-section";
export function OperationListPage() {
const [searchTerm, setSearchTerm] = useState("");
const deferredSearchTerm = useDeferredValue(searchTerm);
const operationsQuery = useQuery({
queryKey: ["operations"],
queryFn: listOperations,
});
const filteredItems = useMemo(() => {
const items = operationsQuery.data?.items ?? [];
const normalizedSearchTerm = deferredSearchTerm.trim().toLowerCase();
if (!normalizedSearchTerm) {
return items;
}
return items.filter((item) =>
`${item.name} ${item.display_name} ${item.protocol} ${item.status}`
.toLowerCase()
.includes(normalizedSearchTerm),
);
}, [deferredSearchTerm, operationsQuery.data?.items]);
return (
<div className="page-stack">
<PageSection
title="Operations"
subtitle="Browse draft and published MCP tools exposed through the admin backend."
actions={
<Link className="button-primary" to="/operations/new">
New operation
</Link>
}
>
<label className="field-block field-inline">
<span>Search</span>
<input
type="text"
placeholder="name, protocol or status"
value={searchTerm}
onChange={(event) => setSearchTerm(event.target.value)}
/>
</label>
{operationsQuery.isLoading ? <p>Loading operations...</p> : null}
{operationsQuery.data ? (
<div className="operation-grid">
{filteredItems.map((item) => (
<Link className="operation-card" key={item.id} to={`/operations/${item.id}`}>
<div className="operation-card-top">
<span className={`protocol-pill protocol-${item.protocol}`}>
{item.protocol}
</span>
<span className={`status-pill status-${item.status}`}>
{item.status}
</span>
</div>
<h3>{item.display_name}</h3>
<p className="operation-name">{item.name}</p>
<dl className="key-value-list">
<div>
<dt>Draft</dt>
<dd>{item.current_draft_version}</dd>
</div>
<div>
<dt>Published</dt>
<dd>{item.latest_published_version ?? "none"}</dd>
</div>
<div>
<dt>Updated</dt>
<dd>{new Date(item.updated_at).toLocaleString()}</dd>
</div>
</dl>
</Link>
))}
</div>
) : null}
</PageSection>
</div>
);
}