feat: improve ticket export and navigation

This commit is contained in:
Esdras Renan 2025-10-13 00:08:18 -03:00
parent 0731c5d1ea
commit 7d6f3bea01
28 changed files with 1612 additions and 609 deletions

View file

@ -18,11 +18,12 @@ import {
Layers3,
UserPlus,
BellRing,
ChevronDown,
} from "lucide-react"
import { usePathname } from "next/navigation"
import { SearchForm } from "@/components/search-form"
import { VersionSwitcher } from "@/components/version-switcher"
import { usePathname } from "next/navigation"
import { SearchForm } from "@/components/search-form"
import { VersionSwitcher } from "@/components/version-switcher"
import {
Sidebar,
SidebarContent,
@ -39,6 +40,7 @@ import {
import { Skeleton } from "@/components/ui/skeleton"
import { NavUser } from "@/components/nav-user"
import { useAuth } from "@/lib/auth-client"
import { cn } from "@/lib/utils"
import type { LucideIcon } from "lucide-react"
@ -47,9 +49,10 @@ type NavRoleRequirement = "staff" | "admin"
type NavigationItem = {
title: string
url: string
icon: LucideIcon
icon?: LucideIcon
requiredRole?: NavRoleRequirement
exact?: boolean
children?: NavigationItem[]
}
type NavigationGroup = {
@ -65,7 +68,13 @@ const navigation: { versions: string[]; navMain: NavigationGroup[] } = {
title: "Operação",
items: [
{ title: "Dashboard", url: "/dashboard", icon: LayoutDashboard, requiredRole: "staff" },
{ title: "Tickets", url: "/tickets", icon: Ticket, requiredRole: "staff" },
{
title: "Tickets",
url: "/tickets",
icon: Ticket,
requiredRole: "staff",
children: [{ title: "Resolvidos", url: "/tickets/resolved", requiredRole: "staff" }],
},
{ title: "Visualizações", url: "/views", icon: PanelsTopLeft, requiredRole: "staff" },
{ title: "Modo Play", url: "/play", icon: PlayCircle, requiredRole: "staff" },
],
@ -105,9 +114,34 @@ const navigation: { versions: string[]; navMain: NavigationGroup[] } = {
}
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const pathname = usePathname()
const pathname = usePathname()
const { session, isLoading, isAdmin, isStaff } = useAuth()
const [isHydrated, setIsHydrated] = React.useState(false)
const initialExpanded = React.useMemo(() => {
const open = new Set<string>()
navigation.navMain.forEach((group) => {
group.items.forEach((item) => {
if (!item.children || item.children.length === 0) return
const shouldOpen = item.children.some((child) => {
if (!canAccess(child.requiredRole)) return false
return pathname === child.url || pathname.startsWith(`${child.url}/`)
})
if (shouldOpen) {
open.add(item.title)
}
})
})
return open
}, [pathname])
const [expanded, setExpanded] = React.useState<Set<string>>(initialExpanded)
React.useEffect(() => {
setExpanded((prev) => {
const next = new Set(prev)
initialExpanded.forEach((key) => next.add(key))
return next
})
}, [initialExpanded])
React.useEffect(() => {
setIsHydrated(true)
@ -131,6 +165,18 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
if (requiredRole === "staff") return isStaff
return false
}
const toggleExpanded = React.useCallback((title: string) => {
setExpanded((prev) => {
const next = new Set(prev)
if (next.has(title)) {
next.delete(title)
} else {
next.add(title)
}
return next
})
}, [])
if (!isHydrated) {
return (
@ -180,16 +226,64 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
<SidebarGroupLabel>{group.title}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{visibleItems.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild isActive={isActive(item)}>
<a href={item.url} className="gap-2">
<item.icon className="size-4" />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
{visibleItems.map((item) => {
if (item.children && item.children.length > 0) {
const childItems = item.children.filter((child) => canAccess(child.requiredRole))
const isExpanded = expanded.has(item.title)
const isChildActive = childItems.some((child) => isActive(child))
const parentActive = isActive(item) || isChildActive
return (
<React.Fragment key={item.title}>
<SidebarMenuItem>
<SidebarMenuButton asChild isActive={parentActive}>
<a href={item.url} className={cn("gap-2", "relative pr-7") }>
{item.icon ? <item.icon className="size-4" /> : null}
<span className="flex-1">{item.title}</span>
<span
role="button"
aria-label={isExpanded ? "Recolher submenu" : "Expandir submenu"}
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
toggleExpanded(item.title)
}}
className={cn(
"absolute right-1.5 top-1/2 inline-flex h-6 w-6 -translate-y-1/2 items-center justify-center rounded-md text-neutral-500 transition hover:bg-slate-200 hover:text-neutral-700",
isExpanded && "rotate-180"
)}
>
<ChevronDown className="size-3" />
</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
{isExpanded
? childItems.map((child) => (
<SidebarMenuItem key={`${item.title}-${child.title}`}>
<SidebarMenuButton asChild isActive={isActive(child)}>
<a href={child.url} className="gap-2 pl-7 text-sm">
<span>{child.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))
: null}
</React.Fragment>
)
}
return (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild isActive={isActive(item)}>
<a href={item.url} className="gap-2">
{item.icon ? <item.icon className="size-4" /> : null}
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
)
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>