mirror of
https://github.com/ForFarmTeam/ForFarm.git
synced 2025-12-19 22:14:08 +01:00
ui: improve sidebar
This commit is contained in:
parent
08f852a397
commit
4b772e20d0
@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import {
|
||||||
AudioWaveform,
|
AudioWaveform,
|
||||||
BookOpen,
|
BookOpen,
|
||||||
@ -12,146 +13,85 @@ import {
|
|||||||
PieChart,
|
PieChart,
|
||||||
Settings2,
|
Settings2,
|
||||||
SquareTerminal,
|
SquareTerminal,
|
||||||
|
User,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
||||||
import { NavMain } from "./nav-main";
|
import { NavMain } from "./nav-main";
|
||||||
import { NavProjects } from "./nav-projects";
|
|
||||||
import { NavUser } from "./nav-user";
|
import { NavUser } from "./nav-user";
|
||||||
import { TeamSwitcher } from "./team-switcher";
|
import { TeamSwitcher } from "./team-switcher";
|
||||||
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarRail } from "@/components/ui/sidebar";
|
import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarRail } from "@/components/ui/sidebar";
|
||||||
import { useEffect } from "react";
|
import { NavCrops } from "./nav-crops";
|
||||||
import { fetchUserMe } from "@/api/user";
|
import { fetchUserMe } from "@/api/user";
|
||||||
|
|
||||||
const data = {
|
interface Team {
|
||||||
user: {
|
name: string;
|
||||||
name: "shadcn",
|
logo: React.ComponentType;
|
||||||
email: "m@example.com",
|
plan: string;
|
||||||
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,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
import { LucideIcon } from "lucide-react";
|
||||||
const [user, setUser] = React.useState<{ name: string; email: string; avatar: string }>({
|
|
||||||
|
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: "",
|
name: "",
|
||||||
email: "",
|
email: "",
|
||||||
avatar: "/avatars/avatar.webp",
|
avatar: "/avatars/avatar.webp",
|
||||||
});
|
});
|
||||||
const [loading, setLoading] = React.useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = React.useState("");
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function getUser() {
|
async function getUser() {
|
||||||
try {
|
try {
|
||||||
const data = await fetchUserMe();
|
const data = await fetchUserMe();
|
||||||
let to_set = user;
|
setUser({
|
||||||
to_set.name = data.user.UUID;
|
name: data.user.UUID,
|
||||||
to_set.email = data.user.Email;
|
email: data.user.Email,
|
||||||
setUser(to_set);
|
avatar: data.user.Avatar || "/avatars/avatar.webp",
|
||||||
|
});
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
@ -164,11 +104,13 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
|||||||
return (
|
return (
|
||||||
<Sidebar collapsible="icon" {...props}>
|
<Sidebar collapsible="icon" {...props}>
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
<TeamSwitcher teams={data.teams} />
|
<TeamSwitcher teams={sidebarConfig.teams} />
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<NavMain items={data.navMain} />
|
<NavMain items={sidebarConfig.navMain} />
|
||||||
<NavProjects projects={data.projects} />
|
<div className="mt-6">
|
||||||
|
<NavCrops crops={sidebarConfig.crops} />
|
||||||
|
</div>
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
<SidebarFooter>{loading ? "Loading..." : error ? error : <NavUser user={user} />}</SidebarFooter>
|
<SidebarFooter>{loading ? "Loading..." : error ? error : <NavUser user={user} />}</SidebarFooter>
|
||||||
<SidebarRail />
|
<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