feat: Ajout de la vue des tâches
This commit is contained in:
parent
df4115d0e3
commit
44f293b766
105
package-lock.json
generated
105
package-lock.json
generated
@ -9,6 +9,7 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-avatar": "^1.0.4",
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
@ -22,8 +23,10 @@
|
|||||||
"@radix-ui/react-switch": "^1.0.3",
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
"@radix-ui/react-tabs": "^1.0.4",
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
|
"@tanstack/react-table": "^8.15.3",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
|
"cmdk": "^1.0.0",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"jotai": "^2.7.2",
|
"jotai": "^2.7.2",
|
||||||
"lucide-react": "^0.364.0",
|
"lucide-react": "^0.364.0",
|
||||||
@ -33,9 +36,11 @@
|
|||||||
"react-resizable-panels": "^2.0.16",
|
"react-resizable-panels": "^2.0.16",
|
||||||
"react-router-dom": "^6.22.3",
|
"react-router-dom": "^6.22.3",
|
||||||
"tailwind-merge": "^2.2.2",
|
"tailwind-merge": "^2.2.2",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@faker-js/faker": "^8.4.1",
|
||||||
"@types/node": "^20.12.4",
|
"@types/node": "^20.12.4",
|
||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.2.66",
|
||||||
"@types/react-dom": "^18.2.22",
|
"@types/react-dom": "^18.2.22",
|
||||||
@ -513,6 +518,22 @@
|
|||||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@faker-js/faker": {
|
||||||
|
"version": "8.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-8.4.1.tgz",
|
||||||
|
"integrity": "sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/fakerjs"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.17.0 || ^16.13.0 || >=18.0.0",
|
||||||
|
"npm": ">=6.14.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@floating-ui/core": {
|
"node_modules/@floating-ui/core": {
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz",
|
||||||
@ -775,6 +796,36 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-checkbox": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.13.10",
|
||||||
|
"@radix-ui/primitive": "1.0.1",
|
||||||
|
"@radix-ui/react-compose-refs": "1.0.1",
|
||||||
|
"@radix-ui/react-context": "1.0.1",
|
||||||
|
"@radix-ui/react-presence": "1.0.1",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.0.1",
|
||||||
|
"@radix-ui/react-use-previous": "1.0.1",
|
||||||
|
"@radix-ui/react-use-size": "1.0.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-collection": {
|
"node_modules/@radix-ui/react-collection": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz",
|
||||||
@ -1661,6 +1712,37 @@
|
|||||||
"linux"
|
"linux"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/@tanstack/react-table": {
|
||||||
|
"version": "8.15.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.15.3.tgz",
|
||||||
|
"integrity": "sha512-aocQ4WpWiAh7R+yxNp+DGQYXeVACh5lv2kk96DjYgFiHDCB0cOFoYMT/pM6eDOzeMXR9AvPoLeumTgq8/0qX+w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@tanstack/table-core": "8.15.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8",
|
||||||
|
"react-dom": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@tanstack/table-core": {
|
||||||
|
"version": "8.15.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.15.3.tgz",
|
||||||
|
"integrity": "sha512-wOgV0HfEvuMOv8RlqdR9MdNNqq0uyvQtP39QOvGlggHvIObOE4exS+D5LGO8LZ3LUXxId2IlUKcHDHaGujWhUg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/tannerlinsley"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/babel__core": {
|
"node_modules/@types/babel__core": {
|
||||||
"version": "7.20.5",
|
"version": "7.20.5",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -2244,6 +2326,19 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cmdk": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-dialog": "1.0.5",
|
||||||
|
"@radix-ui/react-primitive": "1.0.3"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0",
|
||||||
|
"react-dom": "^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/color-convert": {
|
"node_modules/color-convert": {
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
@ -4588,6 +4683,14 @@
|
|||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/zod": {
|
||||||
|
"version": "3.22.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz",
|
||||||
|
"integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@radix-ui/react-avatar": "^1.0.4",
|
"@radix-ui/react-avatar": "^1.0.4",
|
||||||
|
"@radix-ui/react-checkbox": "^1.0.4",
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
@ -25,8 +26,10 @@
|
|||||||
"@radix-ui/react-switch": "^1.0.3",
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
"@radix-ui/react-tabs": "^1.0.4",
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
|
"@tanstack/react-table": "^8.15.3",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
"clsx": "^2.1.0",
|
"clsx": "^2.1.0",
|
||||||
|
"cmdk": "^1.0.0",
|
||||||
"date-fns": "^3.6.0",
|
"date-fns": "^3.6.0",
|
||||||
"jotai": "^2.7.2",
|
"jotai": "^2.7.2",
|
||||||
"lucide-react": "^0.364.0",
|
"lucide-react": "^0.364.0",
|
||||||
@ -36,9 +39,11 @@
|
|||||||
"react-resizable-panels": "^2.0.16",
|
"react-resizable-panels": "^2.0.16",
|
||||||
"react-router-dom": "^6.22.3",
|
"react-router-dom": "^6.22.3",
|
||||||
"tailwind-merge": "^2.2.2",
|
"tailwind-merge": "^2.2.2",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@faker-js/faker": "^8.4.1",
|
||||||
"@types/node": "^20.12.4",
|
"@types/node": "^20.12.4",
|
||||||
"@types/react": "^18.2.66",
|
"@types/react": "^18.2.66",
|
||||||
"@types/react-dom": "^18.2.22",
|
"@types/react-dom": "^18.2.22",
|
||||||
|
@ -105,13 +105,13 @@ import {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<a
|
<Link
|
||||||
href="#"
|
to={`/tasks`}
|
||||||
className="flex h-9 w-9 items-center justify-center rounded-lg bg-accent text-accent-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
className="flex h-9 w-9 items-center justify-center rounded-lg bg-accent text-accent-foreground transition-colors hover:text-foreground md:h-8 md:w-8"
|
||||||
>
|
>
|
||||||
<ShoppingCart className="h-5 w-5" />
|
<ShoppingCart className="h-5 w-5" />
|
||||||
<span className="sr-only">Orders</span>
|
<span className="sr-only">Orders</span>
|
||||||
</a>
|
</Link>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent side="right">Orders</TooltipContent>
|
<TooltipContent side="right">Orders</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
35
src/blocks/tasks/TasksBlocks.tsx
Normal file
35
src/blocks/tasks/TasksBlocks.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { columns } from "./components/columns.tsx"
|
||||||
|
import { DataTable } from "./components/data-table.tsx"
|
||||||
|
import { UserNav } from "./components/user-nav.tsx"
|
||||||
|
import { taskSchema } from "./data/schema"
|
||||||
|
|
||||||
|
import tasks from "./data/tasks.json"
|
||||||
|
|
||||||
|
// Simulate a database read for tasks.
|
||||||
|
function getTasks() {
|
||||||
|
return z.array(taskSchema).parse(tasks)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TaskPage() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className="hidden h-full flex-1 flex-col space-y-8 p-8 md:flex">
|
||||||
|
<div className="flex items-center justify-between space-y-2">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold tracking-tight">Welcome back!</h2>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Here's a list of your tasks for this month!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<UserNav />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DataTable data={getTasks()} columns={columns} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
123
src/blocks/tasks/components/columns.tsx
Normal file
123
src/blocks/tasks/components/columns.tsx
Normal file
@ -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.tsx"
|
||||||
|
import { DataTableRowActions } from "./data-table-row-actions.tsx"
|
||||||
|
|
||||||
|
export const columns: ColumnDef<Task>[] = [
|
||||||
|
{
|
||||||
|
id: "select",
|
||||||
|
header: ({ table }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
table.getIsAllPageRowsSelected() ||
|
||||||
|
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||||
|
aria-label="Select all"
|
||||||
|
className="translate-y-[2px]"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Checkbox
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||||
|
aria-label="Select row"
|
||||||
|
className="translate-y-[2px]"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "id",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader column={column} title="Task" />
|
||||||
|
),
|
||||||
|
cell: ({ row }) => <div className="w-[80px]">{row.getValue("id")}</div>,
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "title",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader column={column} title="Title" />
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const label = labels.find((label) => label.value === row.original.label)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
{label && <Badge variant="outline">{label.label}</Badge>}
|
||||||
|
<span className="max-w-[500px] truncate font-medium">
|
||||||
|
{row.getValue("title")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "status",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader column={column} title="Status" />
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const status = statuses.find(
|
||||||
|
(status) => status.value === row.getValue("status")
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex w-[100px] items-center">
|
||||||
|
{status.icon && (
|
||||||
|
<status.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
<span>{status.label}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
filterFn: (row, id, value) => {
|
||||||
|
return value.includes(row.getValue(id))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "priority",
|
||||||
|
header: ({ column }) => (
|
||||||
|
<DataTableColumnHeader column={column} title="Priority" />
|
||||||
|
),
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const priority = priorities.find(
|
||||||
|
(priority) => priority.value === row.getValue("priority")
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!priority) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center">
|
||||||
|
{priority.icon && (
|
||||||
|
<priority.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
<span>{priority.label}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
filterFn: (row, id, value) => {
|
||||||
|
return value.includes(row.getValue(id))
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
cell: ({ row }) => <DataTableRowActions row={row} />,
|
||||||
|
},
|
||||||
|
]
|
71
src/blocks/tasks/components/data-table-column-header.tsx
Normal file
71
src/blocks/tasks/components/data-table-column-header.tsx
Normal file
@ -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<TData, TValue>
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement> {
|
||||||
|
column: Column<TData, TValue>
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataTableColumnHeader<TData, TValue>({
|
||||||
|
column,
|
||||||
|
title,
|
||||||
|
className,
|
||||||
|
}: DataTableColumnHeaderProps<TData, TValue>) {
|
||||||
|
if (!column.getCanSort()) {
|
||||||
|
return <div className={cn(className)}>{title}</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn("flex items-center space-x-2", className)}>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="-ml-3 h-8 data-[state=open]:bg-accent"
|
||||||
|
>
|
||||||
|
<span>{title}</span>
|
||||||
|
{column.getIsSorted() === "desc" ? (
|
||||||
|
<ArrowDownIcon className="ml-2 h-4 w-4" />
|
||||||
|
) : column.getIsSorted() === "asc" ? (
|
||||||
|
<ArrowUpIcon className="ml-2 h-4 w-4" />
|
||||||
|
) : (
|
||||||
|
<CaretSortIcon className="ml-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="start">
|
||||||
|
<DropdownMenuItem onClick={() => column.toggleSorting(false)}>
|
||||||
|
<ArrowUpIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||||
|
Asc
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => column.toggleSorting(true)}>
|
||||||
|
<ArrowDownIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||||
|
Desc
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem onClick={() => column.toggleVisibility(false)}>
|
||||||
|
<EyeNoneIcon className="mr-2 h-3.5 w-3.5 text-muted-foreground/70" />
|
||||||
|
Hide
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
147
src/blocks/tasks/components/data-table-faceted-filter.tsx
Normal file
147
src/blocks/tasks/components/data-table-faceted-filter.tsx
Normal file
@ -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<TData, TValue> {
|
||||||
|
column?: Column<TData, TValue>
|
||||||
|
title?: string
|
||||||
|
options: {
|
||||||
|
label: string
|
||||||
|
value: string
|
||||||
|
icon?: React.ComponentType<{ className?: string }>
|
||||||
|
}[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataTableFacetedFilter<TData, TValue>({
|
||||||
|
column,
|
||||||
|
title,
|
||||||
|
options,
|
||||||
|
}: DataTableFacetedFilterProps<TData, TValue>) {
|
||||||
|
const facets = column?.getFacetedUniqueValues()
|
||||||
|
const selectedValues = new Set(column?.getFilterValue() as string[])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<Button variant="outline" size="sm" className="h-8 border-dashed">
|
||||||
|
<PlusCircledIcon className="mr-2 h-4 w-4" />
|
||||||
|
{title}
|
||||||
|
{selectedValues?.size > 0 && (
|
||||||
|
<>
|
||||||
|
<Separator orientation="vertical" className="mx-2 h-4" />
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className="rounded-sm px-1 font-normal lg:hidden"
|
||||||
|
>
|
||||||
|
{selectedValues.size}
|
||||||
|
</Badge>
|
||||||
|
<div className="hidden space-x-1 lg:flex">
|
||||||
|
{selectedValues.size > 2 ? (
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className="rounded-sm px-1 font-normal"
|
||||||
|
>
|
||||||
|
{selectedValues.size} selected
|
||||||
|
</Badge>
|
||||||
|
) : (
|
||||||
|
options
|
||||||
|
.filter((option) => selectedValues.has(option.value))
|
||||||
|
.map((option) => (
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
key={option.value}
|
||||||
|
className="rounded-sm px-1 font-normal"
|
||||||
|
>
|
||||||
|
{option.label}
|
||||||
|
</Badge>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent className="w-[200px] p-0" align="start">
|
||||||
|
<Command>
|
||||||
|
<CommandInput placeholder={title} />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>No results found.</CommandEmpty>
|
||||||
|
<CommandGroup>
|
||||||
|
{options.map((option) => {
|
||||||
|
const isSelected = selectedValues.has(option.value)
|
||||||
|
return (
|
||||||
|
<CommandItem
|
||||||
|
key={option.value}
|
||||||
|
onSelect={() => {
|
||||||
|
if (isSelected) {
|
||||||
|
selectedValues.delete(option.value)
|
||||||
|
} else {
|
||||||
|
selectedValues.add(option.value)
|
||||||
|
}
|
||||||
|
const filterValues = Array.from(selectedValues)
|
||||||
|
column?.setFilterValue(
|
||||||
|
filterValues.length ? filterValues : undefined
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"mr-2 flex h-4 w-4 items-center justify-center rounded-sm border border-primary",
|
||||||
|
isSelected
|
||||||
|
? "bg-primary text-primary-foreground"
|
||||||
|
: "opacity-50 [&_svg]:invisible"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CheckIcon className={cn("h-4 w-4")} />
|
||||||
|
</div>
|
||||||
|
{option.icon && (
|
||||||
|
<option.icon className="mr-2 h-4 w-4 text-muted-foreground" />
|
||||||
|
)}
|
||||||
|
<span>{option.label}</span>
|
||||||
|
{facets?.get(option.value) && (
|
||||||
|
<span className="ml-auto flex h-4 w-4 items-center justify-center font-mono text-xs">
|
||||||
|
{facets.get(option.value)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</CommandItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</CommandGroup>
|
||||||
|
{selectedValues.size > 0 && (
|
||||||
|
<>
|
||||||
|
<CommandSeparator />
|
||||||
|
<CommandGroup>
|
||||||
|
<CommandItem
|
||||||
|
onSelect={() => column?.setFilterValue(undefined)}
|
||||||
|
className="justify-center text-center"
|
||||||
|
>
|
||||||
|
Clear filters
|
||||||
|
</CommandItem>
|
||||||
|
</CommandGroup>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CommandList>
|
||||||
|
</Command>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
)
|
||||||
|
}
|
97
src/blocks/tasks/components/data-table-pagination.tsx
Normal file
97
src/blocks/tasks/components/data-table-pagination.tsx
Normal file
@ -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<TData> {
|
||||||
|
table: Table<TData>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataTablePagination<TData>({
|
||||||
|
table,
|
||||||
|
}: DataTablePaginationProps<TData>) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between px-2">
|
||||||
|
<div className="flex-1 text-sm text-muted-foreground">
|
||||||
|
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
||||||
|
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-6 lg:space-x-8">
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<p className="text-sm font-medium">Rows per page</p>
|
||||||
|
<Select
|
||||||
|
value={`${table.getState().pagination.pageSize}`}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
table.setPageSize(Number(value))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="h-8 w-[70px]">
|
||||||
|
<SelectValue placeholder={table.getState().pagination.pageSize} />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent side="top">
|
||||||
|
{[10, 20, 30, 40, 50].map((pageSize) => (
|
||||||
|
<SelectItem key={pageSize} value={`${pageSize}`}>
|
||||||
|
{pageSize}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-[100px] items-center justify-center text-sm font-medium">
|
||||||
|
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
||||||
|
{table.getPageCount()}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
|
onClick={() => table.setPageIndex(0)}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to first page</span>
|
||||||
|
<DoubleArrowLeftIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to previous page</span>
|
||||||
|
<ChevronLeftIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="h-8 w-8 p-0"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to next page</span>
|
||||||
|
<ChevronRightIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
|
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to last page</span>
|
||||||
|
<DoubleArrowRightIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
69
src/blocks/tasks/components/data-table-row-actions.tsx
Normal file
69
src/blocks/tasks/components/data-table-row-actions.tsx
Normal file
@ -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<TData> {
|
||||||
|
row: Row<TData>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataTableRowActions<TData>({
|
||||||
|
row,
|
||||||
|
}: DataTableRowActionsProps<TData>) {
|
||||||
|
const task = taskSchema.parse(row.original)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
|
||||||
|
>
|
||||||
|
<DotsHorizontalIcon className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Open menu</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-[160px]">
|
||||||
|
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Favorite</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuSub>
|
||||||
|
<DropdownMenuSubTrigger>Labels</DropdownMenuSubTrigger>
|
||||||
|
<DropdownMenuSubContent>
|
||||||
|
<DropdownMenuRadioGroup value={task.label}>
|
||||||
|
{labels.map((label) => (
|
||||||
|
<DropdownMenuRadioItem key={label.value} value={label.value}>
|
||||||
|
{label.label}
|
||||||
|
</DropdownMenuRadioItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuRadioGroup>
|
||||||
|
</DropdownMenuSubContent>
|
||||||
|
</DropdownMenuSub>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
Delete
|
||||||
|
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
61
src/blocks/tasks/components/data-table-toolbar.tsx
Normal file
61
src/blocks/tasks/components/data-table-toolbar.tsx
Normal file
@ -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<TData> {
|
||||||
|
table: Table<TData>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataTableToolbar<TData>({
|
||||||
|
table,
|
||||||
|
}: DataTableToolbarProps<TData>) {
|
||||||
|
const isFiltered = table.getState().columnFilters.length > 0
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div className="flex flex-1 items-center space-x-2">
|
||||||
|
<Input
|
||||||
|
placeholder="Filter tasks..."
|
||||||
|
value={(table.getColumn("title")?.getFilterValue() as string) ?? ""}
|
||||||
|
onChange={(event) =>
|
||||||
|
table.getColumn("title")?.setFilterValue(event.target.value)
|
||||||
|
}
|
||||||
|
className="h-8 w-[150px] lg:w-[250px]"
|
||||||
|
/>
|
||||||
|
{table.getColumn("status") && (
|
||||||
|
<DataTableFacetedFilter
|
||||||
|
column={table.getColumn("status")}
|
||||||
|
title="Status"
|
||||||
|
options={statuses}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{table.getColumn("priority") && (
|
||||||
|
<DataTableFacetedFilter
|
||||||
|
column={table.getColumn("priority")}
|
||||||
|
title="Priority"
|
||||||
|
options={priorities}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isFiltered && (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => table.resetColumnFilters()}
|
||||||
|
className="h-8 px-2 lg:px-3"
|
||||||
|
>
|
||||||
|
Reset
|
||||||
|
<Cross2Icon className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<DataTableViewOptions table={table} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
59
src/blocks/tasks/components/data-table-view-options.tsx
Normal file
59
src/blocks/tasks/components/data-table-view-options.tsx
Normal file
@ -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<TData> {
|
||||||
|
table: Table<TData>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataTableViewOptions<TData>({
|
||||||
|
table,
|
||||||
|
}: DataTableViewOptionsProps<TData>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="ml-auto hidden h-8 lg:flex"
|
||||||
|
>
|
||||||
|
<MixerHorizontalIcon className="mr-2 h-4 w-4" />
|
||||||
|
View
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-[150px]">
|
||||||
|
<DropdownMenuLabel>Toggle columns</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
{table
|
||||||
|
.getAllColumns()
|
||||||
|
.filter(
|
||||||
|
(column) =>
|
||||||
|
typeof column.accessorFn !== "undefined" && column.getCanHide()
|
||||||
|
)
|
||||||
|
.map((column) => {
|
||||||
|
return (
|
||||||
|
<DropdownMenuCheckboxItem
|
||||||
|
key={column.id}
|
||||||
|
className="capitalize"
|
||||||
|
checked={column.getIsVisible()}
|
||||||
|
onCheckedChange={(value) => column.toggleVisibility(!!value)}
|
||||||
|
>
|
||||||
|
{column.id}
|
||||||
|
</DropdownMenuCheckboxItem>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
126
src/blocks/tasks/components/data-table.tsx
Normal file
126
src/blocks/tasks/components/data-table.tsx
Normal file
@ -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<TData, TValue> {
|
||||||
|
columns: ColumnDef<TData, TValue>[]
|
||||||
|
data: TData[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataTable<TData, TValue>({
|
||||||
|
columns,
|
||||||
|
data,
|
||||||
|
}: DataTableProps<TData, TValue>) {
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({})
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({})
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<DataTableToolbar table={table} />
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<TableHead key={header.id} colSpan={header.colSpan}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(
|
||||||
|
cell.column.columnDef.cell,
|
||||||
|
cell.getContext()
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={columns.length}
|
||||||
|
className="h-24 text-center"
|
||||||
|
>
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
<DataTablePagination table={table} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
62
src/blocks/tasks/components/user-nav.tsx
Normal file
62
src/blocks/tasks/components/user-nav.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
AvatarFallback,
|
||||||
|
AvatarImage,
|
||||||
|
} from "@/components/ui/avatar"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuShortcut,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
|
||||||
|
export function UserNav() {
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" className="relative h-8 w-8 rounded-full">
|
||||||
|
<Avatar className="h-9 w-9">
|
||||||
|
<AvatarImage src="/avatars/03.png" alt="@shadcn" />
|
||||||
|
<AvatarFallback>SC</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent className="w-56" align="end" forceMount>
|
||||||
|
<DropdownMenuLabel className="font-normal">
|
||||||
|
<div className="flex flex-col space-y-1">
|
||||||
|
<p className="text-sm font-medium leading-none">shadcn</p>
|
||||||
|
<p className="text-xs leading-none text-muted-foreground">
|
||||||
|
m@example.com
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</DropdownMenuLabel>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuGroup>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
Profile
|
||||||
|
<DropdownMenuShortcut>⇧⌘P</DropdownMenuShortcut>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
Billing
|
||||||
|
<DropdownMenuShortcut>⌘B</DropdownMenuShortcut>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
Settings
|
||||||
|
<DropdownMenuShortcut>⌘S</DropdownMenuShortcut>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>New Team</DropdownMenuItem>
|
||||||
|
</DropdownMenuGroup>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem>
|
||||||
|
Log out
|
||||||
|
<DropdownMenuShortcut>⇧⌘Q</DropdownMenuShortcut>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
71
src/blocks/tasks/data/data.tsx
Normal file
71
src/blocks/tasks/data/data.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import {
|
||||||
|
ArrowDownIcon,
|
||||||
|
ArrowRightIcon,
|
||||||
|
ArrowUpIcon,
|
||||||
|
CheckCircledIcon,
|
||||||
|
CircleIcon,
|
||||||
|
CrossCircledIcon,
|
||||||
|
QuestionMarkCircledIcon,
|
||||||
|
StopwatchIcon,
|
||||||
|
} from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
export const labels = [
|
||||||
|
{
|
||||||
|
value: "bug",
|
||||||
|
label: "Bug",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "feature",
|
||||||
|
label: "Feature",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "documentation",
|
||||||
|
label: "Documentation",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const statuses = [
|
||||||
|
{
|
||||||
|
value: "backlog",
|
||||||
|
label: "Backlog",
|
||||||
|
icon: QuestionMarkCircledIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "todo",
|
||||||
|
label: "Todo",
|
||||||
|
icon: CircleIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "in progress",
|
||||||
|
label: "In Progress",
|
||||||
|
icon: StopwatchIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "done",
|
||||||
|
label: "Done",
|
||||||
|
icon: CheckCircledIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: "canceled",
|
||||||
|
label: "Canceled",
|
||||||
|
icon: CrossCircledIcon,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export const priorities = [
|
||||||
|
{
|
||||||
|
label: "Low",
|
||||||
|
value: "low",
|
||||||
|
icon: ArrowDownIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Medium",
|
||||||
|
value: "medium",
|
||||||
|
icon: ArrowRightIcon,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "High",
|
||||||
|
value: "high",
|
||||||
|
icon: ArrowUpIcon,
|
||||||
|
},
|
||||||
|
]
|
13
src/blocks/tasks/data/schema.ts
Normal file
13
src/blocks/tasks/data/schema.ts
Normal file
@ -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<typeof taskSchema>
|
20
src/blocks/tasks/data/seed.ts
Normal file
20
src/blocks/tasks/data/seed.ts
Normal file
@ -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) => 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.")
|
702
src/blocks/tasks/data/tasks.json
Normal file
702
src/blocks/tasks/data/tasks.json
Normal file
@ -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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"label": "documentation",
|
||||||
|
"priority": "medium"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "TASK-2858",
|
||||||
|
"title": "We need to override the online UDP bus!",
|
||||||
|
"status": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"label": "bug",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "TASK-3216",
|
||||||
|
"title": "Transmitting the transmitter won't do anything, we need to compress the virtual HDD sensor!",
|
||||||
|
"status": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"label": "feature",
|
||||||
|
"priority": "low"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "TASK-6319",
|
||||||
|
"title": "We need to copy the multi-byte SCSI program!",
|
||||||
|
"status": "backlog",
|
||||||
|
"label": "bug",
|
||||||
|
"priority": "high"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "TASK-4369",
|
||||||
|
"title": "Try to input the SCSI bus, maybe it will generate the 1080p pixel!",
|
||||||
|
"status": "backlog",
|
||||||
|
"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": "backlog",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
30
src/components/ui/checkbox.tsx
Normal file
30
src/components/ui/checkbox.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||||
|
import { CheckIcon } from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Checkbox = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CheckboxPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={cn("flex items-center justify-center text-current")}
|
||||||
|
>
|
||||||
|
<CheckIcon className="h-4 w-4" />
|
||||||
|
</CheckboxPrimitive.Indicator>
|
||||||
|
</CheckboxPrimitive.Root>
|
||||||
|
))
|
||||||
|
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Checkbox }
|
155
src/components/ui/command.tsx
Normal file
155
src/components/ui/command.tsx
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { type DialogProps } from "@radix-ui/react-dialog"
|
||||||
|
import { MagnifyingGlassIcon } from "@radix-ui/react-icons"
|
||||||
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
||||||
|
|
||||||
|
const Command = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Command.displayName = CommandPrimitive.displayName
|
||||||
|
|
||||||
|
interface CommandDialogProps extends DialogProps {}
|
||||||
|
|
||||||
|
const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogContent className="overflow-hidden p-0">
|
||||||
|
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
|
||||||
|
{children}
|
||||||
|
</Command>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommandInput = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||||
|
<MagnifyingGlassIcon className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandInput.displayName = CommandPrimitive.Input.displayName
|
||||||
|
|
||||||
|
const CommandList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandList.displayName = CommandPrimitive.List.displayName
|
||||||
|
|
||||||
|
const CommandEmpty = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Empty>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
||||||
|
>((props, ref) => (
|
||||||
|
<CommandPrimitive.Empty
|
||||||
|
ref={ref}
|
||||||
|
className="py-6 text-center text-sm"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
||||||
|
|
||||||
|
const CommandGroup = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Group>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
||||||
|
|
||||||
|
const CommandSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 h-px bg-border", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const CommandItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none aria-selected:bg-accent aria-selected:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
|
||||||
|
CommandItem.displayName = CommandPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const CommandShortcut = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CommandShortcut.displayName = "CommandShortcut"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Command,
|
||||||
|
CommandDialog,
|
||||||
|
CommandInput,
|
||||||
|
CommandList,
|
||||||
|
CommandEmpty,
|
||||||
|
CommandGroup,
|
||||||
|
CommandItem,
|
||||||
|
CommandShortcut,
|
||||||
|
CommandSeparator,
|
||||||
|
}
|
122
src/components/ui/dialog.tsx
Normal file
122
src/components/ui/dialog.tsx
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { Cross2Icon } from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Dialog = DialogPrimitive.Root
|
||||||
|
|
||||||
|
const DialogTrigger = DialogPrimitive.Trigger
|
||||||
|
|
||||||
|
const DialogPortal = DialogPrimitive.Portal
|
||||||
|
|
||||||
|
const DialogClose = DialogPrimitive.Close
|
||||||
|
|
||||||
|
const DialogOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const DialogContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
|
<Cross2Icon className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
))
|
||||||
|
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const DialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DialogHeader.displayName = "DialogHeader"
|
||||||
|
|
||||||
|
const DialogFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DialogFooter.displayName = "DialogFooter"
|
||||||
|
|
||||||
|
const DialogTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-lg font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const DialogDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogPortal,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogFooter,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
}
|
@ -10,6 +10,7 @@ import Root from "./routes/Root";
|
|||||||
import Error from "./routes/Error";
|
import Error from "./routes/Error";
|
||||||
import Dashboard05 from "./blocks/Dashboard05";
|
import Dashboard05 from "./blocks/Dashboard05";
|
||||||
import MailBlocks from "./blocks/mail/MailBlocks";
|
import MailBlocks from "./blocks/mail/MailBlocks";
|
||||||
|
import TasksBlocks from "./blocks/tasks/TasksBlocks";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -25,6 +26,10 @@ const router = createBrowserRouter([
|
|||||||
path: "/mail",
|
path: "/mail",
|
||||||
element: <MailBlocks />,
|
element: <MailBlocks />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "/tasks",
|
||||||
|
element: <TasksBlocks />,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
@ -4,7 +4,9 @@ import react from '@vitejs/plugin-react'
|
|||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react()],
|
plugins: [
|
||||||
|
react(),
|
||||||
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
Reference in New Issue
Block a user