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: "新建",