Files
bifrost/ui/app/workspace/routing-rules/views/routingRulesView.tsx
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

139 lines
4.3 KiB
TypeScript

/**
* Routing Rules View
* Main orchestrator component for routing rules management
*/
import { RbacOperation, RbacResource, useRbac } from "@/app/_fallbacks/enterprise/lib/contexts/rbacContext";
import { Button } from "@/components/ui/button";
import { useDebouncedValue } from "@/hooks/useDebounce";
import { useGetRoutingRulesQuery } from "@/lib/store/apis/routingRulesApi";
import { RoutingRule } from "@/lib/types/routingRules";
import { GitBranch, Plus } from "lucide-react";
import { Link } from "@tanstack/react-router";
import { useEffect, useState } from "react";
import { RoutingRuleInfoSheet } from "./routingRuleInfoSheet";
import { RoutingRuleSheet } from "./routingRuleSheet";
import { RoutingRulesEmptyState } from "./routingRulesEmptyState";
import { RoutingRulesTable } from "./routingRulesTable";
const POLLING_INTERVAL = 5000;
const PAGE_SIZE = 25;
export function RoutingRulesView() {
const [dialogOpen, setDialogOpen] = useState(false);
const [editingRule, setEditingRule] = useState<RoutingRule | null>(null);
const [infoSheetOpen, setInfoSheetOpen] = useState(false);
const [selectedRule, setSelectedRule] = useState<RoutingRule | null>(null);
const [search, setSearch] = useState("");
const [offset, setOffset] = useState(0);
const debouncedSearch = useDebouncedValue(search, 300);
// Reset to first page when search changes
useEffect(() => {
setOffset(0);
}, [debouncedSearch]);
// Permissions
const canCreate = useRbac(RbacResource.RoutingRules, RbacOperation.Create);
const canDelete = useRbac(RbacResource.RoutingRules, RbacOperation.Delete);
// API
const { data: rulesData, isLoading } = useGetRoutingRulesQuery(
{
limit: PAGE_SIZE,
offset,
search: debouncedSearch || undefined,
},
{
pollingInterval: POLLING_INTERVAL,
},
);
const rules = rulesData?.rules || [];
const totalCount = rulesData?.total_count || 0;
// Snap offset back when total shrinks past current page (e.g. delete last item on last page)
useEffect(() => {
if (!rulesData || offset < totalCount) return;
setOffset(totalCount === 0 ? 0 : Math.floor((totalCount - 1) / PAGE_SIZE) * PAGE_SIZE);
}, [totalCount, offset]);
const handleCreateNew = () => {
setEditingRule(null);
setDialogOpen(true);
};
const handleEdit = (rule: RoutingRule) => {
setEditingRule(rule);
setDialogOpen(true);
};
const handleRowClick = (rule: RoutingRule) => {
setSelectedRule(rule);
setInfoSheetOpen(true);
};
const handleDialogOpenChange = (open: boolean) => {
setDialogOpen(open);
if (!open) {
setEditingRule(null);
}
};
const hasActiveFilters = debouncedSearch;
// True empty state: no rules at all (not just filtered to zero)
if (!isLoading && totalCount === 0 && !hasActiveFilters) {
return (
<>
<RoutingRulesEmptyState onAddClick={handleCreateNew} canCreate={canCreate} />
<RoutingRuleSheet open={dialogOpen} onOpenChange={handleDialogOpenChange} editingRule={editingRule} />
</>
);
}
return (
<div className="space-y-4">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-foreground text-lg font-semibold">Routing Rules</h1>
<p className="text-muted-foreground text-sm">Manage CEL-based routing rules for intelligent request routing across providers</p>
</div>
<div className="flex items-center gap-2">
<Button variant="outline" size="sm" asChild className="gap-2">
<Link to="/workspace/routing-rules/tree">
<GitBranch className="h-4 w-4" />
<span className="hidden sm:inline">View Tree</span>
</Link>
</Button>
{canCreate && (
<Button data-testid="create-routing-rule-btn" onClick={handleCreateNew} disabled={isLoading} className="gap-2">
<Plus className="h-4 w-4" />
<span className="hidden sm:inline">New Rule</span>
</Button>
)}
</div>
</div>
<RoutingRulesTable
rules={rules}
totalCount={totalCount}
isLoading={isLoading}
onEdit={handleEdit}
onRowClick={handleRowClick}
canDelete={canDelete}
search={search}
onSearchChange={setSearch}
offset={offset}
limit={PAGE_SIZE}
onOffsetChange={setOffset}
/>
<RoutingRuleSheet open={dialogOpen} onOpenChange={handleDialogOpenChange} editingRule={editingRule} />
<RoutingRuleInfoSheet rule={selectedRule} open={infoSheetOpen} onOpenChange={setInfoSheetOpen} />
</div>
);
}