调整dashboard项目结构
This commit is contained in:
+5
-5
@@ -12,10 +12,10 @@ import {
|
|||||||
type DashboardOverview,
|
type DashboardOverview,
|
||||||
type DashboardRange,
|
type DashboardRange,
|
||||||
} from "@/lib/api/dashboard"
|
} from "@/lib/api/dashboard"
|
||||||
import { SummaryCards } from "./_components/summary-cards"
|
import { SummaryCards } from "./summary-cards"
|
||||||
import { TrendPanel } from "./_components/trend-panel"
|
import { TrendPanel } from "./trend-panel"
|
||||||
import { TeamLoadPanel } from "./_components/team-load-panel"
|
import { TeamLoadPanel } from "./team-load-panel"
|
||||||
import { AlertList } from "./_components/alert-list"
|
import { AlertList } from "./alert-list"
|
||||||
|
|
||||||
const rangeOptions: Array<{ value: DashboardRange; label: string }> = [
|
const rangeOptions: Array<{ value: DashboardRange; label: string }> = [
|
||||||
{ value: "today", label: "今天" },
|
{ value: "today", label: "今天" },
|
||||||
@@ -39,7 +39,7 @@ function LoadingCards() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function DashboardPage() {
|
export function DashboardHome() {
|
||||||
const [range, setRange] = useState<DashboardRange>("7d")
|
const [range, setRange] = useState<DashboardRange>("7d")
|
||||||
const [data, setData] = useState<DashboardOverview | null>(null)
|
const [data, setData] = useState<DashboardOverview | null>(null)
|
||||||
const [loading, setLoading] = useState(true)
|
const [loading, setLoading] = useState(true)
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { DashboardHome } from "./_components/dashboard-home"
|
||||||
|
|
||||||
|
export default function DashboardHomePage() {
|
||||||
|
return <DashboardHome />
|
||||||
|
}
|
||||||
+1
-1
@@ -236,7 +236,7 @@ export default function TicketRiskPage() {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Link href="/tickets">
|
<Link href="/dashboard/tickets">
|
||||||
<Button variant="outline">前往工单工作台</Button>
|
<Button variant="outline">前往工单工作台</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Button variant="outline" onClick={() => void loadData()} disabled={loading}>
|
<Button variant="outline" onClick={() => void loadData()} disabled={loading}>
|
||||||
+1
-1
@@ -224,7 +224,7 @@ export function TicketStatusDialog({
|
|||||||
<div className="mt-2 rounded-lg border border-amber-200 bg-amber-50/70 p-3 text-xs text-amber-900">
|
<div className="mt-2 rounded-lg border border-amber-200 bg-amber-50/70 p-3 text-xs text-amber-900">
|
||||||
当前没有可用解决码,解决结果无法标准化统计。
|
当前没有可用解决码,解决结果无法标准化统计。
|
||||||
<Link
|
<Link
|
||||||
href="/ticket-resolution-codes"
|
href="/dashboard/ticket-resolution-codes"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
className="ml-1 font-medium underline underline-offset-4"
|
className="ml-1 font-medium underline underline-offset-4"
|
||||||
+2
-2
@@ -810,7 +810,7 @@ export default function TicketDetailPage() {
|
|||||||
当前工单未生成 SLA 记录,请检查工单 SLA
|
当前工单未生成 SLA 记录,请检查工单 SLA
|
||||||
配置;如果系统仍在使用默认策略,也需要确认配置是否已补齐。
|
配置;如果系统仍在使用默认策略,也需要确认配置是否已补齐。
|
||||||
<Link
|
<Link
|
||||||
href="/ticket-priorities"
|
href="/dashboard/ticket-priorities"
|
||||||
className="ml-1 font-medium underline underline-offset-4"
|
className="ml-1 font-medium underline underline-offset-4"
|
||||||
>
|
>
|
||||||
前往配置优先级
|
前往配置优先级
|
||||||
@@ -912,7 +912,7 @@ export default function TicketDetailPage() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() => router.push("/conversations")}
|
onClick={() => router.push("/dashboard/conversations")}
|
||||||
>
|
>
|
||||||
<RotateCcwIcon className="size-4" />
|
<RotateCcwIcon className="size-4" />
|
||||||
前往会话工作台
|
前往会话工作台
|
||||||
@@ -27,7 +27,7 @@ function WxWorkLoginCallbackContent() {
|
|||||||
|
|
||||||
const ticket = searchParams.get("ticket")?.trim() ?? ""
|
const ticket = searchParams.get("ticket")?.trim() ?? ""
|
||||||
const next = searchParams.get("next")
|
const next = searchParams.get("next")
|
||||||
const nextPath = next && next.startsWith("/") ? next : "/"
|
const nextPath = next && next.startsWith("/") ? next : "/dashboard"
|
||||||
|
|
||||||
if (!ticket) {
|
if (!ticket) {
|
||||||
toast.error("企业微信登录票据不存在")
|
toast.error("企业微信登录票据不存在")
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
|
export default function HomePage() {
|
||||||
|
return (
|
||||||
|
<main className="flex min-h-svh items-center justify-center bg-[linear-gradient(145deg,#fff7ed_0%,#ffffff_38%,#ecfeff_100%)] px-6 py-16">
|
||||||
|
<div className="w-full max-w-3xl rounded-[32px] border border-white/70 bg-white/90 p-10 shadow-[0_24px_80px_rgba(15,23,42,0.08)] backdrop-blur">
|
||||||
|
<div className="inline-flex rounded-full border border-amber-200 bg-amber-50 px-3 py-1 text-xs font-medium tracking-[0.18em] text-amber-900 uppercase">
|
||||||
|
Landing Page
|
||||||
|
</div>
|
||||||
|
<h1 className="mt-6 text-4xl font-semibold tracking-tight text-foreground">TODO</h1>
|
||||||
|
<p className="mt-4 max-w-2xl text-sm leading-6 text-muted-foreground">
|
||||||
|
这里先放一个简单的首页占位,后续可以再补充正式内容与导航信息。
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 flex flex-wrap gap-3">
|
||||||
|
<Link href="/dashboard">
|
||||||
|
进入控制台
|
||||||
|
</Link>
|
||||||
|
<Link href="/login">
|
||||||
|
登录后台
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -46,7 +46,7 @@ export function AppSidebar({ ...props }: ComponentProps<typeof Sidebar>) {
|
|||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
className="data-[slot=sidebar-menu-button]:p-1.5!"
|
className="data-[slot=sidebar-menu-button]:p-1.5!"
|
||||||
render={<Link href="/" />}
|
render={<Link href="/dashboard" />}
|
||||||
>
|
>
|
||||||
<BotMessageSquareIcon className="size-5!" />
|
<BotMessageSquareIcon className="size-5!" />
|
||||||
<span className="text-base font-semibold">贝壳AGENT</span>
|
<span className="text-base font-semibold">贝壳AGENT</span>
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const [session, setSession] = useState<AuthSession | null>(null)
|
const [session, setSession] = useState<AuthSession | null>(null)
|
||||||
const [ready, setReady] = useState(false)
|
const [ready, setReady] = useState(false)
|
||||||
|
const requiresAuth = pathname?.startsWith("/dashboard") ?? false
|
||||||
|
|
||||||
const refreshProfile = useCallback(async () => {
|
const refreshProfile = useCallback(async () => {
|
||||||
const stored = readSession()
|
const stored = readSession()
|
||||||
@@ -55,7 +56,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
} catch {
|
} catch {
|
||||||
clearSession()
|
clearSession()
|
||||||
setSession(null)
|
setSession(null)
|
||||||
if (pathname && !pathname.startsWith("/login")) {
|
if (requiresAuth) {
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
router.replace("/login")
|
router.replace("/login")
|
||||||
})
|
})
|
||||||
@@ -63,7 +64,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
} finally {
|
} finally {
|
||||||
setReady(true)
|
setReady(true)
|
||||||
}
|
}
|
||||||
}, [pathname, router])
|
}, [requiresAuth, router])
|
||||||
|
|
||||||
async function signOut() {
|
async function signOut() {
|
||||||
const current = readSession()
|
const current = readSession()
|
||||||
@@ -83,12 +84,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setReady(true)
|
setReady(true)
|
||||||
if (pathname && !pathname.startsWith("/login")) {
|
if (requiresAuth) {
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
router.replace("/login")
|
router.replace("/login")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [pathname, refreshProfile, router])
|
}, [requiresAuth, refreshProfile, router])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ session, ready, refreshProfile, signOut }}>
|
<AuthContext.Provider value={{ session, ready, refreshProfile, signOut }}>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useEffect, useMemo, useState } from "react"
|
|||||||
import { ChevronsUpDownIcon, PlusIcon } from "lucide-react"
|
import { ChevronsUpDownIcon, PlusIcon } from "lucide-react"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
|
|
||||||
import { EditDialog as CompanyEditDialog } from "@/app/(dashboard)/companies/_components/edit"
|
import { EditDialog as CompanyEditDialog } from "@/app/dashboard/companies/_components/edit"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export function LoginForm({
|
|||||||
const nextPath = searchParams.get("next")
|
const nextPath = searchParams.get("next")
|
||||||
const wxworkError = searchParams.get("wxworkError")
|
const wxworkError = searchParams.get("wxworkError")
|
||||||
const redirectPath =
|
const redirectPath =
|
||||||
nextPath && nextPath.startsWith("/") ? nextPath : "/"
|
nextPath && nextPath.startsWith("/") ? nextPath : "/dashboard"
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session) {
|
if (session) {
|
||||||
|
|||||||
@@ -98,42 +98,42 @@ export const dashboardNavSections: DashboardNavSectionConfig[] = [
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: "总览",
|
title: "总览",
|
||||||
url: "/",
|
url: "/dashboard",
|
||||||
icon: <LayoutDashboardIcon />,
|
icon: <LayoutDashboardIcon />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "会话",
|
title: "会话",
|
||||||
url: "/conversations",
|
url: "/dashboard/conversations",
|
||||||
icon: <BotMessageSquareIcon />,
|
icon: <BotMessageSquareIcon />,
|
||||||
requiredPermission: "conversation.view",
|
requiredPermission: "conversation.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "工单",
|
title: "工单",
|
||||||
url: "/tickets",
|
url: "/dashboard/tickets",
|
||||||
icon: <FileTextIcon />,
|
icon: <FileTextIcon />,
|
||||||
requiredPermission: "ticket.view",
|
requiredPermission: "ticket.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "会话监控",
|
title: "会话监控",
|
||||||
url: "/conversation-monitor",
|
url: "/dashboard/conversation-monitor",
|
||||||
icon: <BotMessageSquareIcon />,
|
icon: <BotMessageSquareIcon />,
|
||||||
requiredPermission: "conversation.view",
|
requiredPermission: "conversation.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "SLA风险",
|
title: "SLA风险",
|
||||||
url: "/ticket-risk",
|
url: "/dashboard/ticket-risk",
|
||||||
icon: <ChartColumnIncreasingIcon />,
|
icon: <ChartColumnIncreasingIcon />,
|
||||||
requiredPermission: "ticket.view",
|
requiredPermission: "ticket.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "客户管理",
|
title: "客户管理",
|
||||||
url: "/customers",
|
url: "/dashboard/customers",
|
||||||
icon: <UsersIcon />,
|
icon: <UsersIcon />,
|
||||||
requiredPermission: "customer.view",
|
requiredPermission: "customer.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "公司管理",
|
title: "公司管理",
|
||||||
url: "/companies",
|
url: "/dashboard/companies",
|
||||||
icon: <Building2Icon />,
|
icon: <Building2Icon />,
|
||||||
requiredPermission: "company.view",
|
requiredPermission: "company.view",
|
||||||
},
|
},
|
||||||
@@ -144,43 +144,43 @@ export const dashboardNavSections: DashboardNavSectionConfig[] = [
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: "分类标签",
|
title: "分类标签",
|
||||||
url: "/tags",
|
url: "/dashboard/tags",
|
||||||
icon: <TagsIcon />,
|
icon: <TagsIcon />,
|
||||||
requiredPermission: "tag.view",
|
requiredPermission: "tag.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "快捷回复",
|
title: "快捷回复",
|
||||||
url: "/quick-replies",
|
url: "/dashboard/quick-replies",
|
||||||
icon: <MessageSquareMoreIcon />,
|
icon: <MessageSquareMoreIcon />,
|
||||||
requiredPermission: "quickReply.view",
|
requiredPermission: "quickReply.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "工单优先级",
|
title: "工单优先级",
|
||||||
url: "/ticket-priorities",
|
url: "/dashboard/ticket-priorities",
|
||||||
icon: <Settings2Icon />,
|
icon: <Settings2Icon />,
|
||||||
requiredPermission: "ticketPriorityConfig.view",
|
requiredPermission: "ticketPriorityConfig.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "工单解决码",
|
title: "工单解决码",
|
||||||
url: "/ticket-resolution-codes",
|
url: "/dashboard/ticket-resolution-codes",
|
||||||
icon: <KeyRoundIcon />,
|
icon: <KeyRoundIcon />,
|
||||||
requiredPermission: "ticketResolutionCode.view",
|
requiredPermission: "ticketResolutionCode.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "客服档案",
|
title: "客服档案",
|
||||||
url: "/agents",
|
url: "/dashboard/agents",
|
||||||
icon: <UserCogIcon />,
|
icon: <UserCogIcon />,
|
||||||
requiredPermission: "agent.view",
|
requiredPermission: "agent.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "客服组排班",
|
title: "客服组排班",
|
||||||
url: "/agent-team-schedules",
|
url: "/dashboard/agent-team-schedules",
|
||||||
icon: <CalendarClockIcon />,
|
icon: <CalendarClockIcon />,
|
||||||
requiredPermission: "agentTeamSchedule.view",
|
requiredPermission: "agentTeamSchedule.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "接入渠道",
|
title: "接入渠道",
|
||||||
url: "/channels",
|
url: "/dashboard/channels",
|
||||||
icon: <GlobeIcon />,
|
icon: <GlobeIcon />,
|
||||||
requiredPermission: "channel.view",
|
requiredPermission: "channel.view",
|
||||||
},
|
},
|
||||||
@@ -191,37 +191,37 @@ export const dashboardNavSections: DashboardNavSectionConfig[] = [
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: "知识库",
|
title: "知识库",
|
||||||
url: "/knowledge",
|
url: "/dashboard/knowledge",
|
||||||
icon: <FileTextIcon />,
|
icon: <FileTextIcon />,
|
||||||
requiredPermission: "knowledgeBase.view",
|
requiredPermission: "knowledgeBase.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "AI配置",
|
title: "AI配置",
|
||||||
url: "/ai-configs",
|
url: "/dashboard/ai-configs",
|
||||||
icon: <BrainCircuitIcon />,
|
icon: <BrainCircuitIcon />,
|
||||||
requiredPermission: "aiConfig.view",
|
requiredPermission: "aiConfig.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "AI Agent",
|
title: "AI Agent",
|
||||||
url: "/ai-agents",
|
url: "/dashboard/ai-agents",
|
||||||
icon: <MessageSquareMoreIcon />,
|
icon: <MessageSquareMoreIcon />,
|
||||||
requiredPermission: "aiAgent.view",
|
requiredPermission: "aiAgent.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Skills",
|
title: "Skills",
|
||||||
url: "/skill-definition",
|
url: "/dashboard/skill-definition",
|
||||||
icon: <MessageSquareCodeIcon />,
|
icon: <MessageSquareCodeIcon />,
|
||||||
requiredPermission: "skillDefinition.view",
|
requiredPermission: "skillDefinition.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "MCP调试",
|
title: "MCP调试",
|
||||||
url: "/mcp",
|
url: "/dashboard/mcp",
|
||||||
icon: <MessageSquareCodeIcon />,
|
icon: <MessageSquareCodeIcon />,
|
||||||
requiredPermission: "mcp.view",
|
requiredPermission: "mcp.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Agent日志",
|
title: "Agent日志",
|
||||||
url: "/agent-run-logs",
|
url: "/dashboard/agent-run-logs",
|
||||||
icon: <ActivitySquareIcon />,
|
icon: <ActivitySquareIcon />,
|
||||||
requiredPermission: "conversation.view",
|
requiredPermission: "conversation.view",
|
||||||
},
|
},
|
||||||
@@ -232,19 +232,19 @@ export const dashboardNavSections: DashboardNavSectionConfig[] = [
|
|||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
title: "用户管理",
|
title: "用户管理",
|
||||||
url: "/users",
|
url: "/dashboard/users",
|
||||||
icon: <UsersIcon />,
|
icon: <UsersIcon />,
|
||||||
requiredPermission: "user.view",
|
requiredPermission: "user.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "角色管理",
|
title: "角色管理",
|
||||||
url: "/roles",
|
url: "/dashboard/roles",
|
||||||
icon: <ShieldCheckIcon />,
|
icon: <ShieldCheckIcon />,
|
||||||
requiredPermission: "role.view",
|
requiredPermission: "role.view",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "权限管理",
|
title: "权限管理",
|
||||||
url: "/permissions",
|
url: "/dashboard/permissions",
|
||||||
icon: <KeyRoundIcon />,
|
icon: <KeyRoundIcon />,
|
||||||
requiredPermission: "permission.view",
|
requiredPermission: "permission.view",
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user