New compact view for applications

This commit is contained in:
headlessdev 2025-04-26 13:11:19 +02:00
parent 677d1c5a58
commit fde7b3b23e
2 changed files with 280 additions and 214 deletions

BIN
ApplicationsFormatted.tsx Normal file

Binary file not shown.

View File

@ -25,6 +25,8 @@ import {
List,
Pencil,
Zap,
ViewIcon,
Grid3X3,
} from "lucide-react";
import {
Card,
@ -76,6 +78,12 @@ import {
import { StatusIndicator } from "@/components/status-indicator";
import { Toaster } from "@/components/ui/sonner"
import { toast } from "sonner"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
interface Application {
id: number;
@ -129,23 +137,42 @@ export default function Dashboard() {
const savedLayout = Cookies.get("layoutPreference-app");
const savedItemsPerPage = Cookies.get("itemsPerPage-app");
const initialIsGridLayout = savedLayout === "grid";
const defaultItemsPerPage = initialIsGridLayout ? 15 : 5;
const initialIsCompactLayout = savedLayout === "compact";
const defaultItemsPerPage = initialIsGridLayout ? 15 : (initialIsCompactLayout ? 30 : 5);
const initialItemsPerPage = savedItemsPerPage ? parseInt(savedItemsPerPage) : defaultItemsPerPage;
const [isGridLayout, setIsGridLayout] = useState<boolean>(initialIsGridLayout);
const [isCompactLayout, setIsCompactLayout] = useState<boolean>(initialIsCompactLayout);
const [itemsPerPage, setItemsPerPage] = useState<number>(initialItemsPerPage);
const customInputRef = useRef<HTMLInputElement>(null);
const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
const toggleLayout = () => {
const newLayout = !isGridLayout;
setIsGridLayout(newLayout);
Cookies.set("layoutPreference-app", newLayout ? "grid" : "standard", {
const toggleLayout = (layout: string) => {
if (layout === "standard") {
setIsGridLayout(false);
setIsCompactLayout(false);
Cookies.set("layoutPreference-app", "standard", {
expires: 365,
path: "/",
sameSite: "strict",
});
// Don't automatically change itemsPerPage when layout changes
} else if (layout === "grid") {
setIsGridLayout(true);
setIsCompactLayout(false);
Cookies.set("layoutPreference-app", "grid", {
expires: 365,
path: "/",
sameSite: "strict",
});
} else if (layout === "compact") {
setIsGridLayout(false);
setIsCompactLayout(true);
Cookies.set("layoutPreference-app", "compact", {
expires: 365,
path: "/",
sameSite: "strict",
});
}
};
const handleItemsPerPageChange = (value: string) => {
@ -335,20 +362,30 @@ export default function Dashboard() {
<div className="flex justify-between items-center">
<span className="text-3xl font-bold">Your Applications</span>
<div className="flex gap-2">
<Button
variant="outline"
size="icon"
onClick={toggleLayout}
title={
isGridLayout ? "Switch to list view" : "Switch to grid view"
}
>
{isGridLayout ? (
<List className="h-4 w-4" />
) : (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon" title="Change view">
{isCompactLayout ? (
<Grid3X3 className="h-4 w-4" />
) : isGridLayout ? (
<LayoutGrid className="h-4 w-4" />
) : (
<List className="h-4 w-4" />
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => toggleLayout("standard")}>
<List className="h-4 w-4 mr-2" /> List View
</DropdownMenuItem>
<DropdownMenuItem onClick={() => toggleLayout("grid")}>
<LayoutGrid className="h-4 w-4 mr-2" /> Grid View
</DropdownMenuItem>
<DropdownMenuItem onClick={() => toggleLayout("compact")}>
<Grid3X3 className="h-4 w-4 mr-2" /> Compact View
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Select
value={String(itemsPerPage)}
onValueChange={handleItemsPerPageChange}
@ -559,12 +596,40 @@ export default function Dashboard() {
{!loading ? (
<div
className={
isGridLayout
isCompactLayout
? "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 xl:grid-cols-8 gap-2"
: isGridLayout
? "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
: "space-y-4"
}
>
{applications.map((app) => (
isCompactLayout ? (
<div
key={app.id}
className="bg-card rounded-md border p-3 flex flex-col items-center justify-between h-[120px] w-full cursor-pointer hover:shadow-md transition-shadow relative"
onClick={() => window.open(app.publicURL, "_blank")}
title={app.name}
>
<div className="absolute top-1 right-1">
<StatusIndicator isOnline={app.online} showLabel={false} />
</div>
<div className="w-16 h-16 flex-shrink-0 flex items-center justify-center">
{app.icon ? (
<img
src={app.icon}
alt={app.name}
className="w-full h-full object-contain rounded-md"
/>
) : (
<span className="text-gray-500 text-xs">Icon</span>
)}
</div>
<div className="text-center mt-2">
<h3 className="text-sm font-medium truncate w-full max-w-[110px]">{app.name}</h3>
</div>
</div>
) : (
<Card
key={app.id}
className={
@ -775,6 +840,7 @@ export default function Dashboard() {
</div>
</CardHeader>
</Card>
)
))}
</div>
) : (