Open Source Team Metrics based on PRs
at main 27 kB view raw
1"use client" 2 3import * as React from "react" 4import { 5 DndContext, 6 KeyboardSensor, 7 MouseSensor, 8 TouchSensor, 9 closestCenter, 10 useSensor, 11 useSensors, 12 type DragEndEvent, 13 type UniqueIdentifier, 14} from "@dnd-kit/core" 15import { restrictToVerticalAxis } from "@dnd-kit/modifiers" 16import { 17 SortableContext, 18 arrayMove, 19 useSortable, 20 verticalListSortingStrategy, 21} from "@dnd-kit/sortable" 22import { CSS } from "@dnd-kit/utilities" 23import { 24 IconChevronDown, 25 IconChevronLeft, 26 IconChevronRight, 27 IconChevronsLeft, 28 IconChevronsRight, 29 IconCircleCheckFilled, 30 IconDotsVertical, 31 IconGripVertical, 32 IconLayoutColumns, 33 IconLoader, 34 IconPlus, 35 IconTrendingUp, 36} from "@tabler/icons-react" 37import { 38 ColumnDef, 39 ColumnFiltersState, 40 Row, 41 SortingState, 42 VisibilityState, 43 flexRender, 44 getCoreRowModel, 45 getFacetedRowModel, 46 getFacetedUniqueValues, 47 getFilteredRowModel, 48 getPaginationRowModel, 49 getSortedRowModel, 50 useReactTable, 51} from "@tanstack/react-table" 52import { Area, AreaChart, CartesianGrid, XAxis } from "recharts" 53import { toast } from "sonner" 54import { z } from "zod" 55 56import { useIsMobile } from "@/hooks/use-mobile" 57import { Badge } from "@/components/ui/badge" 58import { Button } from "@/components/ui/button" 59import { 60 ChartConfig, 61 ChartContainer, 62 ChartTooltip, 63 ChartTooltipContent, 64} from "@/components/ui/chart" 65import { Checkbox } from "@/components/ui/checkbox" 66import { 67 Drawer, 68 DrawerClose, 69 DrawerContent, 70 DrawerDescription, 71 DrawerFooter, 72 DrawerHeader, 73 DrawerTitle, 74 DrawerTrigger, 75} from "@/components/ui/drawer" 76import { 77 DropdownMenu, 78 DropdownMenuCheckboxItem, 79 DropdownMenuContent, 80 DropdownMenuItem, 81 DropdownMenuSeparator, 82 DropdownMenuTrigger, 83} from "@/components/ui/dropdown-menu" 84import { Input } from "@/components/ui/input" 85import { Label } from "@/components/ui/label" 86import { 87 Select, 88 SelectContent, 89 SelectItem, 90 SelectTrigger, 91 SelectValue, 92} from "@/components/ui/select" 93import { Separator } from "@/components/ui/separator" 94import { 95 Table, 96 TableBody, 97 TableCell, 98 TableHead, 99 TableHeader, 100 TableRow, 101} from "@/components/ui/table" 102import { 103 Tabs, 104 TabsContent, 105 TabsList, 106 TabsTrigger, 107} from "@/components/ui/tabs" 108 109export const schema = z.object({ 110 id: z.number(), 111 header: z.string(), 112 type: z.string(), 113 status: z.string(), 114 target: z.string(), 115 limit: z.string(), 116 reviewer: z.string(), 117}) 118 119// Create a separate component for the drag handle 120function DragHandle({ id }: { id: number }) { 121 const { attributes, listeners } = useSortable({ 122 id, 123 }) 124 125 return ( 126 <Button 127 {...attributes} 128 {...listeners} 129 variant="ghost" 130 size="icon" 131 className="text-muted-foreground size-7 hover:bg-transparent" 132 > 133 <IconGripVertical className="text-muted-foreground size-3" /> 134 <span className="sr-only">Drag to reorder</span> 135 </Button> 136 ) 137} 138 139const columns: ColumnDef<z.infer<typeof schema>>[] = [ 140 { 141 id: "drag", 142 header: () => null, 143 cell: ({ row }) => <DragHandle id={row.original.id} />, 144 }, 145 { 146 id: "select", 147 header: ({ table }) => ( 148 <div className="flex items-center justify-center"> 149 <Checkbox 150 checked={ 151 table.getIsAllPageRowsSelected() || 152 (table.getIsSomePageRowsSelected() && "indeterminate") 153 } 154 onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} 155 aria-label="Select all" 156 /> 157 </div> 158 ), 159 cell: ({ row }) => ( 160 <div className="flex items-center justify-center"> 161 <Checkbox 162 checked={row.getIsSelected()} 163 onCheckedChange={(value) => row.toggleSelected(!!value)} 164 aria-label="Select row" 165 /> 166 </div> 167 ), 168 enableSorting: false, 169 enableHiding: false, 170 }, 171 { 172 accessorKey: "header", 173 header: "Header", 174 cell: ({ row }) => { 175 return <TableCellViewer item={row.original} /> 176 }, 177 enableHiding: false, 178 }, 179 { 180 accessorKey: "type", 181 header: "Section Type", 182 cell: ({ row }) => ( 183 <div className="w-32"> 184 <Badge variant="outline" className="text-muted-foreground px-1.5"> 185 {row.original.type} 186 </Badge> 187 </div> 188 ), 189 }, 190 { 191 accessorKey: "status", 192 header: "Status", 193 cell: ({ row }) => ( 194 <Badge variant="outline" className="text-muted-foreground px-1.5"> 195 {row.original.status === "Done" ? ( 196 <IconCircleCheckFilled className="fill-green-500 dark:fill-green-400" /> 197 ) : ( 198 <IconLoader /> 199 )} 200 {row.original.status} 201 </Badge> 202 ), 203 }, 204 { 205 accessorKey: "target", 206 header: () => <div className="w-full text-right">Target</div>, 207 cell: ({ row }) => ( 208 <form 209 onSubmit={(e) => { 210 e.preventDefault() 211 toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { 212 loading: `Saving ${row.original.header}`, 213 success: "Done", 214 error: "Error", 215 }) 216 }} 217 > 218 <Label htmlFor={`${row.original.id}-target`} className="sr-only"> 219 Target 220 </Label> 221 <Input 222 className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent" 223 defaultValue={row.original.target} 224 id={`${row.original.id}-target`} 225 /> 226 </form> 227 ), 228 }, 229 { 230 accessorKey: "limit", 231 header: () => <div className="w-full text-right">Limit</div>, 232 cell: ({ row }) => ( 233 <form 234 onSubmit={(e) => { 235 e.preventDefault() 236 toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), { 237 loading: `Saving ${row.original.header}`, 238 success: "Done", 239 error: "Error", 240 }) 241 }} 242 > 243 <Label htmlFor={`${row.original.id}-limit`} className="sr-only"> 244 Limit 245 </Label> 246 <Input 247 className="hover:bg-input/30 focus-visible:bg-background dark:hover:bg-input/30 dark:focus-visible:bg-input/30 h-8 w-16 border-transparent bg-transparent text-right shadow-none focus-visible:border dark:bg-transparent" 248 defaultValue={row.original.limit} 249 id={`${row.original.id}-limit`} 250 /> 251 </form> 252 ), 253 }, 254 { 255 accessorKey: "reviewer", 256 header: "Reviewer", 257 cell: ({ row }) => { 258 const isAssigned = row.original.reviewer !== "Assign reviewer" 259 260 if (isAssigned) { 261 return row.original.reviewer 262 } 263 264 return ( 265 <> 266 <Label htmlFor={`${row.original.id}-reviewer`} className="sr-only"> 267 Reviewer 268 </Label> 269 <Select> 270 <SelectTrigger 271 className="w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate" 272 size="sm" 273 id={`${row.original.id}-reviewer`} 274 > 275 <SelectValue placeholder="Assign reviewer" /> 276 </SelectTrigger> 277 <SelectContent align="end"> 278 <SelectItem value="Eddie Lake">Eddie Lake</SelectItem> 279 <SelectItem value="Jamik Tashpulatov"> 280 Jamik Tashpulatov 281 </SelectItem> 282 </SelectContent> 283 </Select> 284 </> 285 ) 286 }, 287 }, 288 { 289 id: "actions", 290 cell: () => ( 291 <DropdownMenu> 292 <DropdownMenuTrigger asChild> 293 <Button 294 variant="ghost" 295 className="data-[state=open]:bg-muted text-muted-foreground flex size-8" 296 size="icon" 297 > 298 <IconDotsVertical /> 299 <span className="sr-only">Open menu</span> 300 </Button> 301 </DropdownMenuTrigger> 302 <DropdownMenuContent align="end" className="w-32"> 303 <DropdownMenuItem>Edit</DropdownMenuItem> 304 <DropdownMenuItem>Make a copy</DropdownMenuItem> 305 <DropdownMenuItem>Favorite</DropdownMenuItem> 306 <DropdownMenuSeparator /> 307 <DropdownMenuItem variant="destructive">Delete</DropdownMenuItem> 308 </DropdownMenuContent> 309 </DropdownMenu> 310 ), 311 }, 312] 313 314function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) { 315 const { transform, transition, setNodeRef, isDragging } = useSortable({ 316 id: row.original.id, 317 }) 318 319 return ( 320 <TableRow 321 data-state={row.getIsSelected() && "selected"} 322 data-dragging={isDragging} 323 ref={setNodeRef} 324 className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80" 325 style={{ 326 transform: CSS.Transform.toString(transform), 327 transition: transition, 328 }} 329 > 330 {row.getVisibleCells().map((cell) => ( 331 <TableCell key={cell.id}> 332 {flexRender(cell.column.columnDef.cell, cell.getContext())} 333 </TableCell> 334 ))} 335 </TableRow> 336 ) 337} 338 339export function DataTable({ 340 data: initialData, 341}: { 342 data: z.infer<typeof schema>[] 343}) { 344 const [data, setData] = React.useState(() => initialData) 345 const [rowSelection, setRowSelection] = React.useState({}) 346 const [columnVisibility, setColumnVisibility] = 347 React.useState<VisibilityState>({}) 348 const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>( 349 [] 350 ) 351 const [sorting, setSorting] = React.useState<SortingState>([]) 352 const [pagination, setPagination] = React.useState({ 353 pageIndex: 0, 354 pageSize: 10, 355 }) 356 const sortableId = React.useId() 357 const sensors = useSensors( 358 useSensor(MouseSensor, {}), 359 useSensor(TouchSensor, {}), 360 useSensor(KeyboardSensor, {}) 361 ) 362 363 const dataIds = React.useMemo<UniqueIdentifier[]>( 364 () => data?.map(({ id }) => id) || [], 365 [data] 366 ) 367 368 const table = useReactTable({ 369 data, 370 columns, 371 state: { 372 sorting, 373 columnVisibility, 374 rowSelection, 375 columnFilters, 376 pagination, 377 }, 378 getRowId: (row) => row.id.toString(), 379 enableRowSelection: true, 380 onRowSelectionChange: setRowSelection, 381 onSortingChange: setSorting, 382 onColumnFiltersChange: setColumnFilters, 383 onColumnVisibilityChange: setColumnVisibility, 384 onPaginationChange: setPagination, 385 getCoreRowModel: getCoreRowModel(), 386 getFilteredRowModel: getFilteredRowModel(), 387 getPaginationRowModel: getPaginationRowModel(), 388 getSortedRowModel: getSortedRowModel(), 389 getFacetedRowModel: getFacetedRowModel(), 390 getFacetedUniqueValues: getFacetedUniqueValues(), 391 }) 392 393 function handleDragEnd(event: DragEndEvent) { 394 const { active, over } = event 395 if (active && over && active.id !== over.id) { 396 setData((data) => { 397 const oldIndex = dataIds.indexOf(active.id) 398 const newIndex = dataIds.indexOf(over.id) 399 return arrayMove(data, oldIndex, newIndex) 400 }) 401 } 402 } 403 404 return ( 405 <Tabs 406 defaultValue="outline" 407 className="w-full flex-col justify-start gap-6" 408 > 409 <div className="flex items-center justify-between px-4 lg:px-6"> 410 <Label htmlFor="view-selector" className="sr-only"> 411 View 412 </Label> 413 <Select defaultValue="outline"> 414 <SelectTrigger 415 className="flex w-fit @4xl/main:hidden" 416 size="sm" 417 id="view-selector" 418 > 419 <SelectValue placeholder="Select a view" /> 420 </SelectTrigger> 421 <SelectContent> 422 <SelectItem value="outline">Outline</SelectItem> 423 <SelectItem value="past-performance">Past Performance</SelectItem> 424 <SelectItem value="key-personnel">Key Personnel</SelectItem> 425 <SelectItem value="focus-documents">Focus Documents</SelectItem> 426 </SelectContent> 427 </Select> 428 <TabsList className="**:data-[slot=badge]:bg-muted-foreground/30 hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:px-1 @4xl/main:flex"> 429 <TabsTrigger value="outline">Outline</TabsTrigger> 430 <TabsTrigger value="past-performance"> 431 Past Performance <Badge variant="secondary">3</Badge> 432 </TabsTrigger> 433 <TabsTrigger value="key-personnel"> 434 Key Personnel <Badge variant="secondary">2</Badge> 435 </TabsTrigger> 436 <TabsTrigger value="focus-documents">Focus Documents</TabsTrigger> 437 </TabsList> 438 <div className="flex items-center gap-2"> 439 <DropdownMenu> 440 <DropdownMenuTrigger asChild> 441 <Button variant="outline" size="sm"> 442 <IconLayoutColumns /> 443 <span className="hidden lg:inline">Customize Columns</span> 444 <span className="lg:hidden">Columns</span> 445 <IconChevronDown /> 446 </Button> 447 </DropdownMenuTrigger> 448 <DropdownMenuContent align="end" className="w-56"> 449 {table 450 .getAllColumns() 451 .filter( 452 (column) => 453 typeof column.accessorFn !== "undefined" && 454 column.getCanHide() 455 ) 456 .map((column) => { 457 return ( 458 <DropdownMenuCheckboxItem 459 key={column.id} 460 className="capitalize" 461 checked={column.getIsVisible()} 462 onCheckedChange={(value) => 463 column.toggleVisibility(!!value) 464 } 465 > 466 {column.id} 467 </DropdownMenuCheckboxItem> 468 ) 469 })} 470 </DropdownMenuContent> 471 </DropdownMenu> 472 <Button variant="outline" size="sm"> 473 <IconPlus /> 474 <span className="hidden lg:inline">Add Section</span> 475 </Button> 476 </div> 477 </div> 478 <TabsContent 479 value="outline" 480 className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6" 481 > 482 <div className="overflow-hidden rounded-lg border"> 483 <DndContext 484 collisionDetection={closestCenter} 485 modifiers={[restrictToVerticalAxis]} 486 onDragEnd={handleDragEnd} 487 sensors={sensors} 488 id={sortableId} 489 > 490 <Table> 491 <TableHeader className="bg-muted sticky top-0 z-10"> 492 {table.getHeaderGroups().map((headerGroup) => ( 493 <TableRow key={headerGroup.id}> 494 {headerGroup.headers.map((header) => { 495 return ( 496 <TableHead key={header.id} colSpan={header.colSpan}> 497 {header.isPlaceholder 498 ? null 499 : flexRender( 500 header.column.columnDef.header, 501 header.getContext() 502 )} 503 </TableHead> 504 ) 505 })} 506 </TableRow> 507 ))} 508 </TableHeader> 509 <TableBody className="**:data-[slot=table-cell]:first:w-8"> 510 {table.getRowModel().rows?.length ? ( 511 <SortableContext 512 items={dataIds} 513 strategy={verticalListSortingStrategy} 514 > 515 {table.getRowModel().rows.map((row) => ( 516 <DraggableRow key={row.id} row={row} /> 517 ))} 518 </SortableContext> 519 ) : ( 520 <TableRow> 521 <TableCell 522 colSpan={columns.length} 523 className="h-24 text-center" 524 > 525 No results. 526 </TableCell> 527 </TableRow> 528 )} 529 </TableBody> 530 </Table> 531 </DndContext> 532 </div> 533 <div className="flex items-center justify-between px-4"> 534 <div className="text-muted-foreground hidden flex-1 text-sm lg:flex"> 535 {table.getFilteredSelectedRowModel().rows.length} of{" "} 536 {table.getFilteredRowModel().rows.length} row(s) selected. 537 </div> 538 <div className="flex w-full items-center gap-8 lg:w-fit"> 539 <div className="hidden items-center gap-2 lg:flex"> 540 <Label htmlFor="rows-per-page" className="text-sm font-medium"> 541 Rows per page 542 </Label> 543 <Select 544 value={`${table.getState().pagination.pageSize}`} 545 onValueChange={(value) => { 546 table.setPageSize(Number(value)) 547 }} 548 > 549 <SelectTrigger size="sm" className="w-20" id="rows-per-page"> 550 <SelectValue 551 placeholder={table.getState().pagination.pageSize} 552 /> 553 </SelectTrigger> 554 <SelectContent side="top"> 555 {[10, 20, 30, 40, 50].map((pageSize) => ( 556 <SelectItem key={pageSize} value={`${pageSize}`}> 557 {pageSize} 558 </SelectItem> 559 ))} 560 </SelectContent> 561 </Select> 562 </div> 563 <div className="flex w-fit items-center justify-center text-sm font-medium"> 564 Page {table.getState().pagination.pageIndex + 1} of{" "} 565 {table.getPageCount()} 566 </div> 567 <div className="ml-auto flex items-center gap-2 lg:ml-0"> 568 <Button 569 variant="outline" 570 className="hidden h-8 w-8 p-0 lg:flex" 571 onClick={() => table.setPageIndex(0)} 572 disabled={!table.getCanPreviousPage()} 573 > 574 <span className="sr-only">Go to first page</span> 575 <IconChevronsLeft /> 576 </Button> 577 <Button 578 variant="outline" 579 className="size-8" 580 size="icon" 581 onClick={() => table.previousPage()} 582 disabled={!table.getCanPreviousPage()} 583 > 584 <span className="sr-only">Go to previous page</span> 585 <IconChevronLeft /> 586 </Button> 587 <Button 588 variant="outline" 589 className="size-8" 590 size="icon" 591 onClick={() => table.nextPage()} 592 disabled={!table.getCanNextPage()} 593 > 594 <span className="sr-only">Go to next page</span> 595 <IconChevronRight /> 596 </Button> 597 <Button 598 variant="outline" 599 className="hidden size-8 lg:flex" 600 size="icon" 601 onClick={() => table.setPageIndex(table.getPageCount() - 1)} 602 disabled={!table.getCanNextPage()} 603 > 604 <span className="sr-only">Go to last page</span> 605 <IconChevronsRight /> 606 </Button> 607 </div> 608 </div> 609 </div> 610 </TabsContent> 611 <TabsContent 612 value="past-performance" 613 className="flex flex-col px-4 lg:px-6" 614 > 615 <div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div> 616 </TabsContent> 617 <TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6"> 618 <div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div> 619 </TabsContent> 620 <TabsContent 621 value="focus-documents" 622 className="flex flex-col px-4 lg:px-6" 623 > 624 <div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div> 625 </TabsContent> 626 </Tabs> 627 ) 628} 629 630const chartData = [ 631 { month: "January", desktop: 186, mobile: 80 }, 632 { month: "February", desktop: 305, mobile: 200 }, 633 { month: "March", desktop: 237, mobile: 120 }, 634 { month: "April", desktop: 73, mobile: 190 }, 635 { month: "May", desktop: 209, mobile: 130 }, 636 { month: "June", desktop: 214, mobile: 140 }, 637] 638 639const chartConfig = { 640 desktop: { 641 label: "Desktop", 642 color: "var(--primary)", 643 }, 644 mobile: { 645 label: "Mobile", 646 color: "var(--primary)", 647 }, 648} satisfies ChartConfig 649 650function TableCellViewer({ item }: { item: z.infer<typeof schema> }) { 651 const isMobile = useIsMobile() 652 653 return ( 654 <Drawer direction={isMobile ? "bottom" : "right"}> 655 <DrawerTrigger asChild> 656 <Button variant="link" className="text-foreground w-fit px-0 text-left"> 657 {item.header} 658 </Button> 659 </DrawerTrigger> 660 <DrawerContent> 661 <DrawerHeader className="gap-1"> 662 <DrawerTitle>{item.header}</DrawerTitle> 663 <DrawerDescription> 664 Showing total visitors for the last 6 months 665 </DrawerDescription> 666 </DrawerHeader> 667 <div className="flex flex-col gap-4 overflow-y-auto px-4 text-sm"> 668 {!isMobile && ( 669 <> 670 <ChartContainer config={chartConfig}> 671 <AreaChart 672 accessibilityLayer 673 data={chartData} 674 margin={{ 675 left: 0, 676 right: 10, 677 }} 678 > 679 <CartesianGrid vertical={false} /> 680 <XAxis 681 dataKey="month" 682 tickLine={false} 683 axisLine={false} 684 tickMargin={8} 685 tickFormatter={(value) => value.slice(0, 3)} 686 hide 687 /> 688 <ChartTooltip 689 cursor={false} 690 content={<ChartTooltipContent indicator="dot" />} 691 /> 692 <Area 693 dataKey="mobile" 694 type="natural" 695 fill="var(--color-mobile)" 696 fillOpacity={0.6} 697 stroke="var(--color-mobile)" 698 stackId="a" 699 /> 700 <Area 701 dataKey="desktop" 702 type="natural" 703 fill="var(--color-desktop)" 704 fillOpacity={0.4} 705 stroke="var(--color-desktop)" 706 stackId="a" 707 /> 708 </AreaChart> 709 </ChartContainer> 710 <Separator /> 711 <div className="grid gap-2"> 712 <div className="flex gap-2 leading-none font-medium"> 713 Trending up by 5.2% this month{" "} 714 <IconTrendingUp className="size-4" /> 715 </div> 716 <div className="text-muted-foreground"> 717 Showing total visitors for the last 6 months. This is just 718 some random text to test the layout. It spans multiple lines 719 and should wrap around. 720 </div> 721 </div> 722 <Separator /> 723 </> 724 )} 725 <form className="flex flex-col gap-4"> 726 <div className="flex flex-col gap-3"> 727 <Label htmlFor="header">Header</Label> 728 <Input id="header" defaultValue={item.header} /> 729 </div> 730 <div className="grid grid-cols-2 gap-4"> 731 <div className="flex flex-col gap-3"> 732 <Label htmlFor="type">Type</Label> 733 <Select defaultValue={item.type}> 734 <SelectTrigger id="type" className="w-full"> 735 <SelectValue placeholder="Select a type" /> 736 </SelectTrigger> 737 <SelectContent> 738 <SelectItem value="Table of Contents"> 739 Table of Contents 740 </SelectItem> 741 <SelectItem value="Executive Summary"> 742 Executive Summary 743 </SelectItem> 744 <SelectItem value="Technical Approach"> 745 Technical Approach 746 </SelectItem> 747 <SelectItem value="Design">Design</SelectItem> 748 <SelectItem value="Capabilities">Capabilities</SelectItem> 749 <SelectItem value="Focus Documents"> 750 Focus Documents 751 </SelectItem> 752 <SelectItem value="Narrative">Narrative</SelectItem> 753 <SelectItem value="Cover Page">Cover Page</SelectItem> 754 </SelectContent> 755 </Select> 756 </div> 757 <div className="flex flex-col gap-3"> 758 <Label htmlFor="status">Status</Label> 759 <Select defaultValue={item.status}> 760 <SelectTrigger id="status" className="w-full"> 761 <SelectValue placeholder="Select a status" /> 762 </SelectTrigger> 763 <SelectContent> 764 <SelectItem value="Done">Done</SelectItem> 765 <SelectItem value="In Progress">In Progress</SelectItem> 766 <SelectItem value="Not Started">Not Started</SelectItem> 767 </SelectContent> 768 </Select> 769 </div> 770 </div> 771 <div className="grid grid-cols-2 gap-4"> 772 <div className="flex flex-col gap-3"> 773 <Label htmlFor="target">Target</Label> 774 <Input id="target" defaultValue={item.target} /> 775 </div> 776 <div className="flex flex-col gap-3"> 777 <Label htmlFor="limit">Limit</Label> 778 <Input id="limit" defaultValue={item.limit} /> 779 </div> 780 </div> 781 <div className="flex flex-col gap-3"> 782 <Label htmlFor="reviewer">Reviewer</Label> 783 <Select defaultValue={item.reviewer}> 784 <SelectTrigger id="reviewer" className="w-full"> 785 <SelectValue placeholder="Select a reviewer" /> 786 </SelectTrigger> 787 <SelectContent> 788 <SelectItem value="Eddie Lake">Eddie Lake</SelectItem> 789 <SelectItem value="Jamik Tashpulatov"> 790 Jamik Tashpulatov 791 </SelectItem> 792 <SelectItem value="Emily Whalen">Emily Whalen</SelectItem> 793 </SelectContent> 794 </Select> 795 </div> 796 </form> 797 </div> 798 <DrawerFooter> 799 <Button>Submit</Button> 800 <DrawerClose asChild> 801 <Button variant="outline">Done</Button> 802 </DrawerClose> 803 </DrawerFooter> 804 </DrawerContent> 805 </Drawer> 806 ) 807}