Canvas: Improve overview tab styling

Change-Id: I1bb656e5d277c0e8c46da5d6be0748f964551049
diff --git a/apps/canvas/front/src/Overview.tsx b/apps/canvas/front/src/Overview.tsx
index 43ba776..aca5bd2 100644
--- a/apps/canvas/front/src/Overview.tsx
+++ b/apps/canvas/front/src/Overview.tsx
@@ -1,14 +1,38 @@
 import React, { useMemo } from "react";
-import { useStateStore } from "@/lib/state";
+import { useStateStore, AppNode, NodeType } from "@/lib/state";
 import { NodeDetails } from "./components/node-details";
 import { Actions } from "./components/actions";
 import { Canvas } from "./components/canvas";
+import { Separator } from "./components/ui/separator";
+
+const sections: { title: string; nodes: NodeType[] }[] = [
+	{
+		title: "Services",
+		nodes: ["app"],
+	},
+	{
+		title: "Databases",
+		nodes: ["postgresql", "mongodb"],
+	},
+	{
+		title: "File systems",
+		nodes: ["volume"],
+	},
+];
 
 export function Overview(): React.ReactNode {
 	const store = useStateStore();
 	const nodes = useMemo(() => {
-		return store.nodes.filter((n) => n.type !== "network" && n.type !== "github");
+		return store.nodes.filter((n) => n.type !== "network" && n.type !== "github" && n.type !== undefined);
 	}, [store.nodes]);
+	const groupedNodes = useMemo(() => {
+		return sections
+			.map((s) => ({
+				title: s.title,
+				nodes: nodes.filter((n) => s.nodes.includes(n.type)),
+			}))
+			.filter((s) => s.nodes.length > 0);
+	}, [nodes]);
 	const isDeployMode = useMemo(() => store.mode === "deploy", [store.mode]);
 	return (
 		<div className="h-full w-full overflow-auto bg-white p-2">
@@ -16,14 +40,41 @@
 				<Actions />
 				<Canvas className="hidden" />
 			</div>
-			<div className="flex flex-wrap gap-4 pt-2">
-				{nodes.map((n) => {
-					return (
-						<div key={n.id} className="h-fit w-fit rounded-lg border-gray-200 border-2 p-2">
-							<NodeDetails node={n} disabled={isDeployMode} showName={true} />
-						</div>
-					);
-				})}
+			<div className="flex flex-col gap-4 pt-2">
+				{groupedNodes.map((s, index) => (
+					<>
+						{index > 0 && <Separator />}
+						<Section key={s.title} title={s.title} nodes={s.nodes} isDeployMode={isDeployMode} />
+					</>
+				))}
+			</div>
+		</div>
+	);
+}
+
+function Section({
+	title,
+	nodes,
+	isDeployMode,
+}: {
+	title: string;
+	nodes: AppNode[];
+	isDeployMode: boolean;
+}): React.ReactNode {
+	if (nodes.length === 0) return null;
+	return (
+		<div className="w-full">
+			<h2 className="text-lg font-semibold mb-2">{title}</h2>
+			<div className="flex flex-wrap gap-4 pl-4">
+				{nodes.map((n) => (
+					<NodeDetails
+						key={n.id}
+						node={n}
+						disabled={isDeployMode}
+						showName={true}
+						className="min-w-[500px] rounded-lg border-gray-200 border-2 p-2"
+					/>
+				))}
 			</div>
 		</div>
 	);
diff --git a/apps/canvas/front/src/components/node-details.tsx b/apps/canvas/front/src/components/node-details.tsx
index 6267ac3..ab1d871 100644
--- a/apps/canvas/front/src/components/node-details.tsx
+++ b/apps/canvas/front/src/components/node-details.tsx
@@ -6,11 +6,13 @@
 import { NodeGithubDetails } from "./node-github";
 import { NodeGatewayTCPDetails } from "./node-gateway-tcp";
 import { NodeDetailsProps } from "@/lib/types";
+import { cn } from "@/lib/utils";
 
-export function NodeDetails(props: NodeDetailsProps) {
+export function NodeDetails(props: NodeDetailsProps & { className?: string }) {
+	const { className, ...rest } = props;
 	return (
-		<div className="px-1 flex flex-col gap-2">
-			<NodeDetailsImpl {...props} />
+		<div className={cn("px-1 flex flex-col gap-2", className)}>
+			<NodeDetailsImpl {...rest} />
 		</div>
 	);
 }