mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 04:08:11 +00:00
changes
This commit is contained in:
@@ -4,16 +4,14 @@ import { useAuth } from "@/auth/useAuth";
|
||||
import { canAccessRuleEngineResource } from "@/lib/permissions";
|
||||
import type { ColumnDef } from "@tanstack/react-table";
|
||||
import { Loader2 } from "lucide-react";
|
||||
import { Card, Button, Modal, Stack, Group, Text, List } from "@mantine/core";
|
||||
|
||||
import RuleEngineCardGrid from "@/components/ruleEngine/RuleEngineCardGrid";
|
||||
import RuleEngineFormDialog from "@/components/ruleEngine/RuleEngineFormDialog";
|
||||
import RuleEngineRecordActions from "@/components/ruleEngine/RuleEngineRecordActions";
|
||||
import RuleEngineToolbar from "@/components/ruleEngine/RuleEngineToolbar";
|
||||
import { formatCell } from "@/components/ruleEngine/ruleEngineFormat";
|
||||
import {
|
||||
ruleEngineSurface,
|
||||
ruleEngineTable,
|
||||
} from "@/components/ruleEngine/ruleEngineStyles";
|
||||
import { ruleEngineTable } from "@/components/ruleEngine/ruleEngineStyles";
|
||||
import { useRuleEngineViewMode } from "@/components/ruleEngine/useRuleEngineViewMode";
|
||||
import {
|
||||
DEFAULT_CONFIGURATION_SLUG,
|
||||
@@ -34,15 +32,8 @@ import {
|
||||
} from "@/hooks/rule-engine/useRuleEngine";
|
||||
import type { RuleEngineRecord } from "@/types/rule-engine";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
DataTable,
|
||||
DataTableFooter,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
getCoreRowModel,
|
||||
usePagination,
|
||||
useReactTable,
|
||||
@@ -177,15 +168,6 @@ const RuleEngineResourcePage = () => {
|
||||
[filteredRows.length, meta?.total, pageCount, pagination.pageIndex, pagination.pageSize],
|
||||
);
|
||||
|
||||
const cardTable = useReactTable({
|
||||
data: filteredRows,
|
||||
columns: [] as ColumnDef<RuleEngineRecord>[],
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
manualPagination: true,
|
||||
pageCount,
|
||||
state: { pagination },
|
||||
onPaginationChange: setPagination,
|
||||
});
|
||||
|
||||
const handleApproveRate = useCallback(
|
||||
(record: RuleEngineRecord) => {
|
||||
@@ -284,9 +266,9 @@ const RuleEngineResourcePage = () => {
|
||||
const itemLabel = config.label.toLowerCase();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Card className={ruleEngineSurface.pageCard}>
|
||||
<div className={ruleEngineSurface.pageCardToolbar}>
|
||||
<Stack gap="lg">
|
||||
<Card p="lg" radius="lg" withBorder style={{ background: "white", boxShadow: "0 1px 3px rgba(0, 0, 0, 0.05)" }}>
|
||||
<Stack gap="md">
|
||||
<RuleEngineToolbar
|
||||
search={search}
|
||||
onSearchChange={(v) => {
|
||||
@@ -299,65 +281,64 @@ const RuleEngineResourcePage = () => {
|
||||
viewMode={viewMode}
|
||||
onViewModeChange={setViewMode}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{viewMode === "table" ? (
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={filteredRows}
|
||||
status={tableStatus}
|
||||
error={
|
||||
isError
|
||||
? {
|
||||
message: "Failed to load data",
|
||||
description:
|
||||
error instanceof Error ? error.message : "Unknown error",
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
emptyMessage={`No ${itemLabel} found.`}
|
||||
pagination={paginationState}
|
||||
tableOptions={{
|
||||
manualPagination: true,
|
||||
pageCount,
|
||||
state: { pagination },
|
||||
onPaginationChange: setPagination,
|
||||
}}
|
||||
containerClassName="border-0 shadow-none [&_[data-slot=table-row]]:border-border"
|
||||
footerClassName="border-t border-border bg-card"
|
||||
footer={({ table, pagination: footerPagination }) => (
|
||||
<DataTableFooter
|
||||
table={table}
|
||||
pagination={footerPagination}
|
||||
options={{
|
||||
labels: {
|
||||
showing: "Showing",
|
||||
ofLabel: "of",
|
||||
items: itemLabel,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<RuleEngineCardGrid
|
||||
config={config}
|
||||
rows={filteredRows}
|
||||
status={tableStatus}
|
||||
emptyMessage={`No ${itemLabel} found.`}
|
||||
itemLabel={itemLabel}
|
||||
table={cardTable}
|
||||
pagination={paginationState}
|
||||
readOnly={!canManage}
|
||||
onEdit={canManage ? openEdit : undefined}
|
||||
onDelete={canManage ? setDeleteTarget : undefined}
|
||||
onViewChain={
|
||||
config.slug === "approval-rules" ? () => setChainOpen(true) : undefined
|
||||
}
|
||||
onSubmitRate={canManage ? (id) => submit.mutate(id) : undefined}
|
||||
onApproveRate={canManage ? handleApproveRate : undefined}
|
||||
/>
|
||||
)}
|
||||
{viewMode === "table" ? (
|
||||
<DataTable
|
||||
columns={columns}
|
||||
data={filteredRows}
|
||||
status={tableStatus}
|
||||
error={
|
||||
isError
|
||||
? {
|
||||
message: "Failed to load data",
|
||||
description:
|
||||
error instanceof Error ? error.message : "Unknown error",
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
emptyMessage={`No ${itemLabel} found.`}
|
||||
pagination={paginationState}
|
||||
tableOptions={{
|
||||
manualPagination: true,
|
||||
pageCount,
|
||||
state: { pagination },
|
||||
onPaginationChange: setPagination,
|
||||
}}
|
||||
containerClassName="border-0 shadow-none [&_[data-slot=table-row]]:border-border"
|
||||
footerClassName="border-t border-border bg-card"
|
||||
footer={({ table, pagination: footerPagination }) => (
|
||||
<DataTableFooter
|
||||
table={table}
|
||||
pagination={footerPagination}
|
||||
options={{
|
||||
labels: {
|
||||
showing: "Showing",
|
||||
ofLabel: "of",
|
||||
items: itemLabel,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<RuleEngineCardGrid
|
||||
config={config}
|
||||
rows={filteredRows}
|
||||
status={tableStatus}
|
||||
emptyMessage={`No ${itemLabel} found.`}
|
||||
itemLabel={itemLabel}
|
||||
pagination={paginationState}
|
||||
readOnly={!canManage}
|
||||
onEdit={canManage ? openEdit : undefined}
|
||||
onDelete={canManage ? setDeleteTarget : undefined}
|
||||
onViewChain={
|
||||
config.slug === "approval-rules" ? () => setChainOpen(true) : undefined
|
||||
}
|
||||
onSubmitRate={canManage ? (id) => submit.mutate(id) : undefined}
|
||||
onApproveRate={canManage ? handleApproveRate : undefined}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<RuleEngineFormDialog
|
||||
@@ -380,20 +361,23 @@ const RuleEngineResourcePage = () => {
|
||||
onSubmit={handleFormSubmit}
|
||||
/>
|
||||
|
||||
<Dialog open={Boolean(deleteTarget)} onOpenChange={(o) => !o && setDeleteTarget(null)}>
|
||||
<DialogContent className={ruleEngineSurface.dialogSm}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete record?</DialogTitle>
|
||||
<DialogDescription>
|
||||
This will soft-delete the selected {config.label.toLowerCase()} record.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={() => setDeleteTarget(null)}>
|
||||
<Modal
|
||||
opened={Boolean(deleteTarget)}
|
||||
onClose={() => setDeleteTarget(null)}
|
||||
title="Delete record?"
|
||||
centered
|
||||
size="sm"
|
||||
>
|
||||
<Stack gap="md">
|
||||
<Text size="sm">
|
||||
This will soft-delete the selected {config.label.toLowerCase()} record.
|
||||
</Text>
|
||||
<Group justify="flex-end" gap="sm">
|
||||
<Button variant="default" onClick={() => setDeleteTarget(null)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
color="red"
|
||||
disabled={remove.isPending}
|
||||
onClick={() => {
|
||||
if (!deleteTarget) return;
|
||||
@@ -401,47 +385,51 @@ const RuleEngineResourcePage = () => {
|
||||
onSuccess: () => setDeleteTarget(null),
|
||||
});
|
||||
}}
|
||||
leftSection={remove.isPending && <Loader2 size={16} />}
|
||||
>
|
||||
{remove.isPending ? <Loader2 className="h-4 w-4 animate-spin" /> : "Delete"}
|
||||
{remove.isPending ? "Deleting..." : "Delete"}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Modal>
|
||||
|
||||
<Dialog open={chainOpen} onOpenChange={setChainOpen}>
|
||||
<DialogContent className={ruleEngineSurface.dialog}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Approval chain</DialogTitle>
|
||||
<DialogDescription>Configured approval steps from the API.</DialogDescription>
|
||||
</DialogHeader>
|
||||
<Modal
|
||||
opened={chainOpen}
|
||||
onClose={() => setChainOpen(false)}
|
||||
title="Approval chain"
|
||||
centered
|
||||
size="md"
|
||||
>
|
||||
<Stack gap="md">
|
||||
{chainLoading ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
</div>
|
||||
<Group justify="center" p="xl">
|
||||
<Loader2 size={32} style={{ animation: "spin 1s linear infinite" }} />
|
||||
</Group>
|
||||
) : (
|
||||
<ol className="space-y-3">
|
||||
<>
|
||||
{(chainData ?? []).length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">No approval rules configured.</p>
|
||||
<Text size="sm" c="dimmed">No approval rules configured.</Text>
|
||||
) : (
|
||||
(chainData ?? []).map((step, index) => (
|
||||
<li
|
||||
key={String(step.id ?? index)}
|
||||
className="rounded-md border border-border bg-muted/30 px-4 py-3 text-sm"
|
||||
>
|
||||
<p className="font-medium text-foreground">
|
||||
Step {String(step.stepOrder ?? index + 1)}: {String(step.actionLabel ?? "")}
|
||||
</p>
|
||||
<p className="text-muted-foreground">
|
||||
Role: {String(step.requiredRole ?? "—")}
|
||||
</p>
|
||||
</li>
|
||||
))
|
||||
<List spacing="md">
|
||||
{(chainData ?? []).map((step, index) => (
|
||||
<List.Item key={String(step.id ?? index)}>
|
||||
<Stack gap="xs">
|
||||
<Text size="sm" fw={500}>
|
||||
Step {String(step.stepOrder ?? index + 1)}: {String(step.actionLabel ?? "")}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed">
|
||||
Role: {String(step.requiredRole ?? "—")}
|
||||
</Text>
|
||||
</Stack>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</ol>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</Stack>
|
||||
</Modal>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -270,6 +270,8 @@ export const RULE_ENGINE_RESOURCES: RuleEngineResourceConfig[] = [
|
||||
category: "rules",
|
||||
subtitle: "VGM limits by container and trade direction",
|
||||
searchPlaceholder: "Search weight limit rules...",
|
||||
cardTitleKey: "containerType",
|
||||
cardSubtitleKey: "tradeDirection",
|
||||
columns: [
|
||||
{
|
||||
id: "containerType",
|
||||
|
||||
Reference in New Issue
Block a user