create-bluecopa-react-app 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/README.md +47 -10
  2. package/bin/create-bluecopa-react-app.js +257 -51
  3. package/package.json +6 -5
  4. package/templates/latest/Agent.md +254 -0
  5. package/templates/latest/Dockerfile +22 -0
  6. package/templates/latest/README.md +157 -221
  7. package/templates/latest/app/app.css +134 -0
  8. package/templates/latest/app/app.tsx +46 -0
  9. package/templates/latest/app/components/app-sidebar.tsx +174 -0
  10. package/templates/latest/app/components/chart-area-interactive.tsx +290 -0
  11. package/templates/latest/app/components/data-table.tsx +807 -0
  12. package/templates/latest/app/components/nav-documents.tsx +92 -0
  13. package/templates/latest/app/components/nav-main.tsx +56 -0
  14. package/templates/latest/app/components/nav-secondary.tsx +42 -0
  15. package/templates/latest/app/components/nav-user.tsx +112 -0
  16. package/templates/latest/app/components/section-cards.tsx +102 -0
  17. package/templates/latest/app/components/site-header.tsx +19 -0
  18. package/templates/latest/app/components/ui/avatar.tsx +53 -0
  19. package/templates/latest/app/components/ui/badge.tsx +46 -0
  20. package/templates/latest/app/components/ui/breadcrumb.tsx +109 -0
  21. package/templates/latest/app/components/ui/button.tsx +58 -0
  22. package/templates/latest/app/components/ui/card.tsx +92 -0
  23. package/templates/latest/app/components/ui/chart.tsx +352 -0
  24. package/templates/latest/app/components/ui/checkbox.tsx +30 -0
  25. package/templates/latest/app/components/ui/drawer.tsx +139 -0
  26. package/templates/latest/app/components/ui/dropdown-menu.tsx +258 -0
  27. package/templates/latest/app/components/ui/input.tsx +21 -0
  28. package/templates/latest/app/components/ui/label.tsx +24 -0
  29. package/templates/latest/app/components/ui/select.tsx +183 -0
  30. package/templates/latest/app/components/ui/separator.tsx +26 -0
  31. package/templates/latest/app/components/ui/sheet.tsx +139 -0
  32. package/templates/latest/app/components/ui/sidebar.tsx +731 -0
  33. package/templates/latest/app/components/ui/skeleton.tsx +13 -0
  34. package/templates/latest/app/components/ui/sonner.tsx +23 -0
  35. package/templates/latest/app/components/ui/table.tsx +117 -0
  36. package/templates/latest/app/components/ui/tabs.tsx +66 -0
  37. package/templates/latest/app/components/ui/toggle-group.tsx +73 -0
  38. package/templates/latest/app/components/ui/toggle.tsx +47 -0
  39. package/templates/latest/app/components/ui/tooltip.tsx +59 -0
  40. package/templates/latest/app/dashboard/data.json +614 -0
  41. package/templates/latest/app/hooks/use-bluecopa-user.ts +37 -0
  42. package/templates/latest/app/hooks/use-mobile.ts +19 -0
  43. package/templates/latest/{src → app}/lib/utils.ts +1 -1
  44. package/templates/latest/app/main.tsx +12 -0
  45. package/templates/latest/app/routes/home.tsx +40 -0
  46. package/templates/latest/app/routes.tsx +15 -0
  47. package/templates/latest/{src → app}/single-spa.tsx +38 -28
  48. package/templates/latest/components.json +22 -0
  49. package/templates/latest/dist/assets/__federation_expose_App-DRwKKpS2.js +91 -0
  50. package/templates/latest/dist/assets/__federation_fn_import-CzfA7kmP.js +438 -0
  51. package/templates/latest/dist/assets/__federation_shared_react-Bp6HVBS4.js +16 -0
  52. package/templates/latest/dist/assets/__federation_shared_react-dom-BCcRGiYp.js +17 -0
  53. package/templates/latest/dist/assets/client-DgSav55y.js +12658 -0
  54. package/templates/latest/dist/assets/home-DOL6GrYV.js +54951 -0
  55. package/templates/latest/dist/assets/index-BzNimew1.js +69 -0
  56. package/templates/latest/dist/assets/index-DMFtQdNS.js +412 -0
  57. package/templates/latest/dist/assets/index-DdYpcDMk.js +60 -0
  58. package/templates/latest/dist/assets/remoteEntry.js +88 -0
  59. package/templates/latest/dist/assets/style-36A39bNN.css +3683 -0
  60. package/templates/latest/dist/avatars/shadcn.svg +6 -0
  61. package/templates/latest/dist/favicon.ico +0 -0
  62. package/templates/latest/dist/index.html +19 -0
  63. package/templates/latest/index.html +1 -1
  64. package/templates/latest/package-lock.json +1227 -3353
  65. package/templates/latest/package.json +47 -43
  66. package/templates/latest/pnpm-lock.yaml +4767 -0
  67. package/templates/latest/preview/index.html +32 -2
  68. package/templates/latest/public/avatars/shadcn.svg +6 -0
  69. package/templates/latest/public/favicon.ico +0 -0
  70. package/templates/latest/tsconfig.json +18 -11
  71. package/templates/latest/vite.config.ts +41 -41
  72. package/templates/latest/.env.example +0 -14
  73. package/templates/latest/.eslintrc.cjs +0 -42
  74. package/templates/latest/AGENT.md +0 -284
  75. package/templates/latest/clean.sh +0 -39
  76. package/templates/latest/postcss.config.cjs +0 -6
  77. package/templates/latest/public/bluecopa-logo.svg +0 -30
  78. package/templates/latest/public/favicon-32x32.png +0 -0
  79. package/templates/latest/public/favicon-96x96.png +0 -0
  80. package/templates/latest/setup.sh +0 -55
  81. package/templates/latest/src/App.tsx +0 -15
  82. package/templates/latest/src/components/layout/dashboard-header.tsx +0 -139
  83. package/templates/latest/src/components/layout/dashboard-layout.tsx +0 -29
  84. package/templates/latest/src/components/layout/sidebar.tsx +0 -54
  85. package/templates/latest/src/components/page/dashboard.tsx +0 -1506
  86. package/templates/latest/src/components/page/navbar.tsx +0 -104
  87. package/templates/latest/src/components/tables/data-grid.tsx +0 -439
  88. package/templates/latest/src/components/ui/alert.tsx +0 -59
  89. package/templates/latest/src/components/ui/avatar.tsx +0 -50
  90. package/templates/latest/src/components/ui/badge.tsx +0 -36
  91. package/templates/latest/src/components/ui/bluecopa-logo.tsx +0 -54
  92. package/templates/latest/src/components/ui/button.tsx +0 -58
  93. package/templates/latest/src/components/ui/card.tsx +0 -79
  94. package/templates/latest/src/components/ui/dropdown-menu.tsx +0 -200
  95. package/templates/latest/src/components/ui/input.tsx +0 -24
  96. package/templates/latest/src/components/ui/label.tsx +0 -23
  97. package/templates/latest/src/components/ui/select.tsx +0 -29
  98. package/templates/latest/src/hooks/use-api.ts +0 -55
  99. package/templates/latest/src/index.css +0 -59
  100. package/templates/latest/src/main.tsx +0 -13
  101. package/templates/latest/src/pages/Dashboard.tsx +0 -13
  102. package/templates/latest/src/pages/Home.tsx +0 -622
  103. package/templates/latest/src/providers/query-provider.tsx +0 -48
  104. package/templates/latest/src/types/api.ts +0 -78
  105. package/templates/latest/src/vite-env.d.ts +0 -11
  106. package/templates/latest/tailwind.config.js +0 -88
  107. package/templates/latest/tsconfig.app.json +0 -26
  108. package/templates/latest/tsconfig.node.json +0 -10
@@ -0,0 +1,807 @@
1
+ import * as React from "react"
2
+ import {
3
+ closestCenter,
4
+ DndContext,
5
+ KeyboardSensor,
6
+ MouseSensor,
7
+ TouchSensor,
8
+ useSensor,
9
+ useSensors,
10
+ type DragEndEvent,
11
+ type UniqueIdentifier,
12
+ } from "@dnd-kit/core"
13
+ import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
14
+ import {
15
+ arrayMove,
16
+ SortableContext,
17
+ useSortable,
18
+ verticalListSortingStrategy,
19
+ } from "@dnd-kit/sortable"
20
+ import { CSS } from "@dnd-kit/utilities"
21
+ import {
22
+ IconChevronDown,
23
+ IconChevronLeft,
24
+ IconChevronRight,
25
+ IconChevronsLeft,
26
+ IconChevronsRight,
27
+ IconCircleCheckFilled,
28
+ IconDotsVertical,
29
+ IconGripVertical,
30
+ IconLayoutColumns,
31
+ IconLoader,
32
+ IconPlus,
33
+ IconTrendingUp,
34
+ } from "@tabler/icons-react"
35
+ import {
36
+ flexRender,
37
+ getCoreRowModel,
38
+ getFacetedRowModel,
39
+ getFacetedUniqueValues,
40
+ getFilteredRowModel,
41
+ getPaginationRowModel,
42
+ getSortedRowModel,
43
+ useReactTable,
44
+ type ColumnDef,
45
+ type ColumnFiltersState,
46
+ type Row,
47
+ } from "@tanstack/react-table"
48
+ import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
49
+ import { toast } from "sonner"
50
+ import { z } from "zod"
51
+
52
+ import { useIsMobile } from "~/hooks/use-mobile"
53
+
54
+ // Define types since they're not exported from @tanstack/react-table
55
+ type VisibilityState = Record<string, boolean>
56
+ type SortingState = Array<{ id: string; desc: boolean }>
57
+ import { Badge } from "~/components/ui/badge"
58
+ import { Button } from "~/components/ui/button"
59
+ import {
60
+ ChartContainer,
61
+ ChartTooltip,
62
+ ChartTooltipContent,
63
+ type ChartConfig,
64
+ } from "~/components/ui/chart"
65
+ import { Checkbox } from "~/components/ui/checkbox"
66
+ import {
67
+ Drawer,
68
+ DrawerClose,
69
+ DrawerContent,
70
+ DrawerDescription,
71
+ DrawerFooter,
72
+ DrawerHeader,
73
+ DrawerTitle,
74
+ DrawerTrigger,
75
+ } from "~/components/ui/drawer"
76
+ import {
77
+ DropdownMenu,
78
+ DropdownMenuCheckboxItem,
79
+ DropdownMenuContent,
80
+ DropdownMenuItem,
81
+ DropdownMenuSeparator,
82
+ DropdownMenuTrigger,
83
+ } from "~/components/ui/dropdown-menu"
84
+ import { Input } from "~/components/ui/input"
85
+ import { Label } from "~/components/ui/label"
86
+ import {
87
+ Select,
88
+ SelectContent,
89
+ SelectItem,
90
+ SelectTrigger,
91
+ SelectValue,
92
+ } from "~/components/ui/select"
93
+ import { Separator } from "~/components/ui/separator"
94
+ import {
95
+ Table,
96
+ TableBody,
97
+ TableCell,
98
+ TableHead,
99
+ TableHeader,
100
+ TableRow,
101
+ } from "~/components/ui/table"
102
+ import {
103
+ Tabs,
104
+ TabsContent,
105
+ TabsList,
106
+ TabsTrigger,
107
+ } from "~/components/ui/tabs"
108
+
109
+ export 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
120
+ function 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
+
139
+ const 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
+
314
+ function 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
+
339
+ export 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
+
630
+ const 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
+
639
+ const 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
+
650
+ function 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
+ }