p3rc03 commited on
Commit
60b2d8c
·
unverified ·
1 Parent(s): 8270aad

Upload 98 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +24 -0
  2. README.md +73 -12
  3. bun.lockb +3 -0
  4. components.json +20 -0
  5. eslint.config.js +29 -0
  6. index.html +26 -0
  7. package-lock.json +0 -0
  8. package.json +85 -0
  9. postcss.config.js +6 -0
  10. public/favicon.ico +0 -0
  11. public/placeholder.svg +1 -0
  12. public/robots.txt +14 -0
  13. src/App.css +42 -0
  14. src/App.tsx +48 -0
  15. src/components/dashboard/AiInsights.tsx +268 -0
  16. src/components/dashboard/BlockchainIdentity.tsx +194 -0
  17. src/components/dashboard/EngineArchitecture.tsx +162 -0
  18. src/components/dashboard/Header.tsx +51 -0
  19. src/components/dashboard/KpiCard.tsx +71 -0
  20. src/components/dashboard/Sidebar.tsx +112 -0
  21. src/components/dashboard/SystemArchitecture.tsx +160 -0
  22. src/components/digital-twin/FactoryVisualization.tsx +214 -0
  23. src/components/training/BlockchainMonitor.tsx +330 -0
  24. src/components/training/TrainingMonitor.tsx +363 -0
  25. src/components/ui/accordion.tsx +56 -0
  26. src/components/ui/alert-dialog.tsx +139 -0
  27. src/components/ui/alert.tsx +59 -0
  28. src/components/ui/aspect-ratio.tsx +5 -0
  29. src/components/ui/avatar.tsx +48 -0
  30. src/components/ui/badge.tsx +36 -0
  31. src/components/ui/breadcrumb.tsx +115 -0
  32. src/components/ui/button.tsx +56 -0
  33. src/components/ui/calendar.tsx +64 -0
  34. src/components/ui/card.tsx +79 -0
  35. src/components/ui/carousel.tsx +260 -0
  36. src/components/ui/chart.tsx +363 -0
  37. src/components/ui/checkbox.tsx +28 -0
  38. src/components/ui/collapsible.tsx +9 -0
  39. src/components/ui/command.tsx +153 -0
  40. src/components/ui/context-menu.tsx +198 -0
  41. src/components/ui/dialog.tsx +120 -0
  42. src/components/ui/drawer.tsx +116 -0
  43. src/components/ui/dropdown-menu.tsx +198 -0
  44. src/components/ui/form.tsx +176 -0
  45. src/components/ui/hover-card.tsx +27 -0
  46. src/components/ui/input-otp.tsx +69 -0
  47. src/components/ui/input.tsx +22 -0
  48. src/components/ui/label.tsx +24 -0
  49. src/components/ui/menubar.tsx +234 -0
  50. src/components/ui/navigation-menu.tsx +128 -0
.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
README.md CHANGED
@@ -1,12 +1,73 @@
1
- ---
2
- title: M3S
3
- emoji: 📚
4
- colorFrom: yellow
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- license: mit
9
- short_description: AI CRYPTO MES
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Welcome to your Lovable project
2
+
3
+ ## Project info
4
+
5
+ **URL**: https://lovable.dev/projects/aa9d567c-01da-4e62-82b1-9aa7e2a588e1
6
+
7
+ ## How can I edit this code?
8
+
9
+ There are several ways of editing your application.
10
+
11
+ **Use Lovable**
12
+
13
+ Simply visit the [Lovable Project](https://lovable.dev/projects/aa9d567c-01da-4e62-82b1-9aa7e2a588e1) and start prompting.
14
+
15
+ Changes made via Lovable will be committed automatically to this repo.
16
+
17
+ **Use your preferred IDE**
18
+
19
+ If you want to work locally using your own IDE, you can clone this repo and push changes. Pushed changes will also be reflected in Lovable.
20
+
21
+ The only requirement is having Node.js & npm installed - [install with nvm](https://github.com/nvm-sh/nvm#installing-and-updating)
22
+
23
+ Follow these steps:
24
+
25
+ ```sh
26
+ # Step 1: Clone the repository using the project's Git URL.
27
+ git clone <YOUR_GIT_URL>
28
+
29
+ # Step 2: Navigate to the project directory.
30
+ cd <YOUR_PROJECT_NAME>
31
+
32
+ # Step 3: Install the necessary dependencies.
33
+ npm i
34
+
35
+ # Step 4: Start the development server with auto-reloading and an instant preview.
36
+ npm run dev
37
+ ```
38
+
39
+ **Edit a file directly in GitHub**
40
+
41
+ - Navigate to the desired file(s).
42
+ - Click the "Edit" button (pencil icon) at the top right of the file view.
43
+ - Make your changes and commit the changes.
44
+
45
+ **Use GitHub Codespaces**
46
+
47
+ - Navigate to the main page of your repository.
48
+ - Click on the "Code" button (green button) near the top right.
49
+ - Select the "Codespaces" tab.
50
+ - Click on "New codespace" to launch a new Codespace environment.
51
+ - Edit files directly within the Codespace and commit and push your changes once you're done.
52
+
53
+ ## What technologies are used for this project?
54
+
55
+ This project is built with:
56
+
57
+ - Vite
58
+ - TypeScript
59
+ - React
60
+ - shadcn-ui
61
+ - Tailwind CSS
62
+
63
+ ## How can I deploy this project?
64
+
65
+ Simply open [Lovable](https://lovable.dev/projects/aa9d567c-01da-4e62-82b1-9aa7e2a588e1) and click on Share -> Publish.
66
+
67
+ ## Can I connect a custom domain to my Lovable project?
68
+
69
+ Yes, you can!
70
+
71
+ To connect a domain, navigate to Project > Settings > Domains and click Connect Domain.
72
+
73
+ Read more here: [Setting up a custom domain](https://docs.lovable.dev/tips-tricks/custom-domain#step-by-step-guide)
bun.lockb ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a3c575fd4a99dc9d1d74a4a7a31b979d577c15f379bc5cb0dd7e823586e98c23
3
+ size 198351
components.json ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "default",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "tailwind.config.ts",
8
+ "css": "src/index.css",
9
+ "baseColor": "slate",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@/components",
15
+ "utils": "@/lib/utils",
16
+ "ui": "@/components/ui",
17
+ "lib": "@/lib",
18
+ "hooks": "@/hooks"
19
+ }
20
+ }
eslint.config.js ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import js from "@eslint/js";
2
+ import globals from "globals";
3
+ import reactHooks from "eslint-plugin-react-hooks";
4
+ import reactRefresh from "eslint-plugin-react-refresh";
5
+ import tseslint from "typescript-eslint";
6
+
7
+ export default tseslint.config(
8
+ { ignores: ["dist"] },
9
+ {
10
+ extends: [js.configs.recommended, ...tseslint.configs.recommended],
11
+ files: ["**/*.{ts,tsx}"],
12
+ languageOptions: {
13
+ ecmaVersion: 2020,
14
+ globals: globals.browser,
15
+ },
16
+ plugins: {
17
+ "react-hooks": reactHooks,
18
+ "react-refresh": reactRefresh,
19
+ },
20
+ rules: {
21
+ ...reactHooks.configs.recommended.rules,
22
+ "react-refresh/only-export-components": [
23
+ "warn",
24
+ { allowConstantExport: true },
25
+ ],
26
+ "@typescript-eslint/no-unused-vars": "off",
27
+ },
28
+ }
29
+ );
index.html ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>digital-factory-genesis</title>
7
+ <meta name="description" content="Lovable Generated Project" />
8
+ <meta name="author" content="Lovable" />
9
+
10
+ <meta property="og:title" content="digital-factory-genesis" />
11
+ <meta property="og:description" content="Lovable Generated Project" />
12
+ <meta property="og:type" content="website" />
13
+ <meta property="og:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
14
+
15
+ <meta name="twitter:card" content="summary_large_image" />
16
+ <meta name="twitter:site" content="@lovable_dev" />
17
+ <meta name="twitter:image" content="https://lovable.dev/opengraph-image-p98pqg.png" />
18
+ </head>
19
+
20
+ <body>
21
+ <div id="root"></div>
22
+ <!-- IMPORTANT: DO NOT REMOVE THIS SCRIPT TAG OR THIS VERY COMMENT! -->
23
+ <script src="https://cdn.gpteng.co/gptengineer.js" type="module"></script>
24
+ <script type="module" src="/src/main.tsx"></script>
25
+ </body>
26
+ </html>
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "vite_react_shadcn_ts",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "build:dev": "vite build --mode development",
10
+ "lint": "eslint .",
11
+ "preview": "vite preview"
12
+ },
13
+ "dependencies": {
14
+ "@hookform/resolvers": "^3.9.0",
15
+ "@radix-ui/react-accordion": "^1.2.0",
16
+ "@radix-ui/react-alert-dialog": "^1.1.1",
17
+ "@radix-ui/react-aspect-ratio": "^1.1.0",
18
+ "@radix-ui/react-avatar": "^1.1.0",
19
+ "@radix-ui/react-checkbox": "^1.1.1",
20
+ "@radix-ui/react-collapsible": "^1.1.0",
21
+ "@radix-ui/react-context-menu": "^2.2.1",
22
+ "@radix-ui/react-dialog": "^1.1.2",
23
+ "@radix-ui/react-dropdown-menu": "^2.1.1",
24
+ "@radix-ui/react-hover-card": "^1.1.1",
25
+ "@radix-ui/react-label": "^2.1.0",
26
+ "@radix-ui/react-menubar": "^1.1.1",
27
+ "@radix-ui/react-navigation-menu": "^1.2.0",
28
+ "@radix-ui/react-popover": "^1.1.1",
29
+ "@radix-ui/react-progress": "^1.1.0",
30
+ "@radix-ui/react-radio-group": "^1.2.0",
31
+ "@radix-ui/react-scroll-area": "^1.1.0",
32
+ "@radix-ui/react-select": "^2.1.1",
33
+ "@radix-ui/react-separator": "^1.1.0",
34
+ "@radix-ui/react-slider": "^1.2.0",
35
+ "@radix-ui/react-slot": "^1.1.0",
36
+ "@radix-ui/react-switch": "^1.1.0",
37
+ "@radix-ui/react-tabs": "^1.1.0",
38
+ "@radix-ui/react-toast": "^1.2.1",
39
+ "@radix-ui/react-toggle": "^1.1.0",
40
+ "@radix-ui/react-toggle-group": "^1.1.0",
41
+ "@radix-ui/react-tooltip": "^1.1.4",
42
+ "@react-three/drei": "^9.88.13",
43
+ "@tanstack/react-query": "^5.56.2",
44
+ "class-variance-authority": "^0.7.1",
45
+ "clsx": "^2.1.1",
46
+ "cmdk": "^1.0.0",
47
+ "date-fns": "^3.6.0",
48
+ "embla-carousel-react": "^8.3.0",
49
+ "input-otp": "^1.2.4",
50
+ "lucide-react": "^0.462.0",
51
+ "next-themes": "^0.3.0",
52
+ "react": "^18.3.1",
53
+ "react-day-picker": "^8.10.1",
54
+ "react-dom": "^18.3.1",
55
+ "react-hook-form": "^7.53.0",
56
+ "react-resizable-panels": "^2.1.3",
57
+ "react-router-dom": "^6.26.2",
58
+ "recharts": "^2.12.7",
59
+ "sonner": "^1.5.0",
60
+ "tailwind-merge": "^2.5.2",
61
+ "tailwindcss-animate": "^1.0.7",
62
+ "three": "^0.154.0",
63
+ "vaul": "^0.9.3",
64
+ "zod": "^3.23.8"
65
+ },
66
+ "devDependencies": {
67
+ "@eslint/js": "^9.9.0",
68
+ "@tailwindcss/typography": "^0.5.15",
69
+ "@types/node": "^22.5.5",
70
+ "@types/react": "^18.3.3",
71
+ "@types/react-dom": "^18.3.0",
72
+ "@vitejs/plugin-react-swc": "^3.5.0",
73
+ "autoprefixer": "^10.4.20",
74
+ "eslint": "^9.9.0",
75
+ "eslint-plugin-react-hooks": "^5.1.0-rc.0",
76
+ "eslint-plugin-react-refresh": "^0.4.9",
77
+ "globals": "^15.9.0",
78
+ "lovable-tagger": "^1.1.7",
79
+ "postcss": "^8.4.47",
80
+ "tailwindcss": "^3.4.11",
81
+ "typescript": "^5.5.3",
82
+ "typescript-eslint": "^8.0.1",
83
+ "vite": "^5.4.1"
84
+ }
85
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
public/favicon.ico ADDED
public/placeholder.svg ADDED
public/robots.txt ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ User-agent: Googlebot
2
+ Allow: /
3
+
4
+ User-agent: Bingbot
5
+ Allow: /
6
+
7
+ User-agent: Twitterbot
8
+ Allow: /
9
+
10
+ User-agent: facebookexternalhit
11
+ Allow: /
12
+
13
+ User-agent: *
14
+ Allow: /
src/App.css ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .logo {
9
+ height: 6em;
10
+ padding: 1.5em;
11
+ will-change: filter;
12
+ transition: filter 300ms;
13
+ }
14
+ .logo:hover {
15
+ filter: drop-shadow(0 0 2em #646cffaa);
16
+ }
17
+ .logo.react:hover {
18
+ filter: drop-shadow(0 0 2em #61dafbaa);
19
+ }
20
+
21
+ @keyframes logo-spin {
22
+ from {
23
+ transform: rotate(0deg);
24
+ }
25
+ to {
26
+ transform: rotate(360deg);
27
+ }
28
+ }
29
+
30
+ @media (prefers-reduced-motion: no-preference) {
31
+ a:nth-of-type(2) .logo {
32
+ animation: logo-spin infinite 20s linear;
33
+ }
34
+ }
35
+
36
+ .card {
37
+ padding: 2em;
38
+ }
39
+
40
+ .read-the-docs {
41
+ color: #888;
42
+ }
src/App.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import { Toaster } from "@/components/ui/toaster";
3
+ import { Toaster as Sonner } from "@/components/ui/sonner";
4
+ import { TooltipProvider } from "@/components/ui/tooltip";
5
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
6
+ import { BrowserRouter, Routes, Route } from "react-router-dom";
7
+ import Index from "./pages/Index";
8
+ import NotFound from "./pages/NotFound";
9
+ import Production from "./pages/Production";
10
+ import AIInsights from "./pages/AIInsights";
11
+ import DigitalTwin from "./pages/DigitalTwin";
12
+ import Blockchain from "./pages/Blockchain";
13
+ import Identity from "./pages/Identity";
14
+ import Analytics from "./pages/Analytics";
15
+ import Settings from "./pages/Settings";
16
+ import ModelTraining from "./pages/ModelTraining";
17
+ import AIEngine from "./pages/AIEngine";
18
+ import CryptoEngine from "./pages/CryptoEngine";
19
+
20
+ const queryClient = new QueryClient();
21
+
22
+ const App = () => (
23
+ <QueryClientProvider client={queryClient}>
24
+ <TooltipProvider>
25
+ <Toaster />
26
+ <Sonner />
27
+ <BrowserRouter>
28
+ <Routes>
29
+ <Route path="/" element={<Index />} />
30
+ <Route path="/production" element={<Production />} />
31
+ <Route path="/insights" element={<AIInsights />} />
32
+ <Route path="/digital-twin" element={<DigitalTwin />} />
33
+ <Route path="/blockchain" element={<Blockchain />} />
34
+ <Route path="/identity" element={<Identity />} />
35
+ <Route path="/analytics" element={<Analytics />} />
36
+ <Route path="/settings" element={<Settings />} />
37
+ <Route path="/model-training" element={<ModelTraining />} />
38
+ <Route path="/ai-engine" element={<AIEngine />} />
39
+ <Route path="/crypto-engine" element={<CryptoEngine />} />
40
+ {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
41
+ <Route path="*" element={<NotFound />} />
42
+ </Routes>
43
+ </BrowserRouter>
44
+ </TooltipProvider>
45
+ </QueryClientProvider>
46
+ );
47
+
48
+ export default App;
src/components/dashboard/AiInsights.tsx ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+ import { PieChart, Pie, Cell, ResponsiveContainer, LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts';
4
+
5
+ const AiInsights = () => {
6
+ const [loading, setLoading] = useState(true);
7
+ const [oeeData, setOeeData] = useState({
8
+ availability: 0,
9
+ performance: 0,
10
+ quality: 0,
11
+ overall: 0
12
+ });
13
+
14
+ const [anomalies, setAnomalies] = useState([
15
+ { id: 1, machine: 'CNC Mill #103', metric: 'Spindle Vibration', severity: 'Medium', time: '08:24:15' },
16
+ { id: 2, machine: 'Robotic Arm #47', metric: 'Joint Temperature', severity: 'Low', time: '09:17:32' },
17
+ ]);
18
+
19
+ const [recommendations, setRecommendations] = useState([
20
+ { id: 1, action: 'Adjust pressure settings on Assembly Line #2', impact: 'Potential 5% quality improvement', confidence: 'High' },
21
+ { id: 2, action: 'Schedule preventive maintenance for CNC Mill #103', impact: 'Avoid 4h downtime next week', confidence: 'Medium' },
22
+ ]);
23
+
24
+ // Sample temperature prediction data
25
+ const predictionData = [
26
+ { time: '1h', actual: 76, predicted: 77 },
27
+ { time: '2h', actual: 78, predicted: 80 },
28
+ { time: '3h', actual: 82, predicted: 84 },
29
+ { time: '4h', actual: 85, predicted: 88 },
30
+ { time: '5h', actual: 87, predicted: 92 },
31
+ { time: '6h', actual: null, predicted: 96 },
32
+ { time: '7h', actual: null, predicted: 99 },
33
+ { time: '8h', actual: null, predicted: 102 },
34
+ ];
35
+
36
+ // Simulate data loading
37
+ useEffect(() => {
38
+ // Simulate fetching data
39
+ const timer = setTimeout(() => {
40
+ setOeeData({
41
+ availability: 92,
42
+ performance: 85,
43
+ quality: 97,
44
+ overall: 76
45
+ });
46
+ setLoading(false);
47
+ }, 1500);
48
+
49
+ return () => clearTimeout(timer);
50
+ }, []);
51
+
52
+ // OEE chart data
53
+ const oeeChartData = [
54
+ { name: 'Availability', value: oeeData.availability },
55
+ { name: 'Performance', value: oeeData.performance },
56
+ { name: 'Quality', value: oeeData.quality },
57
+ ];
58
+
59
+ const COLORS = ['#00A9A5', '#FFA500', '#4B5563'];
60
+
61
+ const getSeverityColor = (severity: string) => {
62
+ switch(severity.toLowerCase()) {
63
+ case 'high': return 'text-factory-danger bg-red-50 dark:bg-red-900/20';
64
+ case 'medium': return 'text-factory-amber bg-yellow-50 dark:bg-yellow-900/20';
65
+ case 'low': return 'text-blue-600 bg-blue-50 dark:bg-blue-900/20';
66
+ default: return 'text-gray-600 bg-gray-50 dark:bg-gray-800';
67
+ }
68
+ };
69
+
70
+ return (
71
+ <div className="bg-white dark:bg-factory-blue rounded-lg p-4 shadow h-full">
72
+ <h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">AI Insights & Predictions</h2>
73
+
74
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
75
+ {/* OEE Chart Section */}
76
+ <div className="border dark:border-factory-blue-light rounded-md overflow-hidden">
77
+ <div className="bg-gray-50 dark:bg-factory-blue-light p-3 border-b dark:border-factory-blue-light">
78
+ <h3 className="font-medium text-sm text-gray-700 dark:text-gray-300">Overall Equipment Effectiveness</h3>
79
+ </div>
80
+ <div className="p-3 h-44">
81
+ {loading ? (
82
+ <div className="h-full w-full flex items-center justify-center">
83
+ <div className="w-12 h-12 border-2 border-factory-teal border-t-transparent rounded-full animate-spin"></div>
84
+ </div>
85
+ ) : (
86
+ <div className="flex h-full">
87
+ <div className="w-1/2 h-full">
88
+ <ResponsiveContainer width="100%" height="100%">
89
+ <PieChart>
90
+ <Pie
91
+ data={oeeChartData}
92
+ cx="50%"
93
+ cy="50%"
94
+ innerRadius={35}
95
+ outerRadius={50}
96
+ paddingAngle={2}
97
+ dataKey="value"
98
+ startAngle={90}
99
+ endAngle={-270}
100
+ >
101
+ {oeeChartData.map((entry, index) => (
102
+ <Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
103
+ ))}
104
+ </Pie>
105
+ <text x="50%" y="50%" textAnchor="middle" dominantBaseline="middle" className="text-xl font-bold fill-gray-900 dark:fill-white">
106
+ {oeeData.overall}%
107
+ </text>
108
+ </PieChart>
109
+ </ResponsiveContainer>
110
+ </div>
111
+ <div className="w-1/2 flex flex-col justify-center">
112
+ <div className="space-y-2">
113
+ {oeeChartData.map((item, index) => (
114
+ <div key={index} className="flex items-center">
115
+ <span className="w-3 h-3 rounded-sm mr-2" style={{ backgroundColor: COLORS[index] }}></span>
116
+ <span className="text-xs text-gray-600 dark:text-gray-300">{item.name}:</span>
117
+ <span className="ml-auto text-xs font-medium text-gray-900 dark:text-white">{item.value}%</span>
118
+ </div>
119
+ ))}
120
+ </div>
121
+ </div>
122
+ </div>
123
+ )}
124
+ </div>
125
+ </div>
126
+
127
+ {/* Temperature Prediction Chart */}
128
+ <div className="border dark:border-factory-blue-light rounded-md overflow-hidden">
129
+ <div className="bg-gray-50 dark:bg-factory-blue-light p-3 border-b dark:border-factory-blue-light">
130
+ <h3 className="font-medium text-sm text-gray-700 dark:text-gray-300">Equipment Temperature Prediction</h3>
131
+ </div>
132
+ <div className="p-3 h-44">
133
+ {loading ? (
134
+ <div className="h-full w-full flex items-center justify-center">
135
+ <div className="w-12 h-12 border-2 border-factory-teal border-t-transparent rounded-full animate-spin"></div>
136
+ </div>
137
+ ) : (
138
+ <ResponsiveContainer width="100%" height="100%">
139
+ <LineChart
140
+ data={predictionData}
141
+ margin={{ top: 5, right: 5, left: 0, bottom: 5 }}
142
+ >
143
+ <CartesianGrid strokeDasharray="3 3" stroke="#374151" />
144
+ <XAxis dataKey="time" stroke="#9CA3AF" />
145
+ <YAxis stroke="#9CA3AF" />
146
+ <Tooltip
147
+ contentStyle={{
148
+ backgroundColor: '#1A2942',
149
+ borderColor: '#374151',
150
+ color: '#E5E7EB'
151
+ }}
152
+ />
153
+ <Line
154
+ type="monotone"
155
+ dataKey="actual"
156
+ stroke="#00A9A5"
157
+ strokeWidth={2}
158
+ dot={{ r: 3 }}
159
+ activeDot={{ r: 5 }}
160
+ />
161
+ <Line
162
+ type="monotone"
163
+ dataKey="predicted"
164
+ stroke="#FFA500"
165
+ strokeWidth={2}
166
+ strokeDasharray="5 5"
167
+ dot={{ r: 3 }}
168
+ />
169
+ </LineChart>
170
+ </ResponsiveContainer>
171
+ )}
172
+ </div>
173
+ </div>
174
+
175
+ {/* Anomaly Detection Section */}
176
+ <div className="border dark:border-factory-blue-light rounded-md overflow-hidden">
177
+ <div className="bg-gray-50 dark:bg-factory-blue-light p-3 border-b dark:border-factory-blue-light flex justify-between items-center">
178
+ <h3 className="font-medium text-sm text-gray-700 dark:text-gray-300">Detected Anomalies</h3>
179
+ <span className="text-xs bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-300 px-2 py-0.5 rounded-full">
180
+ {anomalies.length} Active
181
+ </span>
182
+ </div>
183
+ <div className="p-3 max-h-44 overflow-y-auto">
184
+ {loading ? (
185
+ <div className="space-y-3">
186
+ {[1, 2].map((_, idx) => (
187
+ <div key={idx} className="animate-pulse flex space-x-4">
188
+ <div className="flex-1 space-y-2 py-1">
189
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-3/4"></div>
190
+ <div className="h-3 bg-gray-200 dark:bg-factory-blue-light rounded w-5/6"></div>
191
+ </div>
192
+ </div>
193
+ ))}
194
+ </div>
195
+ ) : (
196
+ <div className="space-y-2">
197
+ {anomalies.map((anomaly) => (
198
+ <div key={anomaly.id} className="p-2 border dark:border-factory-blue-light rounded-md">
199
+ <div className="flex justify-between items-start">
200
+ <div>
201
+ <h4 className="text-sm font-medium text-gray-900 dark:text-white">{anomaly.machine}</h4>
202
+ <p className="text-xs text-gray-500 dark:text-gray-400">{anomaly.metric}</p>
203
+ </div>
204
+ <div className={`px-2 py-1 rounded-full text-xs font-medium ${getSeverityColor(anomaly.severity)}`}>
205
+ {anomaly.severity}
206
+ </div>
207
+ </div>
208
+ <div className="mt-1 flex items-center justify-between text-xs">
209
+ <span className="text-gray-500 dark:text-gray-400">Detected at {anomaly.time}</span>
210
+ <button className="text-factory-teal hover:text-factory-teal-dark transition-colors">
211
+ Investigate
212
+ </button>
213
+ </div>
214
+ </div>
215
+ ))}
216
+ </div>
217
+ )}
218
+ </div>
219
+ </div>
220
+
221
+ {/* AI Recommendations Section */}
222
+ <div className="border dark:border-factory-blue-light rounded-md overflow-hidden">
223
+ <div className="bg-gray-50 dark:bg-factory-blue-light p-3 border-b dark:border-factory-blue-light">
224
+ <h3 className="font-medium text-sm text-gray-700 dark:text-gray-300">AI Recommendations</h3>
225
+ </div>
226
+ <div className="p-3 max-h-44 overflow-y-auto">
227
+ {loading ? (
228
+ <div className="space-y-3">
229
+ {[1, 2].map((_, idx) => (
230
+ <div key={idx} className="animate-pulse flex space-x-4">
231
+ <div className="flex-1 space-y-2 py-1">
232
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-3/4"></div>
233
+ <div className="h-3 bg-gray-200 dark:bg-factory-blue-light rounded w-5/6"></div>
234
+ </div>
235
+ </div>
236
+ ))}
237
+ </div>
238
+ ) : (
239
+ <div className="space-y-2">
240
+ {recommendations.map((rec) => (
241
+ <div key={rec.id} className="p-2 border dark:border-factory-blue-light rounded-md">
242
+ <div className="flex justify-between">
243
+ <h4 className="text-sm font-medium text-gray-900 dark:text-white">{rec.action}</h4>
244
+ <span className="text-xs font-medium px-2 py-0.5 rounded-full bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-300">
245
+ {rec.confidence}
246
+ </span>
247
+ </div>
248
+ <p className="mt-1 text-xs text-gray-500 dark:text-gray-400">{rec.impact}</p>
249
+ <div className="mt-2 flex space-x-2 justify-end">
250
+ <button className="text-xs text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors">
251
+ Dismiss
252
+ </button>
253
+ <button className="text-xs text-factory-teal hover:text-factory-teal-dark transition-colors">
254
+ Apply
255
+ </button>
256
+ </div>
257
+ </div>
258
+ ))}
259
+ </div>
260
+ )}
261
+ </div>
262
+ </div>
263
+ </div>
264
+ </div>
265
+ );
266
+ };
267
+
268
+ export default AiInsights;
src/components/dashboard/BlockchainIdentity.tsx ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+ import { Check, Search } from 'lucide-react';
4
+
5
+ const BlockchainIdentity = () => {
6
+ const [loadingId, setLoadingId] = useState(true);
7
+ const [loadingMachines, setLoadingMachines] = useState(true);
8
+ const [loadingBatches, setLoadingBatches] = useState(true);
9
+ const [verifiedItems, setVerifiedItems] = useState<Record<string, boolean>>({});
10
+
11
+ useEffect(() => {
12
+ // Simulate loading states
13
+ const timer1 = setTimeout(() => setLoadingId(false), 1200);
14
+ const timer2 = setTimeout(() => setLoadingMachines(false), 2000);
15
+ const timer3 = setTimeout(() => setLoadingBatches(false), 3000);
16
+
17
+ return () => {
18
+ clearTimeout(timer1);
19
+ clearTimeout(timer2);
20
+ clearTimeout(timer3);
21
+ };
22
+ }, []);
23
+
24
+ const machines = [
25
+ { id: '0x72F8...9A3B', name: 'CNC Mill #103', lastMaintenance: '2023-04-15', status: 'Active' },
26
+ { id: '0x91A3...F721', name: 'Robotic Arm #47', lastMaintenance: '2023-05-02', status: 'Maintenance' },
27
+ { id: '0x45D1...8E32', name: 'Assembly Line #2', lastMaintenance: '2023-04-28', status: 'Active' },
28
+ ];
29
+
30
+ const batches = [
31
+ { id: '0xBF72...1D43', product: 'Engine Block', quantity: 250, date: '2023-05-01', status: 'Completed' },
32
+ { id: '0x31E8...7A92', product: 'Transmission', quantity: 150, date: '2023-05-03', status: 'In Progress' },
33
+ ];
34
+
35
+ const verifyItem = (id: string) => {
36
+ setVerifiedItems(prev => ({ ...prev, [id]: true }));
37
+ };
38
+
39
+ return (
40
+ <div className="bg-white dark:bg-factory-blue rounded-lg p-4 shadow h-full">
41
+ <div className="flex justify-between items-center mb-4">
42
+ <h2 className="text-lg font-semibold text-gray-900 dark:text-white">Blockchain Identity & Traceability</h2>
43
+ <div className="bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-300 text-xs px-2 py-1 rounded-full flex items-center">
44
+ <div className="w-2 h-2 bg-green-500 rounded-full mr-1"></div>
45
+ Connected to Ethereum
46
+ </div>
47
+ </div>
48
+
49
+ <div className="space-y-4">
50
+ {/* Identity Verification Section */}
51
+ <div className="border dark:border-factory-blue-light rounded-md overflow-hidden">
52
+ <div className="bg-gray-50 dark:bg-factory-blue-light p-3 border-b dark:border-factory-blue-light">
53
+ <h3 className="font-medium text-sm text-gray-700 dark:text-gray-300">System Identity</h3>
54
+ </div>
55
+ <div className="p-3">
56
+ {loadingId ? (
57
+ <div className="animate-pulse flex space-x-4">
58
+ <div className="rounded-full bg-gray-200 dark:bg-factory-blue-light h-10 w-10"></div>
59
+ <div className="flex-1 space-y-2 py-1">
60
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-3/4"></div>
61
+ <div className="h-3 bg-gray-200 dark:bg-factory-blue-light rounded w-5/6"></div>
62
+ </div>
63
+ </div>
64
+ ) : (
65
+ <div className="flex items-center">
66
+ <div className="h-10 w-10 rounded-full bg-factory-teal-dark flex items-center justify-center text-white">
67
+ <Check className="h-5 w-5" />
68
+ </div>
69
+ <div className="ml-3">
70
+ <div className="text-sm font-medium text-gray-900 dark:text-white">Digital Factory Genesis</div>
71
+ <div className="text-xs text-gray-500 dark:text-gray-400 flex items-center">
72
+ <span>ID: 0x8F42...7B21</span>
73
+ <span className="mx-1">•</span>
74
+ <span className="text-factory-teal">Verified</span>
75
+ </div>
76
+ </div>
77
+ </div>
78
+ )}
79
+ </div>
80
+ </div>
81
+
82
+ {/* Machines Section */}
83
+ <div className="border dark:border-factory-blue-light rounded-md overflow-hidden">
84
+ <div className="bg-gray-50 dark:bg-factory-blue-light p-3 border-b dark:border-factory-blue-light">
85
+ <h3 className="font-medium text-sm text-gray-700 dark:text-gray-300">Machine NFTs</h3>
86
+ </div>
87
+ <div className="p-3">
88
+ {loadingMachines ? (
89
+ <div className="space-y-3">
90
+ {[1, 2, 3].map((_, idx) => (
91
+ <div key={idx} className="animate-pulse flex space-x-4">
92
+ <div className="flex-1 space-y-2 py-1">
93
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-3/4"></div>
94
+ <div className="h-3 bg-gray-200 dark:bg-factory-blue-light rounded w-5/6"></div>
95
+ </div>
96
+ </div>
97
+ ))}
98
+ </div>
99
+ ) : (
100
+ <div className="space-y-2">
101
+ {machines.map((machine) => (
102
+ <div key={machine.id} className="flex justify-between items-center p-2 hover:bg-gray-50 dark:hover:bg-factory-blue-light rounded-md transition-colors">
103
+ <div>
104
+ <div className="text-sm font-medium text-gray-900 dark:text-white">{machine.name}</div>
105
+ <div className="text-xs text-gray-500 dark:text-gray-400">ID: {machine.id}</div>
106
+ </div>
107
+ <div className="flex space-x-2">
108
+ <span className={`text-xs px-2 py-1 rounded-full ${
109
+ machine.status === 'Active'
110
+ ? 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300'
111
+ : 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-300'
112
+ }`}>
113
+ {machine.status}
114
+ </span>
115
+ {verifiedItems[machine.id] ? (
116
+ <span className="text-xs bg-factory-teal-dark/20 text-factory-teal px-2 py-1 rounded-full flex items-center">
117
+ <Check className="h-3 w-3 mr-1" /> Verified
118
+ </span>
119
+ ) : (
120
+ <button
121
+ onClick={() => verifyItem(machine.id)}
122
+ className="text-xs bg-gray-100 dark:bg-factory-blue-light text-gray-600 dark:text-gray-300 px-2 py-1 rounded-full flex items-center hover:bg-gray-200 dark:hover:bg-factory-blue transition-colors"
123
+ >
124
+ <Search className="h-3 w-3 mr-1" /> Verify
125
+ </button>
126
+ )}
127
+ </div>
128
+ </div>
129
+ ))}
130
+ </div>
131
+ )}
132
+ </div>
133
+ </div>
134
+
135
+ {/* Production Batches Section */}
136
+ <div className="border dark:border-factory-blue-light rounded-md overflow-hidden">
137
+ <div className="bg-gray-50 dark:bg-factory-blue-light p-3 border-b dark:border-factory-blue-light">
138
+ <h3 className="font-medium text-sm text-gray-700 dark:text-gray-300">Production Batch NFTs</h3>
139
+ </div>
140
+ <div className="p-3">
141
+ {loadingBatches ? (
142
+ <div className="space-y-3">
143
+ {[1, 2].map((_, idx) => (
144
+ <div key={idx} className="animate-pulse flex space-x-4">
145
+ <div className="flex-1 space-y-2 py-1">
146
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-3/4"></div>
147
+ <div className="h-3 bg-gray-200 dark:bg-factory-blue-light rounded w-5/6"></div>
148
+ </div>
149
+ </div>
150
+ ))}
151
+ </div>
152
+ ) : (
153
+ <div className="space-y-2">
154
+ {batches.map((batch) => (
155
+ <div key={batch.id} className="flex justify-between items-center p-2 hover:bg-gray-50 dark:hover:bg-factory-blue-light rounded-md transition-colors">
156
+ <div>
157
+ <div className="text-sm font-medium text-gray-900 dark:text-white">{batch.product}</div>
158
+ <div className="text-xs text-gray-500 dark:text-gray-400">
159
+ ID: {batch.id} • Qty: {batch.quantity}
160
+ </div>
161
+ </div>
162
+ <div className="flex space-x-2">
163
+ <span className={`text-xs px-2 py-1 rounded-full ${
164
+ batch.status === 'Completed'
165
+ ? 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300'
166
+ : 'bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-300'
167
+ }`}>
168
+ {batch.status}
169
+ </span>
170
+ {verifiedItems[batch.id] ? (
171
+ <span className="text-xs bg-factory-teal-dark/20 text-factory-teal px-2 py-1 rounded-full flex items-center">
172
+ <Check className="h-3 w-3 mr-1" /> Verified
173
+ </span>
174
+ ) : (
175
+ <button
176
+ onClick={() => verifyItem(batch.id)}
177
+ className="text-xs bg-gray-100 dark:bg-factory-blue-light text-gray-600 dark:text-gray-300 px-2 py-1 rounded-full flex items-center hover:bg-gray-200 dark:hover:bg-factory-blue transition-colors"
178
+ >
179
+ <Search className="h-3 w-3 mr-1" /> Verify
180
+ </button>
181
+ )}
182
+ </div>
183
+ </div>
184
+ ))}
185
+ </div>
186
+ )}
187
+ </div>
188
+ </div>
189
+ </div>
190
+ </div>
191
+ );
192
+ };
193
+
194
+ export default BlockchainIdentity;
src/components/dashboard/EngineArchitecture.tsx ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
4
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
5
+ import { ArrowRight, Database, RefreshCw, Server, Shield, Workflow } from 'lucide-react';
6
+
7
+ const EngineArchitecture = () => {
8
+ return (
9
+ <Card className="h-full">
10
+ <CardHeader>
11
+ <CardTitle>Intelligent Engine Architecture</CardTitle>
12
+ <CardDescription>Real-time processing and continuous learning system</CardDescription>
13
+ </CardHeader>
14
+ <CardContent>
15
+ <Tabs defaultValue="ai">
16
+ <TabsList className="mb-4">
17
+ <TabsTrigger value="ai">AI Engine</TabsTrigger>
18
+ <TabsTrigger value="crypto">Crypto Engine</TabsTrigger>
19
+ </TabsList>
20
+
21
+ <TabsContent value="ai" className="space-y-4">
22
+ <div className="bg-gray-50 dark:bg-factory-blue-light rounded-md p-4">
23
+ <h3 className="text-sm font-semibold flex items-center mb-3">
24
+ <Database className="h-4 w-4 mr-2" /> Data Ingestion & Storage
25
+ </h3>
26
+ <div className="flex flex-col space-y-2 text-xs">
27
+ <div className="flex items-center">
28
+ <span className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded">MES Events</span>
29
+ <ArrowRight className="h-3 w-3 mx-2" />
30
+ <span className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded">Kafka Stream</span>
31
+ <ArrowRight className="h-3 w-3 mx-2" />
32
+ <span className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded">Raw Schema</span>
33
+ </div>
34
+ <div className="ml-8 mt-1 flex items-center">
35
+ <ArrowRight className="h-3 w-3 transform rotate-90" />
36
+ </div>
37
+ <div className="flex items-center ml-8">
38
+ <span className="bg-factory-teal/10 dark:bg-factory-teal/20 p-2 rounded">Analytics DB</span>
39
+ <ArrowRight className="h-3 w-3 mx-2 transform -rotate-90" />
40
+ <span className="bg-factory-teal/10 dark:bg-factory-teal/20 p-2 rounded">SSIS Jobs</span>
41
+ </div>
42
+ </div>
43
+ </div>
44
+
45
+ <div className="bg-gray-50 dark:bg-factory-blue-light rounded-md p-4">
46
+ <h3 className="text-sm font-semibold flex items-center mb-3">
47
+ <Workflow className="h-4 w-4 mr-2" /> Continuous Learning Pipeline
48
+ </h3>
49
+ <div className="flex flex-wrap justify-between text-xs">
50
+ <div className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded mb-2 w-[48%]">
51
+ <p className="font-medium">Data Selection</p>
52
+ <p>Incremental data ingestion with balance policies</p>
53
+ </div>
54
+ <div className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded mb-2 w-[48%]">
55
+ <p className="font-medium">Triggers</p>
56
+ <p>5% data growth or weekly schedule</p>
57
+ </div>
58
+ <div className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded mb-2 w-[48%]">
59
+ <p className="font-medium">Model Training</p>
60
+ <p>In-database training on new data chunks</p>
61
+ </div>
62
+ <div className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded mb-2 w-[48%]">
63
+ <p className="font-medium">CI/CD</p>
64
+ <p>CML for model versioning and deployment</p>
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <div className="bg-gray-50 dark:bg-factory-blue-light rounded-md p-4">
70
+ <h3 className="text-sm font-semibold flex items-center mb-3">
71
+ <Server className="h-4 w-4 mr-2" /> Deployment & Monitoring
72
+ </h3>
73
+ <div className="flex items-center justify-between text-xs mb-2">
74
+ <div className="bg-factory-teal/10 dark:bg-factory-teal/20 p-2 rounded w-[31%]">
75
+ <p className="font-medium">API Endpoint</p>
76
+ <p>/api/mes/predict</p>
77
+ </div>
78
+ <ArrowRight className="h-3 w-3 mx-1" />
79
+ <div className="bg-factory-teal/10 dark:bg-factory-teal/20 p-2 rounded w-[31%]">
80
+ <p className="font-medium">Validation</p>
81
+ <p>≥95% accuracy</p>
82
+ </div>
83
+ <ArrowRight className="h-3 w-3 mx-1" />
84
+ <div className="bg-factory-teal/10 dark:bg-factory-teal/20 p-2 rounded w-[31%]">
85
+ <p className="font-medium">Performance</p>
86
+ <p>Drift monitoring</p>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ </TabsContent>
91
+
92
+ <TabsContent value="crypto" className="space-y-4">
93
+ <div className="bg-gray-50 dark:bg-factory-blue-light rounded-md p-4">
94
+ <h3 className="text-sm font-semibold flex items-center mb-3">
95
+ <Database className="h-4 w-4 mr-2" /> Market Data Feeds
96
+ </h3>
97
+ <div className="flex flex-col space-y-2 text-xs">
98
+ <div className="flex flex-wrap justify-between">
99
+ <span className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded mb-2 w-[31%] text-center">Order Books</span>
100
+ <span className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded mb-2 w-[31%] text-center">Trade Data</span>
101
+ <span className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded mb-2 w-[31%] text-center">Sentiment</span>
102
+ </div>
103
+ <div className="flex items-center justify-center">
104
+ <ArrowRight className="h-3 w-3 transform rotate-90" />
105
+ </div>
106
+ <div className="flex items-center justify-center">
107
+ <span className="bg-factory-teal/10 dark:bg-factory-teal/20 p-2 rounded w-full text-center">
108
+ Time-Series Database (InfluxDB)
109
+ </span>
110
+ </div>
111
+ </div>
112
+ </div>
113
+
114
+ <div className="bg-gray-50 dark:bg-factory-blue-light rounded-md p-4">
115
+ <h3 className="text-sm font-semibold flex items-center mb-3">
116
+ <RefreshCw className="h-4 w-4 mr-2" /> Reinforcement Learning Core
117
+ </h3>
118
+ <div className="flex flex-col space-y-2 text-xs">
119
+ <div className="flex flex-wrap justify-between">
120
+ <div className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded mb-2 w-[48%]">
121
+ <p className="font-medium">Algorithm</p>
122
+ <p>TD3 for continuous action spaces</p>
123
+ </div>
124
+ <div className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded mb-2 w-[48%]">
125
+ <p className="font-medium">State Vector</p>
126
+ <p>Price deltas, volume, sentiment</p>
127
+ </div>
128
+ </div>
129
+ <div className="bg-factory-blue/10 dark:bg-factory-blue-light/70 p-2 rounded w-full">
130
+ <p className="font-medium">Hierarchical RL</p>
131
+ <p>Sub-agents for different market regimes with meta-controller</p>
132
+ </div>
133
+ </div>
134
+ </div>
135
+
136
+ <div className="bg-gray-50 dark:bg-factory-blue-light rounded-md p-4">
137
+ <h3 className="text-sm font-semibold flex items-center mb-3">
138
+ <Shield className="h-4 w-4 mr-2" /> Risk Controls & Deployment
139
+ </h3>
140
+ <div className="flex flex-wrap justify-between text-xs">
141
+ <div className="bg-factory-teal/10 dark:bg-factory-teal/20 p-2 rounded mb-2 w-[31%]">
142
+ <p className="font-medium">Retraining</p>
143
+ <p>Every 1000 trades or hourly</p>
144
+ </div>
145
+ <div className="bg-factory-teal/10 dark:bg-factory-teal/20 p-2 rounded mb-2 w-[31%]">
146
+ <p className="font-medium">Risk Limits</p>
147
+ <p>Max 5% drawdown, 2% position</p>
148
+ </div>
149
+ <div className="bg-factory-teal/10 dark:bg-factory-teal/20 p-2 rounded mb-2 w-[31%]">
150
+ <p className="font-medium">Validation</p>
151
+ <p>Sharpe ratio ≥1.0</p>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </TabsContent>
156
+ </Tabs>
157
+ </CardContent>
158
+ </Card>
159
+ );
160
+ };
161
+
162
+ export default EngineArchitecture;
src/components/dashboard/Header.tsx ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState } from 'react';
3
+ import { Bell, Search, Settings } from 'lucide-react';
4
+
5
+ const Header = () => {
6
+ const [notifications] = useState(3);
7
+
8
+ return (
9
+ <header className="flex justify-between items-center p-4 border-b dark:border-factory-blue-light">
10
+ <div>
11
+ <h1 className="text-xl font-bold text-gray-900 dark:text-white">Digital Factory Genesis</h1>
12
+ <p className="text-sm text-gray-500 dark:text-gray-400">Manufacturing Execution System Dashboard</p>
13
+ </div>
14
+
15
+ <div className="flex items-center space-x-4">
16
+ <div className="relative">
17
+ <div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
18
+ <Search className="h-4 w-4 text-gray-400" />
19
+ </div>
20
+ <input
21
+ type="text"
22
+ placeholder="Search..."
23
+ className="pl-10 pr-4 py-2 text-sm border rounded-lg dark:border-factory-blue-light bg-transparent focus:outline-none focus:ring-2 focus:ring-factory-teal focus:border-transparent transition-colors"
24
+ />
25
+ </div>
26
+
27
+ <button className="relative p-2 rounded-full hover:bg-gray-100 dark:hover:bg-factory-blue-light transition-colors">
28
+ <Bell className="h-5 w-5 text-gray-600 dark:text-gray-300" />
29
+ {notifications > 0 && (
30
+ <span className="absolute top-1 right-1 flex h-4 w-4 items-center justify-center rounded-full bg-factory-danger text-[10px] font-medium text-white">
31
+ {notifications}
32
+ </span>
33
+ )}
34
+ </button>
35
+
36
+ <button className="p-2 rounded-full hover:bg-gray-100 dark:hover:bg-factory-blue-light transition-colors">
37
+ <Settings className="h-5 w-5 text-gray-600 dark:text-gray-300" />
38
+ </button>
39
+
40
+ <button className="flex items-center space-x-2 ml-2 bg-transparent hover:bg-gray-100 dark:hover:bg-factory-blue-light px-3 py-2 rounded-lg transition-colors">
41
+ <div className="h-8 w-8 rounded-full bg-factory-blue flex items-center justify-center text-white">
42
+ <span className="font-medium">JD</span>
43
+ </div>
44
+ <span className="text-sm font-medium text-gray-700 dark:text-gray-300">John Doe</span>
45
+ </button>
46
+ </div>
47
+ </header>
48
+ );
49
+ };
50
+
51
+ export default Header;
src/components/dashboard/KpiCard.tsx ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React from 'react';
3
+ import { cn } from '@/lib/utils';
4
+ import { TrendingUp, TrendingDown } from 'lucide-react';
5
+
6
+ interface KpiCardProps {
7
+ title: string;
8
+ value: string | number;
9
+ trend?: number;
10
+ icon: React.ReactNode;
11
+ className?: string;
12
+ loading?: boolean;
13
+ description?: string;
14
+ }
15
+
16
+ const KpiCard: React.FC<KpiCardProps> = ({
17
+ title,
18
+ value,
19
+ trend,
20
+ icon,
21
+ className,
22
+ loading = false,
23
+ description,
24
+ }) => {
25
+ const getTrendColor = () => {
26
+ if (!trend) return 'text-gray-500';
27
+ return trend > 0 ? 'text-factory-success' : 'text-factory-danger';
28
+ };
29
+
30
+ const getTrendIcon = () => {
31
+ if (!trend) return null;
32
+ return trend > 0 ? <TrendingUp className="h-4 w-4 ml-1" /> : <TrendingDown className="h-4 w-4 ml-1" />;
33
+ };
34
+
35
+ return (
36
+ <div className={cn("bg-white dark:bg-factory-blue rounded-lg p-5 shadow", className)}>
37
+ {loading ? (
38
+ <div className="animate-pulse space-y-2">
39
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-1/3"></div>
40
+ <div className="h-8 bg-gray-200 dark:bg-factory-blue-light rounded w-2/3"></div>
41
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-1/2"></div>
42
+ </div>
43
+ ) : (
44
+ <>
45
+ <div className="flex justify-between items-start">
46
+ <p className="text-sm font-medium text-gray-500 dark:text-gray-400">{title}</p>
47
+ <div className="p-2 rounded-md bg-gray-100 dark:bg-factory-blue-light text-gray-600 dark:text-gray-300">
48
+ {icon}
49
+ </div>
50
+ </div>
51
+ <p className="mt-2 text-2xl font-bold text-gray-900 dark:text-white">{value}</p>
52
+ {(trend !== undefined || description) && (
53
+ <div className="mt-1 flex items-center">
54
+ {trend !== undefined && (
55
+ <div className={cn("flex items-center text-sm", getTrendColor())}>
56
+ <span>{Math.abs(trend)}%</span>
57
+ {getTrendIcon()}
58
+ </div>
59
+ )}
60
+ {description && (
61
+ <span className="ml-2 text-xs text-gray-500 dark:text-gray-400">{description}</span>
62
+ )}
63
+ </div>
64
+ )}
65
+ </>
66
+ )}
67
+ </div>
68
+ );
69
+ };
70
+
71
+ export default KpiCard;
src/components/dashboard/Sidebar.tsx ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState } from 'react';
3
+ import { Link } from 'react-router-dom';
4
+ import {
5
+ LayoutDashboard,
6
+ Settings,
7
+ Database,
8
+ Search,
9
+ Activity,
10
+ BarChart,
11
+ Users,
12
+ Layers,
13
+ Cpu,
14
+ TrendingUp
15
+ } from 'lucide-react';
16
+ import { cn } from '@/lib/utils';
17
+
18
+ const Sidebar = () => {
19
+ const [collapsed, setCollapsed] = useState(false);
20
+
21
+ const menuItems = [
22
+ { icon: LayoutDashboard, label: 'Dashboard', href: '/' },
23
+ { icon: BarChart, label: 'Production', href: '/production' },
24
+ { icon: Activity, label: 'AI Insights', href: '/insights' },
25
+ { icon: Cpu, label: 'AI Engine', href: '/ai-engine' },
26
+ { icon: TrendingUp, label: 'Crypto Engine', href: '/crypto-engine' },
27
+ { icon: Layers, label: 'Digital Twin', href: '/digital-twin' },
28
+ { icon: Database, label: 'Blockchain', href: '/blockchain' },
29
+ { icon: Users, label: 'Identity', href: '/identity' },
30
+ { icon: Search, label: 'Analytics', href: '/analytics' },
31
+ { icon: Settings, label: 'Settings', href: '/settings' },
32
+ ];
33
+
34
+ return (
35
+ <div
36
+ className={cn(
37
+ "h-screen bg-factory-blue flex flex-col transition-all duration-300 text-white border-r border-factory-blue-light",
38
+ collapsed ? "w-16" : "w-64"
39
+ )}
40
+ >
41
+ <div className="p-4 flex items-center justify-between">
42
+ {!collapsed && (
43
+ <div className="flex items-center">
44
+ <div className="w-8 h-8 rounded-md bg-factory-teal flex items-center justify-center mr-2">
45
+ <span className="font-bold text-white">DF</span>
46
+ </div>
47
+ <h2 className="font-bold text-lg">Digital Factory</h2>
48
+ </div>
49
+ )}
50
+ {collapsed && (
51
+ <div className="w-8 h-8 rounded-md bg-factory-teal flex items-center justify-center mx-auto">
52
+ <span className="font-bold text-white">DF</span>
53
+ </div>
54
+ )}
55
+ <button
56
+ onClick={() => setCollapsed(!collapsed)}
57
+ className={cn(
58
+ "p-1 rounded-full hover:bg-factory-blue-light transition-colors",
59
+ collapsed && "mx-auto mt-4"
60
+ )}
61
+ >
62
+ {collapsed ? (
63
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
64
+ <path d="m9 18 6-6-6-6"/>
65
+ </svg>
66
+ ) : (
67
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
68
+ <path d="m15 18-6-6 6-6"/>
69
+ </svg>
70
+ )}
71
+ </button>
72
+ </div>
73
+
74
+ <div className="flex-1 overflow-y-auto pt-5">
75
+ <nav>
76
+ <ul>
77
+ {menuItems.map((item, index) => (
78
+ <li key={index}>
79
+ <Link
80
+ to={item.href}
81
+ className={cn(
82
+ "flex items-center py-3 px-4 text-gray-300 hover:bg-factory-blue-light hover:text-white transition-colors",
83
+ window.location.pathname === item.href && "bg-factory-blue-light text-white border-l-2 border-factory-teal"
84
+ )}
85
+ >
86
+ <item.icon className="h-5 w-5" />
87
+ {!collapsed && <span className="ml-3">{item.label}</span>}
88
+ </Link>
89
+ </li>
90
+ ))}
91
+ </ul>
92
+ </nav>
93
+ </div>
94
+
95
+ <div className="p-4 border-t border-factory-blue-light">
96
+ <div className="flex items-center">
97
+ <div className="w-8 h-8 rounded-full bg-factory-gray-dark flex items-center justify-center">
98
+ <Users size={18} />
99
+ </div>
100
+ {!collapsed && (
101
+ <div className="ml-3">
102
+ <p className="text-sm font-medium">Admin User</p>
103
+ <p className="text-xs text-gray-400">System Engineer</p>
104
+ </div>
105
+ )}
106
+ </div>
107
+ </div>
108
+ </div>
109
+ );
110
+ };
111
+
112
+ export default Sidebar;
src/components/dashboard/SystemArchitecture.tsx ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState } from 'react';
3
+ import { cn } from '@/lib/utils';
4
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
5
+
6
+ const SystemArchitecture = () => {
7
+ const [hoveredNode, setHoveredNode] = useState<string | null>(null);
8
+
9
+ const architectureNodes = [
10
+ {
11
+ id: 'ui',
12
+ label: 'UI Layer',
13
+ description: 'React frontend with dashboards & controls',
14
+ x: 50,
15
+ y: 20,
16
+ connections: ['api']
17
+ },
18
+ {
19
+ id: 'api',
20
+ label: 'API Gateway',
21
+ description: 'REST/gRPC endpoints & authentication',
22
+ x: 50,
23
+ y: 35,
24
+ connections: ['microservices', 'blockchain', 'ai']
25
+ },
26
+ {
27
+ id: 'microservices',
28
+ label: 'Microservices',
29
+ description: 'Kubernetes-orchestrated services',
30
+ x: 25,
31
+ y: 50,
32
+ connections: ['db', 'message']
33
+ },
34
+ {
35
+ id: 'blockchain',
36
+ label: 'Blockchain Gateway',
37
+ description: 'Smart contract integration & identity',
38
+ x: 50,
39
+ y: 50,
40
+ connections: ['db']
41
+ },
42
+ {
43
+ id: 'ai',
44
+ label: 'AI Engine',
45
+ description: 'ML models for prediction & optimization',
46
+ x: 75,
47
+ y: 50,
48
+ connections: ['db', 'message']
49
+ },
50
+ {
51
+ id: 'db',
52
+ label: 'Universal DB',
53
+ description: 'Multi-database connectivity layer',
54
+ x: 50,
55
+ y: 65,
56
+ connections: []
57
+ },
58
+ {
59
+ id: 'message',
60
+ label: 'Message Broker',
61
+ description: 'Kafka event streaming platform',
62
+ x: 75,
63
+ y: 65,
64
+ connections: []
65
+ },
66
+ {
67
+ id: 'edge',
68
+ label: 'Edge Computing',
69
+ description: 'Low-latency data collection nodes',
70
+ x: 25,
71
+ y: 65,
72
+ connections: ['message']
73
+ }
74
+ ];
75
+
76
+ const renderConnections = () => {
77
+ return architectureNodes.flatMap(node =>
78
+ node.connections.map(target => {
79
+ const targetNode = architectureNodes.find(n => n.id === target);
80
+ if (!targetNode) return null;
81
+
82
+ const isHighlighted =
83
+ hoveredNode === node.id ||
84
+ hoveredNode === target;
85
+
86
+ return (
87
+ <line
88
+ key={`${node.id}-${target}`}
89
+ x1={`${node.x}%`}
90
+ y1={`${node.y}%`}
91
+ x2={`${targetNode.x}%`}
92
+ y2={`${targetNode.y}%`}
93
+ stroke={isHighlighted ? "#00A9A5" : "#4B5563"}
94
+ strokeWidth={isHighlighted ? 2 : 1}
95
+ strokeDasharray={isHighlighted ? "" : "4 2"}
96
+ />
97
+ );
98
+ })
99
+ );
100
+ };
101
+
102
+ return (
103
+ <div className="bg-white dark:bg-factory-blue rounded-lg p-4 shadow h-full">
104
+ <h2 className="text-lg font-semibold mb-4 text-gray-900 dark:text-white">System Architecture</h2>
105
+ <div className="relative w-full h-[500px]">
106
+ <svg viewBox="0 0 100 100" className="w-full h-full">
107
+ {renderConnections()}
108
+
109
+ {architectureNodes.map(node => (
110
+ <TooltipProvider key={node.id}>
111
+ <Tooltip delayDuration={300}>
112
+ <TooltipTrigger asChild>
113
+ <g
114
+ onMouseEnter={() => setHoveredNode(node.id)}
115
+ onMouseLeave={() => setHoveredNode(null)}
116
+ className="cursor-pointer"
117
+ >
118
+ <circle
119
+ cx={`${node.x}%`}
120
+ cy={`${node.y}%`}
121
+ r="4.5"
122
+ fill={hoveredNode === node.id ? "#00A9A5" : "#1A2942"}
123
+ className="transition-all duration-300"
124
+ />
125
+ <rect
126
+ x={`${node.x - 10}%`}
127
+ y={`${node.y + 5}%`}
128
+ width="20%"
129
+ height="7%"
130
+ rx="1"
131
+ fill={hoveredNode === node.id ? "#00A9A5" : "#1A2942"}
132
+ fillOpacity={hoveredNode === node.id ? 1 : 0.9}
133
+ className="transition-all duration-300"
134
+ />
135
+ <text
136
+ x={`${node.x}%`}
137
+ y={`${node.y + 9}%`}
138
+ textAnchor="middle"
139
+ fill="white"
140
+ fontSize="2.5"
141
+ className="select-none pointer-events-none"
142
+ >
143
+ {node.label}
144
+ </text>
145
+ </g>
146
+ </TooltipTrigger>
147
+ <TooltipContent side="top" className="bg-white dark:bg-factory-blue-dark border border-gray-200 dark:border-factory-blue-light p-2 text-xs shadow-md">
148
+ <p className="font-bold text-gray-900 dark:text-white">{node.label}</p>
149
+ <p className="text-gray-600 dark:text-gray-300">{node.description}</p>
150
+ </TooltipContent>
151
+ </Tooltip>
152
+ </TooltipProvider>
153
+ ))}
154
+ </svg>
155
+ </div>
156
+ </div>
157
+ );
158
+ };
159
+
160
+ export default SystemArchitecture;
src/components/digital-twin/FactoryVisualization.tsx ADDED
@@ -0,0 +1,214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useRef, useMemo } from 'react';
3
+ import { Canvas, useFrame } from '@react-three/fiber';
4
+ import { OrbitControls, Grid } from '@react-three/drei';
5
+ import { Html } from '@react-three/drei';
6
+ import { useDataSimulation } from '@/hooks/useDataSimulation';
7
+ import * as THREE from 'three';
8
+
9
+ // Define TypeScript interfaces for component props
10
+ interface MachineProps {
11
+ position: [number, number, number];
12
+ color?: string;
13
+ name: string;
14
+ status?: 'running' | 'idle' | 'error';
15
+ efficiency?: number;
16
+ }
17
+
18
+ interface ConveyorProps {
19
+ startPos: [number, number, number];
20
+ endPos: [number, number, number];
21
+ width?: number;
22
+ }
23
+
24
+ // Define the machine data structure to type-check the simulation data
25
+ interface MachineData {
26
+ name: string;
27
+ status: 'running' | 'idle' | 'error';
28
+ efficiency: number;
29
+ }
30
+
31
+ interface SimulationData {
32
+ machines: MachineData[];
33
+ }
34
+
35
+ const Machine = ({
36
+ position,
37
+ color = "#60a5fa",
38
+ name,
39
+ status = "running",
40
+ efficiency = 100
41
+ }: MachineProps) => {
42
+ // Properly type the ref to be a THREE.Mesh object
43
+ const meshRef = useRef<THREE.Mesh>(null);
44
+
45
+ // Animate based on status
46
+ useFrame(() => {
47
+ if (status === "running" && meshRef.current) {
48
+ meshRef.current.rotation.y += 0.01;
49
+ }
50
+ });
51
+
52
+ const statusColor = useMemo(() => {
53
+ if (status === "running") return "#4ade80";
54
+ if (status === "idle") return "#facc15";
55
+ return "#f87171";
56
+ }, [status]);
57
+
58
+ return (
59
+ <group position={position}>
60
+ {/* Base cylinder */}
61
+ <mesh position={[0, -0.6, 0]}>
62
+ <cylinderGeometry args={[1, 1, 0.2, 32]} />
63
+ <meshStandardMaterial color="#475569" />
64
+ </mesh>
65
+
66
+ {/* Main machine body */}
67
+ <mesh ref={meshRef}>
68
+ <boxGeometry args={[2, 1, 1]} />
69
+ <meshStandardMaterial color={color} />
70
+ </mesh>
71
+
72
+ {/* Status indicator */}
73
+ <mesh position={[0, 0.8, 0]}>
74
+ <cylinderGeometry args={[0.1, 0.1, 0.3, 8]} />
75
+ <meshStandardMaterial
76
+ color={statusColor}
77
+ emissive={statusColor}
78
+ emissiveIntensity={0.5}
79
+ />
80
+ </mesh>
81
+
82
+ {/* Label */}
83
+ <Html position={[0, 1.5, 0]} transform>
84
+ <div className="bg-white dark:bg-factory-blue px-2 py-1 rounded-md text-xs shadow-md">
85
+ <p className="font-bold">{name}</p>
86
+ <p className="text-xs opacity-80">Efficiency: {efficiency}%</p>
87
+ </div>
88
+ </Html>
89
+ </group>
90
+ );
91
+ };
92
+
93
+ const Conveyor = ({ startPos, endPos, width = 0.5 }: ConveyorProps) => {
94
+ // Calculate the center position and rotation for the conveyor
95
+ const centerX = (startPos[0] + endPos[0]) / 2;
96
+ const centerZ = (startPos[2] + endPos[2]) / 2;
97
+ const centerY = -0.4; // Fixed Y position
98
+
99
+ const length = Math.sqrt(
100
+ Math.pow(endPos[0] - startPos[0], 2) +
101
+ Math.pow(endPos[2] - startPos[2], 2)
102
+ );
103
+
104
+ const angle = Math.atan2(
105
+ endPos[2] - startPos[2],
106
+ endPos[0] - startPos[0]
107
+ );
108
+
109
+ return (
110
+ <mesh
111
+ position={[centerX, centerY, centerZ]}
112
+ rotation={[0, angle, 0]}
113
+ >
114
+ <boxGeometry args={[length, 0.1, width]} />
115
+ <meshStandardMaterial color="#94a3b8" />
116
+ </mesh>
117
+ );
118
+ };
119
+
120
+ interface FactoryProps {
121
+ running?: boolean;
122
+ }
123
+
124
+ const Factory = ({ running = false }: FactoryProps) => {
125
+ const { data } = useDataSimulation<SimulationData>(() => {
126
+ return {
127
+ machines: [
128
+ { name: "Assembly", status: "running" as const, efficiency: Math.floor(Math.random() * 20) + 80 },
129
+ { name: "Welding", status: Math.random() > 0.1 ? "running" as const : "idle" as const, efficiency: Math.floor(Math.random() * 30) + 70 },
130
+ { name: "Testing", status: Math.random() > 0.15 ? "running" as const : "error" as const, efficiency: Math.floor(Math.random() * 40) + 60 },
131
+ { name: "Packaging", status: "running" as const, efficiency: Math.floor(Math.random() * 15) + 85 }
132
+ ]
133
+ };
134
+ }, { interval: 3000, enabled: running });
135
+
136
+ // Define typed default machines
137
+ const defaultMachines: MachineData[] = [
138
+ { name: "Assembly", status: "idle", efficiency: 0 },
139
+ { name: "Welding", status: "idle", efficiency: 0 },
140
+ { name: "Testing", status: "idle", efficiency: 0 },
141
+ { name: "Packaging", status: "idle", efficiency: 0 }
142
+ ];
143
+
144
+ const machines = data?.machines || defaultMachines;
145
+
146
+ // Define machine positions as proper tuples
147
+ const machinePositions: [number, number, number][] = [
148
+ [-6, 0, 0],
149
+ [-2, 0, 0],
150
+ [2, 0, 0],
151
+ [6, 0, 0]
152
+ ];
153
+
154
+ return (
155
+ <>
156
+ {/* Floor */}
157
+ <Grid
158
+ args={[30, 30]}
159
+ cellSize={1}
160
+ cellThickness={0.5}
161
+ cellColor="#64748b"
162
+ sectionSize={3}
163
+ sectionThickness={1}
164
+ sectionColor="#475569"
165
+ fadeDistance={30}
166
+ position={[0, -0.5, 0]}
167
+ />
168
+
169
+ {/* Factory machines */}
170
+ <Machine position={machinePositions[0]} name={machines[0].name} status={running ? machines[0].status : "idle"} efficiency={machines[0].efficiency} />
171
+ <Machine position={machinePositions[1]} name={machines[1].name} status={running ? machines[1].status : "idle"} efficiency={machines[1].efficiency} color="#8b5cf6" />
172
+ <Machine position={machinePositions[2]} name={machines[2].name} status={running ? machines[2].status : "idle"} efficiency={machines[2].efficiency} color="#10b981" />
173
+ <Machine position={machinePositions[3]} name={machines[3].name} status={running ? machines[3].status : "idle"} efficiency={machines[3].efficiency} color="#f59e0b" />
174
+
175
+ {/* Conveyors */}
176
+ <Conveyor startPos={machinePositions[0]} endPos={machinePositions[1]} />
177
+ <Conveyor startPos={machinePositions[1]} endPos={machinePositions[2]} />
178
+ <Conveyor startPos={machinePositions[2]} endPos={machinePositions[3]} />
179
+ </>
180
+ );
181
+ };
182
+
183
+ interface FactoryVisualizationProps {
184
+ running?: boolean;
185
+ }
186
+
187
+ const FactoryVisualization = ({ running = false }: FactoryVisualizationProps) => {
188
+ return (
189
+ <div className="h-full w-full">
190
+ <Canvas camera={{ position: [0, 5, 10], fov: 50 }}>
191
+ <ambientLight intensity={0.5} />
192
+ <directionalLight position={[10, 10, 10]} intensity={1} />
193
+ <Factory running={running} />
194
+ <OrbitControls
195
+ enablePan={true}
196
+ enableZoom={true}
197
+ enableRotate={true}
198
+ minDistance={5}
199
+ maxDistance={20}
200
+ />
201
+ </Canvas>
202
+ {!running && (
203
+ <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
204
+ <div className="bg-black/40 text-white p-4 rounded-lg text-center max-w-xs">
205
+ <p className="text-lg font-medium">Simulation Paused</p>
206
+ <p className="text-sm mt-1">Start the simulation to activate the digital twin</p>
207
+ </div>
208
+ </div>
209
+ )}
210
+ </div>
211
+ );
212
+ };
213
+
214
+ export default FactoryVisualization;
src/components/training/BlockchainMonitor.tsx ADDED
@@ -0,0 +1,330 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { Progress } from "@/components/ui/progress";
6
+ import { Shield, Link, Activity, FileText } from 'lucide-react';
7
+
8
+ interface BlockchainMonitorProps {
9
+ loading: boolean;
10
+ }
11
+
12
+ const BlockchainMonitor: React.FC<BlockchainMonitorProps> = ({ loading }) => {
13
+ const [transactions, setTransactions] = useState<{
14
+ hash: string;
15
+ type: string;
16
+ status: string;
17
+ timestamp: string;
18
+ gas: number;
19
+ }[]>([
20
+ {
21
+ hash: '0x7f9e4c8d...',
22
+ type: 'Machine NFT Minting',
23
+ status: 'Confirmed',
24
+ timestamp: '10:30:15',
25
+ gas: 98500
26
+ },
27
+ {
28
+ hash: '0x3e6b9d2f...',
29
+ type: 'Batch Transfer',
30
+ status: 'Pending',
31
+ timestamp: '10:29:42',
32
+ gas: 124600
33
+ },
34
+ {
35
+ hash: '0xc12a45e8...',
36
+ type: 'Quality Verification',
37
+ status: 'Confirmed',
38
+ timestamp: '10:28:07',
39
+ gas: 75300
40
+ },
41
+ {
42
+ hash: '0x9f81d73b...',
43
+ type: 'Operator Authorization',
44
+ status: 'Confirmed',
45
+ timestamp: '10:25:55',
46
+ gas: 85700
47
+ }
48
+ ]);
49
+
50
+ const [contracts, setContracts] = useState<{
51
+ name: string;
52
+ address: string;
53
+ type: string;
54
+ events: number;
55
+ lastBlock: number;
56
+ }[]>([
57
+ {
58
+ name: 'MachineIdentity',
59
+ address: '0x7F9abc...',
60
+ type: 'ERC-721',
61
+ events: 346,
62
+ lastBlock: 18451267
63
+ },
64
+ {
65
+ name: 'ProductionBatch',
66
+ address: '0x3E8def...',
67
+ type: 'ERC-721',
68
+ events: 892,
69
+ lastBlock: 18451265
70
+ },
71
+ {
72
+ name: 'OperatorRegistry',
73
+ address: '0x2C5fgh...',
74
+ type: 'Custom',
75
+ events: 127,
76
+ lastBlock: 18451260
77
+ },
78
+ {
79
+ name: 'QualityControl',
80
+ address: '0xA71ijk...',
81
+ type: 'Custom',
82
+ events: 518,
83
+ lastBlock: 18451254
84
+ }
85
+ ]);
86
+
87
+ // Simulate new transactions coming in
88
+ useEffect(() => {
89
+ if (loading) return;
90
+
91
+ const newTxTypes = [
92
+ 'Machine NFT Minting',
93
+ 'Batch Transfer',
94
+ 'Quality Verification',
95
+ 'Operator Authorization',
96
+ 'Maintenance Record',
97
+ 'Parameter Update'
98
+ ];
99
+
100
+ const interval = setInterval(() => {
101
+ // 30% chance of adding a new transaction
102
+ if (Math.random() < 0.3) {
103
+ const randomType = newTxTypes[Math.floor(Math.random() * newTxTypes.length)];
104
+ const randomHash = '0x' + Math.random().toString(16).substr(2, 8) + '...';
105
+ const isPending = Math.random() < 0.2;
106
+
107
+ setTransactions(prev => [
108
+ {
109
+ hash: randomHash,
110
+ type: randomType,
111
+ status: isPending ? 'Pending' : 'Confirmed',
112
+ timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
113
+ gas: Math.floor(Math.random() * 100000) + 50000
114
+ },
115
+ ...prev.slice(0, 3) // Keep only the 4 most recent transactions
116
+ ]);
117
+
118
+ // Update contract event count
119
+ if (!isPending) {
120
+ setContracts(prev => prev.map((contract, idx) => {
121
+ if (idx === Math.floor(Math.random() * contracts.length)) {
122
+ return {
123
+ ...contract,
124
+ events: contract.events + 1,
125
+ lastBlock: contract.lastBlock + (Math.random() < 0.5 ? 1 : 0)
126
+ };
127
+ }
128
+ return contract;
129
+ }));
130
+ }
131
+ }
132
+ }, 3000);
133
+
134
+ return () => clearInterval(interval);
135
+ }, [loading, contracts]);
136
+
137
+ return (
138
+ <div className="space-y-6">
139
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
140
+ <Card>
141
+ <CardHeader>
142
+ <CardTitle className="flex items-center">
143
+ <FileText className="h-5 w-5 mr-2" />
144
+ Smart Contracts
145
+ </CardTitle>
146
+ <CardDescription>
147
+ Active blockchain contracts for identity & traceability
148
+ </CardDescription>
149
+ </CardHeader>
150
+ <CardContent>
151
+ {loading ? (
152
+ <div className="space-y-4">
153
+ {Array(4).fill(0).map((_, idx) => (
154
+ <div key={idx} className="animate-pulse space-y-2 border-b dark:border-factory-blue-light pb-3 last:border-0 last:pb-0">
155
+ <div className="h-5 bg-gray-200 dark:bg-factory-blue-light rounded w-40"></div>
156
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-full"></div>
157
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-24"></div>
158
+ </div>
159
+ ))}
160
+ </div>
161
+ ) : (
162
+ <div className="divide-y dark:divide-factory-blue-light">
163
+ {contracts.map((contract) => (
164
+ <div key={contract.address} className="py-2 first:pt-0 last:pb-0">
165
+ <div className="flex justify-between items-center mb-1">
166
+ <div className="font-medium">{contract.name}</div>
167
+ <Badge variant="outline">{contract.type}</Badge>
168
+ </div>
169
+ <div className="text-sm text-gray-500 dark:text-gray-400 mb-1">
170
+ Address: {contract.address}
171
+ </div>
172
+ <div className="flex items-center text-xs text-gray-500 dark:text-gray-400">
173
+ <div className="flex items-center mr-4">
174
+ <Activity className="h-3.5 w-3.5 mr-1" />
175
+ <span>{contract.events} events</span>
176
+ </div>
177
+ <div className="flex items-center">
178
+ <Link className="h-3.5 w-3.5 mr-1" />
179
+ <span>Block #{contract.lastBlock}</span>
180
+ </div>
181
+ </div>
182
+ </div>
183
+ ))}
184
+ </div>
185
+ )}
186
+ </CardContent>
187
+ </Card>
188
+
189
+ <Card>
190
+ <CardHeader>
191
+ <CardTitle className="flex items-center">
192
+ <Activity className="h-5 w-5 mr-2" />
193
+ Blockchain Transactions
194
+ </CardTitle>
195
+ <CardDescription>
196
+ Recent identity & traceability transactions
197
+ </CardDescription>
198
+ </CardHeader>
199
+ <CardContent>
200
+ {loading ? (
201
+ <div className="space-y-4">
202
+ {Array(4).fill(0).map((_, idx) => (
203
+ <div key={idx} className="animate-pulse space-y-2 border-b dark:border-factory-blue-light pb-3 last:border-0 last:pb-0">
204
+ <div className="h-5 bg-gray-200 dark:bg-factory-blue-light rounded w-40"></div>
205
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-full"></div>
206
+ </div>
207
+ ))}
208
+ </div>
209
+ ) : (
210
+ <div className="space-y-3">
211
+ {transactions.map((tx) => (
212
+ <div key={tx.hash} className="border-b dark:border-factory-blue-light pb-3 last:border-0 last:pb-0">
213
+ <div className="flex justify-between items-center mb-1">
214
+ <div className="font-medium">{tx.type}</div>
215
+ <Badge className={tx.status === 'Confirmed' ?
216
+ 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-300' :
217
+ 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/20 dark:text-yellow-300'}>
218
+ {tx.status}
219
+ </Badge>
220
+ </div>
221
+ <div className="flex justify-between text-sm">
222
+ <span className="text-gray-500 dark:text-gray-400">{tx.hash}</span>
223
+ <span>{tx.timestamp}</span>
224
+ </div>
225
+ <div className="mt-1 text-xs text-gray-500 dark:text-gray-400">
226
+ Gas used: {tx.gas.toLocaleString()} wei
227
+ </div>
228
+ </div>
229
+ ))}
230
+ </div>
231
+ )}
232
+ </CardContent>
233
+ </Card>
234
+ </div>
235
+
236
+ <Card>
237
+ <CardHeader>
238
+ <CardTitle className="flex items-center">
239
+ <Shield className="h-5 w-5 mr-2" />
240
+ Blockchain Gateway Services
241
+ </CardTitle>
242
+ <CardDescription>
243
+ Integration services status and statistics
244
+ </CardDescription>
245
+ </CardHeader>
246
+ <CardContent>
247
+ {loading ? (
248
+ <div className="animate-pulse space-y-4">
249
+ <div className="h-6 bg-gray-200 dark:bg-factory-blue-light rounded w-full"></div>
250
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
251
+ {Array(3).fill(0).map((_, idx) => (
252
+ <div key={idx} className="space-y-2">
253
+ <div className="h-5 bg-gray-200 dark:bg-factory-blue-light rounded w-40"></div>
254
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-full"></div>
255
+ </div>
256
+ ))}
257
+ </div>
258
+ </div>
259
+ ) : (
260
+ <>
261
+ <div className="mb-6">
262
+ <div className="flex justify-between text-sm mb-1">
263
+ <span>Node.js Blockchain Gateway</span>
264
+ <span className="text-green-600 dark:text-green-400">Active</span>
265
+ </div>
266
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
267
+ <div>
268
+ <div className="text-xs text-gray-500 dark:text-gray-400 mb-1">Memory Usage</div>
269
+ <div className="w-full">
270
+ <div className="flex justify-between text-xs mb-1">
271
+ <span>384 MB / 1024 MB</span>
272
+ <span>38%</span>
273
+ </div>
274
+ <Progress value={38} className="h-2" />
275
+ </div>
276
+ </div>
277
+ <div>
278
+ <div className="text-xs text-gray-500 dark:text-gray-400 mb-1">CPU Usage</div>
279
+ <div className="w-full">
280
+ <div className="flex justify-between text-xs mb-1">
281
+ <span>2 cores</span>
282
+ <span>27%</span>
283
+ </div>
284
+ <Progress value={27} className="h-2" />
285
+ </div>
286
+ </div>
287
+ <div>
288
+ <div className="text-xs text-gray-500 dark:text-gray-400 mb-1">Event Queue</div>
289
+ <div className="w-full">
290
+ <div className="flex justify-between text-xs mb-1">
291
+ <span>12 events</span>
292
+ <span>6%</span>
293
+ </div>
294
+ <Progress value={6} className="h-2" />
295
+ </div>
296
+ </div>
297
+ </div>
298
+ </div>
299
+
300
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
301
+ <div className="p-3 bg-gray-50 dark:bg-factory-blue-light rounded-md">
302
+ <div className="text-sm font-medium mb-1">HSM Integration</div>
303
+ <div className="text-xl font-semibold">Secure</div>
304
+ <div className="text-xs text-gray-500 dark:text-gray-400">Private key operations</div>
305
+ </div>
306
+ <div className="p-3 bg-gray-50 dark:bg-factory-blue-light rounded-md">
307
+ <div className="text-sm font-medium mb-1">Solidity Contracts</div>
308
+ <div className="text-xl font-semibold">4 Active</div>
309
+ <div className="text-xs text-gray-500 dark:text-gray-400">Deployed & verified</div>
310
+ </div>
311
+ <div className="p-3 bg-gray-50 dark:bg-factory-blue-light rounded-md">
312
+ <div className="text-sm font-medium mb-1">Event Handlers</div>
313
+ <div className="text-xl font-semibold">8 Listeners</div>
314
+ <div className="text-xs text-gray-500 dark:text-gray-400">Processing blockchain events</div>
315
+ </div>
316
+ <div className="p-3 bg-gray-50 dark:bg-factory-blue-light rounded-md">
317
+ <div className="text-sm font-medium mb-1">Smart Rules</div>
318
+ <div className="text-xl font-semibold">12 Active</div>
319
+ <div className="text-xs text-gray-500 dark:text-gray-400">Quality & maintenance rules</div>
320
+ </div>
321
+ </div>
322
+ </>
323
+ )}
324
+ </CardContent>
325
+ </Card>
326
+ </div>
327
+ );
328
+ };
329
+
330
+ export default BlockchainMonitor;
src/components/training/TrainingMonitor.tsx ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
4
+ import { Alert, AlertDescription } from "@/components/ui/alert";
5
+ import { Badge } from "@/components/ui/badge";
6
+ import { Progress } from "@/components/ui/progress";
7
+ import { CheckCircle, AlertTriangle, Clock, ArrowRight, RefreshCw } from 'lucide-react';
8
+
9
+ interface TrainingMonitorProps {
10
+ type: string;
11
+ schedule: string;
12
+ loading: boolean;
13
+ engine: 'AI' | 'Crypto';
14
+ }
15
+
16
+ const TrainingMonitor: React.FC<TrainingMonitorProps> = ({ type, schedule, loading, engine }) => {
17
+ // Simulated training state with auto-progression
18
+ const [trainingState, setTrainingState] = useState<{
19
+ status: 'running' | 'completed' | 'validating' | 'deploying' | 'idle';
20
+ progress: number;
21
+ currentStep: number;
22
+ lastRun: string;
23
+ nextRun: string;
24
+ metrics: Record<string, number>;
25
+ alerts: { message: string; type: 'warning' | 'error' | 'success' }[];
26
+ }>({
27
+ status: 'idle',
28
+ progress: 0,
29
+ currentStep: 0,
30
+ lastRun: '2023-05-08 14:30',
31
+ nextRun: '2023-05-09 14:30',
32
+ metrics: engine === 'AI' ?
33
+ { accuracy: 96.7, precision: 94.3, recall: 95.8, drift: -0.5 } :
34
+ { sharpeRatio: 1.32, maxDrawdown: 3.8, winRate: 68.5, profitFactor: 1.45 },
35
+ alerts: []
36
+ });
37
+
38
+ // Simulate training cycle
39
+ useEffect(() => {
40
+ if (loading) return;
41
+
42
+ const startTraining = () => {
43
+ // Reset training state
44
+ setTrainingState(prev => ({
45
+ ...prev,
46
+ status: 'running',
47
+ progress: 0,
48
+ currentStep: 1,
49
+ alerts: []
50
+ }));
51
+
52
+ // Step 1: Data export (0-25%)
53
+ const timer1 = setTimeout(() => {
54
+ setTrainingState(prev => ({
55
+ ...prev,
56
+ progress: 25,
57
+ currentStep: 2
58
+ }));
59
+
60
+ // Step 2: Model Training (25-70%)
61
+ const timer2 = setTimeout(() => {
62
+ setTrainingState(prev => ({
63
+ ...prev,
64
+ progress: 70,
65
+ currentStep: 3
66
+ }));
67
+
68
+ // Step 3: Validation (70-85%)
69
+ const timer3 = setTimeout(() => {
70
+ setTrainingState(prev => ({
71
+ ...prev,
72
+ status: 'validating',
73
+ progress: 85,
74
+ currentStep: 4,
75
+ alerts: Math.random() > 0.8 ?
76
+ [...prev.alerts, {
77
+ message: engine === 'AI' ?
78
+ 'Accuracy below threshold (94.8%). Rolling back.' :
79
+ 'Sharpe ratio below threshold (0.95). Rolling back.',
80
+ type: 'error'
81
+ }] : prev.alerts
82
+ }));
83
+
84
+ // Step 4: Deployment (85-100%)
85
+ const timer4 = setTimeout(() => {
86
+ const success = Math.random() > 0.2;
87
+ setTrainingState(prev => ({
88
+ ...prev,
89
+ status: 'completed',
90
+ progress: 100,
91
+ currentStep: 0,
92
+ lastRun: new Date().toLocaleString(),
93
+ nextRun: new Date(Date.now() + (engine === 'AI' ? 24 : 1) * 60 * 60 * 1000).toLocaleString(),
94
+ metrics: engine === 'AI' ?
95
+ {
96
+ accuracy: success ? 96.7 + (Math.random() * 0.6 - 0.3) : 94.8,
97
+ precision: success ? 94.3 + (Math.random() * 0.6 - 0.3) : 93.1,
98
+ recall: success ? 95.8 + (Math.random() * 0.6 - 0.3) : 94.5,
99
+ drift: success ? -0.5 + (Math.random() * 0.4 - 0.2) : -1.2
100
+ } :
101
+ {
102
+ sharpeRatio: success ? 1.32 + (Math.random() * 0.2 - 0.1) : 0.95,
103
+ maxDrawdown: success ? 3.8 + (Math.random() * 0.6 - 0.3) : 5.2,
104
+ winRate: success ? 68.5 + (Math.random() * 1 - 0.5) : 65.2,
105
+ profitFactor: success ? 1.45 + (Math.random() * 0.1 - 0.05) : 1.2
106
+ },
107
+ alerts: success ? prev.alerts : [
108
+ ...prev.alerts,
109
+ {
110
+ message: success ?
111
+ 'Model deployed successfully to production endpoint.' :
112
+ 'Validation failed. Rolled back to previous stable version.',
113
+ type: success ? 'success' : 'error'
114
+ }
115
+ ]
116
+ }));
117
+ }, 3000);
118
+
119
+ return () => clearTimeout(timer4);
120
+ }, 2000);
121
+
122
+ return () => clearTimeout(timer3);
123
+ }, 4000);
124
+
125
+ return () => clearTimeout(timer2);
126
+ }, 2000);
127
+
128
+ return () => clearTimeout(timer1);
129
+ };
130
+
131
+ // Start initial training simulation
132
+ const initialTimer = setTimeout(() => {
133
+ startTraining();
134
+ }, 2000);
135
+
136
+ // Set up recurring training simulation
137
+ const recurringTimer = setInterval(() => {
138
+ if (trainingState.status === 'idle') {
139
+ startTraining();
140
+ }
141
+ }, 15000);
142
+
143
+ return () => {
144
+ clearTimeout(initialTimer);
145
+ clearInterval(recurringTimer);
146
+ };
147
+ }, [loading, engine]);
148
+
149
+ // Auto reset to idle after completion
150
+ useEffect(() => {
151
+ if (trainingState.status === 'completed') {
152
+ const timer = setTimeout(() => {
153
+ setTrainingState(prev => ({
154
+ ...prev,
155
+ status: 'idle'
156
+ }));
157
+ }, 5000);
158
+
159
+ return () => clearTimeout(timer);
160
+ }
161
+ }, [trainingState.status]);
162
+
163
+ const steps = engine === 'AI' ? [
164
+ "Export new rows since last run",
165
+ "Retrain AI model using in-db training",
166
+ "Validate: ≥95% accuracy & no negative drift",
167
+ "Deploy to /api/mes/predict endpoint",
168
+ "Log metrics to monitoring DB"
169
+ ] : [
170
+ "Fetch latest minute-level features",
171
+ "Retrain RL agent with TD3 algorithm",
172
+ "Backtest on last 7 days (Sharpe ≥1.0)",
173
+ "Deploy updated policy to trading service",
174
+ "Enforce risk constraints (drawdown, position)"
175
+ ];
176
+
177
+ return (
178
+ <div className="space-y-6">
179
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
180
+ <Card>
181
+ <CardHeader className="pb-2">
182
+ <div className="flex justify-between items-center">
183
+ <CardTitle className="text-xl">
184
+ {engine === 'AI' ? 'MES Predictor' : 'Crypto Trader'} Training
185
+ </CardTitle>
186
+ <Badge className={trainingState.status === 'running' || trainingState.status === 'validating' ?
187
+ 'bg-blue-100 text-blue-800 dark:bg-blue-900/20 dark:text-blue-300' :
188
+ trainingState.status === 'completed' ?
189
+ 'bg-green-100 text-green-800 dark:bg-green-900/20 dark:text-green-300' :
190
+ 'bg-gray-100 text-gray-800 dark:bg-gray-900/20 dark:text-gray-300'}>
191
+ {trainingState.status === 'running' ? 'Training' :
192
+ trainingState.status === 'validating' ? 'Validating' :
193
+ trainingState.status === 'completed' ? 'Completed' : 'Idle'}
194
+ </Badge>
195
+ </div>
196
+ <CardDescription>
197
+ Schedule: Every {schedule}
198
+ </CardDescription>
199
+ </CardHeader>
200
+ <CardContent>
201
+ {loading ? (
202
+ <div className="space-y-4">
203
+ <div className="animate-pulse h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-full mb-2"></div>
204
+ <div className="space-y-2">
205
+ {Array(5).fill(0).map((_, idx) => (
206
+ <div key={idx} className="animate-pulse h-5 bg-gray-200 dark:bg-factory-blue-light rounded w-full"></div>
207
+ ))}
208
+ </div>
209
+ </div>
210
+ ) : (
211
+ <>
212
+ <div className="mb-4">
213
+ <div className="flex justify-between text-xs mb-1">
214
+ <span>Training Progress</span>
215
+ <span>{trainingState.progress}%</span>
216
+ </div>
217
+ <Progress value={trainingState.progress} className="h-2" />
218
+ </div>
219
+
220
+ <div className="mb-4">
221
+ <div className="text-sm font-medium mb-2">Training Pipeline Steps:</div>
222
+ <div className="space-y-2">
223
+ {steps.map((step, idx) => (
224
+ <div
225
+ key={idx}
226
+ className={`flex items-start p-2 rounded-sm text-sm ${
227
+ trainingState.currentStep === idx + 1
228
+ ? 'bg-blue-100 dark:bg-blue-900/20 border-l-4 border-blue-500'
229
+ : trainingState.currentStep > idx + 1 || (trainingState.status === 'completed' && trainingState.progress === 100)
230
+ ? 'text-gray-500 dark:text-gray-400'
231
+ : ''
232
+ }`}
233
+ >
234
+ <div className="mr-3 mt-0.5">
235
+ {trainingState.currentStep > idx + 1 || (trainingState.status === 'completed' && trainingState.progress === 100) ? (
236
+ <CheckCircle className="h-4 w-4 text-green-500" />
237
+ ) : trainingState.currentStep === idx + 1 ? (
238
+ <RefreshCw className="h-4 w-4 text-blue-500 animate-spin" />
239
+ ) : (
240
+ <div className="h-4 w-4 rounded-full border border-gray-300 dark:border-gray-600"></div>
241
+ )}
242
+ </div>
243
+ <div>{step}</div>
244
+ </div>
245
+ ))}
246
+ </div>
247
+ </div>
248
+
249
+ <div className="grid grid-cols-2 gap-2 text-sm">
250
+ <div className="flex items-center">
251
+ <Clock className="h-4 w-4 mr-2 text-gray-500 dark:text-gray-400" />
252
+ <span className="text-gray-500 dark:text-gray-400">Last run:</span>
253
+ <span className="ml-1">{trainingState.lastRun}</span>
254
+ </div>
255
+ <div className="flex items-center">
256
+ <Clock className="h-4 w-4 mr-2 text-gray-500 dark:text-gray-400" />
257
+ <span className="text-gray-500 dark:text-gray-400">Next run:</span>
258
+ <span className="ml-1">{trainingState.nextRun}</span>
259
+ </div>
260
+ </div>
261
+ </>
262
+ )}
263
+ </CardContent>
264
+ </Card>
265
+
266
+ <Card>
267
+ <CardHeader>
268
+ <CardTitle className="text-xl">Performance Metrics</CardTitle>
269
+ <CardDescription>
270
+ Latest model evaluation results
271
+ </CardDescription>
272
+ </CardHeader>
273
+ <CardContent>
274
+ {loading ? (
275
+ <div className="grid grid-cols-2 gap-4">
276
+ {Array(4).fill(0).map((_, idx) => (
277
+ <div key={idx} className="animate-pulse space-y-2">
278
+ <div className="h-4 bg-gray-200 dark:bg-factory-blue-light rounded w-20"></div>
279
+ <div className="h-6 bg-gray-200 dark:bg-factory-blue-light rounded w-16"></div>
280
+ </div>
281
+ ))}
282
+ </div>
283
+ ) : (
284
+ <>
285
+ <div className="grid grid-cols-2 gap-y-6 gap-x-4">
286
+ {Object.entries(trainingState.metrics).map(([key, value], idx) => (
287
+ <div key={key}>
288
+ <div className="text-sm text-gray-500 dark:text-gray-400 capitalize mb-1">
289
+ {key === 'sharpeRatio' ? 'Sharpe Ratio' :
290
+ key === 'maxDrawdown' ? 'Max Drawdown' :
291
+ key === 'winRate' ? 'Win Rate' :
292
+ key === 'profitFactor' ? 'Profit Factor' : key}
293
+ </div>
294
+ <div className="text-2xl font-semibold">
295
+ {typeof value === 'number' ?
296
+ key === 'maxDrawdown' ? `${value}%` :
297
+ key === 'winRate' ? `${value}%` :
298
+ key === 'drift' ? `${value > 0 ? '+' : ''}${value}%` :
299
+ key === 'accuracy' || key === 'precision' || key === 'recall' ? `${value}%` :
300
+ value.toFixed(2)
301
+ : value}
302
+
303
+ {key === 'accuracy' && value < 95 && (
304
+ <span className="text-sm text-red-500 ml-2">Below threshold</span>
305
+ )}
306
+ {key === 'sharpeRatio' && value < 1.0 && (
307
+ <span className="text-sm text-red-500 ml-2">Below threshold</span>
308
+ )}
309
+ </div>
310
+ </div>
311
+ ))}
312
+ </div>
313
+
314
+ <div className="mt-6 border-t dark:border-factory-blue-light pt-4">
315
+ <div className="text-sm font-medium mb-2">Threshold Requirements:</div>
316
+ <div className="text-sm">
317
+ {engine === 'AI' ? (
318
+ <div className="flex items-center">
319
+ <ArrowRight className="h-4 w-4 mr-2" />
320
+ <span>Accuracy ≥ 95%, No negative drift over 1%</span>
321
+ </div>
322
+ ) : (
323
+ <div className="flex items-center">
324
+ <ArrowRight className="h-4 w-4 mr-2" />
325
+ <span>Sharpe Ratio ≥ 1.0, Max drawdown ≤ 5%</span>
326
+ </div>
327
+ )}
328
+ </div>
329
+ </div>
330
+ </>
331
+ )}
332
+ </CardContent>
333
+ </Card>
334
+ </div>
335
+
336
+ {trainingState.alerts.length > 0 && !loading && (
337
+ <div className="space-y-3">
338
+ {trainingState.alerts.map((alert, idx) => (
339
+ <Alert
340
+ key={idx}
341
+ className={alert.type === 'error' ? 'border-red-500 dark:border-red-700 bg-red-50 dark:bg-red-900/20' :
342
+ alert.type === 'warning' ? 'border-yellow-500 dark:border-yellow-700 bg-yellow-50 dark:bg-yellow-900/20' :
343
+ 'border-green-500 dark:border-green-700 bg-green-50 dark:bg-green-900/20'}
344
+ >
345
+ <AlertDescription className="flex items-center">
346
+ {alert.type === 'error' ? (
347
+ <AlertTriangle className="h-4 w-4 mr-2 text-red-500 dark:text-red-400" />
348
+ ) : alert.type === 'warning' ? (
349
+ <AlertTriangle className="h-4 w-4 mr-2 text-yellow-500 dark:text-yellow-400" />
350
+ ) : (
351
+ <CheckCircle className="h-4 w-4 mr-2 text-green-500 dark:text-green-400" />
352
+ )}
353
+ {alert.message}
354
+ </AlertDescription>
355
+ </Alert>
356
+ ))}
357
+ </div>
358
+ )}
359
+ </div>
360
+ );
361
+ };
362
+
363
+ export default TrainingMonitor;
src/components/ui/accordion.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AccordionPrimitive from "@radix-ui/react-accordion"
3
+ import { ChevronDown } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Accordion = AccordionPrimitive.Root
8
+
9
+ const AccordionItem = React.forwardRef<
10
+ React.ElementRef<typeof AccordionPrimitive.Item>,
11
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
12
+ >(({ className, ...props }, ref) => (
13
+ <AccordionPrimitive.Item
14
+ ref={ref}
15
+ className={cn("border-b", className)}
16
+ {...props}
17
+ />
18
+ ))
19
+ AccordionItem.displayName = "AccordionItem"
20
+
21
+ const AccordionTrigger = React.forwardRef<
22
+ React.ElementRef<typeof AccordionPrimitive.Trigger>,
23
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
24
+ >(({ className, children, ...props }, ref) => (
25
+ <AccordionPrimitive.Header className="flex">
26
+ <AccordionPrimitive.Trigger
27
+ ref={ref}
28
+ className={cn(
29
+ "flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronDown className="h-4 w-4 shrink-0 transition-transform duration-200" />
36
+ </AccordionPrimitive.Trigger>
37
+ </AccordionPrimitive.Header>
38
+ ))
39
+ AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
40
+
41
+ const AccordionContent = React.forwardRef<
42
+ React.ElementRef<typeof AccordionPrimitive.Content>,
43
+ React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
44
+ >(({ className, children, ...props }, ref) => (
45
+ <AccordionPrimitive.Content
46
+ ref={ref}
47
+ className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
48
+ {...props}
49
+ >
50
+ <div className={cn("pb-4 pt-0", className)}>{children}</div>
51
+ </AccordionPrimitive.Content>
52
+ ))
53
+
54
+ AccordionContent.displayName = AccordionPrimitive.Content.displayName
55
+
56
+ export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
src/components/ui/alert-dialog.tsx ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
3
+
4
+ import { cn } from "@/lib/utils"
5
+ import { buttonVariants } from "@/components/ui/button"
6
+
7
+ const AlertDialog = AlertDialogPrimitive.Root
8
+
9
+ const AlertDialogTrigger = AlertDialogPrimitive.Trigger
10
+
11
+ const AlertDialogPortal = AlertDialogPrimitive.Portal
12
+
13
+ const AlertDialogOverlay = React.forwardRef<
14
+ React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
15
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
16
+ >(({ className, ...props }, ref) => (
17
+ <AlertDialogPrimitive.Overlay
18
+ className={cn(
19
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
20
+ className
21
+ )}
22
+ {...props}
23
+ ref={ref}
24
+ />
25
+ ))
26
+ AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
27
+
28
+ const AlertDialogContent = React.forwardRef<
29
+ React.ElementRef<typeof AlertDialogPrimitive.Content>,
30
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
31
+ >(({ className, ...props }, ref) => (
32
+ <AlertDialogPortal>
33
+ <AlertDialogOverlay />
34
+ <AlertDialogPrimitive.Content
35
+ ref={ref}
36
+ className={cn(
37
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
38
+ className
39
+ )}
40
+ {...props}
41
+ />
42
+ </AlertDialogPortal>
43
+ ))
44
+ AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
45
+
46
+ const AlertDialogHeader = ({
47
+ className,
48
+ ...props
49
+ }: React.HTMLAttributes<HTMLDivElement>) => (
50
+ <div
51
+ className={cn(
52
+ "flex flex-col space-y-2 text-center sm:text-left",
53
+ className
54
+ )}
55
+ {...props}
56
+ />
57
+ )
58
+ AlertDialogHeader.displayName = "AlertDialogHeader"
59
+
60
+ const AlertDialogFooter = ({
61
+ className,
62
+ ...props
63
+ }: React.HTMLAttributes<HTMLDivElement>) => (
64
+ <div
65
+ className={cn(
66
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
67
+ className
68
+ )}
69
+ {...props}
70
+ />
71
+ )
72
+ AlertDialogFooter.displayName = "AlertDialogFooter"
73
+
74
+ const AlertDialogTitle = React.forwardRef<
75
+ React.ElementRef<typeof AlertDialogPrimitive.Title>,
76
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
77
+ >(({ className, ...props }, ref) => (
78
+ <AlertDialogPrimitive.Title
79
+ ref={ref}
80
+ className={cn("text-lg font-semibold", className)}
81
+ {...props}
82
+ />
83
+ ))
84
+ AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
85
+
86
+ const AlertDialogDescription = React.forwardRef<
87
+ React.ElementRef<typeof AlertDialogPrimitive.Description>,
88
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
89
+ >(({ className, ...props }, ref) => (
90
+ <AlertDialogPrimitive.Description
91
+ ref={ref}
92
+ className={cn("text-sm text-muted-foreground", className)}
93
+ {...props}
94
+ />
95
+ ))
96
+ AlertDialogDescription.displayName =
97
+ AlertDialogPrimitive.Description.displayName
98
+
99
+ const AlertDialogAction = React.forwardRef<
100
+ React.ElementRef<typeof AlertDialogPrimitive.Action>,
101
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
102
+ >(({ className, ...props }, ref) => (
103
+ <AlertDialogPrimitive.Action
104
+ ref={ref}
105
+ className={cn(buttonVariants(), className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
110
+
111
+ const AlertDialogCancel = React.forwardRef<
112
+ React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
113
+ React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
114
+ >(({ className, ...props }, ref) => (
115
+ <AlertDialogPrimitive.Cancel
116
+ ref={ref}
117
+ className={cn(
118
+ buttonVariants({ variant: "outline" }),
119
+ "mt-2 sm:mt-0",
120
+ className
121
+ )}
122
+ {...props}
123
+ />
124
+ ))
125
+ AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
126
+
127
+ export {
128
+ AlertDialog,
129
+ AlertDialogPortal,
130
+ AlertDialogOverlay,
131
+ AlertDialogTrigger,
132
+ AlertDialogContent,
133
+ AlertDialogHeader,
134
+ AlertDialogFooter,
135
+ AlertDialogTitle,
136
+ AlertDialogDescription,
137
+ AlertDialogAction,
138
+ AlertDialogCancel,
139
+ }
src/components/ui/alert.tsx ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const alertVariants = cva(
7
+ "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default: "bg-background text-foreground",
12
+ destructive:
13
+ "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "default",
18
+ },
19
+ }
20
+ )
21
+
22
+ const Alert = React.forwardRef<
23
+ HTMLDivElement,
24
+ React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
25
+ >(({ className, variant, ...props }, ref) => (
26
+ <div
27
+ ref={ref}
28
+ role="alert"
29
+ className={cn(alertVariants({ variant }), className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ Alert.displayName = "Alert"
34
+
35
+ const AlertTitle = React.forwardRef<
36
+ HTMLParagraphElement,
37
+ React.HTMLAttributes<HTMLHeadingElement>
38
+ >(({ className, ...props }, ref) => (
39
+ <h5
40
+ ref={ref}
41
+ className={cn("mb-1 font-medium leading-none tracking-tight", className)}
42
+ {...props}
43
+ />
44
+ ))
45
+ AlertTitle.displayName = "AlertTitle"
46
+
47
+ const AlertDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <div
52
+ ref={ref}
53
+ className={cn("text-sm [&_p]:leading-relaxed", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ AlertDescription.displayName = "AlertDescription"
58
+
59
+ export { Alert, AlertTitle, AlertDescription }
src/components/ui/aspect-ratio.tsx ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
2
+
3
+ const AspectRatio = AspectRatioPrimitive.Root
4
+
5
+ export { AspectRatio }
src/components/ui/avatar.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as AvatarPrimitive from "@radix-ui/react-avatar"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Avatar = React.forwardRef<
7
+ React.ElementRef<typeof AvatarPrimitive.Root>,
8
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
9
+ >(({ className, ...props }, ref) => (
10
+ <AvatarPrimitive.Root
11
+ ref={ref}
12
+ className={cn(
13
+ "relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
14
+ className
15
+ )}
16
+ {...props}
17
+ />
18
+ ))
19
+ Avatar.displayName = AvatarPrimitive.Root.displayName
20
+
21
+ const AvatarImage = React.forwardRef<
22
+ React.ElementRef<typeof AvatarPrimitive.Image>,
23
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
24
+ >(({ className, ...props }, ref) => (
25
+ <AvatarPrimitive.Image
26
+ ref={ref}
27
+ className={cn("aspect-square h-full w-full", className)}
28
+ {...props}
29
+ />
30
+ ))
31
+ AvatarImage.displayName = AvatarPrimitive.Image.displayName
32
+
33
+ const AvatarFallback = React.forwardRef<
34
+ React.ElementRef<typeof AvatarPrimitive.Fallback>,
35
+ React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
36
+ >(({ className, ...props }, ref) => (
37
+ <AvatarPrimitive.Fallback
38
+ ref={ref}
39
+ className={cn(
40
+ "flex h-full w-full items-center justify-center rounded-full bg-muted",
41
+ className
42
+ )}
43
+ {...props}
44
+ />
45
+ ))
46
+ AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
47
+
48
+ export { Avatar, AvatarImage, AvatarFallback }
src/components/ui/badge.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const badgeVariants = cva(
7
+ "inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ default:
12
+ "border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
13
+ secondary:
14
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
15
+ destructive:
16
+ "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
17
+ outline: "text-foreground",
18
+ },
19
+ },
20
+ defaultVariants: {
21
+ variant: "default",
22
+ },
23
+ }
24
+ )
25
+
26
+ export interface BadgeProps
27
+ extends React.HTMLAttributes<HTMLDivElement>,
28
+ VariantProps<typeof badgeVariants> {}
29
+
30
+ function Badge({ className, variant, ...props }: BadgeProps) {
31
+ return (
32
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
33
+ )
34
+ }
35
+
36
+ export { Badge, badgeVariants }
src/components/ui/breadcrumb.tsx ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { ChevronRight, MoreHorizontal } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Breadcrumb = React.forwardRef<
8
+ HTMLElement,
9
+ React.ComponentPropsWithoutRef<"nav"> & {
10
+ separator?: React.ReactNode
11
+ }
12
+ >(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
13
+ Breadcrumb.displayName = "Breadcrumb"
14
+
15
+ const BreadcrumbList = React.forwardRef<
16
+ HTMLOListElement,
17
+ React.ComponentPropsWithoutRef<"ol">
18
+ >(({ className, ...props }, ref) => (
19
+ <ol
20
+ ref={ref}
21
+ className={cn(
22
+ "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ BreadcrumbList.displayName = "BreadcrumbList"
29
+
30
+ const BreadcrumbItem = React.forwardRef<
31
+ HTMLLIElement,
32
+ React.ComponentPropsWithoutRef<"li">
33
+ >(({ className, ...props }, ref) => (
34
+ <li
35
+ ref={ref}
36
+ className={cn("inline-flex items-center gap-1.5", className)}
37
+ {...props}
38
+ />
39
+ ))
40
+ BreadcrumbItem.displayName = "BreadcrumbItem"
41
+
42
+ const BreadcrumbLink = React.forwardRef<
43
+ HTMLAnchorElement,
44
+ React.ComponentPropsWithoutRef<"a"> & {
45
+ asChild?: boolean
46
+ }
47
+ >(({ asChild, className, ...props }, ref) => {
48
+ const Comp = asChild ? Slot : "a"
49
+
50
+ return (
51
+ <Comp
52
+ ref={ref}
53
+ className={cn("transition-colors hover:text-foreground", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ })
58
+ BreadcrumbLink.displayName = "BreadcrumbLink"
59
+
60
+ const BreadcrumbPage = React.forwardRef<
61
+ HTMLSpanElement,
62
+ React.ComponentPropsWithoutRef<"span">
63
+ >(({ className, ...props }, ref) => (
64
+ <span
65
+ ref={ref}
66
+ role="link"
67
+ aria-disabled="true"
68
+ aria-current="page"
69
+ className={cn("font-normal text-foreground", className)}
70
+ {...props}
71
+ />
72
+ ))
73
+ BreadcrumbPage.displayName = "BreadcrumbPage"
74
+
75
+ const BreadcrumbSeparator = ({
76
+ children,
77
+ className,
78
+ ...props
79
+ }: React.ComponentProps<"li">) => (
80
+ <li
81
+ role="presentation"
82
+ aria-hidden="true"
83
+ className={cn("[&>svg]:size-3.5", className)}
84
+ {...props}
85
+ >
86
+ {children ?? <ChevronRight />}
87
+ </li>
88
+ )
89
+ BreadcrumbSeparator.displayName = "BreadcrumbSeparator"
90
+
91
+ const BreadcrumbEllipsis = ({
92
+ className,
93
+ ...props
94
+ }: React.ComponentProps<"span">) => (
95
+ <span
96
+ role="presentation"
97
+ aria-hidden="true"
98
+ className={cn("flex h-9 w-9 items-center justify-center", className)}
99
+ {...props}
100
+ >
101
+ <MoreHorizontal className="h-4 w-4" />
102
+ <span className="sr-only">More</span>
103
+ </span>
104
+ )
105
+ BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"
106
+
107
+ export {
108
+ Breadcrumb,
109
+ BreadcrumbList,
110
+ BreadcrumbItem,
111
+ BreadcrumbLink,
112
+ BreadcrumbPage,
113
+ BreadcrumbSeparator,
114
+ BreadcrumbEllipsis,
115
+ }
src/components/ui/button.tsx ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Slot } from "@radix-ui/react-slot"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const buttonVariants = cva(
8
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
13
+ destructive:
14
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
15
+ outline:
16
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
17
+ secondary:
18
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
19
+ ghost: "hover:bg-accent hover:text-accent-foreground",
20
+ link: "text-primary underline-offset-4 hover:underline",
21
+ },
22
+ size: {
23
+ default: "h-10 px-4 py-2",
24
+ sm: "h-9 rounded-md px-3",
25
+ lg: "h-11 rounded-md px-8",
26
+ icon: "h-10 w-10",
27
+ },
28
+ },
29
+ defaultVariants: {
30
+ variant: "default",
31
+ size: "default",
32
+ },
33
+ }
34
+ )
35
+
36
+ export interface ButtonProps
37
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
38
+ VariantProps<typeof buttonVariants> {
39
+ asChild?: boolean
40
+ }
41
+
42
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
43
+ ({ className, variant, size, asChild = false, ...props }, ref) => {
44
+ const Comp = asChild ? Slot : "button"
45
+ return (
46
+ <Comp
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+ )
54
+ Button.displayName = "Button"
55
+
56
+ export { Button, buttonVariants }
src/components/ui/calendar.tsx ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react";
2
+ import { ChevronLeft, ChevronRight } from "lucide-react";
3
+ import { DayPicker } from "react-day-picker";
4
+
5
+ import { cn } from "@/lib/utils";
6
+ import { buttonVariants } from "@/components/ui/button";
7
+
8
+ export type CalendarProps = React.ComponentProps<typeof DayPicker>;
9
+
10
+ function Calendar({
11
+ className,
12
+ classNames,
13
+ showOutsideDays = true,
14
+ ...props
15
+ }: CalendarProps) {
16
+ return (
17
+ <DayPicker
18
+ showOutsideDays={showOutsideDays}
19
+ className={cn("p-3", className)}
20
+ classNames={{
21
+ months: "flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0",
22
+ month: "space-y-4",
23
+ caption: "flex justify-center pt-1 relative items-center",
24
+ caption_label: "text-sm font-medium",
25
+ nav: "space-x-1 flex items-center",
26
+ nav_button: cn(
27
+ buttonVariants({ variant: "outline" }),
28
+ "h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100"
29
+ ),
30
+ nav_button_previous: "absolute left-1",
31
+ nav_button_next: "absolute right-1",
32
+ table: "w-full border-collapse space-y-1",
33
+ head_row: "flex",
34
+ head_cell:
35
+ "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem]",
36
+ row: "flex w-full mt-2",
37
+ cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected])]:bg-accent first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20",
38
+ day: cn(
39
+ buttonVariants({ variant: "ghost" }),
40
+ "h-9 w-9 p-0 font-normal aria-selected:opacity-100"
41
+ ),
42
+ day_range_end: "day-range-end",
43
+ day_selected:
44
+ "bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground",
45
+ day_today: "bg-accent text-accent-foreground",
46
+ day_outside:
47
+ "day-outside text-muted-foreground opacity-50 aria-selected:bg-accent/50 aria-selected:text-muted-foreground aria-selected:opacity-30",
48
+ day_disabled: "text-muted-foreground opacity-50",
49
+ day_range_middle:
50
+ "aria-selected:bg-accent aria-selected:text-accent-foreground",
51
+ day_hidden: "invisible",
52
+ ...classNames,
53
+ }}
54
+ components={{
55
+ IconLeft: ({ ..._props }) => <ChevronLeft className="h-4 w-4" />,
56
+ IconRight: ({ ..._props }) => <ChevronRight className="h-4 w-4" />,
57
+ }}
58
+ {...props}
59
+ />
60
+ );
61
+ }
62
+ Calendar.displayName = "Calendar";
63
+
64
+ export { Calendar };
src/components/ui/card.tsx ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Card = React.forwardRef<
6
+ HTMLDivElement,
7
+ React.HTMLAttributes<HTMLDivElement>
8
+ >(({ className, ...props }, ref) => (
9
+ <div
10
+ ref={ref}
11
+ className={cn(
12
+ "rounded-lg border bg-card text-card-foreground shadow-sm",
13
+ className
14
+ )}
15
+ {...props}
16
+ />
17
+ ))
18
+ Card.displayName = "Card"
19
+
20
+ const CardHeader = React.forwardRef<
21
+ HTMLDivElement,
22
+ React.HTMLAttributes<HTMLDivElement>
23
+ >(({ className, ...props }, ref) => (
24
+ <div
25
+ ref={ref}
26
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
27
+ {...props}
28
+ />
29
+ ))
30
+ CardHeader.displayName = "CardHeader"
31
+
32
+ const CardTitle = React.forwardRef<
33
+ HTMLParagraphElement,
34
+ React.HTMLAttributes<HTMLHeadingElement>
35
+ >(({ className, ...props }, ref) => (
36
+ <h3
37
+ ref={ref}
38
+ className={cn(
39
+ "text-2xl font-semibold leading-none tracking-tight",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ CardTitle.displayName = "CardTitle"
46
+
47
+ const CardDescription = React.forwardRef<
48
+ HTMLParagraphElement,
49
+ React.HTMLAttributes<HTMLParagraphElement>
50
+ >(({ className, ...props }, ref) => (
51
+ <p
52
+ ref={ref}
53
+ className={cn("text-sm text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ ))
57
+ CardDescription.displayName = "CardDescription"
58
+
59
+ const CardContent = React.forwardRef<
60
+ HTMLDivElement,
61
+ React.HTMLAttributes<HTMLDivElement>
62
+ >(({ className, ...props }, ref) => (
63
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
64
+ ))
65
+ CardContent.displayName = "CardContent"
66
+
67
+ const CardFooter = React.forwardRef<
68
+ HTMLDivElement,
69
+ React.HTMLAttributes<HTMLDivElement>
70
+ >(({ className, ...props }, ref) => (
71
+ <div
72
+ ref={ref}
73
+ className={cn("flex items-center p-6 pt-0", className)}
74
+ {...props}
75
+ />
76
+ ))
77
+ CardFooter.displayName = "CardFooter"
78
+
79
+ export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
src/components/ui/carousel.tsx ADDED
@@ -0,0 +1,260 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import useEmblaCarousel, {
3
+ type UseEmblaCarouselType,
4
+ } from "embla-carousel-react"
5
+ import { ArrowLeft, ArrowRight } from "lucide-react"
6
+
7
+ import { cn } from "@/lib/utils"
8
+ import { Button } from "@/components/ui/button"
9
+
10
+ type CarouselApi = UseEmblaCarouselType[1]
11
+ type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
12
+ type CarouselOptions = UseCarouselParameters[0]
13
+ type CarouselPlugin = UseCarouselParameters[1]
14
+
15
+ type CarouselProps = {
16
+ opts?: CarouselOptions
17
+ plugins?: CarouselPlugin
18
+ orientation?: "horizontal" | "vertical"
19
+ setApi?: (api: CarouselApi) => void
20
+ }
21
+
22
+ type CarouselContextProps = {
23
+ carouselRef: ReturnType<typeof useEmblaCarousel>[0]
24
+ api: ReturnType<typeof useEmblaCarousel>[1]
25
+ scrollPrev: () => void
26
+ scrollNext: () => void
27
+ canScrollPrev: boolean
28
+ canScrollNext: boolean
29
+ } & CarouselProps
30
+
31
+ const CarouselContext = React.createContext<CarouselContextProps | null>(null)
32
+
33
+ function useCarousel() {
34
+ const context = React.useContext(CarouselContext)
35
+
36
+ if (!context) {
37
+ throw new Error("useCarousel must be used within a <Carousel />")
38
+ }
39
+
40
+ return context
41
+ }
42
+
43
+ const Carousel = React.forwardRef<
44
+ HTMLDivElement,
45
+ React.HTMLAttributes<HTMLDivElement> & CarouselProps
46
+ >(
47
+ (
48
+ {
49
+ orientation = "horizontal",
50
+ opts,
51
+ setApi,
52
+ plugins,
53
+ className,
54
+ children,
55
+ ...props
56
+ },
57
+ ref
58
+ ) => {
59
+ const [carouselRef, api] = useEmblaCarousel(
60
+ {
61
+ ...opts,
62
+ axis: orientation === "horizontal" ? "x" : "y",
63
+ },
64
+ plugins
65
+ )
66
+ const [canScrollPrev, setCanScrollPrev] = React.useState(false)
67
+ const [canScrollNext, setCanScrollNext] = React.useState(false)
68
+
69
+ const onSelect = React.useCallback((api: CarouselApi) => {
70
+ if (!api) {
71
+ return
72
+ }
73
+
74
+ setCanScrollPrev(api.canScrollPrev())
75
+ setCanScrollNext(api.canScrollNext())
76
+ }, [])
77
+
78
+ const scrollPrev = React.useCallback(() => {
79
+ api?.scrollPrev()
80
+ }, [api])
81
+
82
+ const scrollNext = React.useCallback(() => {
83
+ api?.scrollNext()
84
+ }, [api])
85
+
86
+ const handleKeyDown = React.useCallback(
87
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
88
+ if (event.key === "ArrowLeft") {
89
+ event.preventDefault()
90
+ scrollPrev()
91
+ } else if (event.key === "ArrowRight") {
92
+ event.preventDefault()
93
+ scrollNext()
94
+ }
95
+ },
96
+ [scrollPrev, scrollNext]
97
+ )
98
+
99
+ React.useEffect(() => {
100
+ if (!api || !setApi) {
101
+ return
102
+ }
103
+
104
+ setApi(api)
105
+ }, [api, setApi])
106
+
107
+ React.useEffect(() => {
108
+ if (!api) {
109
+ return
110
+ }
111
+
112
+ onSelect(api)
113
+ api.on("reInit", onSelect)
114
+ api.on("select", onSelect)
115
+
116
+ return () => {
117
+ api?.off("select", onSelect)
118
+ }
119
+ }, [api, onSelect])
120
+
121
+ return (
122
+ <CarouselContext.Provider
123
+ value={{
124
+ carouselRef,
125
+ api: api,
126
+ opts,
127
+ orientation:
128
+ orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
129
+ scrollPrev,
130
+ scrollNext,
131
+ canScrollPrev,
132
+ canScrollNext,
133
+ }}
134
+ >
135
+ <div
136
+ ref={ref}
137
+ onKeyDownCapture={handleKeyDown}
138
+ className={cn("relative", className)}
139
+ role="region"
140
+ aria-roledescription="carousel"
141
+ {...props}
142
+ >
143
+ {children}
144
+ </div>
145
+ </CarouselContext.Provider>
146
+ )
147
+ }
148
+ )
149
+ Carousel.displayName = "Carousel"
150
+
151
+ const CarouselContent = React.forwardRef<
152
+ HTMLDivElement,
153
+ React.HTMLAttributes<HTMLDivElement>
154
+ >(({ className, ...props }, ref) => {
155
+ const { carouselRef, orientation } = useCarousel()
156
+
157
+ return (
158
+ <div ref={carouselRef} className="overflow-hidden">
159
+ <div
160
+ ref={ref}
161
+ className={cn(
162
+ "flex",
163
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
164
+ className
165
+ )}
166
+ {...props}
167
+ />
168
+ </div>
169
+ )
170
+ })
171
+ CarouselContent.displayName = "CarouselContent"
172
+
173
+ const CarouselItem = React.forwardRef<
174
+ HTMLDivElement,
175
+ React.HTMLAttributes<HTMLDivElement>
176
+ >(({ className, ...props }, ref) => {
177
+ const { orientation } = useCarousel()
178
+
179
+ return (
180
+ <div
181
+ ref={ref}
182
+ role="group"
183
+ aria-roledescription="slide"
184
+ className={cn(
185
+ "min-w-0 shrink-0 grow-0 basis-full",
186
+ orientation === "horizontal" ? "pl-4" : "pt-4",
187
+ className
188
+ )}
189
+ {...props}
190
+ />
191
+ )
192
+ })
193
+ CarouselItem.displayName = "CarouselItem"
194
+
195
+ const CarouselPrevious = React.forwardRef<
196
+ HTMLButtonElement,
197
+ React.ComponentProps<typeof Button>
198
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
199
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel()
200
+
201
+ return (
202
+ <Button
203
+ ref={ref}
204
+ variant={variant}
205
+ size={size}
206
+ className={cn(
207
+ "absolute h-8 w-8 rounded-full",
208
+ orientation === "horizontal"
209
+ ? "-left-12 top-1/2 -translate-y-1/2"
210
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
211
+ className
212
+ )}
213
+ disabled={!canScrollPrev}
214
+ onClick={scrollPrev}
215
+ {...props}
216
+ >
217
+ <ArrowLeft className="h-4 w-4" />
218
+ <span className="sr-only">Previous slide</span>
219
+ </Button>
220
+ )
221
+ })
222
+ CarouselPrevious.displayName = "CarouselPrevious"
223
+
224
+ const CarouselNext = React.forwardRef<
225
+ HTMLButtonElement,
226
+ React.ComponentProps<typeof Button>
227
+ >(({ className, variant = "outline", size = "icon", ...props }, ref) => {
228
+ const { orientation, scrollNext, canScrollNext } = useCarousel()
229
+
230
+ return (
231
+ <Button
232
+ ref={ref}
233
+ variant={variant}
234
+ size={size}
235
+ className={cn(
236
+ "absolute h-8 w-8 rounded-full",
237
+ orientation === "horizontal"
238
+ ? "-right-12 top-1/2 -translate-y-1/2"
239
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
240
+ className
241
+ )}
242
+ disabled={!canScrollNext}
243
+ onClick={scrollNext}
244
+ {...props}
245
+ >
246
+ <ArrowRight className="h-4 w-4" />
247
+ <span className="sr-only">Next slide</span>
248
+ </Button>
249
+ )
250
+ })
251
+ CarouselNext.displayName = "CarouselNext"
252
+
253
+ export {
254
+ type CarouselApi,
255
+ Carousel,
256
+ CarouselContent,
257
+ CarouselItem,
258
+ CarouselPrevious,
259
+ CarouselNext,
260
+ }
src/components/ui/chart.tsx ADDED
@@ -0,0 +1,363 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as RechartsPrimitive from "recharts"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ // Format: { THEME_NAME: CSS_SELECTOR }
7
+ const THEMES = { light: "", dark: ".dark" } as const
8
+
9
+ export type ChartConfig = {
10
+ [k in string]: {
11
+ label?: React.ReactNode
12
+ icon?: React.ComponentType
13
+ } & (
14
+ | { color?: string; theme?: never }
15
+ | { color?: never; theme: Record<keyof typeof THEMES, string> }
16
+ )
17
+ }
18
+
19
+ type ChartContextProps = {
20
+ config: ChartConfig
21
+ }
22
+
23
+ const ChartContext = React.createContext<ChartContextProps | null>(null)
24
+
25
+ function useChart() {
26
+ const context = React.useContext(ChartContext)
27
+
28
+ if (!context) {
29
+ throw new Error("useChart must be used within a <ChartContainer />")
30
+ }
31
+
32
+ return context
33
+ }
34
+
35
+ const ChartContainer = React.forwardRef<
36
+ HTMLDivElement,
37
+ React.ComponentProps<"div"> & {
38
+ config: ChartConfig
39
+ children: React.ComponentProps<
40
+ typeof RechartsPrimitive.ResponsiveContainer
41
+ >["children"]
42
+ }
43
+ >(({ id, className, children, config, ...props }, ref) => {
44
+ const uniqueId = React.useId()
45
+ const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
46
+
47
+ return (
48
+ <ChartContext.Provider value={{ config }}>
49
+ <div
50
+ data-chart={chartId}
51
+ ref={ref}
52
+ className={cn(
53
+ "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-none [&_.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[stroke='#fff']]:stroke-transparent [&_.recharts-sector]:outline-none [&_.recharts-surface]:outline-none",
54
+ className
55
+ )}
56
+ {...props}
57
+ >
58
+ <ChartStyle id={chartId} config={config} />
59
+ <RechartsPrimitive.ResponsiveContainer>
60
+ {children}
61
+ </RechartsPrimitive.ResponsiveContainer>
62
+ </div>
63
+ </ChartContext.Provider>
64
+ )
65
+ })
66
+ ChartContainer.displayName = "Chart"
67
+
68
+ const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => {
69
+ const colorConfig = Object.entries(config).filter(
70
+ ([_, config]) => config.theme || config.color
71
+ )
72
+
73
+ if (!colorConfig.length) {
74
+ return null
75
+ }
76
+
77
+ return (
78
+ <style
79
+ dangerouslySetInnerHTML={{
80
+ __html: Object.entries(THEMES)
81
+ .map(
82
+ ([theme, prefix]) => `
83
+ ${prefix} [data-chart=${id}] {
84
+ ${colorConfig
85
+ .map(([key, itemConfig]) => {
86
+ const color =
87
+ itemConfig.theme?.[theme as keyof typeof itemConfig.theme] ||
88
+ itemConfig.color
89
+ return color ? ` --color-${key}: ${color};` : null
90
+ })
91
+ .join("\n")}
92
+ }
93
+ `
94
+ )
95
+ .join("\n"),
96
+ }}
97
+ />
98
+ )
99
+ }
100
+
101
+ const ChartTooltip = RechartsPrimitive.Tooltip
102
+
103
+ const ChartTooltipContent = React.forwardRef<
104
+ HTMLDivElement,
105
+ React.ComponentProps<typeof RechartsPrimitive.Tooltip> &
106
+ React.ComponentProps<"div"> & {
107
+ hideLabel?: boolean
108
+ hideIndicator?: boolean
109
+ indicator?: "line" | "dot" | "dashed"
110
+ nameKey?: string
111
+ labelKey?: string
112
+ }
113
+ >(
114
+ (
115
+ {
116
+ active,
117
+ payload,
118
+ className,
119
+ indicator = "dot",
120
+ hideLabel = false,
121
+ hideIndicator = false,
122
+ label,
123
+ labelFormatter,
124
+ labelClassName,
125
+ formatter,
126
+ color,
127
+ nameKey,
128
+ labelKey,
129
+ },
130
+ ref
131
+ ) => {
132
+ const { config } = useChart()
133
+
134
+ const tooltipLabel = React.useMemo(() => {
135
+ if (hideLabel || !payload?.length) {
136
+ return null
137
+ }
138
+
139
+ const [item] = payload
140
+ const key = `${labelKey || item.dataKey || item.name || "value"}`
141
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
142
+ const value =
143
+ !labelKey && typeof label === "string"
144
+ ? config[label as keyof typeof config]?.label || label
145
+ : itemConfig?.label
146
+
147
+ if (labelFormatter) {
148
+ return (
149
+ <div className={cn("font-medium", labelClassName)}>
150
+ {labelFormatter(value, payload)}
151
+ </div>
152
+ )
153
+ }
154
+
155
+ if (!value) {
156
+ return null
157
+ }
158
+
159
+ return <div className={cn("font-medium", labelClassName)}>{value}</div>
160
+ }, [
161
+ label,
162
+ labelFormatter,
163
+ payload,
164
+ hideLabel,
165
+ labelClassName,
166
+ config,
167
+ labelKey,
168
+ ])
169
+
170
+ if (!active || !payload?.length) {
171
+ return null
172
+ }
173
+
174
+ const nestLabel = payload.length === 1 && indicator !== "dot"
175
+
176
+ return (
177
+ <div
178
+ ref={ref}
179
+ className={cn(
180
+ "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",
181
+ className
182
+ )}
183
+ >
184
+ {!nestLabel ? tooltipLabel : null}
185
+ <div className="grid gap-1.5">
186
+ {payload.map((item, index) => {
187
+ const key = `${nameKey || item.name || item.dataKey || "value"}`
188
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
189
+ const indicatorColor = color || item.payload.fill || item.color
190
+
191
+ return (
192
+ <div
193
+ key={item.dataKey}
194
+ className={cn(
195
+ "flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground",
196
+ indicator === "dot" && "items-center"
197
+ )}
198
+ >
199
+ {formatter && item?.value !== undefined && item.name ? (
200
+ formatter(item.value, item.name, item, index, item.payload)
201
+ ) : (
202
+ <>
203
+ {itemConfig?.icon ? (
204
+ <itemConfig.icon />
205
+ ) : (
206
+ !hideIndicator && (
207
+ <div
208
+ className={cn(
209
+ "shrink-0 rounded-[2px] border-[--color-border] bg-[--color-bg]",
210
+ {
211
+ "h-2.5 w-2.5": indicator === "dot",
212
+ "w-1": indicator === "line",
213
+ "w-0 border-[1.5px] border-dashed bg-transparent":
214
+ indicator === "dashed",
215
+ "my-0.5": nestLabel && indicator === "dashed",
216
+ }
217
+ )}
218
+ style={
219
+ {
220
+ "--color-bg": indicatorColor,
221
+ "--color-border": indicatorColor,
222
+ } as React.CSSProperties
223
+ }
224
+ />
225
+ )
226
+ )}
227
+ <div
228
+ className={cn(
229
+ "flex flex-1 justify-between leading-none",
230
+ nestLabel ? "items-end" : "items-center"
231
+ )}
232
+ >
233
+ <div className="grid gap-1.5">
234
+ {nestLabel ? tooltipLabel : null}
235
+ <span className="text-muted-foreground">
236
+ {itemConfig?.label || item.name}
237
+ </span>
238
+ </div>
239
+ {item.value && (
240
+ <span className="font-mono font-medium tabular-nums text-foreground">
241
+ {item.value.toLocaleString()}
242
+ </span>
243
+ )}
244
+ </div>
245
+ </>
246
+ )}
247
+ </div>
248
+ )
249
+ })}
250
+ </div>
251
+ </div>
252
+ )
253
+ }
254
+ )
255
+ ChartTooltipContent.displayName = "ChartTooltip"
256
+
257
+ const ChartLegend = RechartsPrimitive.Legend
258
+
259
+ const ChartLegendContent = React.forwardRef<
260
+ HTMLDivElement,
261
+ React.ComponentProps<"div"> &
262
+ Pick<RechartsPrimitive.LegendProps, "payload" | "verticalAlign"> & {
263
+ hideIcon?: boolean
264
+ nameKey?: string
265
+ }
266
+ >(
267
+ (
268
+ { className, hideIcon = false, payload, verticalAlign = "bottom", nameKey },
269
+ ref
270
+ ) => {
271
+ const { config } = useChart()
272
+
273
+ if (!payload?.length) {
274
+ return null
275
+ }
276
+
277
+ return (
278
+ <div
279
+ ref={ref}
280
+ className={cn(
281
+ "flex items-center justify-center gap-4",
282
+ verticalAlign === "top" ? "pb-3" : "pt-3",
283
+ className
284
+ )}
285
+ >
286
+ {payload.map((item) => {
287
+ const key = `${nameKey || item.dataKey || "value"}`
288
+ const itemConfig = getPayloadConfigFromPayload(config, item, key)
289
+
290
+ return (
291
+ <div
292
+ key={item.value}
293
+ className={cn(
294
+ "flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground"
295
+ )}
296
+ >
297
+ {itemConfig?.icon && !hideIcon ? (
298
+ <itemConfig.icon />
299
+ ) : (
300
+ <div
301
+ className="h-2 w-2 shrink-0 rounded-[2px]"
302
+ style={{
303
+ backgroundColor: item.color,
304
+ }}
305
+ />
306
+ )}
307
+ {itemConfig?.label}
308
+ </div>
309
+ )
310
+ })}
311
+ </div>
312
+ )
313
+ }
314
+ )
315
+ ChartLegendContent.displayName = "ChartLegend"
316
+
317
+ // Helper to extract item config from a payload.
318
+ function getPayloadConfigFromPayload(
319
+ config: ChartConfig,
320
+ payload: unknown,
321
+ key: string
322
+ ) {
323
+ if (typeof payload !== "object" || payload === null) {
324
+ return undefined
325
+ }
326
+
327
+ const payloadPayload =
328
+ "payload" in payload &&
329
+ typeof payload.payload === "object" &&
330
+ payload.payload !== null
331
+ ? payload.payload
332
+ : undefined
333
+
334
+ let configLabelKey: string = key
335
+
336
+ if (
337
+ key in payload &&
338
+ typeof payload[key as keyof typeof payload] === "string"
339
+ ) {
340
+ configLabelKey = payload[key as keyof typeof payload] as string
341
+ } else if (
342
+ payloadPayload &&
343
+ key in payloadPayload &&
344
+ typeof payloadPayload[key as keyof typeof payloadPayload] === "string"
345
+ ) {
346
+ configLabelKey = payloadPayload[
347
+ key as keyof typeof payloadPayload
348
+ ] as string
349
+ }
350
+
351
+ return configLabelKey in config
352
+ ? config[configLabelKey]
353
+ : config[key as keyof typeof config]
354
+ }
355
+
356
+ export {
357
+ ChartContainer,
358
+ ChartTooltip,
359
+ ChartTooltipContent,
360
+ ChartLegend,
361
+ ChartLegendContent,
362
+ ChartStyle,
363
+ }
src/components/ui/checkbox.tsx ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
3
+ import { Check } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Checkbox = React.forwardRef<
8
+ React.ElementRef<typeof CheckboxPrimitive.Root>,
9
+ React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
10
+ >(({ className, ...props }, ref) => (
11
+ <CheckboxPrimitive.Root
12
+ ref={ref}
13
+ className={cn(
14
+ "peer h-4 w-4 shrink-0 rounded-sm border border-primary ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
15
+ className
16
+ )}
17
+ {...props}
18
+ >
19
+ <CheckboxPrimitive.Indicator
20
+ className={cn("flex items-center justify-center text-current")}
21
+ >
22
+ <Check className="h-4 w-4" />
23
+ </CheckboxPrimitive.Indicator>
24
+ </CheckboxPrimitive.Root>
25
+ ))
26
+ Checkbox.displayName = CheckboxPrimitive.Root.displayName
27
+
28
+ export { Checkbox }
src/components/ui/collapsible.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
2
+
3
+ const Collapsible = CollapsiblePrimitive.Root
4
+
5
+ const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
6
+
7
+ const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
8
+
9
+ export { Collapsible, CollapsibleTrigger, CollapsibleContent }
src/components/ui/command.tsx ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { type DialogProps } from "@radix-ui/react-dialog"
3
+ import { Command as CommandPrimitive } from "cmdk"
4
+ import { Search } from "lucide-react"
5
+
6
+ import { cn } from "@/lib/utils"
7
+ import { Dialog, DialogContent } from "@/components/ui/dialog"
8
+
9
+ const Command = React.forwardRef<
10
+ React.ElementRef<typeof CommandPrimitive>,
11
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive>
12
+ >(({ className, ...props }, ref) => (
13
+ <CommandPrimitive
14
+ ref={ref}
15
+ className={cn(
16
+ "flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ ))
22
+ Command.displayName = CommandPrimitive.displayName
23
+
24
+ interface CommandDialogProps extends DialogProps {}
25
+
26
+ const CommandDialog = ({ children, ...props }: CommandDialogProps) => {
27
+ return (
28
+ <Dialog {...props}>
29
+ <DialogContent className="overflow-hidden p-0 shadow-lg">
30
+ <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
31
+ {children}
32
+ </Command>
33
+ </DialogContent>
34
+ </Dialog>
35
+ )
36
+ }
37
+
38
+ const CommandInput = React.forwardRef<
39
+ React.ElementRef<typeof CommandPrimitive.Input>,
40
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
41
+ >(({ className, ...props }, ref) => (
42
+ <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
43
+ <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
44
+ <CommandPrimitive.Input
45
+ ref={ref}
46
+ className={cn(
47
+ "flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ </div>
53
+ ))
54
+
55
+ CommandInput.displayName = CommandPrimitive.Input.displayName
56
+
57
+ const CommandList = React.forwardRef<
58
+ React.ElementRef<typeof CommandPrimitive.List>,
59
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
60
+ >(({ className, ...props }, ref) => (
61
+ <CommandPrimitive.List
62
+ ref={ref}
63
+ className={cn("max-h-[300px] overflow-y-auto overflow-x-hidden", className)}
64
+ {...props}
65
+ />
66
+ ))
67
+
68
+ CommandList.displayName = CommandPrimitive.List.displayName
69
+
70
+ const CommandEmpty = React.forwardRef<
71
+ React.ElementRef<typeof CommandPrimitive.Empty>,
72
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
73
+ >((props, ref) => (
74
+ <CommandPrimitive.Empty
75
+ ref={ref}
76
+ className="py-6 text-center text-sm"
77
+ {...props}
78
+ />
79
+ ))
80
+
81
+ CommandEmpty.displayName = CommandPrimitive.Empty.displayName
82
+
83
+ const CommandGroup = React.forwardRef<
84
+ React.ElementRef<typeof CommandPrimitive.Group>,
85
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
86
+ >(({ className, ...props }, ref) => (
87
+ <CommandPrimitive.Group
88
+ ref={ref}
89
+ className={cn(
90
+ "overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
91
+ className
92
+ )}
93
+ {...props}
94
+ />
95
+ ))
96
+
97
+ CommandGroup.displayName = CommandPrimitive.Group.displayName
98
+
99
+ const CommandSeparator = React.forwardRef<
100
+ React.ElementRef<typeof CommandPrimitive.Separator>,
101
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
102
+ >(({ className, ...props }, ref) => (
103
+ <CommandPrimitive.Separator
104
+ ref={ref}
105
+ className={cn("-mx-1 h-px bg-border", className)}
106
+ {...props}
107
+ />
108
+ ))
109
+ CommandSeparator.displayName = CommandPrimitive.Separator.displayName
110
+
111
+ const CommandItem = React.forwardRef<
112
+ React.ElementRef<typeof CommandPrimitive.Item>,
113
+ React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
114
+ >(({ className, ...props }, ref) => (
115
+ <CommandPrimitive.Item
116
+ ref={ref}
117
+ className={cn(
118
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
119
+ className
120
+ )}
121
+ {...props}
122
+ />
123
+ ))
124
+
125
+ CommandItem.displayName = CommandPrimitive.Item.displayName
126
+
127
+ const CommandShortcut = ({
128
+ className,
129
+ ...props
130
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
131
+ return (
132
+ <span
133
+ className={cn(
134
+ "ml-auto text-xs tracking-widest text-muted-foreground",
135
+ className
136
+ )}
137
+ {...props}
138
+ />
139
+ )
140
+ }
141
+ CommandShortcut.displayName = "CommandShortcut"
142
+
143
+ export {
144
+ Command,
145
+ CommandDialog,
146
+ CommandInput,
147
+ CommandList,
148
+ CommandEmpty,
149
+ CommandGroup,
150
+ CommandItem,
151
+ CommandShortcut,
152
+ CommandSeparator,
153
+ }
src/components/ui/context-menu.tsx ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const ContextMenu = ContextMenuPrimitive.Root
8
+
9
+ const ContextMenuTrigger = ContextMenuPrimitive.Trigger
10
+
11
+ const ContextMenuGroup = ContextMenuPrimitive.Group
12
+
13
+ const ContextMenuPortal = ContextMenuPrimitive.Portal
14
+
15
+ const ContextMenuSub = ContextMenuPrimitive.Sub
16
+
17
+ const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
18
+
19
+ const ContextMenuSubTrigger = React.forwardRef<
20
+ React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
21
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
22
+ inset?: boolean
23
+ }
24
+ >(({ className, inset, children, ...props }, ref) => (
25
+ <ContextMenuPrimitive.SubTrigger
26
+ ref={ref}
27
+ className={cn(
28
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
29
+ inset && "pl-8",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronRight className="ml-auto h-4 w-4" />
36
+ </ContextMenuPrimitive.SubTrigger>
37
+ ))
38
+ ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
39
+
40
+ const ContextMenuSubContent = React.forwardRef<
41
+ React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
42
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
43
+ >(({ className, ...props }, ref) => (
44
+ <ContextMenuPrimitive.SubContent
45
+ ref={ref}
46
+ className={cn(
47
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
48
+ className
49
+ )}
50
+ {...props}
51
+ />
52
+ ))
53
+ ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
54
+
55
+ const ContextMenuContent = React.forwardRef<
56
+ React.ElementRef<typeof ContextMenuPrimitive.Content>,
57
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
58
+ >(({ className, ...props }, ref) => (
59
+ <ContextMenuPrimitive.Portal>
60
+ <ContextMenuPrimitive.Content
61
+ ref={ref}
62
+ className={cn(
63
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
64
+ className
65
+ )}
66
+ {...props}
67
+ />
68
+ </ContextMenuPrimitive.Portal>
69
+ ))
70
+ ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
71
+
72
+ const ContextMenuItem = React.forwardRef<
73
+ React.ElementRef<typeof ContextMenuPrimitive.Item>,
74
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
75
+ inset?: boolean
76
+ }
77
+ >(({ className, inset, ...props }, ref) => (
78
+ <ContextMenuPrimitive.Item
79
+ ref={ref}
80
+ className={cn(
81
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
82
+ inset && "pl-8",
83
+ className
84
+ )}
85
+ {...props}
86
+ />
87
+ ))
88
+ ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
89
+
90
+ const ContextMenuCheckboxItem = React.forwardRef<
91
+ React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
92
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
93
+ >(({ className, children, checked, ...props }, ref) => (
94
+ <ContextMenuPrimitive.CheckboxItem
95
+ ref={ref}
96
+ className={cn(
97
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
98
+ className
99
+ )}
100
+ checked={checked}
101
+ {...props}
102
+ >
103
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
104
+ <ContextMenuPrimitive.ItemIndicator>
105
+ <Check className="h-4 w-4" />
106
+ </ContextMenuPrimitive.ItemIndicator>
107
+ </span>
108
+ {children}
109
+ </ContextMenuPrimitive.CheckboxItem>
110
+ ))
111
+ ContextMenuCheckboxItem.displayName =
112
+ ContextMenuPrimitive.CheckboxItem.displayName
113
+
114
+ const ContextMenuRadioItem = React.forwardRef<
115
+ React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
116
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
117
+ >(({ className, children, ...props }, ref) => (
118
+ <ContextMenuPrimitive.RadioItem
119
+ ref={ref}
120
+ className={cn(
121
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
122
+ className
123
+ )}
124
+ {...props}
125
+ >
126
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
127
+ <ContextMenuPrimitive.ItemIndicator>
128
+ <Circle className="h-2 w-2 fill-current" />
129
+ </ContextMenuPrimitive.ItemIndicator>
130
+ </span>
131
+ {children}
132
+ </ContextMenuPrimitive.RadioItem>
133
+ ))
134
+ ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
135
+
136
+ const ContextMenuLabel = React.forwardRef<
137
+ React.ElementRef<typeof ContextMenuPrimitive.Label>,
138
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
139
+ inset?: boolean
140
+ }
141
+ >(({ className, inset, ...props }, ref) => (
142
+ <ContextMenuPrimitive.Label
143
+ ref={ref}
144
+ className={cn(
145
+ "px-2 py-1.5 text-sm font-semibold text-foreground",
146
+ inset && "pl-8",
147
+ className
148
+ )}
149
+ {...props}
150
+ />
151
+ ))
152
+ ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
153
+
154
+ const ContextMenuSeparator = React.forwardRef<
155
+ React.ElementRef<typeof ContextMenuPrimitive.Separator>,
156
+ React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
157
+ >(({ className, ...props }, ref) => (
158
+ <ContextMenuPrimitive.Separator
159
+ ref={ref}
160
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
161
+ {...props}
162
+ />
163
+ ))
164
+ ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
165
+
166
+ const ContextMenuShortcut = ({
167
+ className,
168
+ ...props
169
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
170
+ return (
171
+ <span
172
+ className={cn(
173
+ "ml-auto text-xs tracking-widest text-muted-foreground",
174
+ className
175
+ )}
176
+ {...props}
177
+ />
178
+ )
179
+ }
180
+ ContextMenuShortcut.displayName = "ContextMenuShortcut"
181
+
182
+ export {
183
+ ContextMenu,
184
+ ContextMenuTrigger,
185
+ ContextMenuContent,
186
+ ContextMenuItem,
187
+ ContextMenuCheckboxItem,
188
+ ContextMenuRadioItem,
189
+ ContextMenuLabel,
190
+ ContextMenuSeparator,
191
+ ContextMenuShortcut,
192
+ ContextMenuGroup,
193
+ ContextMenuPortal,
194
+ ContextMenuSub,
195
+ ContextMenuSubContent,
196
+ ContextMenuSubTrigger,
197
+ ContextMenuRadioGroup,
198
+ }
src/components/ui/dialog.tsx ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
3
+ import { X } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const Dialog = DialogPrimitive.Root
8
+
9
+ const DialogTrigger = DialogPrimitive.Trigger
10
+
11
+ const DialogPortal = DialogPrimitive.Portal
12
+
13
+ const DialogClose = DialogPrimitive.Close
14
+
15
+ const DialogOverlay = React.forwardRef<
16
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
17
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
18
+ >(({ className, ...props }, ref) => (
19
+ <DialogPrimitive.Overlay
20
+ ref={ref}
21
+ className={cn(
22
+ "fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
23
+ className
24
+ )}
25
+ {...props}
26
+ />
27
+ ))
28
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
29
+
30
+ const DialogContent = React.forwardRef<
31
+ React.ElementRef<typeof DialogPrimitive.Content>,
32
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
33
+ >(({ className, children, ...props }, ref) => (
34
+ <DialogPortal>
35
+ <DialogOverlay />
36
+ <DialogPrimitive.Content
37
+ ref={ref}
38
+ className={cn(
39
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
40
+ className
41
+ )}
42
+ {...props}
43
+ >
44
+ {children}
45
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
46
+ <X className="h-4 w-4" />
47
+ <span className="sr-only">Close</span>
48
+ </DialogPrimitive.Close>
49
+ </DialogPrimitive.Content>
50
+ </DialogPortal>
51
+ ))
52
+ DialogContent.displayName = DialogPrimitive.Content.displayName
53
+
54
+ const DialogHeader = ({
55
+ className,
56
+ ...props
57
+ }: React.HTMLAttributes<HTMLDivElement>) => (
58
+ <div
59
+ className={cn(
60
+ "flex flex-col space-y-1.5 text-center sm:text-left",
61
+ className
62
+ )}
63
+ {...props}
64
+ />
65
+ )
66
+ DialogHeader.displayName = "DialogHeader"
67
+
68
+ const DialogFooter = ({
69
+ className,
70
+ ...props
71
+ }: React.HTMLAttributes<HTMLDivElement>) => (
72
+ <div
73
+ className={cn(
74
+ "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
75
+ className
76
+ )}
77
+ {...props}
78
+ />
79
+ )
80
+ DialogFooter.displayName = "DialogFooter"
81
+
82
+ const DialogTitle = React.forwardRef<
83
+ React.ElementRef<typeof DialogPrimitive.Title>,
84
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
85
+ >(({ className, ...props }, ref) => (
86
+ <DialogPrimitive.Title
87
+ ref={ref}
88
+ className={cn(
89
+ "text-lg font-semibold leading-none tracking-tight",
90
+ className
91
+ )}
92
+ {...props}
93
+ />
94
+ ))
95
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
96
+
97
+ const DialogDescription = React.forwardRef<
98
+ React.ElementRef<typeof DialogPrimitive.Description>,
99
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
100
+ >(({ className, ...props }, ref) => (
101
+ <DialogPrimitive.Description
102
+ ref={ref}
103
+ className={cn("text-sm text-muted-foreground", className)}
104
+ {...props}
105
+ />
106
+ ))
107
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
108
+
109
+ export {
110
+ Dialog,
111
+ DialogPortal,
112
+ DialogOverlay,
113
+ DialogClose,
114
+ DialogTrigger,
115
+ DialogContent,
116
+ DialogHeader,
117
+ DialogFooter,
118
+ DialogTitle,
119
+ DialogDescription,
120
+ }
src/components/ui/drawer.tsx ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { Drawer as DrawerPrimitive } from "vaul"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const Drawer = ({
7
+ shouldScaleBackground = true,
8
+ ...props
9
+ }: React.ComponentProps<typeof DrawerPrimitive.Root>) => (
10
+ <DrawerPrimitive.Root
11
+ shouldScaleBackground={shouldScaleBackground}
12
+ {...props}
13
+ />
14
+ )
15
+ Drawer.displayName = "Drawer"
16
+
17
+ const DrawerTrigger = DrawerPrimitive.Trigger
18
+
19
+ const DrawerPortal = DrawerPrimitive.Portal
20
+
21
+ const DrawerClose = DrawerPrimitive.Close
22
+
23
+ const DrawerOverlay = React.forwardRef<
24
+ React.ElementRef<typeof DrawerPrimitive.Overlay>,
25
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
26
+ >(({ className, ...props }, ref) => (
27
+ <DrawerPrimitive.Overlay
28
+ ref={ref}
29
+ className={cn("fixed inset-0 z-50 bg-black/80", className)}
30
+ {...props}
31
+ />
32
+ ))
33
+ DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
34
+
35
+ const DrawerContent = React.forwardRef<
36
+ React.ElementRef<typeof DrawerPrimitive.Content>,
37
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
38
+ >(({ className, children, ...props }, ref) => (
39
+ <DrawerPortal>
40
+ <DrawerOverlay />
41
+ <DrawerPrimitive.Content
42
+ ref={ref}
43
+ className={cn(
44
+ "fixed inset-x-0 bottom-0 z-50 mt-24 flex h-auto flex-col rounded-t-[10px] border bg-background",
45
+ className
46
+ )}
47
+ {...props}
48
+ >
49
+ <div className="mx-auto mt-4 h-2 w-[100px] rounded-full bg-muted" />
50
+ {children}
51
+ </DrawerPrimitive.Content>
52
+ </DrawerPortal>
53
+ ))
54
+ DrawerContent.displayName = "DrawerContent"
55
+
56
+ const DrawerHeader = ({
57
+ className,
58
+ ...props
59
+ }: React.HTMLAttributes<HTMLDivElement>) => (
60
+ <div
61
+ className={cn("grid gap-1.5 p-4 text-center sm:text-left", className)}
62
+ {...props}
63
+ />
64
+ )
65
+ DrawerHeader.displayName = "DrawerHeader"
66
+
67
+ const DrawerFooter = ({
68
+ className,
69
+ ...props
70
+ }: React.HTMLAttributes<HTMLDivElement>) => (
71
+ <div
72
+ className={cn("mt-auto flex flex-col gap-2 p-4", className)}
73
+ {...props}
74
+ />
75
+ )
76
+ DrawerFooter.displayName = "DrawerFooter"
77
+
78
+ const DrawerTitle = React.forwardRef<
79
+ React.ElementRef<typeof DrawerPrimitive.Title>,
80
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
81
+ >(({ className, ...props }, ref) => (
82
+ <DrawerPrimitive.Title
83
+ ref={ref}
84
+ className={cn(
85
+ "text-lg font-semibold leading-none tracking-tight",
86
+ className
87
+ )}
88
+ {...props}
89
+ />
90
+ ))
91
+ DrawerTitle.displayName = DrawerPrimitive.Title.displayName
92
+
93
+ const DrawerDescription = React.forwardRef<
94
+ React.ElementRef<typeof DrawerPrimitive.Description>,
95
+ React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
96
+ >(({ className, ...props }, ref) => (
97
+ <DrawerPrimitive.Description
98
+ ref={ref}
99
+ className={cn("text-sm text-muted-foreground", className)}
100
+ {...props}
101
+ />
102
+ ))
103
+ DrawerDescription.displayName = DrawerPrimitive.Description.displayName
104
+
105
+ export {
106
+ Drawer,
107
+ DrawerPortal,
108
+ DrawerOverlay,
109
+ DrawerTrigger,
110
+ DrawerClose,
111
+ DrawerContent,
112
+ DrawerHeader,
113
+ DrawerFooter,
114
+ DrawerTitle,
115
+ DrawerDescription,
116
+ }
src/components/ui/dropdown-menu.tsx ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const DropdownMenu = DropdownMenuPrimitive.Root
8
+
9
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
10
+
11
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group
12
+
13
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal
14
+
15
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub
16
+
17
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
18
+
19
+ const DropdownMenuSubTrigger = React.forwardRef<
20
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
21
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
22
+ inset?: boolean
23
+ }
24
+ >(({ className, inset, children, ...props }, ref) => (
25
+ <DropdownMenuPrimitive.SubTrigger
26
+ ref={ref}
27
+ className={cn(
28
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
29
+ inset && "pl-8",
30
+ className
31
+ )}
32
+ {...props}
33
+ >
34
+ {children}
35
+ <ChevronRight className="ml-auto h-4 w-4" />
36
+ </DropdownMenuPrimitive.SubTrigger>
37
+ ))
38
+ DropdownMenuSubTrigger.displayName =
39
+ DropdownMenuPrimitive.SubTrigger.displayName
40
+
41
+ const DropdownMenuSubContent = React.forwardRef<
42
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
43
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
44
+ >(({ className, ...props }, ref) => (
45
+ <DropdownMenuPrimitive.SubContent
46
+ ref={ref}
47
+ className={cn(
48
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
49
+ className
50
+ )}
51
+ {...props}
52
+ />
53
+ ))
54
+ DropdownMenuSubContent.displayName =
55
+ DropdownMenuPrimitive.SubContent.displayName
56
+
57
+ const DropdownMenuContent = React.forwardRef<
58
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
59
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
60
+ >(({ className, sideOffset = 4, ...props }, ref) => (
61
+ <DropdownMenuPrimitive.Portal>
62
+ <DropdownMenuPrimitive.Content
63
+ ref={ref}
64
+ sideOffset={sideOffset}
65
+ className={cn(
66
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
67
+ className
68
+ )}
69
+ {...props}
70
+ />
71
+ </DropdownMenuPrimitive.Portal>
72
+ ))
73
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
74
+
75
+ const DropdownMenuItem = React.forwardRef<
76
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
77
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
78
+ inset?: boolean
79
+ }
80
+ >(({ className, inset, ...props }, ref) => (
81
+ <DropdownMenuPrimitive.Item
82
+ ref={ref}
83
+ className={cn(
84
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
85
+ inset && "pl-8",
86
+ className
87
+ )}
88
+ {...props}
89
+ />
90
+ ))
91
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
92
+
93
+ const DropdownMenuCheckboxItem = React.forwardRef<
94
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
95
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
96
+ >(({ className, children, checked, ...props }, ref) => (
97
+ <DropdownMenuPrimitive.CheckboxItem
98
+ ref={ref}
99
+ className={cn(
100
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
101
+ className
102
+ )}
103
+ checked={checked}
104
+ {...props}
105
+ >
106
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
107
+ <DropdownMenuPrimitive.ItemIndicator>
108
+ <Check className="h-4 w-4" />
109
+ </DropdownMenuPrimitive.ItemIndicator>
110
+ </span>
111
+ {children}
112
+ </DropdownMenuPrimitive.CheckboxItem>
113
+ ))
114
+ DropdownMenuCheckboxItem.displayName =
115
+ DropdownMenuPrimitive.CheckboxItem.displayName
116
+
117
+ const DropdownMenuRadioItem = React.forwardRef<
118
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
119
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
120
+ >(({ className, children, ...props }, ref) => (
121
+ <DropdownMenuPrimitive.RadioItem
122
+ ref={ref}
123
+ className={cn(
124
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
125
+ className
126
+ )}
127
+ {...props}
128
+ >
129
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
130
+ <DropdownMenuPrimitive.ItemIndicator>
131
+ <Circle className="h-2 w-2 fill-current" />
132
+ </DropdownMenuPrimitive.ItemIndicator>
133
+ </span>
134
+ {children}
135
+ </DropdownMenuPrimitive.RadioItem>
136
+ ))
137
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
138
+
139
+ const DropdownMenuLabel = React.forwardRef<
140
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
141
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
142
+ inset?: boolean
143
+ }
144
+ >(({ className, inset, ...props }, ref) => (
145
+ <DropdownMenuPrimitive.Label
146
+ ref={ref}
147
+ className={cn(
148
+ "px-2 py-1.5 text-sm font-semibold",
149
+ inset && "pl-8",
150
+ className
151
+ )}
152
+ {...props}
153
+ />
154
+ ))
155
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
156
+
157
+ const DropdownMenuSeparator = React.forwardRef<
158
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
159
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
160
+ >(({ className, ...props }, ref) => (
161
+ <DropdownMenuPrimitive.Separator
162
+ ref={ref}
163
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
164
+ {...props}
165
+ />
166
+ ))
167
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
168
+
169
+ const DropdownMenuShortcut = ({
170
+ className,
171
+ ...props
172
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
173
+ return (
174
+ <span
175
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
176
+ {...props}
177
+ />
178
+ )
179
+ }
180
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
181
+
182
+ export {
183
+ DropdownMenu,
184
+ DropdownMenuTrigger,
185
+ DropdownMenuContent,
186
+ DropdownMenuItem,
187
+ DropdownMenuCheckboxItem,
188
+ DropdownMenuRadioItem,
189
+ DropdownMenuLabel,
190
+ DropdownMenuSeparator,
191
+ DropdownMenuShortcut,
192
+ DropdownMenuGroup,
193
+ DropdownMenuPortal,
194
+ DropdownMenuSub,
195
+ DropdownMenuSubContent,
196
+ DropdownMenuSubTrigger,
197
+ DropdownMenuRadioGroup,
198
+ }
src/components/ui/form.tsx ADDED
@@ -0,0 +1,176 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { Slot } from "@radix-ui/react-slot"
4
+ import {
5
+ Controller,
6
+ ControllerProps,
7
+ FieldPath,
8
+ FieldValues,
9
+ FormProvider,
10
+ useFormContext,
11
+ } from "react-hook-form"
12
+
13
+ import { cn } from "@/lib/utils"
14
+ import { Label } from "@/components/ui/label"
15
+
16
+ const Form = FormProvider
17
+
18
+ type FormFieldContextValue<
19
+ TFieldValues extends FieldValues = FieldValues,
20
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
21
+ > = {
22
+ name: TName
23
+ }
24
+
25
+ const FormFieldContext = React.createContext<FormFieldContextValue>(
26
+ {} as FormFieldContextValue
27
+ )
28
+
29
+ const FormField = <
30
+ TFieldValues extends FieldValues = FieldValues,
31
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
32
+ >({
33
+ ...props
34
+ }: ControllerProps<TFieldValues, TName>) => {
35
+ return (
36
+ <FormFieldContext.Provider value={{ name: props.name }}>
37
+ <Controller {...props} />
38
+ </FormFieldContext.Provider>
39
+ )
40
+ }
41
+
42
+ const useFormField = () => {
43
+ const fieldContext = React.useContext(FormFieldContext)
44
+ const itemContext = React.useContext(FormItemContext)
45
+ const { getFieldState, formState } = useFormContext()
46
+
47
+ const fieldState = getFieldState(fieldContext.name, formState)
48
+
49
+ if (!fieldContext) {
50
+ throw new Error("useFormField should be used within <FormField>")
51
+ }
52
+
53
+ const { id } = itemContext
54
+
55
+ return {
56
+ id,
57
+ name: fieldContext.name,
58
+ formItemId: `${id}-form-item`,
59
+ formDescriptionId: `${id}-form-item-description`,
60
+ formMessageId: `${id}-form-item-message`,
61
+ ...fieldState,
62
+ }
63
+ }
64
+
65
+ type FormItemContextValue = {
66
+ id: string
67
+ }
68
+
69
+ const FormItemContext = React.createContext<FormItemContextValue>(
70
+ {} as FormItemContextValue
71
+ )
72
+
73
+ const FormItem = React.forwardRef<
74
+ HTMLDivElement,
75
+ React.HTMLAttributes<HTMLDivElement>
76
+ >(({ className, ...props }, ref) => {
77
+ const id = React.useId()
78
+
79
+ return (
80
+ <FormItemContext.Provider value={{ id }}>
81
+ <div ref={ref} className={cn("space-y-2", className)} {...props} />
82
+ </FormItemContext.Provider>
83
+ )
84
+ })
85
+ FormItem.displayName = "FormItem"
86
+
87
+ const FormLabel = React.forwardRef<
88
+ React.ElementRef<typeof LabelPrimitive.Root>,
89
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
90
+ >(({ className, ...props }, ref) => {
91
+ const { error, formItemId } = useFormField()
92
+
93
+ return (
94
+ <Label
95
+ ref={ref}
96
+ className={cn(error && "text-destructive", className)}
97
+ htmlFor={formItemId}
98
+ {...props}
99
+ />
100
+ )
101
+ })
102
+ FormLabel.displayName = "FormLabel"
103
+
104
+ const FormControl = React.forwardRef<
105
+ React.ElementRef<typeof Slot>,
106
+ React.ComponentPropsWithoutRef<typeof Slot>
107
+ >(({ ...props }, ref) => {
108
+ const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
109
+
110
+ return (
111
+ <Slot
112
+ ref={ref}
113
+ id={formItemId}
114
+ aria-describedby={
115
+ !error
116
+ ? `${formDescriptionId}`
117
+ : `${formDescriptionId} ${formMessageId}`
118
+ }
119
+ aria-invalid={!!error}
120
+ {...props}
121
+ />
122
+ )
123
+ })
124
+ FormControl.displayName = "FormControl"
125
+
126
+ const FormDescription = React.forwardRef<
127
+ HTMLParagraphElement,
128
+ React.HTMLAttributes<HTMLParagraphElement>
129
+ >(({ className, ...props }, ref) => {
130
+ const { formDescriptionId } = useFormField()
131
+
132
+ return (
133
+ <p
134
+ ref={ref}
135
+ id={formDescriptionId}
136
+ className={cn("text-sm text-muted-foreground", className)}
137
+ {...props}
138
+ />
139
+ )
140
+ })
141
+ FormDescription.displayName = "FormDescription"
142
+
143
+ const FormMessage = React.forwardRef<
144
+ HTMLParagraphElement,
145
+ React.HTMLAttributes<HTMLParagraphElement>
146
+ >(({ className, children, ...props }, ref) => {
147
+ const { error, formMessageId } = useFormField()
148
+ const body = error ? String(error?.message) : children
149
+
150
+ if (!body) {
151
+ return null
152
+ }
153
+
154
+ return (
155
+ <p
156
+ ref={ref}
157
+ id={formMessageId}
158
+ className={cn("text-sm font-medium text-destructive", className)}
159
+ {...props}
160
+ >
161
+ {body}
162
+ </p>
163
+ )
164
+ })
165
+ FormMessage.displayName = "FormMessage"
166
+
167
+ export {
168
+ useFormField,
169
+ Form,
170
+ FormItem,
171
+ FormLabel,
172
+ FormControl,
173
+ FormDescription,
174
+ FormMessage,
175
+ FormField,
176
+ }
src/components/ui/hover-card.tsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
3
+
4
+ import { cn } from "@/lib/utils"
5
+
6
+ const HoverCard = HoverCardPrimitive.Root
7
+
8
+ const HoverCardTrigger = HoverCardPrimitive.Trigger
9
+
10
+ const HoverCardContent = React.forwardRef<
11
+ React.ElementRef<typeof HoverCardPrimitive.Content>,
12
+ React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
13
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
14
+ <HoverCardPrimitive.Content
15
+ ref={ref}
16
+ align={align}
17
+ sideOffset={sideOffset}
18
+ className={cn(
19
+ "z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
20
+ className
21
+ )}
22
+ {...props}
23
+ />
24
+ ))
25
+ HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
26
+
27
+ export { HoverCard, HoverCardTrigger, HoverCardContent }
src/components/ui/input-otp.tsx ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import { OTPInput, OTPInputContext } from "input-otp"
3
+ import { Dot } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const InputOTP = React.forwardRef<
8
+ React.ElementRef<typeof OTPInput>,
9
+ React.ComponentPropsWithoutRef<typeof OTPInput>
10
+ >(({ className, containerClassName, ...props }, ref) => (
11
+ <OTPInput
12
+ ref={ref}
13
+ containerClassName={cn(
14
+ "flex items-center gap-2 has-[:disabled]:opacity-50",
15
+ containerClassName
16
+ )}
17
+ className={cn("disabled:cursor-not-allowed", className)}
18
+ {...props}
19
+ />
20
+ ))
21
+ InputOTP.displayName = "InputOTP"
22
+
23
+ const InputOTPGroup = React.forwardRef<
24
+ React.ElementRef<"div">,
25
+ React.ComponentPropsWithoutRef<"div">
26
+ >(({ className, ...props }, ref) => (
27
+ <div ref={ref} className={cn("flex items-center", className)} {...props} />
28
+ ))
29
+ InputOTPGroup.displayName = "InputOTPGroup"
30
+
31
+ const InputOTPSlot = React.forwardRef<
32
+ React.ElementRef<"div">,
33
+ React.ComponentPropsWithoutRef<"div"> & { index: number }
34
+ >(({ index, className, ...props }, ref) => {
35
+ const inputOTPContext = React.useContext(OTPInputContext)
36
+ const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index]
37
+
38
+ return (
39
+ <div
40
+ ref={ref}
41
+ className={cn(
42
+ "relative flex h-10 w-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md",
43
+ isActive && "z-10 ring-2 ring-ring ring-offset-background",
44
+ className
45
+ )}
46
+ {...props}
47
+ >
48
+ {char}
49
+ {hasFakeCaret && (
50
+ <div className="pointer-events-none absolute inset-0 flex items-center justify-center">
51
+ <div className="h-4 w-px animate-caret-blink bg-foreground duration-1000" />
52
+ </div>
53
+ )}
54
+ </div>
55
+ )
56
+ })
57
+ InputOTPSlot.displayName = "InputOTPSlot"
58
+
59
+ const InputOTPSeparator = React.forwardRef<
60
+ React.ElementRef<"div">,
61
+ React.ComponentPropsWithoutRef<"div">
62
+ >(({ ...props }, ref) => (
63
+ <div ref={ref} role="separator" {...props}>
64
+ <Dot />
65
+ </div>
66
+ ))
67
+ InputOTPSeparator.displayName = "InputOTPSeparator"
68
+
69
+ export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator }
src/components/ui/input.tsx ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/lib/utils"
4
+
5
+ const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<"input">>(
6
+ ({ className, type, ...props }, ref) => {
7
+ return (
8
+ <input
9
+ type={type}
10
+ className={cn(
11
+ "flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ className
13
+ )}
14
+ ref={ref}
15
+ {...props}
16
+ />
17
+ )
18
+ }
19
+ )
20
+ Input.displayName = "Input"
21
+
22
+ export { Input }
src/components/ui/label.tsx ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as LabelPrimitive from "@radix-ui/react-label"
3
+ import { cva, type VariantProps } from "class-variance-authority"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const labelVariants = cva(
8
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
9
+ )
10
+
11
+ const Label = React.forwardRef<
12
+ React.ElementRef<typeof LabelPrimitive.Root>,
13
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
14
+ VariantProps<typeof labelVariants>
15
+ >(({ className, ...props }, ref) => (
16
+ <LabelPrimitive.Root
17
+ ref={ref}
18
+ className={cn(labelVariants(), className)}
19
+ {...props}
20
+ />
21
+ ))
22
+ Label.displayName = LabelPrimitive.Root.displayName
23
+
24
+ export { Label }
src/components/ui/menubar.tsx ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as MenubarPrimitive from "@radix-ui/react-menubar"
3
+ import { Check, ChevronRight, Circle } from "lucide-react"
4
+
5
+ import { cn } from "@/lib/utils"
6
+
7
+ const MenubarMenu = MenubarPrimitive.Menu
8
+
9
+ const MenubarGroup = MenubarPrimitive.Group
10
+
11
+ const MenubarPortal = MenubarPrimitive.Portal
12
+
13
+ const MenubarSub = MenubarPrimitive.Sub
14
+
15
+ const MenubarRadioGroup = MenubarPrimitive.RadioGroup
16
+
17
+ const Menubar = React.forwardRef<
18
+ React.ElementRef<typeof MenubarPrimitive.Root>,
19
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Root>
20
+ >(({ className, ...props }, ref) => (
21
+ <MenubarPrimitive.Root
22
+ ref={ref}
23
+ className={cn(
24
+ "flex h-10 items-center space-x-1 rounded-md border bg-background p-1",
25
+ className
26
+ )}
27
+ {...props}
28
+ />
29
+ ))
30
+ Menubar.displayName = MenubarPrimitive.Root.displayName
31
+
32
+ const MenubarTrigger = React.forwardRef<
33
+ React.ElementRef<typeof MenubarPrimitive.Trigger>,
34
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Trigger>
35
+ >(({ className, ...props }, ref) => (
36
+ <MenubarPrimitive.Trigger
37
+ ref={ref}
38
+ className={cn(
39
+ "flex cursor-default select-none items-center rounded-sm px-3 py-1.5 text-sm font-medium outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ MenubarTrigger.displayName = MenubarPrimitive.Trigger.displayName
46
+
47
+ const MenubarSubTrigger = React.forwardRef<
48
+ React.ElementRef<typeof MenubarPrimitive.SubTrigger>,
49
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubTrigger> & {
50
+ inset?: boolean
51
+ }
52
+ >(({ className, inset, children, ...props }, ref) => (
53
+ <MenubarPrimitive.SubTrigger
54
+ ref={ref}
55
+ className={cn(
56
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
57
+ inset && "pl-8",
58
+ className
59
+ )}
60
+ {...props}
61
+ >
62
+ {children}
63
+ <ChevronRight className="ml-auto h-4 w-4" />
64
+ </MenubarPrimitive.SubTrigger>
65
+ ))
66
+ MenubarSubTrigger.displayName = MenubarPrimitive.SubTrigger.displayName
67
+
68
+ const MenubarSubContent = React.forwardRef<
69
+ React.ElementRef<typeof MenubarPrimitive.SubContent>,
70
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.SubContent>
71
+ >(({ className, ...props }, ref) => (
72
+ <MenubarPrimitive.SubContent
73
+ ref={ref}
74
+ className={cn(
75
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
76
+ className
77
+ )}
78
+ {...props}
79
+ />
80
+ ))
81
+ MenubarSubContent.displayName = MenubarPrimitive.SubContent.displayName
82
+
83
+ const MenubarContent = React.forwardRef<
84
+ React.ElementRef<typeof MenubarPrimitive.Content>,
85
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Content>
86
+ >(
87
+ (
88
+ { className, align = "start", alignOffset = -4, sideOffset = 8, ...props },
89
+ ref
90
+ ) => (
91
+ <MenubarPrimitive.Portal>
92
+ <MenubarPrimitive.Content
93
+ ref={ref}
94
+ align={align}
95
+ alignOffset={alignOffset}
96
+ sideOffset={sideOffset}
97
+ className={cn(
98
+ "z-50 min-w-[12rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
99
+ className
100
+ )}
101
+ {...props}
102
+ />
103
+ </MenubarPrimitive.Portal>
104
+ )
105
+ )
106
+ MenubarContent.displayName = MenubarPrimitive.Content.displayName
107
+
108
+ const MenubarItem = React.forwardRef<
109
+ React.ElementRef<typeof MenubarPrimitive.Item>,
110
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Item> & {
111
+ inset?: boolean
112
+ }
113
+ >(({ className, inset, ...props }, ref) => (
114
+ <MenubarPrimitive.Item
115
+ ref={ref}
116
+ className={cn(
117
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
118
+ inset && "pl-8",
119
+ className
120
+ )}
121
+ {...props}
122
+ />
123
+ ))
124
+ MenubarItem.displayName = MenubarPrimitive.Item.displayName
125
+
126
+ const MenubarCheckboxItem = React.forwardRef<
127
+ React.ElementRef<typeof MenubarPrimitive.CheckboxItem>,
128
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.CheckboxItem>
129
+ >(({ className, children, checked, ...props }, ref) => (
130
+ <MenubarPrimitive.CheckboxItem
131
+ ref={ref}
132
+ className={cn(
133
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
134
+ className
135
+ )}
136
+ checked={checked}
137
+ {...props}
138
+ >
139
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
140
+ <MenubarPrimitive.ItemIndicator>
141
+ <Check className="h-4 w-4" />
142
+ </MenubarPrimitive.ItemIndicator>
143
+ </span>
144
+ {children}
145
+ </MenubarPrimitive.CheckboxItem>
146
+ ))
147
+ MenubarCheckboxItem.displayName = MenubarPrimitive.CheckboxItem.displayName
148
+
149
+ const MenubarRadioItem = React.forwardRef<
150
+ React.ElementRef<typeof MenubarPrimitive.RadioItem>,
151
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.RadioItem>
152
+ >(({ className, children, ...props }, ref) => (
153
+ <MenubarPrimitive.RadioItem
154
+ ref={ref}
155
+ className={cn(
156
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
157
+ className
158
+ )}
159
+ {...props}
160
+ >
161
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
162
+ <MenubarPrimitive.ItemIndicator>
163
+ <Circle className="h-2 w-2 fill-current" />
164
+ </MenubarPrimitive.ItemIndicator>
165
+ </span>
166
+ {children}
167
+ </MenubarPrimitive.RadioItem>
168
+ ))
169
+ MenubarRadioItem.displayName = MenubarPrimitive.RadioItem.displayName
170
+
171
+ const MenubarLabel = React.forwardRef<
172
+ React.ElementRef<typeof MenubarPrimitive.Label>,
173
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Label> & {
174
+ inset?: boolean
175
+ }
176
+ >(({ className, inset, ...props }, ref) => (
177
+ <MenubarPrimitive.Label
178
+ ref={ref}
179
+ className={cn(
180
+ "px-2 py-1.5 text-sm font-semibold",
181
+ inset && "pl-8",
182
+ className
183
+ )}
184
+ {...props}
185
+ />
186
+ ))
187
+ MenubarLabel.displayName = MenubarPrimitive.Label.displayName
188
+
189
+ const MenubarSeparator = React.forwardRef<
190
+ React.ElementRef<typeof MenubarPrimitive.Separator>,
191
+ React.ComponentPropsWithoutRef<typeof MenubarPrimitive.Separator>
192
+ >(({ className, ...props }, ref) => (
193
+ <MenubarPrimitive.Separator
194
+ ref={ref}
195
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
196
+ {...props}
197
+ />
198
+ ))
199
+ MenubarSeparator.displayName = MenubarPrimitive.Separator.displayName
200
+
201
+ const MenubarShortcut = ({
202
+ className,
203
+ ...props
204
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
205
+ return (
206
+ <span
207
+ className={cn(
208
+ "ml-auto text-xs tracking-widest text-muted-foreground",
209
+ className
210
+ )}
211
+ {...props}
212
+ />
213
+ )
214
+ }
215
+ MenubarShortcut.displayname = "MenubarShortcut"
216
+
217
+ export {
218
+ Menubar,
219
+ MenubarMenu,
220
+ MenubarTrigger,
221
+ MenubarContent,
222
+ MenubarItem,
223
+ MenubarSeparator,
224
+ MenubarLabel,
225
+ MenubarCheckboxItem,
226
+ MenubarRadioGroup,
227
+ MenubarRadioItem,
228
+ MenubarPortal,
229
+ MenubarSubContent,
230
+ MenubarSubTrigger,
231
+ MenubarGroup,
232
+ MenubarSub,
233
+ MenubarShortcut,
234
+ }
src/components/ui/navigation-menu.tsx ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from "react"
2
+ import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
3
+ import { cva } from "class-variance-authority"
4
+ import { ChevronDown } from "lucide-react"
5
+
6
+ import { cn } from "@/lib/utils"
7
+
8
+ const NavigationMenu = React.forwardRef<
9
+ React.ElementRef<typeof NavigationMenuPrimitive.Root>,
10
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
11
+ >(({ className, children, ...props }, ref) => (
12
+ <NavigationMenuPrimitive.Root
13
+ ref={ref}
14
+ className={cn(
15
+ "relative z-10 flex max-w-max flex-1 items-center justify-center",
16
+ className
17
+ )}
18
+ {...props}
19
+ >
20
+ {children}
21
+ <NavigationMenuViewport />
22
+ </NavigationMenuPrimitive.Root>
23
+ ))
24
+ NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
25
+
26
+ const NavigationMenuList = React.forwardRef<
27
+ React.ElementRef<typeof NavigationMenuPrimitive.List>,
28
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
29
+ >(({ className, ...props }, ref) => (
30
+ <NavigationMenuPrimitive.List
31
+ ref={ref}
32
+ className={cn(
33
+ "group flex flex-1 list-none items-center justify-center space-x-1",
34
+ className
35
+ )}
36
+ {...props}
37
+ />
38
+ ))
39
+ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
40
+
41
+ const NavigationMenuItem = NavigationMenuPrimitive.Item
42
+
43
+ const navigationMenuTriggerStyle = cva(
44
+ "group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
45
+ )
46
+
47
+ const NavigationMenuTrigger = React.forwardRef<
48
+ React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
49
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
50
+ >(({ className, children, ...props }, ref) => (
51
+ <NavigationMenuPrimitive.Trigger
52
+ ref={ref}
53
+ className={cn(navigationMenuTriggerStyle(), "group", className)}
54
+ {...props}
55
+ >
56
+ {children}{" "}
57
+ <ChevronDown
58
+ className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
59
+ aria-hidden="true"
60
+ />
61
+ </NavigationMenuPrimitive.Trigger>
62
+ ))
63
+ NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
64
+
65
+ const NavigationMenuContent = React.forwardRef<
66
+ React.ElementRef<typeof NavigationMenuPrimitive.Content>,
67
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
68
+ >(({ className, ...props }, ref) => (
69
+ <NavigationMenuPrimitive.Content
70
+ ref={ref}
71
+ className={cn(
72
+ "left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
73
+ className
74
+ )}
75
+ {...props}
76
+ />
77
+ ))
78
+ NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
79
+
80
+ const NavigationMenuLink = NavigationMenuPrimitive.Link
81
+
82
+ const NavigationMenuViewport = React.forwardRef<
83
+ React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
84
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
85
+ >(({ className, ...props }, ref) => (
86
+ <div className={cn("absolute left-0 top-full flex justify-center")}>
87
+ <NavigationMenuPrimitive.Viewport
88
+ className={cn(
89
+ "origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
90
+ className
91
+ )}
92
+ ref={ref}
93
+ {...props}
94
+ />
95
+ </div>
96
+ ))
97
+ NavigationMenuViewport.displayName =
98
+ NavigationMenuPrimitive.Viewport.displayName
99
+
100
+ const NavigationMenuIndicator = React.forwardRef<
101
+ React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
102
+ React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
103
+ >(({ className, ...props }, ref) => (
104
+ <NavigationMenuPrimitive.Indicator
105
+ ref={ref}
106
+ className={cn(
107
+ "top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
108
+ className
109
+ )}
110
+ {...props}
111
+ >
112
+ <div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
113
+ </NavigationMenuPrimitive.Indicator>
114
+ ))
115
+ NavigationMenuIndicator.displayName =
116
+ NavigationMenuPrimitive.Indicator.displayName
117
+
118
+ export {
119
+ navigationMenuTriggerStyle,
120
+ NavigationMenu,
121
+ NavigationMenuList,
122
+ NavigationMenuItem,
123
+ NavigationMenuContent,
124
+ NavigationMenuTrigger,
125
+ NavigationMenuLink,
126
+ NavigationMenuIndicator,
127
+ NavigationMenuViewport,
128
+ }