Merge pull request 'feat: Refactor UI components and add new Drawer, Table, and Tabs components' (#6) from feat/dashboard-template into dev
Reviewed-on: #6
This commit is contained in:
commit
13d6038cb9
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Attributes\Appends;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
||||||
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
use Illuminate\Database\Eloquent\Attributes\Hidden;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
@ -13,6 +14,7 @@
|
|||||||
|
|
||||||
#[Hidden(['password'])]
|
#[Hidden(['password'])]
|
||||||
#[Guarded(['id', 'last_login_at'])]
|
#[Guarded(['id', 'last_login_at'])]
|
||||||
|
#[Appends(['full_name'])]
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable
|
||||||
{
|
{
|
||||||
use HasFactory, Notifiable, SoftDeletes;
|
use HasFactory, Notifiable, SoftDeletes;
|
||||||
|
|||||||
11
package.json
11
package.json
@ -29,6 +29,10 @@
|
|||||||
"typescript-eslint": "^8.23.0"
|
"typescript-eslint": "^8.23.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@dnd-kit/core": "^6.3.1",
|
||||||
|
"@dnd-kit/modifiers": "^9.0.0",
|
||||||
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
|
"@dnd-kit/utilities": "^3.2.2",
|
||||||
"@fontsource-variable/inter": "^5.3.0",
|
"@fontsource-variable/inter": "^5.3.0",
|
||||||
"@inertiajs/react": "^3.0.0",
|
"@inertiajs/react": "^3.0.0",
|
||||||
"@inertiajs/vite": "^3.0.0",
|
"@inertiajs/vite": "^3.0.0",
|
||||||
@ -46,7 +50,9 @@
|
|||||||
"@radix-ui/react-toggle": "^1.1.2",
|
"@radix-ui/react-toggle": "^1.1.2",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.2",
|
"@radix-ui/react-toggle-group": "^1.1.2",
|
||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
|
"@tabler/icons-react": "^3.46.0",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@types/react": "^19.2.0",
|
"@types/react": "^19.2.0",
|
||||||
"@types/react-dom": "^19.2.0",
|
"@types/react-dom": "^19.2.0",
|
||||||
"@vitejs/plugin-react": "^5.2.0",
|
"@vitejs/plugin-react": "^5.2.0",
|
||||||
@ -61,13 +67,16 @@
|
|||||||
"radix-ui": "^1.6.7",
|
"radix-ui": "^1.6.7",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"recharts": "3.8.0",
|
||||||
"shadcn": "^4.16.1",
|
"shadcn": "^4.16.1",
|
||||||
"sonner": "^2.0.0",
|
"sonner": "^2.0.0",
|
||||||
"tailwind-merge": "^3.0.1",
|
"tailwind-merge": "^3.0.1",
|
||||||
"tailwindcss": "^4.0.0",
|
"tailwindcss": "^4.0.0",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
"typescript": "^5.7.2",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^8.0.0"
|
"vaul": "^1.1.2",
|
||||||
|
"vite": "^8.0.0",
|
||||||
|
"zod": "^4.4.3"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@rollup/rollup-linux-x64-gnu": "4.9.5",
|
"@rollup/rollup-linux-x64-gnu": "4.9.5",
|
||||||
|
|||||||
430
pnpm-lock.yaml
generated
430
pnpm-lock.yaml
generated
@ -8,6 +8,18 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@dnd-kit/core':
|
||||||
|
specifier: ^6.3.1
|
||||||
|
version: 6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
|
'@dnd-kit/modifiers':
|
||||||
|
specifier: ^9.0.0
|
||||||
|
version: 9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)
|
||||||
|
'@dnd-kit/sortable':
|
||||||
|
specifier: ^10.0.0
|
||||||
|
version: 10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)
|
||||||
|
'@dnd-kit/utilities':
|
||||||
|
specifier: ^3.2.2
|
||||||
|
version: 3.2.2(react@19.2.8)
|
||||||
'@fontsource-variable/inter':
|
'@fontsource-variable/inter':
|
||||||
specifier: ^5.3.0
|
specifier: ^5.3.0
|
||||||
version: 5.3.0
|
version: 5.3.0
|
||||||
@ -59,9 +71,15 @@ importers:
|
|||||||
'@radix-ui/react-tooltip':
|
'@radix-ui/react-tooltip':
|
||||||
specifier: ^1.1.8
|
specifier: ^1.1.8
|
||||||
version: 1.2.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
version: 1.2.16(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
|
'@tabler/icons-react':
|
||||||
|
specifier: ^3.46.0
|
||||||
|
version: 3.46.0(react@19.2.8)
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.1.11
|
specifier: ^4.1.11
|
||||||
version: 4.3.3(vite@8.2.0(@types/node@22.20.1)(jiti@2.7.0))
|
version: 4.3.3(vite@8.2.0(@types/node@22.20.1)(jiti@2.7.0))
|
||||||
|
'@tanstack/react-table':
|
||||||
|
specifier: ^8.21.3
|
||||||
|
version: 8.21.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: ^19.2.0
|
specifier: ^19.2.0
|
||||||
version: 19.2.18
|
version: 19.2.18
|
||||||
@ -104,6 +122,9 @@ importers:
|
|||||||
react-dom:
|
react-dom:
|
||||||
specifier: ^19.2.0
|
specifier: ^19.2.0
|
||||||
version: 19.2.8(react@19.2.8)
|
version: 19.2.8(react@19.2.8)
|
||||||
|
recharts:
|
||||||
|
specifier: 3.8.0
|
||||||
|
version: 3.8.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react-is@16.13.1)(react@19.2.8)(redux@5.0.1)
|
||||||
shadcn:
|
shadcn:
|
||||||
specifier: ^4.16.1
|
specifier: ^4.16.1
|
||||||
version: 4.16.1(typescript@5.9.3)
|
version: 4.16.1(typescript@5.9.3)
|
||||||
@ -122,9 +143,15 @@ importers:
|
|||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.7.2
|
specifier: ^5.7.2
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
|
vaul:
|
||||||
|
specifier: ^1.1.2
|
||||||
|
version: 1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
vite:
|
vite:
|
||||||
specifier: ^8.0.0
|
specifier: ^8.0.0
|
||||||
version: 8.2.0(@types/node@22.20.1)(jiti@2.7.0)
|
version: 8.2.0(@types/node@22.20.1)(jiti@2.7.0)
|
||||||
|
zod:
|
||||||
|
specifier: ^4.4.3
|
||||||
|
version: 4.4.3
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^9.19.0
|
specifier: ^9.19.0
|
||||||
@ -331,6 +358,34 @@ packages:
|
|||||||
resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
|
resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@dnd-kit/accessibility@3.1.1':
|
||||||
|
resolution: {integrity: sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
|
||||||
|
'@dnd-kit/core@6.3.1':
|
||||||
|
resolution: {integrity: sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
|
||||||
|
'@dnd-kit/modifiers@9.0.0':
|
||||||
|
resolution: {integrity: sha512-ybiLc66qRGuZoC20wdSSG6pDXFikui/dCNGthxv4Ndy8ylErY0N3KVxY2bgo7AWwIbxDmXDg3ylAFmnrjcbVvw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@dnd-kit/core': ^6.3.0
|
||||||
|
react: '>=16.8.0'
|
||||||
|
|
||||||
|
'@dnd-kit/sortable@10.0.0':
|
||||||
|
resolution: {integrity: sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@dnd-kit/core': ^6.3.0
|
||||||
|
react: '>=16.8.0'
|
||||||
|
|
||||||
|
'@dnd-kit/utilities@3.2.2':
|
||||||
|
resolution: {integrity: sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
|
||||||
'@dotenvx/dotenvx@1.75.1':
|
'@dotenvx/dotenvx@1.75.1':
|
||||||
resolution: {integrity: sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==}
|
resolution: {integrity: sha512-/BITOC9dmS/edY2zQwZNicQ059O6RKabtQfyEafV0nGtfYRNHYy1DIPiYVcov40+tob9hfmBnbR963dS+EQ1DQ==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -1213,6 +1268,17 @@ packages:
|
|||||||
'@radix-ui/rect@1.1.3':
|
'@radix-ui/rect@1.1.3':
|
||||||
resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==}
|
resolution: {integrity: sha512-JtyZR+mqgBibTo8xea3B6ZRmzZiM/YeVBtUkas6zMuXjAlfIFIW2FgqeM9eLyvEaYX66vr6DJMK+4U6LV0KhNw==}
|
||||||
|
|
||||||
|
'@reduxjs/toolkit@2.12.0':
|
||||||
|
resolution: {integrity: sha512-KiT+RzZbp6mQET+Mg+h2c97+9j1sNflUxQkIHI7Yuzf6Peu+OYpmkn6nbHWmLLWj+1ZODUJFwGZ7gx3L9R9EOw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.9.0 || ^17.0.0 || ^18 || ^19
|
||||||
|
react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
react-redux:
|
||||||
|
optional: true
|
||||||
|
|
||||||
'@rolldown/binding-android-arm64@1.2.1':
|
'@rolldown/binding-android-arm64@1.2.1':
|
||||||
resolution: {integrity: sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==}
|
resolution: {integrity: sha512-02hOeOSryYxVrOIphmLAsqnCJWxwlzFk+pEt/N/i6OgT3lShHO7xGCU5cpgchRDHboAEbSjzgGh+O/u1GswQmA==}
|
||||||
engines: {node: ^20.19.0 || >=22.12.0}
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
@ -1337,12 +1403,26 @@ packages:
|
|||||||
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
'@standard-schema/spec@1.1.0':
|
||||||
|
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||||
|
|
||||||
|
'@standard-schema/utils@0.3.0':
|
||||||
|
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
|
||||||
|
|
||||||
'@stylistic/eslint-plugin@5.10.0':
|
'@stylistic/eslint-plugin@5.10.0':
|
||||||
resolution: {integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==}
|
resolution: {integrity: sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^9.0.0 || ^10.0.0
|
eslint: ^9.0.0 || ^10.0.0
|
||||||
|
|
||||||
|
'@tabler/icons-react@3.46.0':
|
||||||
|
resolution: {integrity: sha512-CCm7xJWhDT2PH4ZIFkP6AgYKtVhq0gpYkjUN+GVh1AzmIQaa77OW0bQPBPQiTE0PsXMR9oSxFqA3qBglzPyrVQ==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>= 16'
|
||||||
|
|
||||||
|
'@tabler/icons@3.46.0':
|
||||||
|
resolution: {integrity: sha512-f2RYFl3fzPwj5WO82x6en0dmkjefxEfOm16D1ByM6cj/McNiwOkL4VaPUoP9VVIrXAD9WnTSVFr70px703b//A==}
|
||||||
|
|
||||||
'@tailwindcss/node@4.3.3':
|
'@tailwindcss/node@4.3.3':
|
||||||
resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==}
|
resolution: {integrity: sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg==}
|
||||||
|
|
||||||
@ -1437,6 +1517,17 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: ^5.2.0 || ^6 || ^7 || ^8
|
vite: ^5.2.0 || ^6 || ^7 || ^8
|
||||||
|
|
||||||
|
'@tanstack/react-table@8.21.3':
|
||||||
|
resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8'
|
||||||
|
react-dom: '>=16.8'
|
||||||
|
|
||||||
|
'@tanstack/table-core@8.21.3':
|
||||||
|
resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
'@ts-morph/common@0.27.0':
|
'@ts-morph/common@0.27.0':
|
||||||
resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==}
|
resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==}
|
||||||
|
|
||||||
@ -1455,6 +1546,33 @@ packages:
|
|||||||
'@types/babel__traverse@7.28.0':
|
'@types/babel__traverse@7.28.0':
|
||||||
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
|
||||||
|
|
||||||
|
'@types/d3-array@3.2.2':
|
||||||
|
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
|
||||||
|
|
||||||
|
'@types/d3-color@3.1.3':
|
||||||
|
resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==}
|
||||||
|
|
||||||
|
'@types/d3-ease@3.0.2':
|
||||||
|
resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==}
|
||||||
|
|
||||||
|
'@types/d3-interpolate@3.0.4':
|
||||||
|
resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==}
|
||||||
|
|
||||||
|
'@types/d3-path@3.1.1':
|
||||||
|
resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==}
|
||||||
|
|
||||||
|
'@types/d3-scale@4.0.9':
|
||||||
|
resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==}
|
||||||
|
|
||||||
|
'@types/d3-shape@3.1.8':
|
||||||
|
resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==}
|
||||||
|
|
||||||
|
'@types/d3-time@3.0.4':
|
||||||
|
resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==}
|
||||||
|
|
||||||
|
'@types/d3-timer@3.0.2':
|
||||||
|
resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==}
|
||||||
|
|
||||||
'@types/estree@1.0.9':
|
'@types/estree@1.0.9':
|
||||||
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
|
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
|
||||||
|
|
||||||
@ -1475,6 +1593,9 @@ packages:
|
|||||||
'@types/react@19.2.18':
|
'@types/react@19.2.18':
|
||||||
resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==}
|
resolution: {integrity: sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==}
|
||||||
|
|
||||||
|
'@types/use-sync-external-store@0.0.6':
|
||||||
|
resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
|
||||||
|
|
||||||
'@types/validate-npm-package-name@4.0.2':
|
'@types/validate-npm-package-name@4.0.2':
|
||||||
resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==}
|
resolution: {integrity: sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==}
|
||||||
|
|
||||||
@ -1937,6 +2058,50 @@ packages:
|
|||||||
csstype@3.2.3:
|
csstype@3.2.3:
|
||||||
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||||
|
|
||||||
|
d3-array@3.2.4:
|
||||||
|
resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-color@3.1.0:
|
||||||
|
resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-ease@3.0.1:
|
||||||
|
resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-format@3.1.2:
|
||||||
|
resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-interpolate@3.0.1:
|
||||||
|
resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-path@3.1.0:
|
||||||
|
resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-scale@4.0.2:
|
||||||
|
resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-shape@3.2.0:
|
||||||
|
resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-time-format@4.1.0:
|
||||||
|
resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-time@3.1.0:
|
||||||
|
resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
d3-timer@3.0.1:
|
||||||
|
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
data-view-buffer@1.0.2:
|
data-view-buffer@1.0.2:
|
||||||
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
|
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -1970,6 +2135,9 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
decimal.js-light@2.5.1:
|
||||||
|
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||||
|
|
||||||
dedent@1.7.2:
|
dedent@1.7.2:
|
||||||
resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==}
|
resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -2250,6 +2418,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
eventemitter3@5.0.4:
|
||||||
|
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
|
||||||
|
|
||||||
eventsource-parser@3.1.0:
|
eventsource-parser@3.1.0:
|
||||||
resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==}
|
resolution: {integrity: sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
@ -2504,6 +2675,12 @@ packages:
|
|||||||
resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
|
resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
|
immer@10.2.0:
|
||||||
|
resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==}
|
||||||
|
|
||||||
|
immer@11.1.15:
|
||||||
|
resolution: {integrity: sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==}
|
||||||
|
|
||||||
import-fresh@3.3.1:
|
import-fresh@3.3.1:
|
||||||
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@ -2525,6 +2702,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
internmap@2.0.3:
|
||||||
|
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
ip-address@10.4.0:
|
ip-address@10.4.0:
|
||||||
resolution: {integrity: sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==}
|
resolution: {integrity: sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==}
|
||||||
engines: {node: '>= 12'}
|
engines: {node: '>= 12'}
|
||||||
@ -3385,6 +3566,18 @@ packages:
|
|||||||
react-is@16.13.1:
|
react-is@16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
|
|
||||||
|
react-redux@9.3.0:
|
||||||
|
resolution: {integrity: sha512-KQopgqFo/p/fgmAs5qz6p5RWaNAzq40WAu7fJIXnQpYxFPbJYtsJPWvGeF2rOBaY/kEuV77AVsX8TsQzKm+A/g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^18.2.25 || ^19
|
||||||
|
react: ^18.0 || ^19
|
||||||
|
redux: ^5.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
redux:
|
||||||
|
optional: true
|
||||||
|
|
||||||
react-refresh@0.18.0:
|
react-refresh@0.18.0:
|
||||||
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
|
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -3427,6 +3620,22 @@ packages:
|
|||||||
resolution: {integrity: sha512-T98lym7kH+pnZmRaD8yDRdaNqyUbwnbEBx0MuchrzMFOEMray4AO3ZJoTUZ5r78Ao78X/OhzW0DL8GB85w/I2w==}
|
resolution: {integrity: sha512-T98lym7kH+pnZmRaD8yDRdaNqyUbwnbEBx0MuchrzMFOEMray4AO3ZJoTUZ5r78Ao78X/OhzW0DL8GB85w/I2w==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
|
recharts@3.8.0:
|
||||||
|
resolution: {integrity: sha512-Z/m38DX3L73ExO4Tpc9/iZWHmHnlzWG4njQbxsF5aSjwqmHNDDIm0rdEBArkwsBvR8U6EirlEHiQNYWCVh9sGQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
|
redux-thunk@3.1.0:
|
||||||
|
resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==}
|
||||||
|
peerDependencies:
|
||||||
|
redux: ^5.0.0
|
||||||
|
|
||||||
|
redux@5.0.1:
|
||||||
|
resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==}
|
||||||
|
|
||||||
reflect.getprototypeof@1.0.10:
|
reflect.getprototypeof@1.0.10:
|
||||||
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -3443,6 +3652,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
reselect@5.1.1:
|
||||||
|
resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==}
|
||||||
|
|
||||||
resolve-from@4.0.0:
|
resolve-from@4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -3820,6 +4032,11 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
use-sync-external-store@1.6.0:
|
||||||
|
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
||||||
|
|
||||||
util-deprecate@1.0.2:
|
util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
@ -3831,6 +4048,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
vaul@1.1.2:
|
||||||
|
resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
|
||||||
|
victory-vendor@37.3.6:
|
||||||
|
resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==}
|
||||||
|
|
||||||
vite-plugin-full-reload@1.2.0:
|
vite-plugin-full-reload@1.2.0:
|
||||||
resolution: {integrity: sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==}
|
resolution: {integrity: sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==}
|
||||||
|
|
||||||
@ -4160,6 +4386,38 @@ snapshots:
|
|||||||
'@babel/helper-string-parser': 7.29.7
|
'@babel/helper-string-parser': 7.29.7
|
||||||
'@babel/helper-validator-identifier': 7.29.7
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
|
|
||||||
|
'@dnd-kit/accessibility@3.1.1(react@19.2.8)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.8
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@dnd-kit/core@6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||||
|
dependencies:
|
||||||
|
'@dnd-kit/accessibility': 3.1.1(react@19.2.8)
|
||||||
|
'@dnd-kit/utilities': 3.2.2(react@19.2.8)
|
||||||
|
react: 19.2.8
|
||||||
|
react-dom: 19.2.8(react@19.2.8)
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@dnd-kit/modifiers@9.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)':
|
||||||
|
dependencies:
|
||||||
|
'@dnd-kit/core': 6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
|
'@dnd-kit/utilities': 3.2.2(react@19.2.8)
|
||||||
|
react: 19.2.8
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@dnd-kit/sortable@10.0.0(@dnd-kit/core@6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8))(react@19.2.8)':
|
||||||
|
dependencies:
|
||||||
|
'@dnd-kit/core': 6.3.1(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
|
'@dnd-kit/utilities': 3.2.2(react@19.2.8)
|
||||||
|
react: 19.2.8
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
'@dnd-kit/utilities@3.2.2(react@19.2.8)':
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.8
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@dotenvx/dotenvx@1.75.1':
|
'@dotenvx/dotenvx@1.75.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@dotenvx/primitives': 0.8.0
|
'@dotenvx/primitives': 0.8.0
|
||||||
@ -5146,6 +5404,18 @@ snapshots:
|
|||||||
|
|
||||||
'@radix-ui/rect@1.1.3': {}
|
'@radix-ui/rect@1.1.3': {}
|
||||||
|
|
||||||
|
'@reduxjs/toolkit@2.12.0(react-redux@9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1))(react@19.2.8)':
|
||||||
|
dependencies:
|
||||||
|
'@standard-schema/spec': 1.1.0
|
||||||
|
'@standard-schema/utils': 0.3.0
|
||||||
|
immer: 11.1.15
|
||||||
|
redux: 5.0.1
|
||||||
|
redux-thunk: 3.1.0(redux@5.0.1)
|
||||||
|
reselect: 5.1.1
|
||||||
|
optionalDependencies:
|
||||||
|
react: 19.2.8
|
||||||
|
react-redux: 9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1)
|
||||||
|
|
||||||
'@rolldown/binding-android-arm64@1.2.1':
|
'@rolldown/binding-android-arm64@1.2.1':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -5213,6 +5483,10 @@ snapshots:
|
|||||||
|
|
||||||
'@sindresorhus/merge-streams@4.0.0': {}
|
'@sindresorhus/merge-streams@4.0.0': {}
|
||||||
|
|
||||||
|
'@standard-schema/spec@1.1.0': {}
|
||||||
|
|
||||||
|
'@standard-schema/utils@0.3.0': {}
|
||||||
|
|
||||||
'@stylistic/eslint-plugin@5.10.0(eslint@9.39.5(jiti@2.7.0))':
|
'@stylistic/eslint-plugin@5.10.0(eslint@9.39.5(jiti@2.7.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0))
|
'@eslint-community/eslint-utils': 4.10.1(eslint@9.39.5(jiti@2.7.0))
|
||||||
@ -5223,6 +5497,13 @@ snapshots:
|
|||||||
estraverse: 5.3.0
|
estraverse: 5.3.0
|
||||||
picomatch: 4.0.5
|
picomatch: 4.0.5
|
||||||
|
|
||||||
|
'@tabler/icons-react@3.46.0(react@19.2.8)':
|
||||||
|
dependencies:
|
||||||
|
'@tabler/icons': 3.46.0
|
||||||
|
react: 19.2.8
|
||||||
|
|
||||||
|
'@tabler/icons@3.46.0': {}
|
||||||
|
|
||||||
'@tailwindcss/node@4.3.3':
|
'@tailwindcss/node@4.3.3':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jridgewell/remapping': 2.3.5
|
'@jridgewell/remapping': 2.3.5
|
||||||
@ -5291,6 +5572,14 @@ snapshots:
|
|||||||
tailwindcss: 4.3.3
|
tailwindcss: 4.3.3
|
||||||
vite: 8.2.0(@types/node@22.20.1)(jiti@2.7.0)
|
vite: 8.2.0(@types/node@22.20.1)(jiti@2.7.0)
|
||||||
|
|
||||||
|
'@tanstack/react-table@8.21.3(react-dom@19.2.8(react@19.2.8))(react@19.2.8)':
|
||||||
|
dependencies:
|
||||||
|
'@tanstack/table-core': 8.21.3
|
||||||
|
react: 19.2.8
|
||||||
|
react-dom: 19.2.8(react@19.2.8)
|
||||||
|
|
||||||
|
'@tanstack/table-core@8.21.3': {}
|
||||||
|
|
||||||
'@ts-morph/common@0.27.0':
|
'@ts-morph/common@0.27.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-glob: 3.3.3
|
fast-glob: 3.3.3
|
||||||
@ -5323,6 +5612,30 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.29.8
|
'@babel/types': 7.29.8
|
||||||
|
|
||||||
|
'@types/d3-array@3.2.2': {}
|
||||||
|
|
||||||
|
'@types/d3-color@3.1.3': {}
|
||||||
|
|
||||||
|
'@types/d3-ease@3.0.2': {}
|
||||||
|
|
||||||
|
'@types/d3-interpolate@3.0.4':
|
||||||
|
dependencies:
|
||||||
|
'@types/d3-color': 3.1.3
|
||||||
|
|
||||||
|
'@types/d3-path@3.1.1': {}
|
||||||
|
|
||||||
|
'@types/d3-scale@4.0.9':
|
||||||
|
dependencies:
|
||||||
|
'@types/d3-time': 3.0.4
|
||||||
|
|
||||||
|
'@types/d3-shape@3.1.8':
|
||||||
|
dependencies:
|
||||||
|
'@types/d3-path': 3.1.1
|
||||||
|
|
||||||
|
'@types/d3-time@3.0.4': {}
|
||||||
|
|
||||||
|
'@types/d3-timer@3.0.2': {}
|
||||||
|
|
||||||
'@types/estree@1.0.9': {}
|
'@types/estree@1.0.9': {}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
@ -5341,6 +5654,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
csstype: 3.2.3
|
csstype: 3.2.3
|
||||||
|
|
||||||
|
'@types/use-sync-external-store@0.0.6': {}
|
||||||
|
|
||||||
'@types/validate-npm-package-name@4.0.2': {}
|
'@types/validate-npm-package-name@4.0.2': {}
|
||||||
|
|
||||||
'@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
|
'@typescript-eslint/eslint-plugin@8.65.0(@typescript-eslint/parser@8.65.0(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.5(jiti@2.7.0))(typescript@5.9.3)':
|
||||||
@ -5813,6 +6128,44 @@ snapshots:
|
|||||||
|
|
||||||
csstype@3.2.3: {}
|
csstype@3.2.3: {}
|
||||||
|
|
||||||
|
d3-array@3.2.4:
|
||||||
|
dependencies:
|
||||||
|
internmap: 2.0.3
|
||||||
|
|
||||||
|
d3-color@3.1.0: {}
|
||||||
|
|
||||||
|
d3-ease@3.0.1: {}
|
||||||
|
|
||||||
|
d3-format@3.1.2: {}
|
||||||
|
|
||||||
|
d3-interpolate@3.0.1:
|
||||||
|
dependencies:
|
||||||
|
d3-color: 3.1.0
|
||||||
|
|
||||||
|
d3-path@3.1.0: {}
|
||||||
|
|
||||||
|
d3-scale@4.0.2:
|
||||||
|
dependencies:
|
||||||
|
d3-array: 3.2.4
|
||||||
|
d3-format: 3.1.2
|
||||||
|
d3-interpolate: 3.0.1
|
||||||
|
d3-time: 3.1.0
|
||||||
|
d3-time-format: 4.1.0
|
||||||
|
|
||||||
|
d3-shape@3.2.0:
|
||||||
|
dependencies:
|
||||||
|
d3-path: 3.1.0
|
||||||
|
|
||||||
|
d3-time-format@4.1.0:
|
||||||
|
dependencies:
|
||||||
|
d3-time: 3.1.0
|
||||||
|
|
||||||
|
d3-time@3.1.0:
|
||||||
|
dependencies:
|
||||||
|
d3-array: 3.2.4
|
||||||
|
|
||||||
|
d3-timer@3.0.1: {}
|
||||||
|
|
||||||
data-view-buffer@1.0.2:
|
data-view-buffer@1.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bound: 1.0.4
|
call-bound: 1.0.4
|
||||||
@ -5843,6 +6196,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
|
decimal.js-light@2.5.1: {}
|
||||||
|
|
||||||
dedent@1.7.2: {}
|
dedent@1.7.2: {}
|
||||||
|
|
||||||
deep-is@0.1.4: {}
|
deep-is@0.1.4: {}
|
||||||
@ -6222,6 +6577,8 @@ snapshots:
|
|||||||
|
|
||||||
etag@1.8.1: {}
|
etag@1.8.1: {}
|
||||||
|
|
||||||
|
eventemitter3@5.0.4: {}
|
||||||
|
|
||||||
eventsource-parser@3.1.0: {}
|
eventsource-parser@3.1.0: {}
|
||||||
|
|
||||||
eventsource@3.0.7:
|
eventsource@3.0.7:
|
||||||
@ -6512,6 +6869,10 @@ snapshots:
|
|||||||
|
|
||||||
ignore@7.0.6: {}
|
ignore@7.0.6: {}
|
||||||
|
|
||||||
|
immer@10.2.0: {}
|
||||||
|
|
||||||
|
immer@11.1.15: {}
|
||||||
|
|
||||||
import-fresh@3.3.1:
|
import-fresh@3.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
parent-module: 1.0.1
|
parent-module: 1.0.1
|
||||||
@ -6532,6 +6893,8 @@ snapshots:
|
|||||||
hasown: 2.0.4
|
hasown: 2.0.4
|
||||||
side-channel: 1.1.1
|
side-channel: 1.1.1
|
||||||
|
|
||||||
|
internmap@2.0.3: {}
|
||||||
|
|
||||||
ip-address@10.4.0: {}
|
ip-address@10.4.0: {}
|
||||||
|
|
||||||
ipaddr.js@1.9.1: {}
|
ipaddr.js@1.9.1: {}
|
||||||
@ -7273,6 +7636,15 @@ snapshots:
|
|||||||
|
|
||||||
react-is@16.13.1: {}
|
react-is@16.13.1: {}
|
||||||
|
|
||||||
|
react-redux@9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1):
|
||||||
|
dependencies:
|
||||||
|
'@types/use-sync-external-store': 0.0.6
|
||||||
|
react: 19.2.8
|
||||||
|
use-sync-external-store: 1.6.0(react@19.2.8)
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/react': 19.2.18
|
||||||
|
redux: 5.0.1
|
||||||
|
|
||||||
react-refresh@0.18.0: {}
|
react-refresh@0.18.0: {}
|
||||||
|
|
||||||
react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.2.8):
|
react-remove-scroll-bar@2.3.8(@types/react@19.2.18)(react@19.2.8):
|
||||||
@ -7312,6 +7684,32 @@ snapshots:
|
|||||||
tiny-invariant: 1.3.3
|
tiny-invariant: 1.3.3
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
recharts@3.8.0(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react-is@16.13.1)(react@19.2.8)(redux@5.0.1):
|
||||||
|
dependencies:
|
||||||
|
'@reduxjs/toolkit': 2.12.0(react-redux@9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1))(react@19.2.8)
|
||||||
|
clsx: 2.1.1
|
||||||
|
decimal.js-light: 2.5.1
|
||||||
|
es-toolkit: 1.50.0
|
||||||
|
eventemitter3: 5.0.4
|
||||||
|
immer: 10.2.0
|
||||||
|
react: 19.2.8
|
||||||
|
react-dom: 19.2.8(react@19.2.8)
|
||||||
|
react-is: 16.13.1
|
||||||
|
react-redux: 9.3.0(@types/react@19.2.18)(react@19.2.8)(redux@5.0.1)
|
||||||
|
reselect: 5.1.1
|
||||||
|
tiny-invariant: 1.3.3
|
||||||
|
use-sync-external-store: 1.6.0(react@19.2.8)
|
||||||
|
victory-vendor: 37.3.6
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- redux
|
||||||
|
|
||||||
|
redux-thunk@3.1.0(redux@5.0.1):
|
||||||
|
dependencies:
|
||||||
|
redux: 5.0.1
|
||||||
|
|
||||||
|
redux@5.0.1: {}
|
||||||
|
|
||||||
reflect.getprototypeof@1.0.10:
|
reflect.getprototypeof@1.0.10:
|
||||||
dependencies:
|
dependencies:
|
||||||
call-bind: 1.0.9
|
call-bind: 1.0.9
|
||||||
@ -7336,6 +7734,8 @@ snapshots:
|
|||||||
|
|
||||||
require-from-string@2.0.2: {}
|
require-from-string@2.0.2: {}
|
||||||
|
|
||||||
|
reselect@5.1.1: {}
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|
||||||
resolve-pkg-maps@1.0.0: {}
|
resolve-pkg-maps@1.0.0: {}
|
||||||
@ -7838,12 +8238,42 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.2.18
|
'@types/react': 19.2.18
|
||||||
|
|
||||||
|
use-sync-external-store@1.6.0(react@19.2.8):
|
||||||
|
dependencies:
|
||||||
|
react: 19.2.8
|
||||||
|
|
||||||
util-deprecate@1.0.2: {}
|
util-deprecate@1.0.2: {}
|
||||||
|
|
||||||
validate-npm-package-name@7.0.2: {}
|
validate-npm-package-name@7.0.2: {}
|
||||||
|
|
||||||
vary@1.1.2: {}
|
vary@1.1.2: {}
|
||||||
|
|
||||||
|
vaul@1.1.2(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
|
||||||
|
dependencies:
|
||||||
|
'@radix-ui/react-dialog': 1.1.23(@types/react-dom@19.2.4(@types/react@19.2.18))(@types/react@19.2.18)(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
|
||||||
|
react: 19.2.8
|
||||||
|
react-dom: 19.2.8(react@19.2.8)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- '@types/react-dom'
|
||||||
|
|
||||||
|
victory-vendor@37.3.6:
|
||||||
|
dependencies:
|
||||||
|
'@types/d3-array': 3.2.2
|
||||||
|
'@types/d3-ease': 3.0.2
|
||||||
|
'@types/d3-interpolate': 3.0.4
|
||||||
|
'@types/d3-scale': 4.0.9
|
||||||
|
'@types/d3-shape': 3.1.8
|
||||||
|
'@types/d3-time': 3.0.4
|
||||||
|
'@types/d3-timer': 3.0.2
|
||||||
|
d3-array: 3.2.4
|
||||||
|
d3-ease: 3.0.1
|
||||||
|
d3-interpolate: 3.0.1
|
||||||
|
d3-scale: 4.0.2
|
||||||
|
d3-shape: 3.2.0
|
||||||
|
d3-time: 3.1.0
|
||||||
|
d3-timer: 3.0.1
|
||||||
|
|
||||||
vite-plugin-full-reload@1.2.0:
|
vite-plugin-full-reload@1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Breadcrumbs } from '@/components/breadcrumbs';
|
import { Breadcrumbs } from '@/components/breadcrumbs';
|
||||||
|
import { NavUser } from '@/components/nav-user';
|
||||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||||
import type { BreadcrumbItem as BreadcrumbItemType } from '@/types';
|
import type { BreadcrumbItem as BreadcrumbItemType } from '@/types';
|
||||||
|
|
||||||
@ -13,6 +14,9 @@ export function AppSidebarHeader({
|
|||||||
<SidebarTrigger className="-ml-1" />
|
<SidebarTrigger className="-ml-1" />
|
||||||
<Breadcrumbs breadcrumbs={breadcrumbs} />
|
<Breadcrumbs breadcrumbs={breadcrumbs} />
|
||||||
</div>
|
</div>
|
||||||
|
<div className="ml-auto">
|
||||||
|
<NavUser />
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,65 +1,141 @@
|
|||||||
import { Link } from '@inertiajs/react';
|
import {
|
||||||
import { BookOpen, FolderGit2, LayoutGrid } from 'lucide-react';
|
IconCamera,
|
||||||
import AppLogo from '@/components/app-logo';
|
IconChartBar,
|
||||||
import { NavFooter } from '@/components/nav-footer';
|
IconDashboard,
|
||||||
import { NavMain } from '@/components/nav-main';
|
IconFileAi,
|
||||||
import { NavUser } from '@/components/nav-user';
|
IconFileDescription,
|
||||||
|
IconFolder,
|
||||||
|
IconHelp,
|
||||||
|
IconInnerShadowTop,
|
||||||
|
IconListDetails,
|
||||||
|
IconMessageDots,
|
||||||
|
IconUsers
|
||||||
|
} from "@tabler/icons-react";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { NavMain } from "@/components/nav-main";
|
||||||
|
import { NavSecondary } from "@/components/nav-secondary";
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
SidebarFooter,
|
|
||||||
SidebarHeader,
|
SidebarHeader,
|
||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from '@/components/ui/sidebar';
|
} from "@/components/ui/sidebar";
|
||||||
import { dashboard } from '@/routes';
|
|
||||||
import type { NavItem } from '@/types';
|
|
||||||
|
|
||||||
const mainNavItems: NavItem[] = [
|
const data = {
|
||||||
|
navMain: [
|
||||||
{
|
{
|
||||||
title: 'Dashboard',
|
title: "Dashboard",
|
||||||
href: dashboard(),
|
url: "#",
|
||||||
icon: LayoutGrid,
|
icon: IconDashboard,
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const footerNavItems: NavItem[] = [
|
|
||||||
{
|
|
||||||
title: 'Repository',
|
|
||||||
href: 'https://github.com/laravel/react-starter-kit',
|
|
||||||
icon: FolderGit2,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Documentation',
|
title: "Lifecycle",
|
||||||
href: 'https://laravel.com/docs/starter-kits#react',
|
url: "#",
|
||||||
icon: BookOpen,
|
icon: IconListDetails,
|
||||||
},
|
},
|
||||||
];
|
{
|
||||||
|
title: "Analytics",
|
||||||
|
url: "#",
|
||||||
|
icon: IconChartBar,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Projects",
|
||||||
|
url: "#",
|
||||||
|
icon: IconFolder,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Team",
|
||||||
|
url: "#",
|
||||||
|
icon: IconUsers,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
navClouds: [
|
||||||
|
{
|
||||||
|
title: "Capture",
|
||||||
|
icon: IconCamera,
|
||||||
|
isActive: true,
|
||||||
|
url: "#",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Active Proposals",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Archived",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Proposal",
|
||||||
|
icon: IconFileDescription,
|
||||||
|
url: "#",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Active Proposals",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Archived",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Prompts",
|
||||||
|
icon: IconFileAi,
|
||||||
|
url: "#",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
title: "Active Proposals",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Archived",
|
||||||
|
url: "#",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
navSecondary: [
|
||||||
|
{
|
||||||
|
title: "Kritik dan Saran",
|
||||||
|
url: "#",
|
||||||
|
icon: IconMessageDots,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Bantuan",
|
||||||
|
url: "#",
|
||||||
|
icon: IconHelp,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
export function AppSidebar() {
|
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||||
return (
|
return (
|
||||||
<Sidebar collapsible="icon" variant="inset">
|
<Sidebar collapsible="icon" {...props}>
|
||||||
<SidebarHeader>
|
<SidebarHeader>
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton size="lg" asChild>
|
<SidebarMenuButton
|
||||||
<Link href={dashboard()} prefetch>
|
asChild
|
||||||
<AppLogo />
|
className="data-[slot=sidebar-menu-button]:p-1.5!"
|
||||||
</Link>
|
>
|
||||||
|
<a href="#">
|
||||||
|
<IconInnerShadowTop className="size-5!" />
|
||||||
|
<span className="text-base font-semibold">Acme Inc.</span>
|
||||||
|
</a>
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarHeader>
|
</SidebarHeader>
|
||||||
|
|
||||||
<SidebarContent>
|
<SidebarContent>
|
||||||
<NavMain items={mainNavItems} />
|
<NavMain items={data.navMain} />
|
||||||
|
<NavSecondary items={data.navSecondary} className="mt-auto" />
|
||||||
</SidebarContent>
|
</SidebarContent>
|
||||||
|
|
||||||
<SidebarFooter>
|
|
||||||
<NavFooter items={footerNavItems} className="mt-auto" />
|
|
||||||
<NavUser />
|
|
||||||
</SidebarFooter>
|
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
291
resources/js/components/chart-area-interactive.tsx
Normal file
291
resources/js/components/chart-area-interactive.tsx
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||||
|
|
||||||
|
import { useIsMobile } from "@/hooks/use-mobile"
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardAction,
|
||||||
|
CardContent,
|
||||||
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card"
|
||||||
|
import {
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
type ChartConfig,
|
||||||
|
} from "@/components/ui/chart"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select"
|
||||||
|
import {
|
||||||
|
ToggleGroup,
|
||||||
|
ToggleGroupItem,
|
||||||
|
} from "@/components/ui/toggle-group"
|
||||||
|
|
||||||
|
export const description = "An interactive area chart"
|
||||||
|
|
||||||
|
const chartData = [
|
||||||
|
{ date: "2024-04-01", desktop: 222, mobile: 150 },
|
||||||
|
{ date: "2024-04-02", desktop: 97, mobile: 180 },
|
||||||
|
{ date: "2024-04-03", desktop: 167, mobile: 120 },
|
||||||
|
{ date: "2024-04-04", desktop: 242, mobile: 260 },
|
||||||
|
{ date: "2024-04-05", desktop: 373, mobile: 290 },
|
||||||
|
{ date: "2024-04-06", desktop: 301, mobile: 340 },
|
||||||
|
{ date: "2024-04-07", desktop: 245, mobile: 180 },
|
||||||
|
{ date: "2024-04-08", desktop: 409, mobile: 320 },
|
||||||
|
{ date: "2024-04-09", desktop: 59, mobile: 110 },
|
||||||
|
{ date: "2024-04-10", desktop: 261, mobile: 190 },
|
||||||
|
{ date: "2024-04-11", desktop: 327, mobile: 350 },
|
||||||
|
{ date: "2024-04-12", desktop: 292, mobile: 210 },
|
||||||
|
{ date: "2024-04-13", desktop: 342, mobile: 380 },
|
||||||
|
{ date: "2024-04-14", desktop: 137, mobile: 220 },
|
||||||
|
{ date: "2024-04-15", desktop: 120, mobile: 170 },
|
||||||
|
{ date: "2024-04-16", desktop: 138, mobile: 190 },
|
||||||
|
{ date: "2024-04-17", desktop: 446, mobile: 360 },
|
||||||
|
{ date: "2024-04-18", desktop: 364, mobile: 410 },
|
||||||
|
{ date: "2024-04-19", desktop: 243, mobile: 180 },
|
||||||
|
{ date: "2024-04-20", desktop: 89, mobile: 150 },
|
||||||
|
{ date: "2024-04-21", desktop: 137, mobile: 200 },
|
||||||
|
{ date: "2024-04-22", desktop: 224, mobile: 170 },
|
||||||
|
{ date: "2024-04-23", desktop: 138, mobile: 230 },
|
||||||
|
{ date: "2024-04-24", desktop: 387, mobile: 290 },
|
||||||
|
{ date: "2024-04-25", desktop: 215, mobile: 250 },
|
||||||
|
{ date: "2024-04-26", desktop: 75, mobile: 130 },
|
||||||
|
{ date: "2024-04-27", desktop: 383, mobile: 420 },
|
||||||
|
{ date: "2024-04-28", desktop: 122, mobile: 180 },
|
||||||
|
{ date: "2024-04-29", desktop: 315, mobile: 240 },
|
||||||
|
{ date: "2024-04-30", desktop: 454, mobile: 380 },
|
||||||
|
{ date: "2024-05-01", desktop: 165, mobile: 220 },
|
||||||
|
{ date: "2024-05-02", desktop: 293, mobile: 310 },
|
||||||
|
{ date: "2024-05-03", desktop: 247, mobile: 190 },
|
||||||
|
{ date: "2024-05-04", desktop: 385, mobile: 420 },
|
||||||
|
{ date: "2024-05-05", desktop: 481, mobile: 390 },
|
||||||
|
{ date: "2024-05-06", desktop: 498, mobile: 520 },
|
||||||
|
{ date: "2024-05-07", desktop: 388, mobile: 300 },
|
||||||
|
{ date: "2024-05-08", desktop: 149, mobile: 210 },
|
||||||
|
{ date: "2024-05-09", desktop: 227, mobile: 180 },
|
||||||
|
{ date: "2024-05-10", desktop: 293, mobile: 330 },
|
||||||
|
{ date: "2024-05-11", desktop: 335, mobile: 270 },
|
||||||
|
{ date: "2024-05-12", desktop: 197, mobile: 240 },
|
||||||
|
{ date: "2024-05-13", desktop: 197, mobile: 160 },
|
||||||
|
{ date: "2024-05-14", desktop: 448, mobile: 490 },
|
||||||
|
{ date: "2024-05-15", desktop: 473, mobile: 380 },
|
||||||
|
{ date: "2024-05-16", desktop: 338, mobile: 400 },
|
||||||
|
{ date: "2024-05-17", desktop: 499, mobile: 420 },
|
||||||
|
{ date: "2024-05-18", desktop: 315, mobile: 350 },
|
||||||
|
{ date: "2024-05-19", desktop: 235, mobile: 180 },
|
||||||
|
{ date: "2024-05-20", desktop: 177, mobile: 230 },
|
||||||
|
{ date: "2024-05-21", desktop: 82, mobile: 140 },
|
||||||
|
{ date: "2024-05-22", desktop: 81, mobile: 120 },
|
||||||
|
{ date: "2024-05-23", desktop: 252, mobile: 290 },
|
||||||
|
{ date: "2024-05-24", desktop: 294, mobile: 220 },
|
||||||
|
{ date: "2024-05-25", desktop: 201, mobile: 250 },
|
||||||
|
{ date: "2024-05-26", desktop: 213, mobile: 170 },
|
||||||
|
{ date: "2024-05-27", desktop: 420, mobile: 460 },
|
||||||
|
{ date: "2024-05-28", desktop: 233, mobile: 190 },
|
||||||
|
{ date: "2024-05-29", desktop: 78, mobile: 130 },
|
||||||
|
{ date: "2024-05-30", desktop: 340, mobile: 280 },
|
||||||
|
{ date: "2024-05-31", desktop: 178, mobile: 230 },
|
||||||
|
{ date: "2024-06-01", desktop: 178, mobile: 200 },
|
||||||
|
{ date: "2024-06-02", desktop: 470, mobile: 410 },
|
||||||
|
{ date: "2024-06-03", desktop: 103, mobile: 160 },
|
||||||
|
{ date: "2024-06-04", desktop: 439, mobile: 380 },
|
||||||
|
{ date: "2024-06-05", desktop: 88, mobile: 140 },
|
||||||
|
{ date: "2024-06-06", desktop: 294, mobile: 250 },
|
||||||
|
{ date: "2024-06-07", desktop: 323, mobile: 370 },
|
||||||
|
{ date: "2024-06-08", desktop: 385, mobile: 320 },
|
||||||
|
{ date: "2024-06-09", desktop: 438, mobile: 480 },
|
||||||
|
{ date: "2024-06-10", desktop: 155, mobile: 200 },
|
||||||
|
{ date: "2024-06-11", desktop: 92, mobile: 150 },
|
||||||
|
{ date: "2024-06-12", desktop: 492, mobile: 420 },
|
||||||
|
{ date: "2024-06-13", desktop: 81, mobile: 130 },
|
||||||
|
{ date: "2024-06-14", desktop: 426, mobile: 380 },
|
||||||
|
{ date: "2024-06-15", desktop: 307, mobile: 350 },
|
||||||
|
{ date: "2024-06-16", desktop: 371, mobile: 310 },
|
||||||
|
{ date: "2024-06-17", desktop: 475, mobile: 520 },
|
||||||
|
{ date: "2024-06-18", desktop: 107, mobile: 170 },
|
||||||
|
{ date: "2024-06-19", desktop: 341, mobile: 290 },
|
||||||
|
{ date: "2024-06-20", desktop: 408, mobile: 450 },
|
||||||
|
{ date: "2024-06-21", desktop: 169, mobile: 210 },
|
||||||
|
{ date: "2024-06-22", desktop: 317, mobile: 270 },
|
||||||
|
{ date: "2024-06-23", desktop: 480, mobile: 530 },
|
||||||
|
{ date: "2024-06-24", desktop: 132, mobile: 180 },
|
||||||
|
{ date: "2024-06-25", desktop: 141, mobile: 190 },
|
||||||
|
{ date: "2024-06-26", desktop: 434, mobile: 380 },
|
||||||
|
{ date: "2024-06-27", desktop: 448, mobile: 490 },
|
||||||
|
{ date: "2024-06-28", desktop: 149, mobile: 200 },
|
||||||
|
{ date: "2024-06-29", desktop: 103, mobile: 160 },
|
||||||
|
{ date: "2024-06-30", desktop: 446, mobile: 400 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
visitors: {
|
||||||
|
label: "Visitors",
|
||||||
|
},
|
||||||
|
desktop: {
|
||||||
|
label: "Desktop",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
mobile: {
|
||||||
|
label: "Mobile",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig
|
||||||
|
|
||||||
|
export function ChartAreaInteractive() {
|
||||||
|
const isMobile = useIsMobile()
|
||||||
|
const [timeRange, setTimeRange] = React.useState("90d")
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isMobile) {
|
||||||
|
setTimeRange("7d")
|
||||||
|
}
|
||||||
|
}, [isMobile])
|
||||||
|
|
||||||
|
const filteredData = chartData.filter((item) => {
|
||||||
|
const date = new Date(item.date)
|
||||||
|
const referenceDate = new Date("2024-06-30")
|
||||||
|
let daysToSubtract = 90
|
||||||
|
if (timeRange === "30d") {
|
||||||
|
daysToSubtract = 30
|
||||||
|
} else if (timeRange === "7d") {
|
||||||
|
daysToSubtract = 7
|
||||||
|
}
|
||||||
|
const startDate = new Date(referenceDate)
|
||||||
|
startDate.setDate(startDate.getDate() - daysToSubtract)
|
||||||
|
return date >= startDate
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="@container/card">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Total Visitors</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
<span className="hidden @[540px]/card:block">
|
||||||
|
Total for the last 3 months
|
||||||
|
</span>
|
||||||
|
<span className="@[540px]/card:hidden">Last 3 months</span>
|
||||||
|
</CardDescription>
|
||||||
|
<CardAction>
|
||||||
|
<ToggleGroup
|
||||||
|
type="single"
|
||||||
|
value={timeRange}
|
||||||
|
onValueChange={setTimeRange}
|
||||||
|
variant="outline"
|
||||||
|
className="hidden *:data-[slot=toggle-group-item]:px-4! @[767px]/card:flex"
|
||||||
|
>
|
||||||
|
<ToggleGroupItem value="90d">Last 3 months</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="30d">Last 30 days</ToggleGroupItem>
|
||||||
|
<ToggleGroupItem value="7d">Last 7 days</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
<Select value={timeRange} onValueChange={setTimeRange}>
|
||||||
|
<SelectTrigger
|
||||||
|
className="flex w-40 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate @[767px]/card:hidden"
|
||||||
|
size="sm"
|
||||||
|
aria-label="Select a value"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Last 3 months" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent className="rounded-xl">
|
||||||
|
<SelectItem value="90d" className="rounded-lg">
|
||||||
|
Last 3 months
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="30d" className="rounded-lg">
|
||||||
|
Last 30 days
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="7d" className="rounded-lg">
|
||||||
|
Last 7 days
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</CardAction>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="px-2 pt-4 sm:px-6 sm:pt-6">
|
||||||
|
<ChartContainer
|
||||||
|
config={chartConfig}
|
||||||
|
className="aspect-auto h-[250px] w-full"
|
||||||
|
>
|
||||||
|
<AreaChart data={filteredData}>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="fillDesktop" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop
|
||||||
|
offset="5%"
|
||||||
|
stopColor="var(--color-desktop)"
|
||||||
|
stopOpacity={1.0}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="95%"
|
||||||
|
stopColor="var(--color-desktop)"
|
||||||
|
stopOpacity={0.1}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient id="fillMobile" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop
|
||||||
|
offset="5%"
|
||||||
|
stopColor="var(--color-mobile)"
|
||||||
|
stopOpacity={0.8}
|
||||||
|
/>
|
||||||
|
<stop
|
||||||
|
offset="95%"
|
||||||
|
stopColor="var(--color-mobile)"
|
||||||
|
stopOpacity={0.1}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<XAxis
|
||||||
|
dataKey="date"
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
tickMargin={8}
|
||||||
|
minTickGap={32}
|
||||||
|
tickFormatter={(value) => {
|
||||||
|
const date = new Date(value)
|
||||||
|
return date.toLocaleDateString("en-US", {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ChartTooltip
|
||||||
|
cursor={false}
|
||||||
|
content={
|
||||||
|
<ChartTooltipContent
|
||||||
|
labelFormatter={(value) => {
|
||||||
|
return new Date(value).toLocaleDateString("en-US", {
|
||||||
|
month: "short",
|
||||||
|
day: "numeric",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
indicator="dot"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="mobile"
|
||||||
|
type="natural"
|
||||||
|
fill="url(#fillMobile)"
|
||||||
|
stroke="var(--color-mobile)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="desktop"
|
||||||
|
type="natural"
|
||||||
|
fill="url(#fillDesktop)"
|
||||||
|
stroke="var(--color-desktop)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
805
resources/js/components/data-table.tsx
Normal file
805
resources/js/components/data-table.tsx
Normal file
@ -0,0 +1,805 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
closestCenter,
|
||||||
|
DndContext,
|
||||||
|
KeyboardSensor,
|
||||||
|
MouseSensor,
|
||||||
|
TouchSensor,
|
||||||
|
useSensor,
|
||||||
|
useSensors,
|
||||||
|
type DragEndEvent,
|
||||||
|
type UniqueIdentifier,
|
||||||
|
} from "@dnd-kit/core"
|
||||||
|
import { restrictToVerticalAxis } from "@dnd-kit/modifiers"
|
||||||
|
import {
|
||||||
|
arrayMove,
|
||||||
|
SortableContext,
|
||||||
|
useSortable,
|
||||||
|
verticalListSortingStrategy,
|
||||||
|
} from "@dnd-kit/sortable"
|
||||||
|
import { CSS } from "@dnd-kit/utilities"
|
||||||
|
import {
|
||||||
|
IconChevronDown,
|
||||||
|
IconChevronLeft,
|
||||||
|
IconChevronRight,
|
||||||
|
IconChevronsLeft,
|
||||||
|
IconChevronsRight,
|
||||||
|
IconCircleCheckFilled,
|
||||||
|
IconDotsVertical,
|
||||||
|
IconGripVertical,
|
||||||
|
IconLayoutColumns,
|
||||||
|
IconLoader,
|
||||||
|
IconPlus,
|
||||||
|
IconTrendingUp,
|
||||||
|
} from "@tabler/icons-react"
|
||||||
|
import {
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFacetedRowModel,
|
||||||
|
getFacetedUniqueValues,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getPaginationRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable,
|
||||||
|
type ColumnDef,
|
||||||
|
type ColumnFiltersState,
|
||||||
|
type Row,
|
||||||
|
type SortingState,
|
||||||
|
type VisibilityState,
|
||||||
|
} from "@tanstack/react-table"
|
||||||
|
import { Area, AreaChart, CartesianGrid, XAxis } from "recharts"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
import { useIsMobile } from "@/hooks/use-mobile"
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
type ChartConfig,
|
||||||
|
} from "@/components/ui/chart"
|
||||||
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerDescription,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerTrigger,
|
||||||
|
} from "@/components/ui/drawer"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
import { Input } from "@/components/ui/input"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select"
|
||||||
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/components/ui/table"
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from "@/components/ui/tabs"
|
||||||
|
|
||||||
|
export const schema = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
header: z.string(),
|
||||||
|
type: z.string(),
|
||||||
|
status: z.string(),
|
||||||
|
target: z.string(),
|
||||||
|
limit: z.string(),
|
||||||
|
reviewer: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Create a separate component for the drag handle
|
||||||
|
function DragHandle({ id }: { id: number }) {
|
||||||
|
const { attributes, listeners } = useSortable({
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
{...attributes}
|
||||||
|
{...listeners}
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="size-7 text-muted-foreground hover:bg-transparent"
|
||||||
|
>
|
||||||
|
<IconGripVertical className="size-3 text-muted-foreground" />
|
||||||
|
<span className="sr-only">Drag to reorder</span>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const columns: ColumnDef<z.infer<typeof schema>>[] = [
|
||||||
|
{
|
||||||
|
id: "drag",
|
||||||
|
header: () => null,
|
||||||
|
cell: ({ row }) => <DragHandle id={row.original.id} />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "select",
|
||||||
|
header: ({ table }) => (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
table.getIsAllPageRowsSelected() ||
|
||||||
|
(table.getIsSomePageRowsSelected() && "indeterminate")
|
||||||
|
}
|
||||||
|
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
||||||
|
aria-label="Select all"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex items-center justify-center">
|
||||||
|
<Checkbox
|
||||||
|
checked={row.getIsSelected()}
|
||||||
|
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
||||||
|
aria-label="Select row"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "header",
|
||||||
|
header: "Header",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return <TableCellViewer item={row.original} />
|
||||||
|
},
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "type",
|
||||||
|
header: "Section Type",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="w-32">
|
||||||
|
<Badge variant="outline" className="px-1.5 text-muted-foreground">
|
||||||
|
{row.original.type}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "status",
|
||||||
|
header: "Status",
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Badge variant="outline" className="px-1.5 text-muted-foreground">
|
||||||
|
{row.original.status === "Done" ? (
|
||||||
|
<IconCircleCheckFilled className="fill-green-500 dark:fill-green-400" />
|
||||||
|
) : (
|
||||||
|
<IconLoader />
|
||||||
|
)}
|
||||||
|
{row.original.status}
|
||||||
|
</Badge>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "target",
|
||||||
|
header: () => <div className="w-full text-right">Target</div>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||||
|
loading: `Saving ${row.original.header}`,
|
||||||
|
success: "Done",
|
||||||
|
error: "Error",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label htmlFor={`${row.original.id}-target`} className="sr-only">
|
||||||
|
Target
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
className="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30"
|
||||||
|
defaultValue={row.original.target}
|
||||||
|
id={`${row.original.id}-target`}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "limit",
|
||||||
|
header: () => <div className="w-full text-right">Limit</div>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
toast.promise(new Promise((resolve) => setTimeout(resolve, 1000)), {
|
||||||
|
loading: `Saving ${row.original.header}`,
|
||||||
|
success: "Done",
|
||||||
|
error: "Error",
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Label htmlFor={`${row.original.id}-limit`} className="sr-only">
|
||||||
|
Limit
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
className="h-8 w-16 border-transparent bg-transparent text-right shadow-none hover:bg-input/30 focus-visible:border focus-visible:bg-background dark:bg-transparent dark:hover:bg-input/30 dark:focus-visible:bg-input/30"
|
||||||
|
defaultValue={row.original.limit}
|
||||||
|
id={`${row.original.id}-limit`}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "reviewer",
|
||||||
|
header: "Reviewer",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const isAssigned = row.original.reviewer !== "Assign reviewer"
|
||||||
|
|
||||||
|
if (isAssigned) {
|
||||||
|
return row.original.reviewer
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Label htmlFor={`${row.original.id}-reviewer`} className="sr-only">
|
||||||
|
Reviewer
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger
|
||||||
|
className="w-38 **:data-[slot=select-value]:block **:data-[slot=select-value]:truncate"
|
||||||
|
size="sm"
|
||||||
|
id={`${row.original.id}-reviewer`}
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Assign reviewer" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent align="end">
|
||||||
|
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
||||||
|
<SelectItem value="Jamik Tashpulatov">
|
||||||
|
Jamik Tashpulatov
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
cell: () => (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="flex size-8 text-muted-foreground data-[state=open]:bg-muted"
|
||||||
|
size="icon"
|
||||||
|
>
|
||||||
|
<IconDotsVertical />
|
||||||
|
<span className="sr-only">Open menu</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-32">
|
||||||
|
<DropdownMenuItem>Edit</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Make a copy</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>Favorite</DropdownMenuItem>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItem variant="destructive">Delete</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
function DraggableRow({ row }: { row: Row<z.infer<typeof schema>> }) {
|
||||||
|
const { transform, transition, setNodeRef, isDragging } = useSortable({
|
||||||
|
id: row.original.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
data-state={row.getIsSelected() && "selected"}
|
||||||
|
data-dragging={isDragging}
|
||||||
|
ref={setNodeRef}
|
||||||
|
className="relative z-0 data-[dragging=true]:z-10 data-[dragging=true]:opacity-80"
|
||||||
|
style={{
|
||||||
|
transform: CSS.Transform.toString(transform),
|
||||||
|
transition: transition,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell key={cell.id}>
|
||||||
|
{flexRender(cell.column.columnDef.cell, cell.getContext())}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DataTable({
|
||||||
|
data: initialData,
|
||||||
|
}: {
|
||||||
|
data: z.infer<typeof schema>[]
|
||||||
|
}) {
|
||||||
|
const [data, setData] = React.useState(() => initialData)
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({})
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({})
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([])
|
||||||
|
const [pagination, setPagination] = React.useState({
|
||||||
|
pageIndex: 0,
|
||||||
|
pageSize: 10,
|
||||||
|
})
|
||||||
|
const sortableId = React.useId()
|
||||||
|
const sensors = useSensors(
|
||||||
|
useSensor(MouseSensor, {}),
|
||||||
|
useSensor(TouchSensor, {}),
|
||||||
|
useSensor(KeyboardSensor, {})
|
||||||
|
)
|
||||||
|
|
||||||
|
const dataIds = React.useMemo<UniqueIdentifier[]>(
|
||||||
|
() => data?.map(({ id }) => id) || [],
|
||||||
|
[data]
|
||||||
|
)
|
||||||
|
|
||||||
|
const table = useReactTable({
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
state: {
|
||||||
|
sorting,
|
||||||
|
columnVisibility,
|
||||||
|
rowSelection,
|
||||||
|
columnFilters,
|
||||||
|
pagination,
|
||||||
|
},
|
||||||
|
getRowId: (row) => row.id.toString(),
|
||||||
|
enableRowSelection: true,
|
||||||
|
onRowSelectionChange: setRowSelection,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
onColumnVisibilityChange: setColumnVisibility,
|
||||||
|
onPaginationChange: setPagination,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
getFilteredRowModel: getFilteredRowModel(),
|
||||||
|
getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
getFacetedRowModel: getFacetedRowModel(),
|
||||||
|
getFacetedUniqueValues: getFacetedUniqueValues(),
|
||||||
|
})
|
||||||
|
|
||||||
|
function handleDragEnd(event: DragEndEvent) {
|
||||||
|
const { active, over } = event
|
||||||
|
if (active && over && active.id !== over.id) {
|
||||||
|
setData((data) => {
|
||||||
|
const oldIndex = dataIds.indexOf(active.id)
|
||||||
|
const newIndex = dataIds.indexOf(over.id)
|
||||||
|
return arrayMove(data, oldIndex, newIndex)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs
|
||||||
|
defaultValue="outline"
|
||||||
|
className="w-full flex-col justify-start gap-6"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between px-4 lg:px-6">
|
||||||
|
<Label htmlFor="view-selector" className="sr-only">
|
||||||
|
View
|
||||||
|
</Label>
|
||||||
|
<Select defaultValue="outline">
|
||||||
|
<SelectTrigger
|
||||||
|
className="flex w-fit @4xl/main:hidden"
|
||||||
|
size="sm"
|
||||||
|
id="view-selector"
|
||||||
|
>
|
||||||
|
<SelectValue placeholder="Select a view" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="outline">Outline</SelectItem>
|
||||||
|
<SelectItem value="past-performance">Past Performance</SelectItem>
|
||||||
|
<SelectItem value="key-personnel">Key Personnel</SelectItem>
|
||||||
|
<SelectItem value="focus-documents">Focus Documents</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<TabsList className="hidden **:data-[slot=badge]:size-5 **:data-[slot=badge]:rounded-full **:data-[slot=badge]:bg-muted-foreground/30 **:data-[slot=badge]:px-1 @4xl/main:flex">
|
||||||
|
<TabsTrigger value="outline">Outline</TabsTrigger>
|
||||||
|
<TabsTrigger value="past-performance">
|
||||||
|
Past Performance <Badge variant="secondary">3</Badge>
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="key-personnel">
|
||||||
|
Key Personnel <Badge variant="secondary">2</Badge>
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="focus-documents">Focus Documents</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<IconLayoutColumns />
|
||||||
|
<span className="hidden lg:inline">Customize Columns</span>
|
||||||
|
<span className="lg:hidden">Columns</span>
|
||||||
|
<IconChevronDown />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end" className="w-56">
|
||||||
|
{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>
|
||||||
|
<Button variant="outline" size="sm">
|
||||||
|
<IconPlus />
|
||||||
|
<span className="hidden lg:inline">Add Section</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<TabsContent
|
||||||
|
value="outline"
|
||||||
|
className="relative flex flex-col gap-4 overflow-auto px-4 lg:px-6"
|
||||||
|
>
|
||||||
|
<div className="overflow-hidden rounded-lg border">
|
||||||
|
<DndContext
|
||||||
|
collisionDetection={closestCenter}
|
||||||
|
modifiers={[restrictToVerticalAxis]}
|
||||||
|
onDragEnd={handleDragEnd}
|
||||||
|
sensors={sensors}
|
||||||
|
id={sortableId}
|
||||||
|
>
|
||||||
|
<Table>
|
||||||
|
<TableHeader className="sticky top-0 z-10 bg-muted">
|
||||||
|
{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 className="**:data-[slot=table-cell]:first:w-8">
|
||||||
|
{table.getRowModel().rows?.length ? (
|
||||||
|
<SortableContext
|
||||||
|
items={dataIds}
|
||||||
|
strategy={verticalListSortingStrategy}
|
||||||
|
>
|
||||||
|
{table.getRowModel().rows.map((row) => (
|
||||||
|
<DraggableRow key={row.id} row={row} />
|
||||||
|
))}
|
||||||
|
</SortableContext>
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={columns.length}
|
||||||
|
className="h-24 text-center"
|
||||||
|
>
|
||||||
|
No results.
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</DndContext>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center justify-between px-4">
|
||||||
|
<div className="hidden flex-1 text-sm text-muted-foreground lg:flex">
|
||||||
|
{table.getFilteredSelectedRowModel().rows.length} of{" "}
|
||||||
|
{table.getFilteredRowModel().rows.length} row(s) selected.
|
||||||
|
</div>
|
||||||
|
<div className="flex w-full items-center gap-8 lg:w-fit">
|
||||||
|
<div className="hidden items-center gap-2 lg:flex">
|
||||||
|
<Label htmlFor="rows-per-page" className="text-sm font-medium">
|
||||||
|
Rows per page
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={`${table.getState().pagination.pageSize}`}
|
||||||
|
onValueChange={(value) => {
|
||||||
|
table.setPageSize(Number(value))
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger size="sm" className="w-20" id="rows-per-page">
|
||||||
|
<SelectValue
|
||||||
|
placeholder={table.getState().pagination.pageSize}
|
||||||
|
/>
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent side="top">
|
||||||
|
{[10, 20, 30, 40, 50].map((pageSize) => (
|
||||||
|
<SelectItem key={pageSize} value={`${pageSize}`}>
|
||||||
|
{pageSize}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex w-fit items-center justify-center text-sm font-medium">
|
||||||
|
Page {table.getState().pagination.pageIndex + 1} of{" "}
|
||||||
|
{table.getPageCount()}
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto flex items-center gap-2 lg:ml-0">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="hidden h-8 w-8 p-0 lg:flex"
|
||||||
|
onClick={() => table.setPageIndex(0)}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to first page</span>
|
||||||
|
<IconChevronsLeft />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="size-8"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to previous page</span>
|
||||||
|
<IconChevronLeft />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="size-8"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to next page</span>
|
||||||
|
<IconChevronRight />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="hidden size-8 lg:flex"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => table.setPageIndex(table.getPageCount() - 1)}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Go to last page</span>
|
||||||
|
<IconChevronsRight />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent
|
||||||
|
value="past-performance"
|
||||||
|
className="flex flex-col px-4 lg:px-6"
|
||||||
|
>
|
||||||
|
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="key-personnel" className="flex flex-col px-4 lg:px-6">
|
||||||
|
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent
|
||||||
|
value="focus-documents"
|
||||||
|
className="flex flex-col px-4 lg:px-6"
|
||||||
|
>
|
||||||
|
<div className="aspect-video w-full flex-1 rounded-lg border border-dashed"></div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const chartData = [
|
||||||
|
{ month: "January", desktop: 186, mobile: 80 },
|
||||||
|
{ month: "February", desktop: 305, mobile: 200 },
|
||||||
|
{ month: "March", desktop: 237, mobile: 120 },
|
||||||
|
{ month: "April", desktop: 73, mobile: 190 },
|
||||||
|
{ month: "May", desktop: 209, mobile: 130 },
|
||||||
|
{ month: "June", desktop: 214, mobile: 140 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const chartConfig = {
|
||||||
|
desktop: {
|
||||||
|
label: "Desktop",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
mobile: {
|
||||||
|
label: "Mobile",
|
||||||
|
color: "var(--primary)",
|
||||||
|
},
|
||||||
|
} satisfies ChartConfig
|
||||||
|
|
||||||
|
function TableCellViewer({ item }: { item: z.infer<typeof schema> }) {
|
||||||
|
const isMobile = useIsMobile()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer direction={isMobile ? "bottom" : "right"}>
|
||||||
|
<DrawerTrigger asChild>
|
||||||
|
<Button variant="link" className="w-fit px-0 text-left text-foreground">
|
||||||
|
{item.header}
|
||||||
|
</Button>
|
||||||
|
</DrawerTrigger>
|
||||||
|
<DrawerContent>
|
||||||
|
<DrawerHeader className="gap-1">
|
||||||
|
<DrawerTitle>{item.header}</DrawerTitle>
|
||||||
|
<DrawerDescription>
|
||||||
|
Showing total visitors for the last 6 months
|
||||||
|
</DrawerDescription>
|
||||||
|
</DrawerHeader>
|
||||||
|
<div className="flex flex-col gap-4 overflow-y-auto px-4 text-sm">
|
||||||
|
{!isMobile && (
|
||||||
|
<>
|
||||||
|
<ChartContainer config={chartConfig}>
|
||||||
|
<AreaChart
|
||||||
|
accessibilityLayer
|
||||||
|
data={chartData}
|
||||||
|
margin={{
|
||||||
|
left: 0,
|
||||||
|
right: 10,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CartesianGrid vertical={false} />
|
||||||
|
<XAxis
|
||||||
|
dataKey="month"
|
||||||
|
tickLine={false}
|
||||||
|
axisLine={false}
|
||||||
|
tickMargin={8}
|
||||||
|
tickFormatter={(value) => value.slice(0, 3)}
|
||||||
|
hide
|
||||||
|
/>
|
||||||
|
<ChartTooltip
|
||||||
|
cursor={false}
|
||||||
|
content={<ChartTooltipContent indicator="dot" />}
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="mobile"
|
||||||
|
type="natural"
|
||||||
|
fill="var(--color-mobile)"
|
||||||
|
fillOpacity={0.6}
|
||||||
|
stroke="var(--color-mobile)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
<Area
|
||||||
|
dataKey="desktop"
|
||||||
|
type="natural"
|
||||||
|
fill="var(--color-desktop)"
|
||||||
|
fillOpacity={0.4}
|
||||||
|
stroke="var(--color-desktop)"
|
||||||
|
stackId="a"
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
</ChartContainer>
|
||||||
|
<Separator />
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<div className="flex gap-2 leading-none font-medium">
|
||||||
|
Trending up by 5.2% this month{" "}
|
||||||
|
<IconTrendingUp className="size-4" />
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Showing total visitors for the last 6 months. This is just
|
||||||
|
some random text to test the layout. It spans multiple lines
|
||||||
|
and should wrap around.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<form className="flex flex-col gap-4">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="header">Header</Label>
|
||||||
|
<Input id="header" defaultValue={item.header} />
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="type">Type</Label>
|
||||||
|
<Select defaultValue={item.type}>
|
||||||
|
<SelectTrigger id="type" className="w-full">
|
||||||
|
<SelectValue placeholder="Select a type" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Table of Contents">
|
||||||
|
Table of Contents
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Executive Summary">
|
||||||
|
Executive Summary
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Technical Approach">
|
||||||
|
Technical Approach
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Design">Design</SelectItem>
|
||||||
|
<SelectItem value="Capabilities">Capabilities</SelectItem>
|
||||||
|
<SelectItem value="Focus Documents">
|
||||||
|
Focus Documents
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Narrative">Narrative</SelectItem>
|
||||||
|
<SelectItem value="Cover Page">Cover Page</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="status">Status</Label>
|
||||||
|
<Select defaultValue={item.status}>
|
||||||
|
<SelectTrigger id="status" className="w-full">
|
||||||
|
<SelectValue placeholder="Select a status" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Done">Done</SelectItem>
|
||||||
|
<SelectItem value="In Progress">In Progress</SelectItem>
|
||||||
|
<SelectItem value="Not Started">Not Started</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-2 gap-4">
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="target">Target</Label>
|
||||||
|
<Input id="target" defaultValue={item.target} />
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="limit">Limit</Label>
|
||||||
|
<Input id="limit" defaultValue={item.limit} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Label htmlFor="reviewer">Reviewer</Label>
|
||||||
|
<Select defaultValue={item.reviewer}>
|
||||||
|
<SelectTrigger id="reviewer" className="w-full">
|
||||||
|
<SelectValue placeholder="Select a reviewer" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="Eddie Lake">Eddie Lake</SelectItem>
|
||||||
|
<SelectItem value="Jamik Tashpulatov">
|
||||||
|
Jamik Tashpulatov
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Emily Whalen">Emily Whalen</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<DrawerFooter>
|
||||||
|
<Button>Submit</Button>
|
||||||
|
<DrawerClose asChild>
|
||||||
|
<Button variant="outline">Done</Button>
|
||||||
|
</DrawerClose>
|
||||||
|
</DrawerFooter>
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,36 +1,36 @@
|
|||||||
import { Link } from '@inertiajs/react';
|
import { type Icon } from "@tabler/icons-react";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
SidebarGroup,
|
SidebarGroup,
|
||||||
SidebarGroupLabel,
|
SidebarGroupContent,
|
||||||
SidebarMenu,
|
SidebarMenu,
|
||||||
SidebarMenuButton,
|
SidebarMenuButton,
|
||||||
SidebarMenuItem,
|
SidebarMenuItem,
|
||||||
} from '@/components/ui/sidebar';
|
} from "@/components/ui/sidebar";
|
||||||
import { useCurrentUrl } from '@/hooks/use-current-url';
|
|
||||||
import type { NavItem } from '@/types';
|
|
||||||
|
|
||||||
export function NavMain({ items = [] }: { items: NavItem[] }) {
|
|
||||||
const { isCurrentUrl } = useCurrentUrl();
|
|
||||||
|
|
||||||
|
export function NavMain({
|
||||||
|
items,
|
||||||
|
}: {
|
||||||
|
items: {
|
||||||
|
title: string
|
||||||
|
url: string
|
||||||
|
icon?: Icon
|
||||||
|
}[]
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<SidebarGroup className="px-2 py-0">
|
<SidebarGroup>
|
||||||
<SidebarGroupLabel>Platform</SidebarGroupLabel>
|
<SidebarGroupContent className="flex flex-col gap-2">
|
||||||
<SidebarMenu>
|
<SidebarMenu>
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<SidebarMenuItem key={item.title}>
|
<SidebarMenuItem key={item.title}>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton tooltip={item.title}>
|
||||||
asChild
|
|
||||||
isActive={isCurrentUrl(item.href)}
|
|
||||||
tooltip={{ children: item.title }}
|
|
||||||
>
|
|
||||||
<Link href={item.href} prefetch>
|
|
||||||
{item.icon && <item.icon />}
|
{item.icon && <item.icon />}
|
||||||
<span>{item.title}</span>
|
<span>{item.title}</span>
|
||||||
</Link>
|
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
))}
|
))}
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
42
resources/js/components/nav-secondary.tsx
Normal file
42
resources/js/components/nav-secondary.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { type Icon } from "@tabler/icons-react"
|
||||||
|
|
||||||
|
import {
|
||||||
|
SidebarGroup,
|
||||||
|
SidebarGroupContent,
|
||||||
|
SidebarMenu,
|
||||||
|
SidebarMenuButton,
|
||||||
|
SidebarMenuItem,
|
||||||
|
} from "@/components/ui/sidebar"
|
||||||
|
|
||||||
|
export function NavSecondary({
|
||||||
|
items,
|
||||||
|
...props
|
||||||
|
}: {
|
||||||
|
items: {
|
||||||
|
title: string
|
||||||
|
url: string
|
||||||
|
icon: Icon
|
||||||
|
}[]
|
||||||
|
} & React.ComponentPropsWithoutRef<typeof SidebarGroup>) {
|
||||||
|
return (
|
||||||
|
<SidebarGroup {...props}>
|
||||||
|
<SidebarGroupContent>
|
||||||
|
<SidebarMenu>
|
||||||
|
{items.map((item) => (
|
||||||
|
<SidebarMenuItem key={item.title}>
|
||||||
|
<SidebarMenuButton asChild>
|
||||||
|
<a href={item.url}>
|
||||||
|
<item.icon />
|
||||||
|
<span>{item.title}</span>
|
||||||
|
</a>
|
||||||
|
</SidebarMenuButton>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
))}
|
||||||
|
</SidebarMenu>
|
||||||
|
</SidebarGroupContent>
|
||||||
|
</SidebarGroup>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,58 +1,48 @@
|
|||||||
import { usePage } from '@inertiajs/react';
|
import { usePage } from '@inertiajs/react';
|
||||||
import { ChevronsUpDown } from 'lucide-react';
|
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import {
|
|
||||||
SidebarMenu,
|
|
||||||
SidebarMenuButton,
|
|
||||||
SidebarMenuItem,
|
|
||||||
useSidebar,
|
|
||||||
} from '@/components/ui/sidebar';
|
|
||||||
import { UserInfo } from '@/components/user-info';
|
|
||||||
import { UserMenuContent } from '@/components/user-menu-content';
|
import { UserMenuContent } from '@/components/user-menu-content';
|
||||||
import { useIsMobile } from '@/hooks/use-mobile';
|
import { useInitials } from '@/hooks/use-initials';
|
||||||
|
import type { User } from '@/types';
|
||||||
|
|
||||||
export function NavUser() {
|
export function NavUser() {
|
||||||
const { auth } = usePage().props;
|
const page = usePage();
|
||||||
const { state } = useSidebar();
|
const { auth } = page.props;
|
||||||
const isMobile = useIsMobile();
|
const getInitials = useInitials();
|
||||||
|
const user = auth.user as User | undefined;
|
||||||
|
|
||||||
if (!auth.user) {
|
if (!user) return null;
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenu>
|
|
||||||
<SidebarMenuItem>
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<SidebarMenuButton
|
<button className="flex items-center gap-2 rounded-md p-1.5 text-left text-sm transition-colors hover:bg-sidebar-accent data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground">
|
||||||
size="lg"
|
<Avatar className="h-8 w-8 rounded-lg">
|
||||||
className="group text-sidebar-accent-foreground data-[state=open]:bg-sidebar-accent"
|
<AvatarImage src={user.avatar} alt={user.full_name} />
|
||||||
data-test="sidebar-menu-button"
|
<AvatarFallback className="rounded-lg">
|
||||||
>
|
{getInitials(user.full_name ?? '')}
|
||||||
<UserInfo user={auth.user} />
|
</AvatarFallback>
|
||||||
<ChevronsUpDown className="ml-auto size-4" />
|
</Avatar>
|
||||||
</SidebarMenuButton>
|
<div className="hidden grid flex-1 text-left text-sm leading-tight md:grid">
|
||||||
|
<span className="truncate font-medium">{user.full_name}</span>
|
||||||
|
<span className="truncate text-xs text-muted-foreground">
|
||||||
|
{user.email}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
|
className="w-56 rounded-lg"
|
||||||
align="end"
|
align="end"
|
||||||
side={
|
sideOffset={4}
|
||||||
isMobile
|
|
||||||
? 'bottom'
|
|
||||||
: state === 'collapsed'
|
|
||||||
? 'left'
|
|
||||||
: 'bottom'
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<UserMenuContent user={auth.user} />
|
<UserMenuContent user={user} />
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</SidebarMenuItem>
|
|
||||||
</SidebarMenu>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
102
resources/js/components/section-cards.tsx
Normal file
102
resources/js/components/section-cards.tsx
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
import { IconTrendingDown, IconTrendingUp } from "@tabler/icons-react"
|
||||||
|
|
||||||
|
import { Badge } from "@/components/ui/badge"
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardAction,
|
||||||
|
CardDescription,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card"
|
||||||
|
|
||||||
|
export function SectionCards() {
|
||||||
|
return (
|
||||||
|
<div className="grid grid-cols-1 gap-4 px-4 *:data-[slot=card]:bg-gradient-to-t *:data-[slot=card]:from-primary/5 *:data-[slot=card]:to-card *:data-[slot=card]:shadow-xs lg:px-6 @xl/main:grid-cols-2 @5xl/main:grid-cols-4 dark:*:data-[slot=card]:bg-card">
|
||||||
|
<Card className="@container/card">
|
||||||
|
<CardHeader>
|
||||||
|
<CardDescription>Total Revenue</CardDescription>
|
||||||
|
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||||
|
$1,250.00
|
||||||
|
</CardTitle>
|
||||||
|
<CardAction>
|
||||||
|
<Badge variant="outline">
|
||||||
|
<IconTrendingUp />
|
||||||
|
+12.5%
|
||||||
|
</Badge>
|
||||||
|
</CardAction>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||||
|
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||||
|
Trending up this month <IconTrendingUp className="size-4" />
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Visitors for the last 6 months
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
<Card className="@container/card">
|
||||||
|
<CardHeader>
|
||||||
|
<CardDescription>New Customers</CardDescription>
|
||||||
|
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||||
|
1,234
|
||||||
|
</CardTitle>
|
||||||
|
<CardAction>
|
||||||
|
<Badge variant="outline">
|
||||||
|
<IconTrendingDown />
|
||||||
|
-20%
|
||||||
|
</Badge>
|
||||||
|
</CardAction>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||||
|
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||||
|
Down 20% this period <IconTrendingDown className="size-4" />
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground">
|
||||||
|
Acquisition needs attention
|
||||||
|
</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
<Card className="@container/card">
|
||||||
|
<CardHeader>
|
||||||
|
<CardDescription>Active Accounts</CardDescription>
|
||||||
|
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||||
|
45,678
|
||||||
|
</CardTitle>
|
||||||
|
<CardAction>
|
||||||
|
<Badge variant="outline">
|
||||||
|
<IconTrendingUp />
|
||||||
|
+12.5%
|
||||||
|
</Badge>
|
||||||
|
</CardAction>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||||
|
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||||
|
Strong user retention <IconTrendingUp className="size-4" />
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground">Engagement exceed targets</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
<Card className="@container/card">
|
||||||
|
<CardHeader>
|
||||||
|
<CardDescription>Growth Rate</CardDescription>
|
||||||
|
<CardTitle className="text-2xl font-semibold tabular-nums @[250px]/card:text-3xl">
|
||||||
|
4.5%
|
||||||
|
</CardTitle>
|
||||||
|
<CardAction>
|
||||||
|
<Badge variant="outline">
|
||||||
|
<IconTrendingUp />
|
||||||
|
+4.5%
|
||||||
|
</Badge>
|
||||||
|
</CardAction>
|
||||||
|
</CardHeader>
|
||||||
|
<CardFooter className="flex-col items-start gap-1.5 text-sm">
|
||||||
|
<div className="line-clamp-1 flex gap-2 font-medium">
|
||||||
|
Steady performance increase <IconTrendingUp className="size-4" />
|
||||||
|
</div>
|
||||||
|
<div className="text-muted-foreground">Meets growth projections</div>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
30
resources/js/components/site-header.tsx
Normal file
30
resources/js/components/site-header.tsx
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
import { SidebarTrigger } from "@/components/ui/sidebar"
|
||||||
|
|
||||||
|
export function SiteHeader() {
|
||||||
|
return (
|
||||||
|
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
|
||||||
|
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||||
|
<SidebarTrigger className="-ml-1" />
|
||||||
|
<Separator
|
||||||
|
orientation="vertical"
|
||||||
|
className="mx-2 data-[orientation=vertical]:h-4"
|
||||||
|
/>
|
||||||
|
<h1 className="text-base font-medium">Documents</h1>
|
||||||
|
<div className="ml-auto flex items-center gap-2">
|
||||||
|
<Button variant="ghost" asChild size="sm" className="hidden sm:flex">
|
||||||
|
<a
|
||||||
|
href="https://github.com/shadcn-ui/ui/tree/main/apps/v4/app/(examples)/dashboard"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
className="dark:text-foreground"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Avatar as AvatarPrimitive } from "radix-ui"
|
import { Avatar as AvatarPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
|||||||
372
resources/js/components/ui/chart.tsx
Normal file
372
resources/js/components/ui/chart.tsx
Normal file
@ -0,0 +1,372 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as RechartsPrimitive from "recharts"
|
||||||
|
import type { TooltipValueType } from "recharts"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
// Format: { THEME_NAME: CSS_SELECTOR }
|
||||||
|
const THEMES = { light: "", dark: ".dark" } as const
|
||||||
|
|
||||||
|
const INITIAL_DIMENSION = { width: 320, height: 200 } as const
|
||||||
|
type TooltipNameType = number | string
|
||||||
|
|
||||||
|
export type ChartConfig = Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
label?: React.ReactNode
|
||||||
|
icon?: React.ComponentType
|
||||||
|
} & (
|
||||||
|
| { color?: string; theme?: never }
|
||||||
|
| { color?: never; theme: Record<keyof typeof THEMES, string> }
|
||||||
|
)
|
||||||
|
>
|
||||||
|
|
||||||
|
type ChartContextProps = {
|
||||||
|
config: ChartConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChartContext = React.createContext<ChartContextProps | null>(null)
|
||||||
|
|
||||||
|
function useChart() {
|
||||||
|
const context = React.useContext(ChartContext)
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useChart must be used within a <ChartContainer />")
|
||||||
|
}
|
||||||
|
|
||||||
|
return context
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChartContainer({
|
||||||
|
id,
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
config,
|
||||||
|
initialDimension = INITIAL_DIMENSION,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"div"> & {
|
||||||
|
config: ChartConfig
|
||||||
|
children: React.ComponentProps<
|
||||||
|
typeof RechartsPrimitive.ResponsiveContainer
|
||||||
|
>["children"]
|
||||||
|
initialDimension?: {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
const uniqueId = React.useId()
|
||||||
|
const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ChartContext.Provider value={{ config }}>
|
||||||
|
<div
|
||||||
|
data-slot="chart"
|
||||||
|
data-chart={chartId}
|
||||||
|
className={cn(
|
||||||
|
"flex aspect-video justify-center text-xs [&_.recharts-cartesian-axis-tick_text]:fill-muted-foreground [&_.recharts-cartesian-grid_line[stroke='#ccc']]:stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChartStyle id={chartId} config={config} />
|
||||||
|
<RechartsPrimitive.ResponsiveContainer
|
||||||
|
initialDimension={initialDimension}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</RechartsPrimitive.ResponsiveContainer>
|
||||||
|
</div>
|
||||||
|
</ChartContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
|
||||||
|
const colorConfig = Object.entries(config).filter(
|
||||||
|
([, config]) => config.theme ?? config.color
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!colorConfig.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<style
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: Object.entries(THEMES)
|
||||||
|
.map(
|
||||||
|
([theme, prefix]) => `
|
||||||
|
${prefix} [data-chart=${id}] {
|
||||||
|
${colorConfig
|
||||||
|
.map(([key, itemConfig]) => {
|
||||||
|
const color =
|
||||||
|
itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ??
|
||||||
|
itemConfig.color
|
||||||
|
return color ? ` --color-${key}: ${color};` : null
|
||||||
|
})
|
||||||
|
.join("\n")}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join("\n"),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChartTooltip = RechartsPrimitive.Tooltip
|
||||||
|
|
||||||
|
function ChartTooltipContent({
|
||||||
|
active,
|
||||||
|
payload,
|
||||||
|
className,
|
||||||
|
indicator = "dot",
|
||||||
|
hideLabel = false,
|
||||||
|
hideIndicator = false,
|
||||||
|
label,
|
||||||
|
labelFormatter,
|
||||||
|
labelClassName,
|
||||||
|
formatter,
|
||||||
|
color,
|
||||||
|
nameKey,
|
||||||
|
labelKey,
|
||||||
|
}: React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
|
||||||
|
React.ComponentProps<"div"> & {
|
||||||
|
hideLabel?: boolean
|
||||||
|
hideIndicator?: boolean
|
||||||
|
indicator?: "line" | "dot" | "dashed"
|
||||||
|
nameKey?: string
|
||||||
|
labelKey?: string
|
||||||
|
} & Omit<
|
||||||
|
RechartsPrimitive.DefaultTooltipContentProps<
|
||||||
|
TooltipValueType,
|
||||||
|
TooltipNameType
|
||||||
|
>,
|
||||||
|
"accessibilityLayer"
|
||||||
|
>) {
|
||||||
|
const { config } = useChart()
|
||||||
|
|
||||||
|
const tooltipLabel = React.useMemo(() => {
|
||||||
|
if (hideLabel || !payload?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const [item] = payload
|
||||||
|
const key = `${labelKey ?? item?.dataKey ?? item?.name ?? "value"}`
|
||||||
|
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||||
|
const value =
|
||||||
|
!labelKey && typeof label === "string"
|
||||||
|
? (config[label]?.label ?? label)
|
||||||
|
: itemConfig?.label
|
||||||
|
|
||||||
|
if (labelFormatter) {
|
||||||
|
return (
|
||||||
|
<div className={cn("font-medium", labelClassName)}>
|
||||||
|
{labelFormatter(value, payload)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={cn("font-medium", labelClassName)}>{value}</div>
|
||||||
|
}, [
|
||||||
|
label,
|
||||||
|
labelFormatter,
|
||||||
|
payload,
|
||||||
|
hideLabel,
|
||||||
|
labelClassName,
|
||||||
|
config,
|
||||||
|
labelKey,
|
||||||
|
])
|
||||||
|
|
||||||
|
if (!active || !payload?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const nestLabel = payload.length === 1 && indicator !== "dot"
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{!nestLabel ? tooltipLabel : null}
|
||||||
|
<div className="grid gap-1.5">
|
||||||
|
{payload
|
||||||
|
.filter((item) => item.type !== "none")
|
||||||
|
.map((item, index) => {
|
||||||
|
const key = `${nameKey ?? item.name ?? item.dataKey ?? "value"}`
|
||||||
|
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||||
|
const indicatorColor = color ?? item.payload?.fill ?? item.color
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={cn(
|
||||||
|
"flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
|
||||||
|
indicator === "dot" && "items-center"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{formatter && item?.value !== undefined && item.name ? (
|
||||||
|
formatter(item.value, item.name, item, index, item.payload)
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{itemConfig?.icon ? (
|
||||||
|
<itemConfig.icon />
|
||||||
|
) : (
|
||||||
|
!hideIndicator && (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 rounded-[2px] border-(--color-border) bg-(--color-bg)",
|
||||||
|
{
|
||||||
|
"h-2.5 w-2.5": indicator === "dot",
|
||||||
|
"w-1": indicator === "line",
|
||||||
|
"w-0 border-[1.5px] border-dashed bg-transparent":
|
||||||
|
indicator === "dashed",
|
||||||
|
"my-0.5": nestLabel && indicator === "dashed",
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
style={
|
||||||
|
{
|
||||||
|
"--color-bg": indicatorColor,
|
||||||
|
"--color-border": indicatorColor,
|
||||||
|
} as React.CSSProperties
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-1 justify-between leading-none",
|
||||||
|
nestLabel ? "items-end" : "items-center"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="grid gap-1.5">
|
||||||
|
{nestLabel ? tooltipLabel : null}
|
||||||
|
<span className="text-muted-foreground">
|
||||||
|
{itemConfig?.label ?? item.name}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{item.value != null && (
|
||||||
|
<span className="font-mono font-medium text-foreground tabular-nums">
|
||||||
|
{typeof item.value === "number"
|
||||||
|
? item.value.toLocaleString()
|
||||||
|
: String(item.value)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const ChartLegend = RechartsPrimitive.Legend
|
||||||
|
|
||||||
|
function ChartLegendContent({
|
||||||
|
className,
|
||||||
|
hideIcon = false,
|
||||||
|
payload,
|
||||||
|
verticalAlign = "bottom",
|
||||||
|
nameKey,
|
||||||
|
}: React.ComponentProps<"div"> & {
|
||||||
|
hideIcon?: boolean
|
||||||
|
nameKey?: string
|
||||||
|
} & RechartsPrimitive.DefaultLegendContentProps) {
|
||||||
|
const { config } = useChart()
|
||||||
|
|
||||||
|
if (!payload?.length) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex items-center justify-center gap-4",
|
||||||
|
verticalAlign === "top" ? "pb-3" : "pt-3",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{payload
|
||||||
|
.filter((item) => item.type !== "none")
|
||||||
|
.map((item, index) => {
|
||||||
|
const key = `${nameKey ?? item.dataKey ?? "value"}`
|
||||||
|
const itemConfig = getPayloadConfigFromPayload(config, item, key)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={index}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{itemConfig?.icon && !hideIcon ? (
|
||||||
|
<itemConfig.icon />
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="h-2 w-2 shrink-0 rounded-[2px]"
|
||||||
|
style={{
|
||||||
|
backgroundColor: item.color,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{itemConfig?.label}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to extract item config from a payload.
|
||||||
|
function getPayloadConfigFromPayload(
|
||||||
|
config: ChartConfig,
|
||||||
|
payload: unknown,
|
||||||
|
key: string
|
||||||
|
) {
|
||||||
|
if (typeof payload !== "object" || payload === null) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const payloadPayload =
|
||||||
|
"payload" in payload &&
|
||||||
|
typeof payload.payload === "object" &&
|
||||||
|
payload.payload !== null
|
||||||
|
? payload.payload
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
let configLabelKey: string = key
|
||||||
|
|
||||||
|
if (
|
||||||
|
key in payload &&
|
||||||
|
typeof payload[key as keyof typeof payload] === "string"
|
||||||
|
) {
|
||||||
|
configLabelKey = payload[key as keyof typeof payload] as string
|
||||||
|
} else if (
|
||||||
|
payloadPayload &&
|
||||||
|
key in payloadPayload &&
|
||||||
|
typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
|
||||||
|
) {
|
||||||
|
configLabelKey = payloadPayload[
|
||||||
|
key as keyof typeof payloadPayload
|
||||||
|
] as string
|
||||||
|
}
|
||||||
|
|
||||||
|
return configLabelKey in config ? config[configLabelKey] : config[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ChartContainer,
|
||||||
|
ChartTooltip,
|
||||||
|
ChartTooltipContent,
|
||||||
|
ChartLegend,
|
||||||
|
ChartLegendContent,
|
||||||
|
ChartStyle,
|
||||||
|
}
|
||||||
@ -1,5 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { CheckIcon } from "lucide-react"
|
import { CheckIcon } from "lucide-react"
|
||||||
import { Checkbox as CheckboxPrimitive } from "radix-ui"
|
import { Checkbox as CheckboxPrimitive } from "radix-ui"
|
||||||
|
|||||||
133
resources/js/components/ui/drawer.tsx
Normal file
133
resources/js/components/ui/drawer.tsx
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Drawer as DrawerPrimitive } from "vaul"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Drawer({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Root>) {
|
||||||
|
return <DrawerPrimitive.Root data-slot="drawer" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DrawerTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Trigger>) {
|
||||||
|
return <DrawerPrimitive.Trigger data-slot="drawer-trigger" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DrawerPortal({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Portal>) {
|
||||||
|
return <DrawerPrimitive.Portal data-slot="drawer-portal" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DrawerClose({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Close>) {
|
||||||
|
return <DrawerPrimitive.Close data-slot="drawer-close" {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
function DrawerOverlay({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Overlay>) {
|
||||||
|
return (
|
||||||
|
<DrawerPrimitive.Overlay
|
||||||
|
data-slot="drawer-overlay"
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DrawerContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<DrawerPortal data-slot="drawer-portal">
|
||||||
|
<DrawerOverlay />
|
||||||
|
<DrawerPrimitive.Content
|
||||||
|
data-slot="drawer-content"
|
||||||
|
className={cn(
|
||||||
|
"group/drawer-content fixed z-50 flex h-auto flex-col bg-background",
|
||||||
|
"data-[vaul-drawer-direction=top]:inset-x-0 data-[vaul-drawer-direction=top]:top-0 data-[vaul-drawer-direction=top]:mb-24 data-[vaul-drawer-direction=top]:max-h-[80vh] data-[vaul-drawer-direction=top]:rounded-b-lg data-[vaul-drawer-direction=top]:border-b",
|
||||||
|
"data-[vaul-drawer-direction=bottom]:inset-x-0 data-[vaul-drawer-direction=bottom]:bottom-0 data-[vaul-drawer-direction=bottom]:mt-24 data-[vaul-drawer-direction=bottom]:max-h-[80vh] data-[vaul-drawer-direction=bottom]:rounded-t-lg data-[vaul-drawer-direction=bottom]:border-t",
|
||||||
|
"data-[vaul-drawer-direction=right]:inset-y-0 data-[vaul-drawer-direction=right]:right-0 data-[vaul-drawer-direction=right]:w-3/4 data-[vaul-drawer-direction=right]:border-l data-[vaul-drawer-direction=right]:sm:max-w-sm",
|
||||||
|
"data-[vaul-drawer-direction=left]:inset-y-0 data-[vaul-drawer-direction=left]:left-0 data-[vaul-drawer-direction=left]:w-3/4 data-[vaul-drawer-direction=left]:border-r data-[vaul-drawer-direction=left]:sm:max-w-sm",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="mx-auto mt-4 hidden h-2 w-[100px] shrink-0 rounded-full bg-muted group-data-[vaul-drawer-direction=bottom]/drawer-content:block" />
|
||||||
|
{children}
|
||||||
|
</DrawerPrimitive.Content>
|
||||||
|
</DrawerPortal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DrawerHeader({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="drawer-header"
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col gap-0.5 p-4 group-data-[vaul-drawer-direction=bottom]/drawer-content:text-center group-data-[vaul-drawer-direction=top]/drawer-content:text-center md:gap-1.5 md:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DrawerFooter({ className, ...props }: React.ComponentProps<"div">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="drawer-footer"
|
||||||
|
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DrawerTitle({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Title>) {
|
||||||
|
return (
|
||||||
|
<DrawerPrimitive.Title
|
||||||
|
data-slot="drawer-title"
|
||||||
|
className={cn("font-semibold text-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DrawerDescription({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DrawerPrimitive.Description>) {
|
||||||
|
return (
|
||||||
|
<DrawerPrimitive.Description
|
||||||
|
data-slot="drawer-description"
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Drawer,
|
||||||
|
DrawerPortal,
|
||||||
|
DrawerOverlay,
|
||||||
|
DrawerTrigger,
|
||||||
|
DrawerClose,
|
||||||
|
DrawerContent,
|
||||||
|
DrawerHeader,
|
||||||
|
DrawerFooter,
|
||||||
|
DrawerTitle,
|
||||||
|
DrawerDescription,
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
|
||||||
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
|
import { DropdownMenu as DropdownMenuPrimitive } from "radix-ui"
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Label as LabelPrimitive } from "radix-ui"
|
import { Label as LabelPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
"use client"
|
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
|
||||||
import { Select as SelectPrimitive } from "radix-ui"
|
import { Select as SelectPrimitive } from "radix-ui"
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { PanelLeftIcon } from "lucide-react"
|
import { PanelLeftIcon } from "lucide-react"
|
||||||
|
|||||||
114
resources/js/components/ui/table.tsx
Normal file
114
resources/js/components/ui/table.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Table({ className, ...props }: React.ComponentProps<"table">) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-slot="table-container"
|
||||||
|
className="relative w-full overflow-x-auto"
|
||||||
|
>
|
||||||
|
<table
|
||||||
|
data-slot="table"
|
||||||
|
className={cn("w-full caption-bottom text-sm", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
|
||||||
|
return (
|
||||||
|
<thead
|
||||||
|
data-slot="table-header"
|
||||||
|
className={cn("[&_tr]:border-b", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
|
||||||
|
return (
|
||||||
|
<tbody
|
||||||
|
data-slot="table-body"
|
||||||
|
className={cn("[&_tr:last-child]:border-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
|
||||||
|
return (
|
||||||
|
<tfoot
|
||||||
|
data-slot="table-footer"
|
||||||
|
className={cn(
|
||||||
|
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
|
||||||
|
return (
|
||||||
|
<tr
|
||||||
|
data-slot="table-row"
|
||||||
|
className={cn(
|
||||||
|
"border-b transition-colors hover:bg-muted/50 has-aria-expanded:bg-muted/50 data-[state=selected]:bg-muted",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
|
||||||
|
return (
|
||||||
|
<th
|
||||||
|
data-slot="table-head"
|
||||||
|
className={cn(
|
||||||
|
"h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
|
||||||
|
return (
|
||||||
|
<td
|
||||||
|
data-slot="table-cell"
|
||||||
|
className={cn(
|
||||||
|
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TableCaption({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"caption">) {
|
||||||
|
return (
|
||||||
|
<caption
|
||||||
|
data-slot="table-caption"
|
||||||
|
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Table,
|
||||||
|
TableHeader,
|
||||||
|
TableBody,
|
||||||
|
TableFooter,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
TableCell,
|
||||||
|
TableCaption,
|
||||||
|
}
|
||||||
91
resources/js/components/ui/tabs.tsx
Normal file
91
resources/js/components/ui/tabs.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
import { Tabs as TabsPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Tabs({
|
||||||
|
className,
|
||||||
|
orientation = "horizontal",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Root
|
||||||
|
data-slot="tabs"
|
||||||
|
data-orientation={orientation}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"group/tabs flex gap-2 data-[orientation=horizontal]:flex-col",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tabsListVariants = cva(
|
||||||
|
"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-[orientation=horizontal]/tabs:h-9 group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col data-[variant=line]:rounded-none",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-muted",
|
||||||
|
line: "gap-1 bg-transparent",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function TabsList({
|
||||||
|
className,
|
||||||
|
variant = "default",
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.List> &
|
||||||
|
VariantProps<typeof tabsListVariants>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
data-slot="tabs-list"
|
||||||
|
data-variant={variant}
|
||||||
|
className={cn(tabsListVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsTrigger({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
data-slot="tabs-trigger"
|
||||||
|
className={cn(
|
||||||
|
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent",
|
||||||
|
"data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground",
|
||||||
|
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsContent({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
data-slot="tabs-content"
|
||||||
|
className={cn("flex-1 outline-none", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
||||||
@ -14,13 +14,13 @@ export function UserInfo({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Avatar className="h-8 w-8 overflow-hidden rounded-full">
|
<Avatar className="h-8 w-8 overflow-hidden rounded-full">
|
||||||
<AvatarImage src={user.avatar} alt={user.name} />
|
<AvatarImage src={user.avatar} alt={user.full_name} />
|
||||||
<AvatarFallback className="rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white">
|
<AvatarFallback className="rounded-lg bg-neutral-200 text-black dark:bg-neutral-700 dark:text-white">
|
||||||
{getInitials(user.name)}
|
{getInitials(user.full_name ?? '')}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="grid flex-1 text-left text-sm leading-tight">
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
||||||
<span className="truncate font-medium">{user.name}</span>
|
<span className="truncate font-medium">{user.full_name}</span>
|
||||||
{showEmail && (
|
{showEmail && (
|
||||||
<span className="truncate text-xs text-muted-foreground">
|
<span className="truncate text-xs text-muted-foreground">
|
||||||
{user.email}
|
{user.email}
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
import { Link, router } from '@inertiajs/react';
|
|
||||||
import { LogOut, Settings } from 'lucide-react';
|
|
||||||
import {
|
import {
|
||||||
DropdownMenuGroup,
|
DropdownMenuGroup,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuLabel,
|
DropdownMenuSeparator
|
||||||
DropdownMenuSeparator,
|
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
import { UserInfo } from '@/components/user-info';
|
|
||||||
import { useMobileNavigation } from '@/hooks/use-mobile-navigation';
|
import { useMobileNavigation } from '@/hooks/use-mobile-navigation';
|
||||||
import { logout } from '@/routes';
|
import { logout } from '@/routes';
|
||||||
import { edit } from '@/routes/profile';
|
import { edit } from '@/routes/profile';
|
||||||
import type { User } from '@/types';
|
import type { User } from '@/types';
|
||||||
|
import { Link, router } from '@inertiajs/react';
|
||||||
|
import { LogOut, Settings } from 'lucide-react';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
user: User;
|
user: User;
|
||||||
@ -26,12 +24,6 @@ export function UserMenuContent({ user }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<DropdownMenuLabel className="p-0 font-normal">
|
|
||||||
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
||||||
<UserInfo user={user} showEmail={true} />
|
|
||||||
</div>
|
|
||||||
</DropdownMenuLabel>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<DropdownMenuItem asChild>
|
<DropdownMenuItem asChild>
|
||||||
<Link
|
<Link
|
||||||
@ -41,7 +33,7 @@ export function UserMenuContent({ user }: Props) {
|
|||||||
onClick={cleanup}
|
onClick={cleanup}
|
||||||
>
|
>
|
||||||
<Settings className="mr-2" />
|
<Settings className="mr-2" />
|
||||||
Settings
|
Pengaturan
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
@ -55,7 +47,7 @@ export function UserMenuContent({ user }: Props) {
|
|||||||
data-test="logout-button"
|
data-test="logout-button"
|
||||||
>
|
>
|
||||||
<LogOut className="mr-2" />
|
<LogOut className="mr-2" />
|
||||||
Log out
|
Keluar
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -8,6 +8,7 @@ function getInitial(name: string): string {
|
|||||||
|
|
||||||
export function useInitials(): GetInitialsFn {
|
export function useInitials(): GetInitialsFn {
|
||||||
return useCallback((fullName: string): string => {
|
return useCallback((fullName: string): string => {
|
||||||
|
console.log(fullName)
|
||||||
const names = fullName.trim().split(/\s+/u).filter(Boolean);
|
const names = fullName.trim().split(/\s+/u).filter(Boolean);
|
||||||
|
|
||||||
if (names.length === 0) {
|
if (names.length === 0) {
|
||||||
|
|||||||
@ -1,36 +1,36 @@
|
|||||||
import { Head } from '@inertiajs/react';
|
import { ChartAreaInteractive } from "@/components/chart-area-interactive";
|
||||||
import { PlaceholderPattern } from '@/components/ui/placeholder-pattern';
|
import { DataTable } from "@/components/data-table";
|
||||||
import { dashboard } from '@/routes';
|
import { SectionCards } from "@/components/section-cards";
|
||||||
|
import {
|
||||||
|
SidebarInset,
|
||||||
|
SidebarProvider,
|
||||||
|
} from "@/components/ui/sidebar";
|
||||||
|
|
||||||
export default function Dashboard() {
|
import data from "./data/data.json";
|
||||||
|
|
||||||
|
export default function Page() {
|
||||||
return (
|
return (
|
||||||
<>
|
<SidebarProvider
|
||||||
<Head title="Dashboard" />
|
style={
|
||||||
<div className="flex h-full flex-1 flex-col gap-4 overflow-x-auto rounded-xl p-4">
|
|
||||||
<div className="grid auto-rows-min gap-4 md:grid-cols-3">
|
|
||||||
<div className="relative aspect-video overflow-hidden rounded-xl border border-sidebar-border/70 dark:border-sidebar-border">
|
|
||||||
<PlaceholderPattern className="absolute inset-0 size-full stroke-neutral-900/20 dark:stroke-neutral-100/20" />
|
|
||||||
</div>
|
|
||||||
<div className="relative aspect-video overflow-hidden rounded-xl border border-sidebar-border/70 dark:border-sidebar-border">
|
|
||||||
<PlaceholderPattern className="absolute inset-0 size-full stroke-neutral-900/20 dark:stroke-neutral-100/20" />
|
|
||||||
</div>
|
|
||||||
<div className="relative aspect-video overflow-hidden rounded-xl border border-sidebar-border/70 dark:border-sidebar-border">
|
|
||||||
<PlaceholderPattern className="absolute inset-0 size-full stroke-neutral-900/20 dark:stroke-neutral-100/20" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="relative min-h-[100vh] flex-1 overflow-hidden rounded-xl border border-sidebar-border/70 md:min-h-min dark:border-sidebar-border">
|
|
||||||
<PlaceholderPattern className="absolute inset-0 size-full stroke-neutral-900/20 dark:stroke-neutral-100/20" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Dashboard.layout = {
|
|
||||||
breadcrumbs: [
|
|
||||||
{
|
{
|
||||||
title: 'Dashboard',
|
"--sidebar-width": "calc(var(--spacing) * 72)",
|
||||||
href: dashboard(),
|
"--header-height": "calc(var(--spacing) * 12)",
|
||||||
},
|
} as React.CSSProperties
|
||||||
],
|
}
|
||||||
};
|
>
|
||||||
|
<SidebarInset>
|
||||||
|
<div className="flex flex-1 flex-col">
|
||||||
|
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||||
|
<div className="flex flex-col gap-4 py-4 md:gap-6 md:py-6">
|
||||||
|
<SectionCards />
|
||||||
|
<div className="px-4 lg:px-6">
|
||||||
|
<ChartAreaInteractive />
|
||||||
|
</div>
|
||||||
|
<DataTable data={data} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SidebarInset>
|
||||||
|
</SidebarProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
614
resources/js/pages/data/data.json
Normal file
614
resources/js/pages/data/data.json
Normal file
@ -0,0 +1,614 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"header": "Cover page",
|
||||||
|
"type": "Cover page",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "18",
|
||||||
|
"limit": "5",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"header": "Table of contents",
|
||||||
|
"type": "Table of contents",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "29",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"header": "Executive summary",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "10",
|
||||||
|
"limit": "13",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"header": "Technical approach",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"header": "Design",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "2",
|
||||||
|
"limit": "16",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"header": "Capabilities",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "20",
|
||||||
|
"limit": "8",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 7,
|
||||||
|
"header": "Integration with existing systems",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "21",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 8,
|
||||||
|
"header": "Innovation and Advantages",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "25",
|
||||||
|
"limit": "26",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 9,
|
||||||
|
"header": "Overview of EMR's Innovative Solutions",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "7",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 10,
|
||||||
|
"header": "Advanced Algorithms and Machine Learning",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "30",
|
||||||
|
"limit": "28",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"header": "Adaptive Communication Protocols",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "9",
|
||||||
|
"limit": "31",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"header": "Advantages Over Current Technologies",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "12",
|
||||||
|
"limit": "0",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"header": "Past Performance",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "33",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"header": "Customer Feedback and Satisfaction Levels",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "34",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"header": "Implementation Challenges and Solutions",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "3",
|
||||||
|
"limit": "35",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 16,
|
||||||
|
"header": "Security Measures and Data Protection Policies",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "6",
|
||||||
|
"limit": "36",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 17,
|
||||||
|
"header": "Scalability and Future Proofing",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "4",
|
||||||
|
"limit": "37",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 18,
|
||||||
|
"header": "Cost-Benefit Analysis",
|
||||||
|
"type": "Plain language",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "14",
|
||||||
|
"limit": "38",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 19,
|
||||||
|
"header": "User Training and Onboarding Experience",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "17",
|
||||||
|
"limit": "39",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 20,
|
||||||
|
"header": "Future Development Roadmap",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "11",
|
||||||
|
"limit": "40",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"header": "System Architecture Overview",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "24",
|
||||||
|
"limit": "18",
|
||||||
|
"reviewer": "Maya Johnson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"header": "Risk Management Plan",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "22",
|
||||||
|
"reviewer": "Carlos Rodriguez"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"header": "Compliance Documentation",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "31",
|
||||||
|
"limit": "27",
|
||||||
|
"reviewer": "Sarah Chen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 24,
|
||||||
|
"header": "API Documentation",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "8",
|
||||||
|
"limit": "12",
|
||||||
|
"reviewer": "Raj Patel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 25,
|
||||||
|
"header": "User Interface Mockups",
|
||||||
|
"type": "Visual",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "25",
|
||||||
|
"reviewer": "Leila Ahmadi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 26,
|
||||||
|
"header": "Database Schema",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "20",
|
||||||
|
"reviewer": "Thomas Wilson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 27,
|
||||||
|
"header": "Testing Methodology",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "17",
|
||||||
|
"limit": "14",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 28,
|
||||||
|
"header": "Deployment Strategy",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "26",
|
||||||
|
"limit": "30",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 29,
|
||||||
|
"header": "Budget Breakdown",
|
||||||
|
"type": "Financial",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "13",
|
||||||
|
"limit": "16",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 30,
|
||||||
|
"header": "Market Analysis",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "29",
|
||||||
|
"limit": "32",
|
||||||
|
"reviewer": "Sophia Martinez"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"header": "Competitor Comparison",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "21",
|
||||||
|
"limit": "19",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"header": "Maintenance Plan",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "16",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Alex Thompson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"header": "User Personas",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Nina Patel"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 34,
|
||||||
|
"header": "Accessibility Compliance",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "18",
|
||||||
|
"limit": "21",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 35,
|
||||||
|
"header": "Performance Metrics",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "23",
|
||||||
|
"limit": "26",
|
||||||
|
"reviewer": "David Kim"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 36,
|
||||||
|
"header": "Disaster Recovery Plan",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "14",
|
||||||
|
"limit": "17",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 37,
|
||||||
|
"header": "Third-party Integrations",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "25",
|
||||||
|
"limit": "28",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 38,
|
||||||
|
"header": "User Feedback Summary",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "20",
|
||||||
|
"limit": "15",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 39,
|
||||||
|
"header": "Localization Strategy",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "12",
|
||||||
|
"limit": "19",
|
||||||
|
"reviewer": "Maria Garcia"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 40,
|
||||||
|
"header": "Mobile Compatibility",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "28",
|
||||||
|
"limit": "31",
|
||||||
|
"reviewer": "James Wilson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 41,
|
||||||
|
"header": "Data Migration Plan",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "22",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 42,
|
||||||
|
"header": "Quality Assurance Protocols",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "30",
|
||||||
|
"limit": "33",
|
||||||
|
"reviewer": "Priya Singh"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 43,
|
||||||
|
"header": "Stakeholder Analysis",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "11",
|
||||||
|
"limit": "14",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 44,
|
||||||
|
"header": "Environmental Impact Assessment",
|
||||||
|
"type": "Research",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "24",
|
||||||
|
"limit": "27",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 45,
|
||||||
|
"header": "Intellectual Property Rights",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "17",
|
||||||
|
"limit": "20",
|
||||||
|
"reviewer": "Sarah Johnson"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 46,
|
||||||
|
"header": "Customer Support Framework",
|
||||||
|
"type": "Narrative",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "25",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 47,
|
||||||
|
"header": "Version Control Strategy",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "18",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 48,
|
||||||
|
"header": "Continuous Integration Pipeline",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "26",
|
||||||
|
"limit": "29",
|
||||||
|
"reviewer": "Michael Chen"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 49,
|
||||||
|
"header": "Regulatory Compliance",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "13",
|
||||||
|
"limit": "16",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 50,
|
||||||
|
"header": "User Authentication System",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "28",
|
||||||
|
"limit": "31",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 51,
|
||||||
|
"header": "Data Analytics Framework",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "21",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 52,
|
||||||
|
"header": "Cloud Infrastructure",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "16",
|
||||||
|
"limit": "19",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 53,
|
||||||
|
"header": "Network Security Measures",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "29",
|
||||||
|
"limit": "32",
|
||||||
|
"reviewer": "Lisa Wong"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 54,
|
||||||
|
"header": "Project Timeline",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "14",
|
||||||
|
"limit": "17",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 55,
|
||||||
|
"header": "Resource Allocation",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "30",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 56,
|
||||||
|
"header": "Team Structure and Roles",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "20",
|
||||||
|
"limit": "23",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 57,
|
||||||
|
"header": "Communication Protocols",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "15",
|
||||||
|
"limit": "18",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 58,
|
||||||
|
"header": "Success Metrics",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "30",
|
||||||
|
"limit": "33",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 59,
|
||||||
|
"header": "Internationalization Support",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "23",
|
||||||
|
"limit": "26",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 60,
|
||||||
|
"header": "Backup and Recovery Procedures",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "18",
|
||||||
|
"limit": "21",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 61,
|
||||||
|
"header": "Monitoring and Alerting System",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "25",
|
||||||
|
"limit": "28",
|
||||||
|
"reviewer": "Daniel Park"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 62,
|
||||||
|
"header": "Code Review Guidelines",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "12",
|
||||||
|
"limit": "15",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 63,
|
||||||
|
"header": "Documentation Standards",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "27",
|
||||||
|
"limit": "30",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 64,
|
||||||
|
"header": "Release Management Process",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "22",
|
||||||
|
"limit": "25",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 65,
|
||||||
|
"header": "Feature Prioritization Matrix",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "19",
|
||||||
|
"limit": "22",
|
||||||
|
"reviewer": "Emma Davis"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 66,
|
||||||
|
"header": "Technical Debt Assessment",
|
||||||
|
"type": "Technical content",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "24",
|
||||||
|
"limit": "27",
|
||||||
|
"reviewer": "Eddie Lake"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 67,
|
||||||
|
"header": "Capacity Planning",
|
||||||
|
"type": "Planning",
|
||||||
|
"status": "In Process",
|
||||||
|
"target": "21",
|
||||||
|
"limit": "24",
|
||||||
|
"reviewer": "Jamik Tashpulatov"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 68,
|
||||||
|
"header": "Service Level Agreements",
|
||||||
|
"type": "Legal",
|
||||||
|
"status": "Done",
|
||||||
|
"target": "26",
|
||||||
|
"limit": "29",
|
||||||
|
"reviewer": "Assign reviewer"
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -1,6 +1,6 @@
|
|||||||
export type User = {
|
export type User = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
full_name: string;
|
||||||
email: string;
|
email: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
email_verified_at: string | null;
|
email_verified_at: string | null;
|
||||||
@ -13,22 +13,3 @@ export type User = {
|
|||||||
export type Auth = {
|
export type Auth = {
|
||||||
user: User;
|
user: User;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* @chisel-passkeys */
|
|
||||||
export type Passkey = {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
authenticator: string | null;
|
|
||||||
created_at_diff: string;
|
|
||||||
last_used_at_diff: string | null;
|
|
||||||
};
|
|
||||||
/* @end-chisel-passkeys */
|
|
||||||
|
|
||||||
export type TwoFactorSetupData = {
|
|
||||||
svg: string;
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TwoFactorSecretKey = {
|
|
||||||
secretKey: string;
|
|
||||||
};
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user