mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 02:00:56 +00:00
203 lines
7.0 KiB
TypeScript
203 lines
7.0 KiB
TypeScript
import type { OnChangeFn, PaginationState } from "@edr/ui-common";
|
|
import { Card, Group, SimpleGrid, Stack, Text } from "@mantine/core";
|
|
import { Badge } from "@mantine/core";
|
|
|
|
import type { FleetResourceConfig } from "@/pages/fleet/config/resources";
|
|
import type { FleetRecord } from "@/services/fleet/fleet.service";
|
|
|
|
import FleetRecordActions from "./FleetRecordActions";
|
|
import { cardInitials, resolveFleetCardPresentation } from "./fleetCardMeta";
|
|
import { formatFleetCell } from "./fleetFormat";
|
|
import RuleEngineListFooter from "../ruleEngine/RuleEngineListFooter";
|
|
|
|
export interface FleetCardGridProps {
|
|
config: FleetResourceConfig;
|
|
rows: FleetRecord[];
|
|
status: "loading" | "error" | "success";
|
|
emptyMessage: string;
|
|
pagination: PaginationState;
|
|
pageCount: number;
|
|
totalCount: number;
|
|
onPaginationChange: OnChangeFn<PaginationState>;
|
|
onEdit: (record: FleetRecord) => void;
|
|
onRemove: (record: FleetRecord) => void;
|
|
}
|
|
|
|
const FleetCardGrid = ({
|
|
config,
|
|
rows,
|
|
status,
|
|
emptyMessage,
|
|
pagination,
|
|
pageCount,
|
|
totalCount,
|
|
onPaginationChange,
|
|
onEdit,
|
|
onRemove,
|
|
}: FleetCardGridProps) => {
|
|
const presentation = resolveFleetCardPresentation(config);
|
|
|
|
if (status === "loading") {
|
|
return (
|
|
<Text size="sm" c="dimmed" py="xl" ta="center">
|
|
Loading…
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
if (status === "error") {
|
|
return (
|
|
<Text size="sm" c="red" py="xl" ta="center">
|
|
Failed to load data
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
if (!rows.length) {
|
|
return (
|
|
<Text size="sm" c="dimmed" py="xl" ta="center">
|
|
{emptyMessage}
|
|
</Text>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Stack gap={0}>
|
|
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="md" p="md">
|
|
{rows.map((record) => {
|
|
const title = String(
|
|
(record as unknown as Record<string, unknown>)[presentation.titleKey] ?? config.entityLabel,
|
|
);
|
|
const code = presentation.codeKey
|
|
? (record as unknown as Record<string, unknown>)[presentation.codeKey]
|
|
: null;
|
|
const subtitle = presentation.subtitleKey
|
|
? (record as unknown as Record<string, unknown>)[presentation.subtitleKey]
|
|
: null;
|
|
const statusValue = presentation.statusKey
|
|
? (record as unknown as Record<string, unknown>)[presentation.statusKey]
|
|
: null;
|
|
|
|
return (
|
|
<Card
|
|
key={String((record as { id: string }).id)}
|
|
radius="lg"
|
|
padding="lg"
|
|
withBorder
|
|
style={{
|
|
borderColor: "var(--mantine-color-gray-2)",
|
|
transition: "all 200ms ease",
|
|
cursor: "pointer",
|
|
}}
|
|
className="hover:shadow-md hover:border-green-3"
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.boxShadow =
|
|
"0 4px 12px rgba(0, 0, 0, 0.08)";
|
|
e.currentTarget.style.borderColor =
|
|
"var(--mantine-color-green-3)";
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.boxShadow = "none";
|
|
e.currentTarget.style.borderColor =
|
|
"var(--mantine-color-gray-2)";
|
|
}}
|
|
>
|
|
<Stack gap="md">
|
|
<Group justify="space-between" align="flex-start" wrap="nowrap">
|
|
<Group gap="sm" wrap="nowrap" style={{ flex: 1 }}>
|
|
<div
|
|
style={{
|
|
width: 48,
|
|
height: 48,
|
|
borderRadius: 12,
|
|
background: "linear-gradient(135deg, var(--mantine-color-green-0), var(--mantine-color-green-1))",
|
|
color: "var(--mantine-color-green-8)",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
fontWeight: 700,
|
|
fontSize: 16,
|
|
flexShrink: 0,
|
|
}}
|
|
>
|
|
{cardInitials(title)}
|
|
</div>
|
|
<Stack gap={4} style={{ flex: 1, minWidth: 0 }}>
|
|
<Text fw={700} size="sm" lineClamp={1} c="dark">
|
|
{title || "—"}
|
|
</Text>
|
|
{subtitle != null && subtitle !== "" ? (
|
|
<Text size="xs" c="dimmed" lineClamp={1}>
|
|
{presentation.subtitleKey === "currentYard" ||
|
|
presentation.subtitleKey === "currentYardId"
|
|
? formatFleetCell(subtitle, "entityLabel", presentation.subtitleKey)
|
|
: String(subtitle)}
|
|
</Text>
|
|
) : null}
|
|
</Stack>
|
|
</Group>
|
|
{code != null && code !== "" ? (
|
|
<Badge variant="filled" color="green" size="sm" radius="md" style={{ flexShrink: 0 }}>
|
|
{String(code)}
|
|
</Badge>
|
|
) : null}
|
|
</Group>
|
|
|
|
<Stack gap={6}>
|
|
{config.columns
|
|
.filter(
|
|
(col) =>
|
|
col.accessorKey !== presentation.titleKey &&
|
|
col.accessorKey !== presentation.codeKey &&
|
|
col.accessorKey !== presentation.statusKey,
|
|
)
|
|
.slice(0, 4)
|
|
.map((col) => (
|
|
<Group key={col.id} justify="space-between" gap="xs">
|
|
<Text size="xs" c="dimmed">
|
|
{col.header}
|
|
</Text>
|
|
<Text size="xs" fw={500}>
|
|
{formatFleetCell(
|
|
(record as unknown as Record<string, unknown>)[col.accessorKey],
|
|
col.format,
|
|
col.accessorKey,
|
|
)}
|
|
</Text>
|
|
</Group>
|
|
))}
|
|
{statusValue != null ? (
|
|
<Group justify="space-between" gap="xs">
|
|
<Text size="xs" c="dimmed">
|
|
Status
|
|
</Text>
|
|
{formatFleetCell(statusValue, "statusBadge")}
|
|
</Group>
|
|
) : null}
|
|
</Stack>
|
|
|
|
<FleetRecordActions
|
|
record={record}
|
|
config={config}
|
|
layout="compact"
|
|
onEdit={onEdit}
|
|
onRemove={onRemove}
|
|
/>
|
|
</Stack>
|
|
</Card>
|
|
);
|
|
})}
|
|
</SimpleGrid>
|
|
<RuleEngineListFooter
|
|
pagination={pagination}
|
|
pageCount={pageCount}
|
|
totalCount={totalCount}
|
|
itemLabel={config.entityLabel.toLowerCase() + "s"}
|
|
onPaginationChange={onPaginationChange}
|
|
/>
|
|
</Stack>
|
|
);
|
|
};
|
|
|
|
export default FleetCardGrid;
|