mirror of
https://github.com/massbug/judge4c.git
synced 2025-07-03 23:30:50 +00:00
6.18
This commit is contained in:
parent
69dfadd81a
commit
24c58b8329
@ -1,4 +1,4 @@
|
|||||||
import { AppSidebar } from "@/components/app-sidebar"
|
import { AppSidebar } from "@/components/sidebar/app-sidebar"
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
|
39
src/components/UncompletedProject/sharedialog.tsx
Normal file
39
src/components/UncompletedProject/sharedialog.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
84
src/components/UncompletedProject/wrongbook-dialog.tsx
Normal file
84
src/components/UncompletedProject/wrongbook-dialog.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
@ -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,28 +37,72 @@ 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">
|
<SidebarGroup className="group-data-[collapsible=icon]:hidden">
|
||||||
<SidebarGroupLabel>Recent programming topics</SidebarGroupLabel>
|
<SidebarGroupLabel>待完成项目</SidebarGroupLabel>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{projects.map((item) => (
|
{projects.slice(0, 1).map((item) => (
|
||||||
<SidebarMenuItem key={item.name}>
|
<SidebarMenuItem key={item.id}>
|
||||||
<SidebarMenuButton asChild>
|
<SidebarMenuButton asChild>
|
||||||
<a href={item.url}>
|
<a href={`/problem/${item.id}`}>
|
||||||
<item.icon />
|
<BookX />
|
||||||
<span>{item.name}</span>
|
<span className="flex w-full items-center">
|
||||||
|
<span
|
||||||
|
className="truncate max-w-[120px] flex-1"
|
||||||
|
title={item.name}
|
||||||
|
>
|
||||||
|
{item.name}
|
||||||
|
</span>
|
||||||
|
{(() => {
|
||||||
|
if (item.status === "AC") {
|
||||||
|
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-green-500 bg-green-500 text-white">
|
||||||
|
<Check className="w-3 h-3" />
|
||||||
|
{item.status}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
} else if (item.status === "WA") {
|
||||||
|
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-red-500 bg-red-500 text-white">
|
||||||
|
<X className="w-3 h-3" />
|
||||||
|
{item.status}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
} else if (["RE", "CE", "MLE", "TLE"].includes(item.status)) {
|
||||||
|
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-orange-500 bg-orange-500 text-white">
|
||||||
|
<AlertTriangle className="w-3 h-3" />
|
||||||
|
{item.status}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
} 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>
|
</a>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
@ -56,34 +113,46 @@ export function NavProjects({
|
|||||||
</SidebarMenuAction>
|
</SidebarMenuAction>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
className="w-48"
|
className="w-48 rounded-lg"
|
||||||
side={isMobile ? "bottom" : "right"}
|
side={isMobile ? "bottom" : "right"}
|
||||||
align={isMobile ? "end" : "start"}
|
align={isMobile ? "end" : "start"}
|
||||||
>
|
>
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<Folder className="text-muted-foreground" />
|
<Folder className="text-muted-foreground" />
|
||||||
<span>View Project</span>
|
<span>查看</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem
|
||||||
<Share className="text-muted-foreground" />
|
onClick={(e) => {
|
||||||
<span>Share Project</span>
|
e.stopPropagation()
|
||||||
|
setShareLink(`${window.location.origin}/problem/${item.id}`)
|
||||||
|
setShareOpen(true)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Share className="text-muted-foreground mr-2" />
|
||||||
|
<span>复制链接</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<Trash2 className="text-muted-foreground" />
|
<Trash2 className="text-muted-foreground" />
|
||||||
<span>Delete Project</span>
|
<span>移除</span>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
))}
|
))}
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
|
<WrongbookDialog problems={projects}>
|
||||||
<SidebarMenuButton>
|
<SidebarMenuButton>
|
||||||
<MoreHorizontal />
|
<MoreHorizontal />
|
||||||
<span>More</span>
|
<span>更多</span>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
|
</WrongbookDialog>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
|
<Dialog open={shareOpen} onOpenChange={setShareOpen}>
|
||||||
|
<ShareDialogContent link={shareLink} />
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
Loading…
Reference in New Issue
Block a user