This commit is contained in:
Asuka 2025-06-18 17:49:07 +08:00
parent 69dfadd81a
commit 24c58b8329
6 changed files with 266 additions and 196 deletions

View File

@ -1,4 +1,4 @@
import { AppSidebar } from "@/components/app-sidebar" import { AppSidebar } from "@/components/sidebar/app-sidebar"
import { import {
Breadcrumb, Breadcrumb,
BreadcrumbItem, BreadcrumbItem,

View File

@ -0,0 +1,39 @@
import { Button } from "@/components/ui/button"
import {
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogClose,
} from "@/components/ui/dialog"
import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
export function ShareDialogContent({ link }: { link: string }) {
return (
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Share link</DialogTitle>
<DialogDescription>
Anyone who has this link will be able to view this.
</DialogDescription>
</DialogHeader>
<div className="flex items-center gap-2">
<div className="grid flex-1 gap-2">
<Label htmlFor="link" className="sr-only">
Link
</Label>
<Input id="link" defaultValue={link} readOnly />
</div>
</div>
<DialogFooter className="sm:justify-start">
<DialogClose asChild>
<Button type="button" variant="secondary">
Close
</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
)
}

View File

@ -0,0 +1,84 @@
"use client"
import * as React from "react"
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { Check, X, Info, AlertTriangle } from "lucide-react"
import { Badge } from "@/components/ui/badge"
import Link from "next/link"
export function WrongbookDialog({ problems, children }: { problems: { id: string; name: string; status: string }[]; children?: React.ReactNode }) {
return (
<Dialog>
<DialogTrigger asChild>
{children ? children : (
<button className="px-4 py-2 rounded bg-primary text-primary-foreground hover:bg-primary/90"></button>
)}
</DialogTrigger>
<DialogContent className="max-w-2xl p-0">
<DialogHeader className="px-6 pt-6">
<DialogTitle className="text-lg font-bold"></DialogTitle>
</DialogHeader>
<div className="p-6">
<div className="rounded-lg border bg-card text-card-foreground shadow-sm overflow-x-auto">
<table className="min-w-full text-sm">
<thead>
<tr className="border-b bg-muted/50">
<th className="px-3 py-2 text-left font-semibold">ID</th>
<th className="px-3 py-2 text-left font-semibold"></th>
<th className="px-3 py-2 text-left font-semibold"></th>
</tr>
</thead>
<tbody>
{problems.map((item) => (
<tr key={item.id} className="border-b last:border-0 hover:bg-muted/30 transition">
<td className="px-3 py-2 text-gray-500 font-mono">{item.id}</td>
<td className="px-3 py-2">
<Link href={`/problem/${item.id}`} className="text-primary underline underline-offset-2 hover:text-primary/80">
{item.name}
</Link>
</td>
<td className="px-3 py-2">
{(() => {
if (item.status === "AC") {
return (
<Badge className="bg-green-500 text-white" variant="default">
<Check className="w-3 h-3 mr-1" />{item.status}
</Badge>
)
} else if (item.status === "WA") {
return (
<Badge className="bg-red-500 text-white" variant="destructive">
<X className="w-3 h-3 mr-1" />{item.status}
</Badge>
)
} else if (["RE", "CE", "MLE", "TLE"].includes(item.status)) {
return (
<Badge className="bg-orange-500 text-white" variant="secondary">
<AlertTriangle className="w-3 h-3 mr-1" />{item.status}
</Badge>
)
} else {
return (
<Badge className="bg-gray-200 text-gray-700" variant="secondary">
<Info className="w-3 h-3 mr-1" />{item.status}
</Badge>
)
}
})()}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</DialogContent>
</Dialog>
)
}

View File

@ -1,122 +0,0 @@
"use client"
import { useSession } from "next-auth/react";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarRail,
} from "@/components/ui/sidebar";
import { NavMain } from "@/components/nav-main";
import { NavProjects } from "@/components/nav-projects";
import { NavUser } from "@/components/nav-user";
import {
Command,
House,
PieChart,
Settings2,
} from "lucide-react";
import { useEffect, useState } from "react";
import { PrismaClient } from "@prisma/client";
// 如果 adminData.teams 没有在别处定义,请取消注释下面的代码并提供实际值
/*
const teams = [
// 在这里放置你的团队数据
];
*/
const adminData = {
// teams: [
// {
// name: "Admin Team",
// logo: GalleryVerticalEnd,
// plan: "Enterprise",
// },
// ],
navMain: [
{
title: "OverView",
url: "/",
icon: House,
},
{
title: "Dashboard",
url: "/admin",
icon: Settings2,
items: [
{
title: "User",
url: "/admin/users",
},
{
title: "Teacher",
url: "/admin/problems",
},
],
},
],
projects: [
{
name: "System Monitoring",
url: "/admin/monitoring",
icon: PieChart,
},
{
name: "Admin Tools",
url: "/admin/tools",
icon: Command,
},
]
};
export const AdminSidebar = ({ ...props }: React.ComponentProps<typeof Sidebar>) => {
const { data: session } = useSession();
const [userAvatar, setUserAvatar] = useState("");
useEffect(() => {
const fetchUserAvatar = async () => {
if (session?.user?.email) {
const prisma = new PrismaClient();
try {
const user = await prisma.user.findUnique({
where: { email: session.user.email },
select: { image: true }
});
setUserAvatar(user?.image || "");
} catch (error) {
console.error("Failed to fetch user avatar:", error);
} finally {
await prisma.$disconnect();
}
}
};
fetchUserAvatar();
}, [session?.user?.email]);
const user = {
name: session?.user?.name || "Admin",
email: session?.user?.email || "admin@example.com",
avatar: userAvatar
};
return (
<Sidebar collapsible="icon" {...props}>
{/*<SidebarHeader>*/}
{/* <TeamSwitcher teams={adminData.teams} />*/}
{/*</SidebarHeader>*/}
<SidebarContent>
<NavMain items={adminData.navMain} />
<NavProjects projects={adminData.projects} />
</SidebarContent>
<SidebarFooter>
<NavUser user={user} />
</SidebarFooter>
<SidebarRail />
</Sidebar>
);
};

View File

@ -1,12 +1,25 @@
"use client" "use client"
import { import {
BookX,
Folder, Folder,
MoreHorizontal, MoreHorizontal,
Share, Share,
Trash2, Trash2,
Check,
X,
Info,
AlertTriangle,
type LucideIcon, type LucideIcon,
} from "lucide-react" } from "lucide-react"
import React, { useState } from "react"
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
} from "@/components/ui/dialog"
import { import {
DropdownMenu, DropdownMenu,
@ -24,66 +37,122 @@ import {
SidebarMenuItem, SidebarMenuItem,
useSidebar, useSidebar,
} from "@/components/ui/sidebar" } from "@/components/ui/sidebar"
import { Badge } from "@/components/ui/badge"
import { WrongbookDialog } from "@/components/UncompletedProject/wrongbook-dialog"
import { ShareDialogContent } from "@/components/UncompletedProject/sharedialog"
export function NavProjects({ export function NavProjects({
projects, projects,
}: { }: {
projects: { projects: {
id: string
name: string name: string
url: string status: string
icon: LucideIcon
}[] }[]
}) { }) {
const { isMobile } = useSidebar() const { isMobile } = useSidebar()
const [shareOpen, setShareOpen] = useState(false)
const [shareLink, setShareLink] = useState("")
return ( return (
<SidebarGroup className="group-data-[collapsible=icon]:hidden"> <>
<SidebarGroupLabel>Recent programming topics</SidebarGroupLabel> <SidebarGroup className="group-data-[collapsible=icon]:hidden">
<SidebarMenu> <SidebarGroupLabel></SidebarGroupLabel>
{projects.map((item) => ( <SidebarMenu>
<SidebarMenuItem key={item.name}> {projects.slice(0, 1).map((item) => (
<SidebarMenuButton asChild> <SidebarMenuItem key={item.id}>
<a href={item.url}> <SidebarMenuButton asChild>
<item.icon /> <a href={`/problem/${item.id}`}>
<span>{item.name}</span> <BookX />
</a> <span className="flex w-full items-center">
</SidebarMenuButton> <span
<DropdownMenu> className="truncate max-w-[120px] flex-1"
<DropdownMenuTrigger asChild> title={item.name}
<SidebarMenuAction showOnHover> >
<MoreHorizontal /> {item.name}
<span className="sr-only">More</span> </span>
</SidebarMenuAction> {(() => {
</DropdownMenuTrigger> if (item.status === "AC") {
<DropdownMenuContent return (
className="w-48" <span className="ml-2 min-w-[60px] text-xs text-right px-2 py-0.5 rounded-full border flex items-center gap-1 border-green-500 bg-green-500 text-white">
side={isMobile ? "bottom" : "right"} <Check className="w-3 h-3" />
align={isMobile ? "end" : "start"} {item.status}
> </span>
<DropdownMenuItem> )
<Folder className="text-muted-foreground" /> } else if (item.status === "WA") {
<span>View Project</span> return (
</DropdownMenuItem> <span className="ml-2 min-w-[60px] text-xs text-right px-2 py-0.5 rounded-full border flex items-center gap-1 border-red-500 bg-red-500 text-white">
<DropdownMenuItem> <X className="w-3 h-3" />
<Share className="text-muted-foreground" /> {item.status}
<span>Share Project</span> </span>
</DropdownMenuItem> )
<DropdownMenuSeparator /> } else if (["RE", "CE", "MLE", "TLE"].includes(item.status)) {
<DropdownMenuItem> return (
<Trash2 className="text-muted-foreground" /> <span className="ml-2 min-w-[60px] text-xs text-right px-2 py-0.5 rounded-full border flex items-center gap-1 border-orange-500 bg-orange-500 text-white">
<span>Delete Project</span> <AlertTriangle className="w-3 h-3" />
</DropdownMenuItem> {item.status}
</DropdownMenuContent> </span>
</DropdownMenu> )
} else {
return (
<span className="ml-2 min-w-[60px] text-xs text-right px-2 py-0.5 rounded-full border flex items-center gap-1 border-gray-400 bg-gray-100 text-gray-700">
<Info className="w-3 h-3" />
{item.status}
</span>
)
}
})()}
</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></span>
</DropdownMenuItem>
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation()
setShareLink(`${window.location.origin}/problem/${item.id}`)
setShareOpen(true)
}}
>
<Share className="text-muted-foreground mr-2" />
<span></span>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>
<Trash2 className="text-muted-foreground" />
<span></span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
))}
<SidebarMenuItem>
<WrongbookDialog problems={projects}>
<SidebarMenuButton>
<MoreHorizontal />
<span></span>
</SidebarMenuButton>
</WrongbookDialog>
</SidebarMenuItem> </SidebarMenuItem>
))} </SidebarMenu>
<SidebarMenuItem> </SidebarGroup>
<SidebarMenuButton> <Dialog open={shareOpen} onOpenChange={setShareOpen}>
<MoreHorizontal /> <ShareDialogContent link={shareLink} />
<span>More</span> </Dialog>
</SidebarMenuButton> </>
</SidebarMenuItem>
</SidebarMenu>
</SidebarGroup>
) )
} }

View File

@ -35,56 +35,56 @@ const data = {
}, },
navMain: [ navMain: [
{ {
title: "Dashboard", title: "页面",
url: "#", url: "#",
icon: SquareTerminal, icon: SquareTerminal,
isActive: true, isActive: true,
items: [ items: [
{ {
title: "Home", title: "主页",
url: "/", url: "/dashboard",
}, },
{ {
title: "Personal interface", title: "历史记录",
url: "#", url: "#",
}, },
{ {
title: "Problems", title: "题目集",
url: "/problemset", url: "/problemset",
}, },
], ],
}, },
{ {
title: "Done Topics", title: "已完成事项",
url: "#", url: "#",
icon: BookOpen, icon: BookOpen,
items: [ items: [
{ {
title: "All Coding", title: "全部编程集",
url: "#", url: "#",
}, },
{ {
title: "Correct Codingset", title: "错题集",
url: "#", url: "#",
}, },
{ {
title: "Wrong Codingset", title: "收藏集",
url: "#", url: "#",
}, },
], ],
}, },
{ {
title: "Settings", title: "设置",
url: "#", url: "#",
icon: Settings2, icon: Settings2,
items: [ items: [
{ {
title: "General", title: "一般设置",
url: "#", url: "#",
}, },
{ {
title: "Language", title: "语言",
url: "#", url: "#",
}, },
], ],
@ -102,21 +102,21 @@ const data = {
icon: Send, icon: Send,
}, },
], ],
projects: [ wrongProblems: [
{ {
name: "Design Engineering", id: "abc123",
url: "#", name: "Two Sum",
icon: Frame, status: "WA",
}, },
{ {
name: "Sales & Marketing", id: "def456",
url: "#", name: "Reverse Linked List",
icon: PieChart, status: "RE",
}, },
{ {
name: "Travel", id: "ghi789",
url: "#", name: "Binary Tree Paths",
icon: Map, status: "TLE",
}, },
], ],
} }
@ -143,7 +143,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent>
<NavMain items={data.navMain} /> <NavMain items={data.navMain} />
<NavProjects projects={data.projects} /> <NavProjects projects={data.wrongProblems} />
<NavSecondary items={data.navSecondary} className="mt-auto" /> <NavSecondary items={data.navSecondary} className="mt-auto" />
</SidebarContent> </SidebarContent>
<SidebarFooter> <SidebarFooter>