mirror of
https://github.com/ForFarmTeam/ForFarm.git
synced 2025-12-18 21:44:08 +01:00
ui: improve sidebar
This commit is contained in:
parent
08f852a397
commit
4b772e20d0
@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
AudioWaveform,
|
||||
BookOpen,
|
||||
@ -12,146 +13,85 @@ import {
|
||||
PieChart,
|
||||
Settings2,
|
||||
SquareTerminal,
|
||||
User,
|
||||
} from "lucide-react";
|
||||
|
||||
import { NavMain } from "./nav-main";
|
||||
import { NavProjects } from "./nav-projects";
|
||||
import { NavUser } from "./nav-user";
|
||||
import { TeamSwitcher } from "./team-switcher";
|
||||
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarRail } from "@/components/ui/sidebar";
|
||||
import { useEffect } from "react";
|
||||
import { NavCrops } from "./nav-crops";
|
||||
import { fetchUserMe } from "@/api/user";
|
||||
|
||||
const data = {
|
||||
user: {
|
||||
name: "shadcn",
|
||||
email: "m@example.com",
|
||||
avatar: "/avatars/avatar.webp",
|
||||
},
|
||||
teams: [
|
||||
{
|
||||
name: "Farm 1",
|
||||
logo: GalleryVerticalEnd,
|
||||
plan: "Hatyai",
|
||||
},
|
||||
{
|
||||
name: "Farm 2",
|
||||
logo: AudioWaveform,
|
||||
plan: "Songkla",
|
||||
},
|
||||
{
|
||||
name: "Farm 3",
|
||||
logo: Command,
|
||||
plan: "Layong",
|
||||
},
|
||||
],
|
||||
navMain: [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "#",
|
||||
icon: SquareTerminal,
|
||||
isActive: true,
|
||||
items: [
|
||||
{
|
||||
title: "Analytic",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "AI Chatbot",
|
||||
url: "#",
|
||||
icon: Bot,
|
||||
items: [
|
||||
{
|
||||
title: "Main model",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Documentation",
|
||||
url: "#",
|
||||
icon: BookOpen,
|
||||
items: [
|
||||
{
|
||||
title: "Introduction",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Get Started",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Tutorials",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Changelog",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: "#",
|
||||
icon: Settings2,
|
||||
items: [
|
||||
{
|
||||
title: "General",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Team",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Billing",
|
||||
url: "#",
|
||||
},
|
||||
{
|
||||
title: "Limits",
|
||||
url: "#",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
name: "Crops 1",
|
||||
url: "#",
|
||||
icon: Frame,
|
||||
},
|
||||
{
|
||||
name: "Crops 2",
|
||||
url: "#",
|
||||
icon: PieChart,
|
||||
},
|
||||
{
|
||||
name: "Crops 3",
|
||||
url: "#",
|
||||
icon: Map,
|
||||
},
|
||||
],
|
||||
};
|
||||
interface Team {
|
||||
name: string;
|
||||
logo: React.ComponentType;
|
||||
plan: string;
|
||||
}
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
const [user, setUser] = React.useState<{ name: string; email: string; avatar: string }>({
|
||||
import { LucideIcon } from "lucide-react";
|
||||
|
||||
interface NavItem {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: LucideIcon;
|
||||
}
|
||||
|
||||
interface SidebarConfig {
|
||||
teams: Team[];
|
||||
navMain: NavItem[];
|
||||
crops: NavItem[];
|
||||
}
|
||||
|
||||
interface AppSidebarProps extends React.ComponentProps<typeof Sidebar> {
|
||||
config?: SidebarConfig;
|
||||
}
|
||||
|
||||
export function AppSidebar({ config, ...props }: AppSidebarProps) {
|
||||
const defaultConfig: SidebarConfig = {
|
||||
teams: [
|
||||
{ name: "Farm 1", logo: GalleryVerticalEnd, plan: "Hatyai" },
|
||||
{ name: "Farm 2", logo: AudioWaveform, plan: "Songkla" },
|
||||
{ name: "Farm 3", logo: Command, plan: "Layong" },
|
||||
],
|
||||
navMain: [
|
||||
{ title: "Farms", url: "/farms", icon: Map },
|
||||
{ title: "Crops list", url: "/crops", icon: Frame },
|
||||
{ title: "Inventory", url: "/inventory", icon: SquareTerminal },
|
||||
{ title: "Marketplace Information", url: "/marketplace", icon: PieChart },
|
||||
{ title: "Knowledge Hub", url: "/knowledge", icon: BookOpen },
|
||||
{ title: "Users", url: "/users", icon: User },
|
||||
{ title: "AI Chatbot", url: "/chatbot", icon: Bot },
|
||||
{ title: "Settings", url: "/settings", icon: Settings2 },
|
||||
],
|
||||
// Define crops with dynamic URLs – replace [farmId] with your actual farm identifier as needed.
|
||||
crops: [
|
||||
{ title: "Crops 1", url: "/farms/[farmId]/crops/1", icon: Map },
|
||||
{ title: "Crops 2", url: "/farms/[farmId]/crops/2", icon: Map },
|
||||
{ title: "Crops 3", url: "/farms/[farmId]/crops/3", icon: Map },
|
||||
],
|
||||
};
|
||||
|
||||
// Allow external configuration override
|
||||
const sidebarConfig = config || defaultConfig;
|
||||
|
||||
const [user, setUser] = useState<{ name: string; email: string; avatar: string }>({
|
||||
name: "",
|
||||
email: "",
|
||||
avatar: "/avatars/avatar.webp",
|
||||
});
|
||||
const [loading, setLoading] = React.useState(true);
|
||||
const [error, setError] = React.useState("");
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
async function getUser() {
|
||||
try {
|
||||
const data = await fetchUserMe();
|
||||
let to_set = user;
|
||||
to_set.name = data.user.UUID;
|
||||
to_set.email = data.user.Email;
|
||||
setUser(to_set);
|
||||
setUser({
|
||||
name: data.user.UUID,
|
||||
email: data.user.Email,
|
||||
avatar: data.user.Avatar || "/avatars/avatar.webp",
|
||||
});
|
||||
} catch (err: any) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
@ -164,11 +104,13 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
return (
|
||||
<Sidebar collapsible="icon" {...props}>
|
||||
<SidebarHeader>
|
||||
<TeamSwitcher teams={data.teams} />
|
||||
<TeamSwitcher teams={sidebarConfig.teams} />
|
||||
</SidebarHeader>
|
||||
<SidebarContent>
|
||||
<NavMain items={data.navMain} />
|
||||
<NavProjects projects={data.projects} />
|
||||
<NavMain items={sidebarConfig.navMain} />
|
||||
<div className="mt-6">
|
||||
<NavCrops crops={sidebarConfig.crops} />
|
||||
</div>
|
||||
</SidebarContent>
|
||||
<SidebarFooter>{loading ? "Loading..." : error ? error : <NavUser user={user} />}</SidebarFooter>
|
||||
<SidebarRail />
|
||||
|
||||
41
frontend/components/sidebar/nav-crops.tsx
Normal file
41
frontend/components/sidebar/nav-crops.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
"use client";
|
||||
|
||||
import { LucideIcon } from "lucide-react";
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
interface CropItem {
|
||||
title: string;
|
||||
url: string;
|
||||
icon: LucideIcon;
|
||||
}
|
||||
|
||||
interface NavCropsProps {
|
||||
crops: CropItem[];
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export function NavCrops({ crops, title = "Crops" }: NavCropsProps) {
|
||||
return (
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>{title}</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{crops.map((crop) => (
|
||||
<SidebarMenuItem key={crop.title}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={crop.url}>
|
||||
<crop.icon />
|
||||
<span>{crop.title}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
@ -1,82 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Folder, Forward, MoreHorizontal, Trash2, type LucideIcon } from "lucide-react";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import {
|
||||
SidebarGroup,
|
||||
SidebarGroupLabel,
|
||||
SidebarMenu,
|
||||
SidebarMenuAction,
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
export function NavProjects({
|
||||
projects,
|
||||
}: {
|
||||
projects: {
|
||||
name: string;
|
||||
url: string;
|
||||
icon: LucideIcon;
|
||||
}[];
|
||||
}) {
|
||||
const { isMobile } = useSidebar();
|
||||
|
||||
return (
|
||||
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||
<SidebarGroupLabel>Projects</SidebarGroupLabel>
|
||||
<SidebarMenu>
|
||||
{projects.map((item) => (
|
||||
<SidebarMenuItem key={item.name}>
|
||||
<SidebarMenuButton asChild>
|
||||
<a href={item.url}>
|
||||
<item.icon />
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<SidebarMenuAction showOnHover>
|
||||
<MoreHorizontal />
|
||||
<span className="sr-only">More</span>
|
||||
</SidebarMenuAction>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent
|
||||
className="w-48 rounded-lg"
|
||||
side={isMobile ? "bottom" : "right"}
|
||||
align={isMobile ? "end" : "start"}>
|
||||
<DropdownMenuItem>
|
||||
<Folder className="text-muted-foreground" />
|
||||
<span>View Project</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem>
|
||||
<Forward className="text-muted-foreground" />
|
||||
<span>Share Project</span>
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem>
|
||||
<Trash2 className="text-muted-foreground" />
|
||||
<span>Delete Project</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton className="text-sidebar-foreground/70">
|
||||
<MoreHorizontal className="text-sidebar-foreground/70" />
|
||||
<span>More</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroup>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user