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.
- package/README.md +47 -10
- package/bin/create-bluecopa-react-app.js +257 -51
- package/package.json +6 -5
- package/templates/latest/Agent.md +254 -0
- package/templates/latest/Dockerfile +22 -0
- package/templates/latest/README.md +157 -221
- package/templates/latest/app/app.css +134 -0
- package/templates/latest/app/app.tsx +46 -0
- package/templates/latest/app/components/app-sidebar.tsx +174 -0
- package/templates/latest/app/components/chart-area-interactive.tsx +290 -0
- package/templates/latest/app/components/data-table.tsx +807 -0
- package/templates/latest/app/components/nav-documents.tsx +92 -0
- package/templates/latest/app/components/nav-main.tsx +56 -0
- package/templates/latest/app/components/nav-secondary.tsx +42 -0
- package/templates/latest/app/components/nav-user.tsx +112 -0
- package/templates/latest/app/components/section-cards.tsx +102 -0
- package/templates/latest/app/components/site-header.tsx +19 -0
- package/templates/latest/app/components/ui/avatar.tsx +53 -0
- package/templates/latest/app/components/ui/badge.tsx +46 -0
- package/templates/latest/app/components/ui/breadcrumb.tsx +109 -0
- package/templates/latest/app/components/ui/button.tsx +58 -0
- package/templates/latest/app/components/ui/card.tsx +92 -0
- package/templates/latest/app/components/ui/chart.tsx +352 -0
- package/templates/latest/app/components/ui/checkbox.tsx +30 -0
- package/templates/latest/app/components/ui/drawer.tsx +139 -0
- package/templates/latest/app/components/ui/dropdown-menu.tsx +258 -0
- package/templates/latest/app/components/ui/input.tsx +21 -0
- package/templates/latest/app/components/ui/label.tsx +24 -0
- package/templates/latest/app/components/ui/select.tsx +183 -0
- package/templates/latest/app/components/ui/separator.tsx +26 -0
- package/templates/latest/app/components/ui/sheet.tsx +139 -0
- package/templates/latest/app/components/ui/sidebar.tsx +731 -0
- package/templates/latest/app/components/ui/skeleton.tsx +13 -0
- package/templates/latest/app/components/ui/sonner.tsx +23 -0
- package/templates/latest/app/components/ui/table.tsx +117 -0
- package/templates/latest/app/components/ui/tabs.tsx +66 -0
- package/templates/latest/app/components/ui/toggle-group.tsx +73 -0
- package/templates/latest/app/components/ui/toggle.tsx +47 -0
- package/templates/latest/app/components/ui/tooltip.tsx +59 -0
- package/templates/latest/app/dashboard/data.json +614 -0
- package/templates/latest/app/hooks/use-bluecopa-user.ts +37 -0
- package/templates/latest/app/hooks/use-mobile.ts +19 -0
- package/templates/latest/{src → app}/lib/utils.ts +1 -1
- package/templates/latest/app/main.tsx +12 -0
- package/templates/latest/app/routes/home.tsx +40 -0
- package/templates/latest/app/routes.tsx +15 -0
- package/templates/latest/{src → app}/single-spa.tsx +38 -28
- package/templates/latest/components.json +22 -0
- package/templates/latest/dist/assets/__federation_expose_App-DRwKKpS2.js +91 -0
- package/templates/latest/dist/assets/__federation_fn_import-CzfA7kmP.js +438 -0
- package/templates/latest/dist/assets/__federation_shared_react-Bp6HVBS4.js +16 -0
- package/templates/latest/dist/assets/__federation_shared_react-dom-BCcRGiYp.js +17 -0
- package/templates/latest/dist/assets/client-DgSav55y.js +12658 -0
- package/templates/latest/dist/assets/home-DOL6GrYV.js +54951 -0
- package/templates/latest/dist/assets/index-BzNimew1.js +69 -0
- package/templates/latest/dist/assets/index-DMFtQdNS.js +412 -0
- package/templates/latest/dist/assets/index-DdYpcDMk.js +60 -0
- package/templates/latest/dist/assets/remoteEntry.js +88 -0
- package/templates/latest/dist/assets/style-36A39bNN.css +3683 -0
- package/templates/latest/dist/avatars/shadcn.svg +6 -0
- package/templates/latest/dist/favicon.ico +0 -0
- package/templates/latest/dist/index.html +19 -0
- package/templates/latest/index.html +1 -1
- package/templates/latest/package-lock.json +1227 -3353
- package/templates/latest/package.json +47 -43
- package/templates/latest/pnpm-lock.yaml +4767 -0
- package/templates/latest/preview/index.html +32 -2
- package/templates/latest/public/avatars/shadcn.svg +6 -0
- package/templates/latest/public/favicon.ico +0 -0
- package/templates/latest/tsconfig.json +18 -11
- package/templates/latest/vite.config.ts +41 -41
- package/templates/latest/.env.example +0 -14
- package/templates/latest/.eslintrc.cjs +0 -42
- package/templates/latest/AGENT.md +0 -284
- package/templates/latest/clean.sh +0 -39
- package/templates/latest/postcss.config.cjs +0 -6
- package/templates/latest/public/bluecopa-logo.svg +0 -30
- package/templates/latest/public/favicon-32x32.png +0 -0
- package/templates/latest/public/favicon-96x96.png +0 -0
- package/templates/latest/setup.sh +0 -55
- package/templates/latest/src/App.tsx +0 -15
- package/templates/latest/src/components/layout/dashboard-header.tsx +0 -139
- package/templates/latest/src/components/layout/dashboard-layout.tsx +0 -29
- package/templates/latest/src/components/layout/sidebar.tsx +0 -54
- package/templates/latest/src/components/page/dashboard.tsx +0 -1506
- package/templates/latest/src/components/page/navbar.tsx +0 -104
- package/templates/latest/src/components/tables/data-grid.tsx +0 -439
- package/templates/latest/src/components/ui/alert.tsx +0 -59
- package/templates/latest/src/components/ui/avatar.tsx +0 -50
- package/templates/latest/src/components/ui/badge.tsx +0 -36
- package/templates/latest/src/components/ui/bluecopa-logo.tsx +0 -54
- package/templates/latest/src/components/ui/button.tsx +0 -58
- package/templates/latest/src/components/ui/card.tsx +0 -79
- package/templates/latest/src/components/ui/dropdown-menu.tsx +0 -200
- package/templates/latest/src/components/ui/input.tsx +0 -24
- package/templates/latest/src/components/ui/label.tsx +0 -23
- package/templates/latest/src/components/ui/select.tsx +0 -29
- package/templates/latest/src/hooks/use-api.ts +0 -55
- package/templates/latest/src/index.css +0 -59
- package/templates/latest/src/main.tsx +0 -13
- package/templates/latest/src/pages/Dashboard.tsx +0 -13
- package/templates/latest/src/pages/Home.tsx +0 -622
- package/templates/latest/src/providers/query-provider.tsx +0 -48
- package/templates/latest/src/types/api.ts +0 -78
- package/templates/latest/src/vite-env.d.ts +0 -11
- package/templates/latest/tailwind.config.js +0 -88
- package/templates/latest/tsconfig.app.json +0 -26
- 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
|
+
}
|