Skip to content

Commit

Permalink
Merge pull request #294 from tylerslaton/tools-grid
Browse files Browse the repository at this point in the history
feat: replace tool table with a tool grid
  • Loading branch information
tylerslaton authored Oct 23, 2024
2 parents b351aea + 0db967e commit 04e8783
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 106 deletions.
32 changes: 32 additions & 0 deletions ui/admin/app/components/TruncatedText.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { TypographyP } from "~/components/Typography";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "~/components/ui/tooltip";

export function TruncatedText({
content,
maxWidth,
}: {
content: string;
maxWidth: string;
}) {
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className={`${maxWidth} truncate cursor-pointer`}>
<TypographyP className="truncate">
{content}
</TypographyP>
</div>
</TooltipTrigger>
<TooltipContent>
<p>{content}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
);
}
104 changes: 0 additions & 104 deletions ui/admin/app/components/tools/ToolTable.tsx

This file was deleted.

34 changes: 34 additions & 0 deletions ui/admin/app/components/tools/toolGrid/CategoryHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Folder } from "lucide-react";

import { ToolReference } from "~/lib/model/toolReferences";

import { TypographyH2 } from "~/components/Typography";
import { ToolIcon } from "~/components/tools/ToolIcon";
import { Badge } from "~/components/ui/badge";

interface CategoryHeaderProps {
category: string;
tools: ToolReference[];
}

export function CategoryHeader({ category, tools }: CategoryHeaderProps) {
return (
<div className="flex items-center space-x-4">
<div className="w-10 h-10 flex items-center justify-center bg-muted rounded-full mb-2 border">
{tools[0]?.metadata?.icon ? (
<ToolIcon
className="w-6 h-6"
name={tools[0].name}
icon={tools[0].metadata.icon}
/>
) : (
<Folder className="w-6 h-6" />
)}
</div>
<TypographyH2 className="flex items-center space-x-2">
<span>{category}</span>
<Badge>{tools.length}</Badge>
</TypographyH2>
</div>
);
}
18 changes: 18 additions & 0 deletions ui/admin/app/components/tools/toolGrid/CategoryTools.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ToolReference } from "~/lib/model/toolReferences";

import { ToolCard } from "~/components/tools/toolGrid/ToolCard";

interface CategoryToolsProps {
tools: ToolReference[];
onDelete: (id: string) => void;
}

export function CategoryTools({ tools, onDelete }: CategoryToolsProps) {
return (
<div className="grid grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{tools.map((tool) => (
<ToolCard key={tool.id} tool={tool} onDelete={onDelete} />
))}
</div>
);
}
89 changes: 89 additions & 0 deletions ui/admin/app/components/tools/toolGrid/ToolCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { Trash } from "lucide-react";

import { ToolReference } from "~/lib/model/toolReferences";
import { cn, timeSince } from "~/lib/utils";

import { TruncatedText } from "~/components/TruncatedText";
import {
TypographyH4,
TypographyP,
TypographySmall,
} from "~/components/Typography";
import { ConfirmationDialog } from "~/components/composed/ConfirmationDialog";
import { Badge } from "~/components/ui/badge";
import { Button } from "~/components/ui/button";
import {
Card,
CardContent,
CardFooter,
CardHeader,
} from "~/components/ui/card";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "~/components/ui/tooltip";

interface ToolCardProps {
tool: ToolReference;
onDelete: (id: string) => void;
}

export function ToolCard({ tool, onDelete }: ToolCardProps) {
return (
<Card
className={cn("flex flex-col h-full", {
"border-2 border-info": tool.metadata?.bundle,
"border-2 border-error": tool.error,
})}
>
<CardHeader className="pb-2">
<TypographyH4 className="truncate flex items-center">
{tool.name}
{tool.error && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Badge className="ml-2 bg-error mb-1">
Failed
</Badge>
</TooltipTrigger>
<TooltipContent className="max-w-xs bg-error-foreground border border-error text-foreground">
<TypographyP>{tool.error}</TypographyP>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{tool.metadata?.bundle && (
<Badge className="ml-2 bg-info">Bundle</Badge>
)}
</TypographyH4>
</CardHeader>
<CardContent className="flex-grow">
<TruncatedText content={tool.reference} maxWidth="max-w-full" />
<TypographyP className="mt-2 text-sm text-muted-foreground line-clamp-2">
{tool.description || "No description available"}
</TypographyP>
</CardContent>
<CardFooter className="flex justify-between items-center pt-2">
<TypographySmall className="text-muted-foreground">
{timeSince(new Date(tool.created))} ago
</TypographySmall>
<ConfirmationDialog
title="Delete Tool Reference"
description="Are you sure you want to delete this tool reference? This action cannot be undone."
onConfirm={() => onDelete(tool.id)}
confirmProps={{
variant: "destructive",
children: "Delete",
}}
>
<Button variant="ghost" size="sm" className="w-8 h-8 p-0">
<Trash className="w-5 h-5" />
</Button>
</ConfirmationDialog>
</CardFooter>
</Card>
);
}
57 changes: 57 additions & 0 deletions ui/admin/app/components/tools/toolGrid/ToolGrid.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { useMemo } from "react";

import { ToolReference } from "~/lib/model/toolReferences";

import { CategoryHeader } from "~/components/tools/toolGrid/CategoryHeader";
import { CategoryTools } from "~/components/tools/toolGrid/CategoryTools";

interface ToolGridProps {
tools: ToolReference[];
filter: string;
onDelete: (id: string) => void;
}

export function ToolGrid({ tools, filter, onDelete }: ToolGridProps) {
const filteredTools = useMemo(() => {
return tools?.filter(
(tool) =>
tool.name?.toLowerCase().includes(filter.toLowerCase()) ||
tool.metadata?.category
?.toLowerCase()
.includes(filter.toLowerCase()) ||
tool.description?.toLowerCase().includes(filter.toLowerCase())
);
}, [tools, filter]);

const sortedCategories = useMemo(() => {
const categorizedTools = filteredTools?.reduce(
(acc, tool) => {
const category = tool.metadata?.category ?? "Uncategorized";
if (!acc[category]) {
acc[category] = [];
}
acc[category].push(tool);
return acc;
},
{} as Record<string, ToolReference[]>
);

// Sort categories to put "Uncategorized" first
return Object.entries(categorizedTools).sort(([a], [b]) => {
if (a === "Uncategorized") return -1;
if (b === "Uncategorized") return 1;
return a.localeCompare(b);
});
}, [filteredTools]);

return (
<div className="space-y-8 pb-16">
{sortedCategories.map(([category, categoryTools]) => (
<div key={category} className="space-y-4">
<CategoryHeader category={category} tools={categoryTools} />
<CategoryTools tools={categoryTools} onDelete={onDelete} />
</div>
))}
</div>
);
}
1 change: 1 addition & 0 deletions ui/admin/app/components/tools/toolGrid/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "~/components/tools/toolGrid/ToolGrid";
4 changes: 2 additions & 2 deletions ui/admin/app/routes/_auth.tools._index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ToolReferenceService } from "~/lib/service/api/toolreferenceService";

import { TypographyH2 } from "~/components/Typography";
import { CreateTool } from "~/components/tools/CreateTool";
import { ToolTable } from "~/components/tools/ToolTable";
import { ToolGrid } from "~/components/tools/toolGrid";
import { Button } from "~/components/ui/button";
import {
Dialog,
Expand Down Expand Up @@ -80,7 +80,7 @@ export default function Tools() {
</div>

{tools && (
<ToolTable
<ToolGrid
tools={tools}
filter={searchQuery}
onDelete={handleDelete}
Expand Down

0 comments on commit 04e8783

Please sign in to comment.