diff --git a/src/app/(main)/problems/[slug]/components/tabs-card.tsx b/src/app/(app)/problems/[slug]/components/tabs-card.tsx
similarity index 100%
rename from src/app/(main)/problems/[slug]/components/tabs-card.tsx
rename to src/app/(app)/problems/[slug]/components/tabs-card.tsx
diff --git a/src/app/(main)/problems/[slug]/layout.tsx b/src/app/(app)/problems/[slug]/layout.tsx
similarity index 90%
rename from src/app/(main)/problems/[slug]/layout.tsx
rename to src/app/(app)/problems/[slug]/layout.tsx
index 5622fc3..0c6932a 100644
--- a/src/app/(main)/problems/[slug]/layout.tsx
+++ b/src/app/(app)/problems/[slug]/layout.tsx
@@ -2,12 +2,14 @@ import {
CodeXmlIcon,
FileChartColumnIcon,
SquareChevronRightIcon,
+ GitCommitHorizontalIcon,
} from "lucide-react";
import {
ResizableHandle,
ResizablePanel,
ResizablePanelGroup,
} from "@/components/ui/resizable";
+import CommitPage from "./views/commit-page";
import AnswerPage from "./views/answer-page";
import ConsolePage from "./views/console-page";
import { TabsCard } from "./components/tabs-card";
@@ -28,6 +30,12 @@ export default async function ProblemLayout({
value: "description",
content: ,
},
+ {
+ icon: GitCommitHorizontalIcon,
+ label: "提交记录",
+ value: "commit",
+ content: ,
+ },
];
const answerTabsItems = [
{
diff --git a/src/app/(main)/problems/[slug]/page.tsx b/src/app/(app)/problems/[slug]/page.tsx
similarity index 100%
rename from src/app/(main)/problems/[slug]/page.tsx
rename to src/app/(app)/problems/[slug]/page.tsx
diff --git a/src/app/(main)/problems/[slug]/views/answer-page.tsx b/src/app/(app)/problems/[slug]/views/answer-page.tsx
similarity index 100%
rename from src/app/(main)/problems/[slug]/views/answer-page.tsx
rename to src/app/(app)/problems/[slug]/views/answer-page.tsx
diff --git a/src/app/(app)/problems/[slug]/views/commit-page.tsx b/src/app/(app)/problems/[slug]/views/commit-page.tsx
new file mode 100644
index 0000000..6fb7578
--- /dev/null
+++ b/src/app/(app)/problems/[slug]/views/commit-page.tsx
@@ -0,0 +1,7 @@
+export default function CommitPage({ slug }: { slug: string }) {
+ return (
+
+
Commit Page: {slug}
+
+ );
+}
diff --git a/src/app/(main)/problems/[slug]/views/console-page.tsx b/src/app/(app)/problems/[slug]/views/console-page.tsx
similarity index 100%
rename from src/app/(main)/problems/[slug]/views/console-page.tsx
rename to src/app/(app)/problems/[slug]/views/console-page.tsx
diff --git a/src/app/(main)/problems/[slug]/views/description-page.tsx b/src/app/(app)/problems/[slug]/views/description-page.tsx
similarity index 100%
rename from src/app/(main)/problems/[slug]/views/description-page.tsx
rename to src/app/(app)/problems/[slug]/views/description-page.tsx
diff --git a/src/app/(app)/problems/examples/components/columns.tsx b/src/app/(app)/problems/examples/components/columns.tsx
new file mode 100644
index 0000000..9583458
--- /dev/null
+++ b/src/app/(app)/problems/examples/components/columns.tsx
@@ -0,0 +1,123 @@
+"use client";
+
+import { ColumnDef } from "@tanstack/react-table";
+
+import { Badge } from "@/components/ui/badge";
+import { Checkbox } from "@/components/ui/checkbox";
+
+import { labels, priorities, statuses } from "../data/data";
+import { Task } from "../data/schema";
+import { DataTableColumnHeader } from "./data-table-column-header";
+import { DataTableRowActions } from "./data-table-row-actions";
+
+export const columns: ColumnDef[] = [
+ {
+ id: "select",
+ header: ({ table }) => (
+ table.toggleAllPageRowsSelected(!!value)}
+ aria-label="Select all"
+ className="translate-y-[2px]"
+ />
+ ),
+ cell: ({ row }) => (
+ row.toggleSelected(!!value)}
+ aria-label="Select row"
+ className="translate-y-[2px]"
+ />
+ ),
+ enableSorting: false,
+ enableHiding: false,
+ },
+ {
+ accessorKey: "id",
+ header: ({ column }) => (
+
+ ),
+ cell: ({ row }) => {row.getValue("id")}
,
+ enableSorting: false,
+ enableHiding: false,
+ },
+ {
+ accessorKey: "title",
+ header: ({ column }) => (
+
+ ),
+ cell: ({ row }) => {
+ const label = labels.find((label) => label.value === row.original.label);
+
+ return (
+
+ {label && {label.label}}
+
+ {row.getValue("title")}
+
+
+ );
+ },
+ },
+ {
+ accessorKey: "status",
+ header: ({ column }) => (
+
+ ),
+ cell: ({ row }) => {
+ const status = statuses.find(
+ (status) => status.value === row.getValue("status")
+ );
+
+ if (!status) {
+ return null;
+ }
+
+ return (
+
+ {status.icon && (
+
+ )}
+ {status.label}
+
+ );
+ },
+ filterFn: (row, id, value) => {
+ return value.includes(row.getValue(id));
+ },
+ },
+ {
+ accessorKey: "priority",
+ header: ({ column }) => (
+
+ ),
+ cell: ({ row }) => {
+ const priority = priorities.find(
+ (priority) => priority.value === row.getValue("priority")
+ );
+
+ if (!priority) {
+ return null;
+ }
+
+ return (
+
+ {priority.icon && (
+
+ )}
+
{priority.label}
+
+ );
+ },
+ filterFn: (row, id, value) => {
+ return value.includes(row.getValue(id));
+ },
+ },
+ {
+ id: "actions",
+ cell: ({ row }) => ,
+ },
+];
diff --git a/src/app/(app)/problems/examples/components/data-table-column-header.tsx b/src/app/(app)/problems/examples/components/data-table-column-header.tsx
new file mode 100644
index 0000000..5d7aa42
--- /dev/null
+++ b/src/app/(app)/problems/examples/components/data-table-column-header.tsx
@@ -0,0 +1,71 @@
+import {
+ ArrowDownIcon,
+ ArrowUpIcon,
+ CaretSortIcon,
+ EyeNoneIcon,
+} from "@radix-ui/react-icons";
+import { Column } from "@tanstack/react-table";
+
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+interface DataTableColumnHeaderProps
+ extends React.HTMLAttributes {
+ column: Column;
+ title: string;
+}
+
+export function DataTableColumnHeader({
+ column,
+ title,
+ className,
+}: DataTableColumnHeaderProps) {
+ if (!column.getCanSort()) {
+ return {title}
;
+ }
+
+ return (
+
+
+
+
+
+
+ column.toggleSorting(false)}>
+
+ 升序
+
+ column.toggleSorting(true)}>
+
+ 降序
+
+
+ column.toggleVisibility(false)}>
+
+ 隐藏
+
+
+
+
+ );
+}
diff --git a/src/app/(app)/problems/examples/components/data-table-faceted-filter.tsx b/src/app/(app)/problems/examples/components/data-table-faceted-filter.tsx
new file mode 100644
index 0000000..7f04ded
--- /dev/null
+++ b/src/app/(app)/problems/examples/components/data-table-faceted-filter.tsx
@@ -0,0 +1,147 @@
+import * as React from "react";
+import { CheckIcon, PlusCircledIcon } from "@radix-ui/react-icons";
+import { Column } from "@tanstack/react-table";
+
+import { cn } from "@/lib/utils";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+ CommandSeparator,
+} from "@/components/ui/command";
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@/components/ui/popover";
+import { Separator } from "@/components/ui/separator";
+
+interface DataTableFacetedFilterProps {
+ column?: Column;
+ title?: string;
+ options: {
+ label: string;
+ value: string;
+ icon?: React.ComponentType<{ className?: string }>;
+ }[];
+}
+
+export function DataTableFacetedFilter({
+ column,
+ title,
+ options,
+}: DataTableFacetedFilterProps) {
+ const facets = column?.getFacetedUniqueValues();
+ const selectedValues = new Set(column?.getFilterValue() as string[]);
+
+ return (
+
+
+
+
+
+
+
+
+ No results found.
+
+ {options.map((option) => {
+ const isSelected = selectedValues.has(option.value);
+ return (
+ {
+ if (isSelected) {
+ selectedValues.delete(option.value);
+ } else {
+ selectedValues.add(option.value);
+ }
+ const filterValues = Array.from(selectedValues);
+ column?.setFilterValue(
+ filterValues.length ? filterValues : undefined
+ );
+ }}
+ >
+
+
+
+ {option.icon && (
+
+ )}
+ {option.label}
+ {facets?.get(option.value) && (
+
+ {facets.get(option.value)}
+
+ )}
+
+ );
+ })}
+
+ {selectedValues.size > 0 && (
+ <>
+
+
+ column?.setFilterValue(undefined)}
+ className="justify-center text-center"
+ >
+ Clear filters
+
+
+ >
+ )}
+
+
+
+
+ );
+}
diff --git a/src/app/(app)/problems/examples/components/data-table-pagination.tsx b/src/app/(app)/problems/examples/components/data-table-pagination.tsx
new file mode 100644
index 0000000..68c93ac
--- /dev/null
+++ b/src/app/(app)/problems/examples/components/data-table-pagination.tsx
@@ -0,0 +1,97 @@
+import {
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ DoubleArrowLeftIcon,
+ DoubleArrowRightIcon,
+} from "@radix-ui/react-icons";
+import { Table } from "@tanstack/react-table";
+
+import { Button } from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+
+interface DataTablePaginationProps {
+ table: Table;
+}
+
+export function DataTablePagination({
+ table,
+}: DataTablePaginationProps) {
+ return (
+
+
+ 已选中 {table.getFilteredSelectedRowModel().rows.length} 行,共{" "}
+ {table.getFilteredRowModel().rows.length} 行。
+
+
+
+
每页行数
+
+
+
+ 第 {table.getState().pagination.pageIndex + 1} 页,共{" "}
+ {table.getPageCount()} 页
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(app)/problems/examples/components/data-table-row-actions.tsx b/src/app/(app)/problems/examples/components/data-table-row-actions.tsx
new file mode 100644
index 0000000..54ca25b
--- /dev/null
+++ b/src/app/(app)/problems/examples/components/data-table-row-actions.tsx
@@ -0,0 +1,69 @@
+"use client";
+
+import { DotsHorizontalIcon } from "@radix-ui/react-icons";
+import { Row } from "@tanstack/react-table";
+
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu";
+
+import { labels } from "../data/data";
+import { taskSchema } from "../data/schema";
+
+interface DataTableRowActionsProps {
+ row: Row;
+}
+
+export function DataTableRowActions({
+ row,
+}: DataTableRowActionsProps) {
+ const task = taskSchema.parse(row.original);
+
+ return (
+
+
+
+
+
+ Edit
+ Make a copy
+ Favorite
+
+
+ Labels
+
+
+ {labels.map((label) => (
+
+ {label.label}
+
+ ))}
+
+
+
+
+
+ Delete
+ ⌘⌫
+
+
+
+ );
+}
diff --git a/src/app/(app)/problems/examples/components/data-table-toolbar.tsx b/src/app/(app)/problems/examples/components/data-table-toolbar.tsx
new file mode 100644
index 0000000..a1b417b
--- /dev/null
+++ b/src/app/(app)/problems/examples/components/data-table-toolbar.tsx
@@ -0,0 +1,61 @@
+"use client";
+
+import { Cross2Icon } from "@radix-ui/react-icons";
+import { Table } from "@tanstack/react-table";
+
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { DataTableViewOptions } from "./data-table-view-options";
+
+import { priorities, statuses } from "../data/data";
+import { DataTableFacetedFilter } from "./data-table-faceted-filter";
+
+interface DataTableToolbarProps {
+ table: Table;
+}
+
+export function DataTableToolbar({
+ table,
+}: DataTableToolbarProps) {
+ const isFiltered = table.getState().columnFilters.length > 0;
+
+ return (
+
+
+
+ table.getColumn("title")?.setFilterValue(event.target.value)
+ }
+ className="h-8 w-[150px] lg:w-[250px]"
+ />
+ {table.getColumn("status") && (
+
+ )}
+ {table.getColumn("priority") && (
+
+ )}
+ {isFiltered && (
+
+ )}
+
+
+
+ );
+}
diff --git a/src/app/(app)/problems/examples/components/data-table-view-options.tsx b/src/app/(app)/problems/examples/components/data-table-view-options.tsx
new file mode 100644
index 0000000..7719879
--- /dev/null
+++ b/src/app/(app)/problems/examples/components/data-table-view-options.tsx
@@ -0,0 +1,59 @@
+"use client";
+
+import { DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
+import { MixerHorizontalIcon } from "@radix-ui/react-icons";
+import { Table } from "@tanstack/react-table";
+
+import { Button } from "@/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuCheckboxItem,
+ DropdownMenuContent,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+} from "@/components/ui/dropdown-menu";
+
+interface DataTableViewOptionsProps {
+ table: Table;
+}
+
+export function DataTableViewOptions({
+ table,
+}: DataTableViewOptionsProps) {
+ return (
+
+
+
+
+
+ 显示/隐藏列
+
+ {table
+ .getAllColumns()
+ .filter(
+ (column) =>
+ typeof column.accessorFn !== "undefined" && column.getCanHide()
+ )
+ .map((column) => {
+ return (
+ column.toggleVisibility(!!value)}
+ >
+ {column.id}
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/app/(app)/problems/examples/components/data-table.tsx b/src/app/(app)/problems/examples/components/data-table.tsx
new file mode 100644
index 0000000..91e801f
--- /dev/null
+++ b/src/app/(app)/problems/examples/components/data-table.tsx
@@ -0,0 +1,126 @@
+"use client";
+
+import * as React from "react";
+import {
+ ColumnDef,
+ ColumnFiltersState,
+ SortingState,
+ VisibilityState,
+ flexRender,
+ getCoreRowModel,
+ getFacetedRowModel,
+ getFacetedUniqueValues,
+ getFilteredRowModel,
+ getPaginationRowModel,
+ getSortedRowModel,
+ useReactTable,
+} from "@tanstack/react-table";
+
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+
+import { DataTablePagination } from "./data-table-pagination";
+import { DataTableToolbar } from "./data-table-toolbar";
+
+interface DataTableProps {
+ columns: ColumnDef[];
+ data: TData[];
+}
+
+export function DataTable({
+ columns,
+ data,
+}: DataTableProps) {
+ const [rowSelection, setRowSelection] = React.useState({});
+ const [columnVisibility, setColumnVisibility] =
+ React.useState({});
+ const [columnFilters, setColumnFilters] = React.useState(
+ []
+ );
+ const [sorting, setSorting] = React.useState([]);
+
+ const table = useReactTable({
+ data,
+ columns,
+ state: {
+ sorting,
+ columnVisibility,
+ rowSelection,
+ columnFilters,
+ },
+ enableRowSelection: true,
+ onRowSelectionChange: setRowSelection,
+ onSortingChange: setSorting,
+ onColumnFiltersChange: setColumnFilters,
+ onColumnVisibilityChange: setColumnVisibility,
+ getCoreRowModel: getCoreRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFacetedRowModel: getFacetedRowModel(),
+ getFacetedUniqueValues: getFacetedUniqueValues(),
+ });
+
+ return (
+
+
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+
+ );
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows?.length ? (
+ table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+
+ ))}
+
+ ))
+ ) : (
+
+
+ No results.
+
+
+ )}
+
+
+
+
+
+ );
+}
diff --git a/src/app/(app)/problems/examples/data/data.tsx b/src/app/(app)/problems/examples/data/data.tsx
new file mode 100644
index 0000000..f21c7cf
--- /dev/null
+++ b/src/app/(app)/problems/examples/data/data.tsx
@@ -0,0 +1,65 @@
+import {
+ ArrowDownIcon,
+ ArrowRightIcon,
+ ArrowUpIcon,
+ CheckCircledIcon,
+ CircleIcon,
+ CrossCircledIcon,
+ StopwatchIcon,
+} from "@radix-ui/react-icons";
+
+export const labels = [
+ {
+ value: "bug",
+ label: "漏洞",
+ },
+ {
+ value: "feature",
+ label: "特性",
+ },
+ {
+ value: "documentation",
+ label: "文档",
+ },
+];
+
+export const statuses = [
+ {
+ value: "todo",
+ label: "待完成",
+ icon: CircleIcon,
+ },
+ {
+ value: "in progress",
+ label: "进行中",
+ icon: StopwatchIcon,
+ },
+ {
+ value: "done",
+ label: "已完成",
+ icon: CheckCircledIcon,
+ },
+ {
+ value: "canceled",
+ label: "已取消",
+ icon: CrossCircledIcon,
+ },
+];
+
+export const priorities = [
+ {
+ label: "简单",
+ value: "low",
+ icon: ArrowDownIcon,
+ },
+ {
+ label: "中等",
+ value: "medium",
+ icon: ArrowRightIcon,
+ },
+ {
+ label: "困难",
+ value: "high",
+ icon: ArrowUpIcon,
+ },
+];
diff --git a/src/app/(app)/problems/examples/data/schema.tsx b/src/app/(app)/problems/examples/data/schema.tsx
new file mode 100644
index 0000000..8cd11b0
--- /dev/null
+++ b/src/app/(app)/problems/examples/data/schema.tsx
@@ -0,0 +1,13 @@
+import { z } from "zod";
+
+// We're keeping a simple non-relational schema here.
+// IRL, you will have a schema for your data models.
+export const taskSchema = z.object({
+ id: z.string(),
+ title: z.string(),
+ status: z.string(),
+ label: z.string(),
+ priority: z.string(),
+});
+
+export type Task = z.infer;
diff --git a/src/app/(app)/problems/examples/data/seed.ts b/src/app/(app)/problems/examples/data/seed.ts
new file mode 100644
index 0000000..3c47cdb
--- /dev/null
+++ b/src/app/(app)/problems/examples/data/seed.ts
@@ -0,0 +1,20 @@
+import fs from "fs";
+import path from "path";
+import { faker } from "@faker-js/faker";
+
+import { labels, priorities, statuses } from "./data";
+
+const tasks = Array.from({ length: 100 }, () => ({
+ id: `TASK-${faker.number.int({ min: 1000, max: 9999 })}`,
+ title: faker.hacker.phrase().replace(/^./, (letter: string) => letter.toUpperCase()),
+ status: faker.helpers.arrayElement(statuses).value,
+ label: faker.helpers.arrayElement(labels).value,
+ priority: faker.helpers.arrayElement(priorities).value,
+}));
+
+fs.writeFileSync(
+ path.join(__dirname, "tasks.json"),
+ JSON.stringify(tasks, null, 2)
+);
+
+console.log("✅ Tasks data generated.");
diff --git a/src/app/(app)/problems/examples/data/tasks.json b/src/app/(app)/problems/examples/data/tasks.json
new file mode 100644
index 0000000..af654a1
--- /dev/null
+++ b/src/app/(app)/problems/examples/data/tasks.json
@@ -0,0 +1,702 @@
+[
+ {
+ "id": "TASK-8782",
+ "title": "You can't compress the program without quantifying the open-source SSD pixel!",
+ "status": "in progress",
+ "label": "documentation",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-7878",
+ "title": "Try to calculate the EXE feed, maybe it will index the multi-byte pixel!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-7839",
+ "title": "We need to bypass the neural TCP card!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-5562",
+ "title": "The SAS interface is down, bypass the open-source pixel so we can back up the PNG bandwidth!",
+ "status": "todo",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-8686",
+ "title": "I'll parse the wireless SSL protocol, that should driver the API panel!",
+ "status": "canceled",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-1280",
+ "title": "Use the digital TLS panel, then you can transmit the haptic system!",
+ "status": "done",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-7262",
+ "title": "The UTF8 application is down, parse the neural bandwidth so we can back up the PNG firewall!",
+ "status": "done",
+ "label": "feature",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-1138",
+ "title": "Generating the driver won't do anything, we need to quantify the 1080p SMTP bandwidth!",
+ "status": "in progress",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-7184",
+ "title": "We need to program the back-end THX pixel!",
+ "status": "todo",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-5160",
+ "title": "Calculating the bus won't do anything, we need to navigate the back-end JSON protocol!",
+ "status": "in progress",
+ "label": "documentation",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-5618",
+ "title": "Generating the driver won't do anything, we need to index the online SSL application!",
+ "status": "done",
+ "label": "documentation",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-6699",
+ "title": "I'll transmit the wireless JBOD capacitor, that should hard drive the SSD feed!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-2858",
+ "title": "We need to override the online UDP bus!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-9864",
+ "title": "I'll reboot the 1080p FTP panel, that should matrix the HEX hard drive!",
+ "status": "done",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-8404",
+ "title": "We need to generate the virtual HEX alarm!",
+ "status": "in progress",
+ "label": "bug",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-5365",
+ "title": "Backing up the pixel won't do anything, we need to transmit the primary IB array!",
+ "status": "in progress",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-1780",
+ "title": "The CSS feed is down, index the bluetooth transmitter so we can compress the CLI protocol!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-6938",
+ "title": "Use the redundant SCSI application, then you can hack the optical alarm!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-9885",
+ "title": "We need to compress the auxiliary VGA driver!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-3216",
+ "title": "Transmitting the transmitter won't do anything, we need to compress the virtual HDD sensor!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-9285",
+ "title": "The IP monitor is down, copy the haptic alarm so we can generate the HTTP transmitter!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-1024",
+ "title": "Overriding the microchip won't do anything, we need to transmit the digital OCR transmitter!",
+ "status": "in progress",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-7068",
+ "title": "You can't generate the capacitor without indexing the wireless HEX pixel!",
+ "status": "canceled",
+ "label": "bug",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-6502",
+ "title": "Navigating the microchip won't do anything, we need to bypass the back-end SQL bus!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-5326",
+ "title": "We need to hack the redundant UTF8 transmitter!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-6274",
+ "title": "Use the virtual PCI circuit, then you can parse the bluetooth alarm!",
+ "status": "canceled",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-1571",
+ "title": "I'll input the neural DRAM circuit, that should protocol the SMTP interface!",
+ "status": "in progress",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-9518",
+ "title": "Compressing the interface won't do anything, we need to compress the online SDD matrix!",
+ "status": "canceled",
+ "label": "documentation",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-5581",
+ "title": "I'll synthesize the digital COM pixel, that should transmitter the UTF8 protocol!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-2197",
+ "title": "Parsing the feed won't do anything, we need to copy the bluetooth DRAM bus!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-8484",
+ "title": "We need to parse the solid state UDP firewall!",
+ "status": "in progress",
+ "label": "bug",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-9892",
+ "title": "If we back up the application, we can get to the UDP application through the multi-byte THX capacitor!",
+ "status": "done",
+ "label": "documentation",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-9616",
+ "title": "We need to synthesize the cross-platform ASCII pixel!",
+ "status": "in progress",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-9744",
+ "title": "Use the back-end IP card, then you can input the solid state hard drive!",
+ "status": "done",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-1376",
+ "title": "Generating the alarm won't do anything, we need to generate the mobile IP capacitor!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-7382",
+ "title": "If we back up the firewall, we can get to the RAM alarm through the primary UTF8 pixel!",
+ "status": "todo",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-2290",
+ "title": "I'll compress the virtual JSON panel, that should application the UTF8 bus!",
+ "status": "canceled",
+ "label": "documentation",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-1533",
+ "title": "You can't input the firewall without overriding the wireless TCP firewall!",
+ "status": "done",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-4920",
+ "title": "Bypassing the hard drive won't do anything, we need to input the bluetooth JSON program!",
+ "status": "in progress",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-5168",
+ "title": "If we synthesize the bus, we can get to the IP panel through the virtual TLS array!",
+ "status": "in progress",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-7103",
+ "title": "We need to parse the multi-byte EXE bandwidth!",
+ "status": "canceled",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-4314",
+ "title": "If we compress the program, we can get to the XML alarm through the multi-byte COM matrix!",
+ "status": "in progress",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-3415",
+ "title": "Use the cross-platform XML application, then you can quantify the solid state feed!",
+ "status": "todo",
+ "label": "feature",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-8339",
+ "title": "Try to calculate the DNS interface, maybe it will input the bluetooth capacitor!",
+ "status": "in progress",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-6995",
+ "title": "Try to hack the XSS bandwidth, maybe it will override the bluetooth matrix!",
+ "status": "todo",
+ "label": "feature",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-8053",
+ "title": "If we connect the program, we can get to the UTF8 matrix through the digital UDP protocol!",
+ "status": "todo",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-4336",
+ "title": "If we synthesize the microchip, we can get to the SAS sensor through the optical UDP program!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-8790",
+ "title": "I'll back up the optical COM alarm, that should alarm the RSS capacitor!",
+ "status": "done",
+ "label": "bug",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-8980",
+ "title": "Try to navigate the SQL transmitter, maybe it will back up the virtual firewall!",
+ "status": "canceled",
+ "label": "bug",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-7342",
+ "title": "Use the neural CLI card, then you can parse the online port!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-5608",
+ "title": "I'll hack the haptic SSL program, that should bus the UDP transmitter!",
+ "status": "canceled",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-1606",
+ "title": "I'll generate the bluetooth PNG firewall, that should pixel the SSL driver!",
+ "status": "done",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-7872",
+ "title": "Transmitting the circuit won't do anything, we need to reboot the 1080p RSS monitor!",
+ "status": "canceled",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-4167",
+ "title": "Use the cross-platform SMS circuit, then you can synthesize the optical feed!",
+ "status": "canceled",
+ "label": "bug",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-9581",
+ "title": "You can't index the port without hacking the cross-platform XSS monitor!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-8806",
+ "title": "We need to bypass the back-end SSL panel!",
+ "status": "done",
+ "label": "bug",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-6542",
+ "title": "Try to quantify the RSS firewall, maybe it will quantify the open-source system!",
+ "status": "done",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-6806",
+ "title": "The VGA protocol is down, reboot the back-end matrix so we can parse the CSS panel!",
+ "status": "canceled",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-9549",
+ "title": "You can't bypass the bus without connecting the neural JBOD bus!",
+ "status": "todo",
+ "label": "feature",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-1075",
+ "title": "Backing up the driver won't do anything, we need to parse the redundant RAM pixel!",
+ "status": "done",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-1427",
+ "title": "Use the auxiliary PCI circuit, then you can calculate the cross-platform interface!",
+ "status": "done",
+ "label": "documentation",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-1907",
+ "title": "Hacking the circuit won't do anything, we need to back up the online DRAM system!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-4309",
+ "title": "If we generate the system, we can get to the TCP sensor through the optical GB pixel!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-3973",
+ "title": "I'll parse the back-end ADP array, that should bandwidth the RSS bandwidth!",
+ "status": "todo",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-7962",
+ "title": "Use the wireless RAM program, then you can hack the cross-platform feed!",
+ "status": "canceled",
+ "label": "bug",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-3360",
+ "title": "You can't quantify the program without synthesizing the neural OCR interface!",
+ "status": "done",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-9887",
+ "title": "Use the auxiliary ASCII sensor, then you can connect the solid state port!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-3649",
+ "title": "I'll input the virtual USB system, that should circuit the DNS monitor!",
+ "status": "in progress",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-3586",
+ "title": "If we quantify the circuit, we can get to the CLI feed through the mobile SMS hard drive!",
+ "status": "in progress",
+ "label": "bug",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-5150",
+ "title": "I'll hack the wireless XSS port, that should transmitter the IP interface!",
+ "status": "canceled",
+ "label": "feature",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-3652",
+ "title": "The SQL interface is down, override the optical bus so we can program the ASCII interface!",
+ "status": "todo",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-6884",
+ "title": "Use the digital PCI circuit, then you can synthesize the multi-byte microchip!",
+ "status": "canceled",
+ "label": "feature",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-1591",
+ "title": "We need to connect the mobile XSS driver!",
+ "status": "in progress",
+ "label": "feature",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-3802",
+ "title": "Try to override the ASCII protocol, maybe it will parse the virtual matrix!",
+ "status": "in progress",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-7253",
+ "title": "Programming the capacitor won't do anything, we need to bypass the neural IB hard drive!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-9739",
+ "title": "We need to hack the multi-byte HDD bus!",
+ "status": "done",
+ "label": "documentation",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-4424",
+ "title": "Try to hack the HEX alarm, maybe it will connect the optical pixel!",
+ "status": "in progress",
+ "label": "documentation",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-3922",
+ "title": "You can't back up the capacitor without generating the wireless PCI program!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-4921",
+ "title": "I'll index the open-source IP feed, that should system the GB application!",
+ "status": "canceled",
+ "label": "bug",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-5814",
+ "title": "We need to calculate the 1080p AGP feed!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-2645",
+ "title": "Synthesizing the system won't do anything, we need to navigate the multi-byte HDD firewall!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-4535",
+ "title": "Try to copy the JSON circuit, maybe it will connect the wireless feed!",
+ "status": "in progress",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-4463",
+ "title": "We need to copy the solid state AGP monitor!",
+ "status": "done",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-9745",
+ "title": "If we connect the protocol, we can get to the GB system through the bluetooth PCI microchip!",
+ "status": "canceled",
+ "label": "feature",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-2080",
+ "title": "If we input the bus, we can get to the RAM matrix through the auxiliary RAM card!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-3838",
+ "title": "I'll bypass the online TCP application, that should panel the AGP system!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-1340",
+ "title": "We need to navigate the virtual PNG circuit!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-6665",
+ "title": "If we parse the monitor, we can get to the SSD hard drive through the cross-platform AGP alarm!",
+ "status": "canceled",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-7585",
+ "title": "If we calculate the hard drive, we can get to the SSL program through the multi-byte CSS microchip!",
+ "status": "todo",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-6319",
+ "title": "We need to copy the multi-byte SCSI program!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-4369",
+ "title": "Try to input the SCSI bus, maybe it will generate the 1080p pixel!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-9035",
+ "title": "We need to override the solid state PNG array!",
+ "status": "canceled",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-3970",
+ "title": "You can't index the transmitter without quantifying the haptic ASCII card!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-4473",
+ "title": "You can't bypass the protocol without overriding the neural RSS program!",
+ "status": "todo",
+ "label": "documentation",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-4136",
+ "title": "You can't hack the hard drive without hacking the primary JSON program!",
+ "status": "canceled",
+ "label": "bug",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-3939",
+ "title": "Use the back-end SQL firewall, then you can connect the neural hard drive!",
+ "status": "done",
+ "label": "feature",
+ "priority": "low"
+ },
+ {
+ "id": "TASK-2007",
+ "title": "I'll input the back-end USB protocol, that should bandwidth the PCI system!",
+ "status": "todo",
+ "label": "bug",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-7516",
+ "title": "Use the primary SQL program, then you can generate the auxiliary transmitter!",
+ "status": "done",
+ "label": "documentation",
+ "priority": "medium"
+ },
+ {
+ "id": "TASK-6906",
+ "title": "Try to back up the DRAM system, maybe it will reboot the online transmitter!",
+ "status": "done",
+ "label": "feature",
+ "priority": "high"
+ },
+ {
+ "id": "TASK-5207",
+ "title": "The SMS interface is down, copy the bluetooth bus so we can quantify the VGA card!",
+ "status": "in progress",
+ "label": "bug",
+ "priority": "low"
+ }
+]
diff --git a/src/app/(app)/problems/examples/page.tsx b/src/app/(app)/problems/examples/page.tsx
new file mode 100644
index 0000000..bba4f62
--- /dev/null
+++ b/src/app/(app)/problems/examples/page.tsx
@@ -0,0 +1,27 @@
+import path from "path";
+import { z } from "zod";
+import { promises as fs } from "fs";
+import { taskSchema } from "./data/schema";
+import { columns } from "./components/columns";
+import { DataTable } from "./components/data-table";
+
+// Simulate a database read for tasks.
+async function getTasks() {
+ const data = await fs.readFile(
+ path.join(process.cwd(), "src/app/(app)/problems/examples/data/tasks.json")
+ );
+
+ const tasks = JSON.parse(data.toString());
+
+ return z.array(taskSchema).parse(tasks);
+}
+
+export default async function ProblemExamplePage() {
+ const tasks = await getTasks();
+
+ return (
+
+
+
+ );
+}
diff --git a/src/app/(main)/problems/new/components/basic-form.tsx b/src/app/(app)/problems/new/components/basic-form.tsx
similarity index 93%
rename from src/app/(main)/problems/new/components/basic-form.tsx
rename to src/app/(app)/problems/new/components/basic-form.tsx
index 1db700e..98d0ed4 100644
--- a/src/app/(main)/problems/new/components/basic-form.tsx
+++ b/src/app/(app)/problems/new/components/basic-form.tsx
@@ -29,7 +29,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
const basicFormSchema = z.object({
title: z.string().min(1, { message: "题目标题不能为空" }),
- difficulty: z.enum(["简单", "中等", "困难"], {
+ difficulty: z.enum(["low", "medium", "high"], {
required_error: "请选择题目难度",
}),
});
@@ -86,9 +86,9 @@ export function BasicForm() {
- 简单
- 中等
- 困难
+ 简单
+ 中等
+ 困难
diff --git a/src/app/(main)/problems/new/components/details-form.tsx b/src/app/(app)/problems/new/components/details-form.tsx
similarity index 100%
rename from src/app/(main)/problems/new/components/details-form.tsx
rename to src/app/(app)/problems/new/components/details-form.tsx
diff --git a/src/app/(main)/problems/new/components/sidebar-nav.tsx b/src/app/(app)/problems/new/components/sidebar-nav.tsx
similarity index 100%
rename from src/app/(main)/problems/new/components/sidebar-nav.tsx
rename to src/app/(app)/problems/new/components/sidebar-nav.tsx
diff --git a/src/app/(main)/problems/new/details/page.tsx b/src/app/(app)/problems/new/details/page.tsx
similarity index 100%
rename from src/app/(main)/problems/new/details/page.tsx
rename to src/app/(app)/problems/new/details/page.tsx
diff --git a/src/app/(main)/problems/new/layout.tsx b/src/app/(app)/problems/new/layout.tsx
similarity index 100%
rename from src/app/(main)/problems/new/layout.tsx
rename to src/app/(app)/problems/new/layout.tsx
diff --git a/src/app/(main)/problems/new/page.tsx b/src/app/(app)/problems/new/page.tsx
similarity index 100%
rename from src/app/(main)/problems/new/page.tsx
rename to src/app/(app)/problems/new/page.tsx
diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx
index 4b1dfdc..d32ee51 100644
--- a/src/components/app-sidebar.tsx
+++ b/src/components/app-sidebar.tsx
@@ -41,8 +41,8 @@ const data = {
isActive: true,
items: [
{
- title: "提交记录",
- url: "#",
+ title: "示例",
+ url: "/problems/examples",
},
{
title: "新建",