feat: implement ui v1
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user