working around on table

This commit is contained in:
Kenta420 2024-01-24 16:56:16 +07:00
parent e6f5d152f0
commit 23fd193c34
36 changed files with 3999 additions and 82 deletions

View file

@ -6,5 +6,5 @@
"arrowParens": "avoid",
"endOfLine": "lf",
"tabWidth": 2,
"printWidth": 150
"printWidth": 120
}

View file

@ -13,9 +13,7 @@ import { eventGetKeyChain } from './keychain'
// │ │ └── preload.js
// │
process.env.DIST = path.join(__dirname, '../dist-renderer')
process.env.VITE_PUBLIC = app.isPackaged
? process.env.DIST
: path.join(process.env.DIST, '../public')
process.env.VITE_PUBLIC = app.isPackaged ? process.env.DIST : path.join(process.env.DIST, '../public')
let win: BrowserWindow | null
// 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite@2.x
@ -26,7 +24,9 @@ function createWindow() {
icon: path.join(process.env.VITE_PUBLIC, 'electron-vite.svg'),
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
},
width: 1270,
height: 720
})
// Test active push message to Renderer-process.
@ -36,9 +36,7 @@ function createWindow() {
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('taobin-electron', process.execPath, [
path.resolve(process.argv[1])
])
app.setAsDefaultProtocolClient('taobin-electron', process.execPath, [path.resolve(process.argv[1])])
}
} else {
app.setAsDefaultProtocolClient('taobin-electron')
@ -46,43 +44,36 @@ function createWindow() {
let grantedDeviceThroughPermHandler: Electron.USBDevice
win.webContents.session.on(
'select-usb-device',
(event, details, callback) => {
// Add events to handle devices being added or removed before the callback on
// `select-usb-device` is called.
win?.webContents.session.on('usb-device-added', (_event, device) => {
console.log('usb-device-added FIRED WITH', device)
// Optionally update details.deviceList
})
win.webContents.session.on('select-usb-device', (event, details, callback) => {
// Add events to handle devices being added or removed before the callback on
// `select-usb-device` is called.
win?.webContents.session.on('usb-device-added', (_event, device) => {
console.log('usb-device-added FIRED WITH', device)
// Optionally update details.deviceList
})
win?.webContents.session.on('usb-device-removed', (_event, device) => {
console.log('usb-device-removed FIRED WITH', device)
// Optionally update details.deviceList
})
win?.webContents.session.on('usb-device-removed', (_event, device) => {
console.log('usb-device-removed FIRED WITH', device)
// Optionally update details.deviceList
})
event.preventDefault()
if (details.deviceList && details.deviceList.length > 0) {
const deviceToReturn = details.deviceList.find(
device => device.vendorId === 1478
)
if (deviceToReturn) {
callback(deviceToReturn.deviceId)
} else {
callback()
}
event.preventDefault()
if (details.deviceList && details.deviceList.length > 0) {
const deviceToReturn = details.deviceList.find(device => device.vendorId === 1478)
if (deviceToReturn) {
callback(deviceToReturn.deviceId)
} else {
callback()
}
}
)
})
win.webContents.session.setPermissionCheckHandler(
(_webContents, permission) => {
if (permission === 'usb') {
return true
}
return false
win.webContents.session.setPermissionCheckHandler((_webContents, permission) => {
if (permission === 'usb') {
return true
}
)
return false
})
win.webContents.session.setDevicePermissionHandler(details => {
console.log(details)

File diff suppressed because it is too large Load diff

View file

@ -20,10 +20,19 @@
"preview": "vite preview"
},
"dependencies": {
"@hookform/resolvers": "^3.3.4",
"@postman/node-keytar": "^7.9.3",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
"@radix-ui/react-popover": "^1.0.7",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-toast": "^1.1.5",
"@tanstack/react-table": "^8.11.7",
"@yume-chan/adb": "^0.0.22",
"@yume-chan/adb-credential-web": "^0.0.22",
"@yume-chan/adb-daemon-webusb": "^0.0.22",
@ -34,12 +43,17 @@
"axios": "^1.6.5",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"cmdk": "^0.2.0",
"next-themes": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.49.3",
"react-router-dom": "^6.21.1",
"sonner": "^1.3.1",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
"usb": "^2.11.0",
"zod": "^3.22.4",
"zustand": "^4.4.7"
},
"devDependencies": {

View file

@ -1,14 +1,10 @@
import {
createBrowserRouter,
createHashRouter,
RouterProvider,
type RouteObject
} from 'react-router-dom'
import { createBrowserRouter, createHashRouter, RouterProvider, type RouteObject } from 'react-router-dom'
import AuthCallBack from './AuthCallBack'
import MainLayout from './layouts/MainLayout'
import HomePage from './pages/Home'
import LoginPage from './pages/Login'
import AndroidPage from './pages/Android'
import RecipesPage from './pages/recipes/recipes'
function router() {
const routes: RouteObject[] = [
@ -22,7 +18,7 @@ function router() {
},
{
path: 'recipes',
element: <div>Recipes</div>
element: <RecipesPage />
},
{
path: 'android',

View file

@ -1,4 +1,4 @@
import { DashboardIcon, RocketIcon } from '@radix-ui/react-icons'
import { DashboardIcon, FileTextIcon, RocketIcon } from '@radix-ui/react-icons'
import { Link } from 'react-router-dom'
const Sidebar: React.FC = () => {
@ -7,19 +7,19 @@ const Sidebar: React.FC = () => {
<div className="h-full px-3 pb-4 overflow-y-auto bg-zinc-700">
<ul className="space-y-2 font-medium">
<li>
<Link
to="/"
className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500"
>
<Link to="/" className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500">
<DashboardIcon className="inline-block w-6 h-6 mr-3" />
Dashboard
</Link>
</li>
<li>
<Link
to="/android"
className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500"
>
<Link to="/recipes" className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500">
<FileTextIcon className="inline-block w-6 h-6 mr-3" />
Recipes
</Link>
</li>
<li>
<Link to="/android" className="flex items-center px-3 py-2 text-sm text-white rounded-md hover:bg-zinc-500">
<RocketIcon className="inline-block w-6 h-6 mr-3" />
Android
</Link>

View file

@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View file

@ -0,0 +1,28 @@
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 }

View file

@ -0,0 +1,153 @@
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,
}

View file

@ -0,0 +1,120 @@
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,
}

View file

@ -0,0 +1,176 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-[0.8rem] text-muted-foreground", className)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children
if (!body) {
return null
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-[0.8rem] font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}

View file

@ -0,0 +1,25 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

View file

@ -0,0 +1,24 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View file

@ -0,0 +1,31 @@
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
const Popover = PopoverPrimitive.Root
const PopoverTrigger = PopoverPrimitive.Trigger
const PopoverAnchor = PopoverPrimitive.Anchor
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none 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-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
))
PopoverContent.displayName = PopoverPrimitive.Content.displayName
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }

View file

@ -0,0 +1,42 @@
import * as React from "react"
import { CheckIcon } from "@radix-ui/react-icons"
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group"
import { cn } from "@/lib/utils"
const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root
className={cn("grid gap-2", className)}
{...props}
ref={ref}
/>
)
})
RadioGroup.displayName = RadioGroupPrimitive.Root.displayName
const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Item>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"aspect-square h-4 w-4 rounded-full border border-primary text-primary shadow focus:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}
>
<RadioGroupPrimitive.Indicator className="flex items-center justify-center">
<CheckIcon className="h-3.5 w-3.5 fill-primary" />
</RadioGroupPrimitive.Indicator>
</RadioGroupPrimitive.Item>
)
})
RadioGroupItem.displayName = RadioGroupPrimitive.Item.displayName
export { RadioGroup, RadioGroupItem }

View file

@ -0,0 +1,29 @@
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }

View file

@ -0,0 +1,120 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Table = React.forwardRef<
HTMLTableElement,
React.HTMLAttributes<HTMLTableElement>
>(({ className, ...props }, ref) => (
<div className="relative w-full overflow-auto">
<table
ref={ref}
className={cn("w-full caption-bottom text-sm", className)}
{...props}
/>
</div>
))
Table.displayName = "Table"
const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
))
TableHeader.displayName = "TableHeader"
const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody
ref={ref}
className={cn("[&_tr:last-child]:border-0", className)}
{...props}
/>
))
TableBody.displayName = "TableBody"
const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn(
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
className
)}
{...props}
/>
))
TableFooter.displayName = "TableFooter"
const TableRow = React.forwardRef<
HTMLTableRowElement,
React.HTMLAttributes<HTMLTableRowElement>
>(({ className, ...props }, ref) => (
<tr
ref={ref}
className={cn(
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
className
)}
{...props}
/>
))
TableRow.displayName = "TableRow"
const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableHead.displayName = "TableHead"
const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn(
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}
/>
))
TableCell.displayName = "TableCell"
const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption
ref={ref}
className={cn("mt-4 text-sm text-muted-foreground", className)}
{...props}
/>
))
TableCaption.displayName = "TableCaption"
export {
Table,
TableHeader,
TableBody,
TableFooter,
TableHead,
TableRow,
TableCell,
TableCaption,
}

View file

@ -0,0 +1,127 @@
import * as React from "react"
import { Cross2Icon } from "@radix-ui/react-icons"
import * as ToastPrimitives from "@radix-ui/react-toast"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const ToastProvider = ToastPrimitives.Provider
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
className
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const toastVariants = cva(
"group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
{
variants: {
variant: {
default: "border bg-background text-foreground",
destructive:
"destructive group border-destructive bg-destructive text-destructive-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
"inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
className
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
"absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
className
)}
toast-close=""
{...props}
>
<Cross2Icon className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn("text-sm font-semibold [&+div]:text-xs", className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn("text-sm opacity-90", className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastActionElement = React.ReactElement<typeof ToastAction>
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}

View file

@ -0,0 +1,33 @@
import {
Toast,
ToastClose,
ToastDescription,
ToastProvider,
ToastTitle,
ToastViewport,
} from "@/components/ui/toast"
import { useToast } from "@/components/ui/use-toast"
export function Toaster() {
const { toasts } = useToast()
return (
<ToastProvider>
{toasts.map(function ({ id, title, description, action, ...props }) {
return (
<Toast key={id} {...props}>
<div className="grid gap-1">
{title && <ToastTitle>{title}</ToastTitle>}
{description && (
<ToastDescription>{description}</ToastDescription>
)}
</div>
{action}
<ToastClose />
</Toast>
)
})}
<ToastViewport />
</ToastProvider>
)
}

View file

@ -0,0 +1,192 @@
// Inspired by react-hot-toast library
import * as React from "react"
import type {
ToastActionElement,
ToastProps,
} from "@/components/ui/toast"
const TOAST_LIMIT = 1
const TOAST_REMOVE_DELAY = 1000000
type ToasterToast = ToastProps & {
id: string
title?: React.ReactNode
description?: React.ReactNode
action?: ToastActionElement
}
const actionTypes = {
ADD_TOAST: "ADD_TOAST",
UPDATE_TOAST: "UPDATE_TOAST",
DISMISS_TOAST: "DISMISS_TOAST",
REMOVE_TOAST: "REMOVE_TOAST",
} as const
let count = 0
function genId() {
count = (count + 1) % Number.MAX_SAFE_INTEGER
return count.toString()
}
type ActionType = typeof actionTypes
type Action =
| {
type: ActionType["ADD_TOAST"]
toast: ToasterToast
}
| {
type: ActionType["UPDATE_TOAST"]
toast: Partial<ToasterToast>
}
| {
type: ActionType["DISMISS_TOAST"]
toastId?: ToasterToast["id"]
}
| {
type: ActionType["REMOVE_TOAST"]
toastId?: ToasterToast["id"]
}
interface State {
toasts: ToasterToast[]
}
const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
const addToRemoveQueue = (toastId: string) => {
if (toastTimeouts.has(toastId)) {
return
}
const timeout = setTimeout(() => {
toastTimeouts.delete(toastId)
dispatch({
type: "REMOVE_TOAST",
toastId: toastId,
})
}, TOAST_REMOVE_DELAY)
toastTimeouts.set(toastId, timeout)
}
export const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "ADD_TOAST":
return {
...state,
toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
}
case "UPDATE_TOAST":
return {
...state,
toasts: state.toasts.map((t) =>
t.id === action.toast.id ? { ...t, ...action.toast } : t
),
}
case "DISMISS_TOAST": {
const { toastId } = action
// ! Side effects ! - This could be extracted into a dismissToast() action,
// but I'll keep it here for simplicity
if (toastId) {
addToRemoveQueue(toastId)
} else {
state.toasts.forEach((toast) => {
addToRemoveQueue(toast.id)
})
}
return {
...state,
toasts: state.toasts.map((t) =>
t.id === toastId || toastId === undefined
? {
...t,
open: false,
}
: t
),
}
}
case "REMOVE_TOAST":
if (action.toastId === undefined) {
return {
...state,
toasts: [],
}
}
return {
...state,
toasts: state.toasts.filter((t) => t.id !== action.toastId),
}
}
}
const listeners: Array<(state: State) => void> = []
let memoryState: State = { toasts: [] }
function dispatch(action: Action) {
memoryState = reducer(memoryState, action)
listeners.forEach((listener) => {
listener(memoryState)
})
}
type Toast = Omit<ToasterToast, "id">
function toast({ ...props }: Toast) {
const id = genId()
const update = (props: ToasterToast) =>
dispatch({
type: "UPDATE_TOAST",
toast: { ...props, id },
})
const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
dispatch({
type: "ADD_TOAST",
toast: {
...props,
id,
open: true,
onOpenChange: (open) => {
if (!open) dismiss()
},
},
})
return {
id: id,
dismiss,
update,
}
}
function useToast() {
const [state, setState] = React.useState<State>(memoryState)
React.useEffect(() => {
listeners.push(setState)
return () => {
const index = listeners.indexOf(setState)
if (index > -1) {
listeners.splice(index, 1)
}
}
}, [state])
return {
...state,
toast,
dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
}
}
export { useToast, toast }

View file

@ -1,5 +1,5 @@
import { create } from 'zustand'
import customAxios from '../utils/customAxios'
import customAxios from '../lib/customAxios'
export interface UserInfo {
id: string

View file

@ -4,6 +4,7 @@ import Sidebar from '../components/Sidebar'
import userAuthStore from '../hooks/userAuth'
import { useShallow } from 'zustand/react/shallow'
import { useCallback } from 'react'
import { Toaster } from '@/components/ui/toaster'
const MainLayout = () => {
const { userInfo, getUserInfo } = userAuthStore(
@ -35,6 +36,7 @@ const MainLayout = () => {
<Sidebar />
<main className="p-8 sm:ml-64 mt-20">
<Outlet />
<Toaster />
</main>
</>
)

View file

@ -0,0 +1,98 @@
import { type ColumnDef } from '@tanstack/react-table'
import { type Task } from '../models/schema'
import { Checkbox } from '@/components/ui/checkbox'
import DataTableColumnHeader from './data-table-column-header'
import { labels, priorities, statuses } from '../models/data'
import { Badge } from '@/components/ui/badge'
import DataTableRowActions from './data-table-row-actions'
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} />
}
]

View file

@ -0,0 +1,62 @@
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { cn } from '@/lib/utils'
import { ArrowDownIcon, ArrowUpIcon, CaretSortIcon, EyeNoneIcon } from '@radix-ui/react-icons'
import { type Column } from '@tanstack/react-table'
interface DataTableColumnHeaderProps<TData, TValue> extends React.HTMLAttributes<HTMLDivElement> {
column: Column<TData, TValue>
title: string
}
const 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>
)
}
export default DataTableColumnHeader

View file

@ -0,0 +1,127 @@
import { Button } from '@/components/ui/button'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { cn } from '@/lib/utils'
import { Separator } from '@/components/ui/separator'
import { PlusCircledIcon, CheckIcon } from '@radix-ui/react-icons'
import { type Column } from '@tanstack/react-table'
import { Badge } from '@/components/ui/badge'
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator
} from '@/components/ui/command'
interface DataTableFacetedFilterProps<TData, TValue> {
column?: Column<TData, TValue>
title?: string
options: {
label: string
value: string
icon?: React.ComponentType<{ className?: string }>
}[]
}
const 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>
)
}
export default DataTableFacetedFilter

View file

@ -0,0 +1,62 @@
import { type Row } from '@tanstack/react-table'
import { taskSchema } from '../models/schema'
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { Button } from '@/components/ui/button'
import { DotsHorizontalIcon } from '@radix-ui/react-icons'
import { labels } from '../models/data'
interface DataTableRowActionsProps<TData> {
row: Row<TData>
}
const 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>
)
}
export default DataTableRowActions

View file

@ -0,0 +1,18 @@
import { type Table } from '@tanstack/react-table'
import SearchForm from './search-form'
interface DataTableToolbarProps<TData> {
table: Table<TData>
}
const DataTableToolbar = <TData,>({ table }: DataTableToolbarProps<TData>) => {
const isFiltered = table.getState().columnFilters.length > 0
return (
<div className="flex items-center justify-between">
<SearchForm isFiltered={isFiltered} table={table} />
</div>
)
}
export default DataTableToolbar

View file

@ -0,0 +1,49 @@
import { Button } from '@/components/ui/button'
import {
DropdownMenu,
DropdownMenuCheckboxItem,
DropdownMenuContent,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger
} from '@/components/ui/dropdown-menu'
import { MixerHorizontalIcon } from '@radix-ui/react-icons'
import { type Table } from '@tanstack/react-table'
interface DataTableViewOptionsProps<TData> {
table: Table<TData>
}
const 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>
)
}
export default DataTableViewOptions

View file

@ -0,0 +1,91 @@
import type { VisibilityState, ColumnFiltersState, SortingState, ColumnDef } from '@tanstack/react-table'
import {
useReactTable,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
getFacetedRowModel,
getFacetedUniqueValues,
flexRender
} from '@tanstack/react-table'
import { useState } from 'react'
import DataTableToolbar from './data-table-toolbar'
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]
data: TData[]
}
const DataTable = <TData, TValue>({ columns, data }: DataTableProps<TData, TValue>) => {
const [rowSelection, setRowSelection] = useState({})
const [columnVisibility, setColumnVisibility] = useState<VisibilityState>({})
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([])
const [sorting, setSorting] = 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>
)
}
export default DataTable

View file

@ -0,0 +1,76 @@
import { Input } from '@/components/ui/input'
import { Button } from '@/components/ui/button'
import { Cross2Icon, MixerVerticalIcon, PlusIcon, Share2Icon } from '@radix-ui/react-icons'
import { Separator } from '@/components/ui/separator'
import DataTableFacetedFilter from './data-table-faceted-filter'
import { type Table } from '@tanstack/react-table'
import { priorities, statuses } from '../models/data'
import DataTableViewOptions from './data-table-view-options'
interface SearchFromProps<TData> {
isFiltered: boolean
table: Table<TData>
}
const SearchForm = <TData,>({ isFiltered, table }: SearchFromProps<TData>) => {
return (
<div className="flex flex-col w-full rounded-lg bg-white shadow-xl p-3 space-y-4">
<div className="flex justify-between items-center">
<span className="text-gray-700 text-sm font-semibold px-4 py-2">Search</span>
<div className="flex justify-center items-center space-x-3">
<Button variant={'outline'} className="px-4 py-2 text-sm">
View JSON
</Button>
<Button variant={'outline'} className="px-4 py-2 text-sm">
<Share2Icon className="inline-block w-4 h-4 mr-2" />
Export
</Button>
</div>
</div>
<div className="flex justify-between items-center">
<div className="flex space-x-3">
<div className="flex w-80">
<Input placeholder="Search..." className="flex-grow rounded-tr-none rounded-br-none text-sm" />
<Button className="px-4 py-2 text-sm rounded-tl-none rounded-bl-none">Search</Button>
</div>
<Button variant={'outline'} className="px-4 py-2 text-sm">
<MixerVerticalIcon className="inline-block w-4 h-4 mr-2" />
Filter
</Button>
</div>
<div className="flex space-x-3">
<Button className="px-4 py-2 text-sm">
<PlusIcon className="inline-block w-4 h-4 mr-2 text-white" />
Add new recipe
</Button>
</div>
</div>
<Separator className="my-4" />
<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>
</div>
)
}
export default SearchForm

View 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
}
]

View file

@ -0,0 +1,11 @@
import { z } from 'zod'
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>

View 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"
}
]

View file

@ -0,0 +1,37 @@
import { type Task, taskSchema } from './models/schema'
import { z } from 'zod'
import DataTable from './components/data-table'
import { columns } from './components/columns'
import { useEffect, useState } from 'react'
// Simulate a database read for tasks.
async function getTasks() {
const data = await fetch(new URL('./models/tasks.json', import.meta.url)).then(res => res.text())
const tasks = JSON.parse(data.toString())
return z.array(taskSchema).parse(tasks)
}
const RecipesPage = () => {
const [tasks, setTasks] = useState<Task[]>([])
useEffect(() => {
getTasks().then(tasks => {
setTasks(tasks)
})
}, [])
return (
<div className="flex flex-col w-full gap-3">
<section>
<h1 className="text-3xl font-bold text-gray-900">Recipes</h1>
</section>
<section>
<DataTable data={tasks} columns={columns} />
</section>
</div>
)
}
export default RecipesPage

View file

@ -1,14 +0,0 @@
import { AdbDaemonWebUsbDeviceManager } from '@yume-chan/adb-daemon-webusb'
const Manager: AdbDaemonWebUsbDeviceManager | undefined =
AdbDaemonWebUsbDeviceManager.BROWSER
export async function getDevices() {
if (!Manager) {
alert('WebUSB is not supported in this browser')
return
}
const devices = await Manager.getDevices()
return devices
}