feat: núcleo de tickets com Convex (CRUD, play, comentários com anexos) + auth placeholder; docs em AGENTS.md; toasts e updates otimistas; mapeadores Zod; refinos PT-BR e layout do painel de detalhes

This commit is contained in:
esdrasrenan 2025-10-04 00:31:44 -03:00
parent 2230590e57
commit 27b103cb46
97 changed files with 15117 additions and 15715 deletions

View file

@ -1,116 +1,116 @@
"use client"
import * as React from "react"
import {
LayoutDashboard,
LifeBuoy,
Ticket,
PlayCircle,
BookOpen,
BarChart3,
Gauge,
PanelsTopLeft,
Users,
Waypoints,
Timer,
Plug,
Layers3,
} from "lucide-react"
import { usePathname } from "next/navigation"
import { SearchForm } from "@/components/search-form"
import { VersionSwitcher } from "@/components/version-switcher"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
} from "@/components/ui/sidebar"
const navigation = {
versions: ["MVP", "Beta", "Roadmap"],
navMain: [
{
title: "Operação",
items: [
{ title: "Dashboard", url: "/dashboard", icon: LayoutDashboard },
{ title: "Tickets", url: "/tickets", icon: Ticket },
{ title: "Visualizações", url: "/views", icon: PanelsTopLeft },
{ title: "Modo Play", url: "/play", icon: PlayCircle },
{ title: "Base de conhecimento", url: "/knowledge", icon: BookOpen },
],
},
{
title: "Relatorios",
items: [
{ title: "SLA e produtividade", url: "/reports/sla", icon: Gauge },
{ title: "Qualidade (CSAT)", url: "/reports/csat", icon: LifeBuoy },
{ title: "Backlog", url: "/reports/backlog", icon: BarChart3 },
],
},
{
title: "Configuração",
items: [
{ title: "Canais & roteamento", url: "/admin/channels", icon: Waypoints },
{ title: "Times & papéis", url: "/admin/teams", icon: Users },
{ title: "Campos personalizados", url: "/admin/fields", icon: Layers3 },
{ title: "SLAs", url: "/admin/slas", icon: Timer },
{ title: "Integrações", url: "/admin/integrations", icon: Plug },
],
},
],
} as const
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const pathname = usePathname()
function isActive(url: string) {
if (!pathname) return false
if (url === "/dashboard" && pathname === "/") {
return true
}
return pathname === url || pathname.startsWith(`${url}/`)
}
return (
<Sidebar {...props}>
<SidebarHeader className="gap-3">
<VersionSwitcher
label="Release"
versions={navigation.versions}
defaultVersion={navigation.versions[0]}
/>
<SearchForm placeholder="Buscar tickets, macros ou artigos" />
</SidebarHeader>
<SidebarContent>
{navigation.navMain.map((group) => (
<SidebarGroup key={group.title}>
<SidebarGroupLabel>{group.title}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{group.items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild isActive={isActive(item.url)}>
<a href={item.url} className="gap-2">
<item.icon className="size-4" />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
</SidebarContent>
<SidebarRail />
</Sidebar>
)
}
"use client"
import * as React from "react"
import {
LayoutDashboard,
LifeBuoy,
Ticket,
PlayCircle,
BookOpen,
BarChart3,
Gauge,
PanelsTopLeft,
Users,
Waypoints,
Timer,
Plug,
Layers3,
} from "lucide-react"
import { usePathname } from "next/navigation"
import { SearchForm } from "@/components/search-form"
import { VersionSwitcher } from "@/components/version-switcher"
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarGroupContent,
SidebarGroupLabel,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
SidebarRail,
} from "@/components/ui/sidebar"
const navigation = {
versions: ["MVP", "Beta", "Roadmap"],
navMain: [
{
title: "Operação",
items: [
{ title: "Dashboard", url: "/dashboard", icon: LayoutDashboard },
{ title: "Tickets", url: "/tickets", icon: Ticket },
{ title: "Visualizações", url: "/views", icon: PanelsTopLeft },
{ title: "Modo Play", url: "/play", icon: PlayCircle },
{ title: "Base de conhecimento", url: "/knowledge", icon: BookOpen },
],
},
{
title: "Relatorios",
items: [
{ title: "SLA e produtividade", url: "/reports/sla", icon: Gauge },
{ title: "Qualidade (CSAT)", url: "/reports/csat", icon: LifeBuoy },
{ title: "Backlog", url: "/reports/backlog", icon: BarChart3 },
],
},
{
title: "Configuração",
items: [
{ title: "Canais & roteamento", url: "/admin/channels", icon: Waypoints },
{ title: "Times & papéis", url: "/admin/teams", icon: Users },
{ title: "Campos personalizados", url: "/admin/fields", icon: Layers3 },
{ title: "SLAs", url: "/admin/slas", icon: Timer },
{ title: "Integrações", url: "/admin/integrations", icon: Plug },
],
},
],
} as const
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const pathname = usePathname()
function isActive(url: string) {
if (!pathname) return false
if (url === "/dashboard" && pathname === "/") {
return true
}
return pathname === url || pathname.startsWith(`${url}/`)
}
return (
<Sidebar {...props}>
<SidebarHeader className="gap-3">
<VersionSwitcher
label="Release"
versions={navigation.versions}
defaultVersion={navigation.versions[0]}
/>
<SearchForm placeholder="Buscar tickets, macros ou artigos" />
</SidebarHeader>
<SidebarContent>
{navigation.navMain.map((group) => (
<SidebarGroup key={group.title}>
<SidebarGroupLabel>{group.title}</SidebarGroupLabel>
<SidebarGroupContent>
<SidebarMenu>
{group.items.map((item) => (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild isActive={isActive(item.url)}>
<a href={item.url} className="gap-2">
<item.icon className="size-4" />
<span>{item.title}</span>
</a>
</SidebarMenuButton>
</SidebarMenuItem>
))}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
))}
</SidebarContent>
<SidebarRail />
</Sidebar>
)
}