From 441d6f69e32686a5f434d42f45914744f5905d7d Mon Sep 17 00:00:00 2001 From: Mahen Date: Fri, 6 Feb 2026 11:26:57 +0700 Subject: [PATCH] feat: add model info get data action --- components/dashboard/ModelInfo.tsx | 110 --- .../{dashboard => dashboards}/BrandFilter.tsx | 0 .../{dashboard => dashboards}/Header.tsx | 0 components/dashboards/ModelInfo.tsx | 100 +++ .../{dashboard => dashboards}/ReviewTable.tsx | 0 .../SentimentAnalyzer.tsx | 0 .../SentimentChart.tsx | 0 .../{dashboard => dashboards}/StatCard.tsx | 0 .../{dashboard => dashboards}/TrendChart.tsx | 23 +- .../{dashboard => dashboards}/WordCloud.tsx | 0 components/skeletons/ModelInfoSkeleton.tsx | 52 ++ package-lock.json | 656 +++++++++++++++++- package.json | 8 +- prisma.config.ts | 3 +- prisma/schema.prisma | 79 +++ prisma/seed.ts | 53 ++ src/app/dashboard/lib/actions.ts | 30 + src/app/dashboard/page.tsx | 52 +- src/app/globals.css | 6 + src/app/layout.tsx | 2 +- src/app/page.tsx | 1 + src/hooks/useModelInfo.ts | 34 + src/types/index.ts | 8 + 23 files changed, 1073 insertions(+), 144 deletions(-) delete mode 100644 components/dashboard/ModelInfo.tsx rename components/{dashboard => dashboards}/BrandFilter.tsx (100%) rename components/{dashboard => dashboards}/Header.tsx (100%) create mode 100644 components/dashboards/ModelInfo.tsx rename components/{dashboard => dashboards}/ReviewTable.tsx (100%) rename components/{dashboard => dashboards}/SentimentAnalyzer.tsx (100%) rename components/{dashboard => dashboards}/SentimentChart.tsx (100%) rename components/{dashboard => dashboards}/StatCard.tsx (100%) rename components/{dashboard => dashboards}/TrendChart.tsx (89%) rename components/{dashboard => dashboards}/WordCloud.tsx (100%) create mode 100644 components/skeletons/ModelInfoSkeleton.tsx create mode 100644 prisma/seed.ts create mode 100644 src/app/dashboard/lib/actions.ts create mode 100644 src/hooks/useModelInfo.ts create mode 100644 src/types/index.ts diff --git a/components/dashboard/ModelInfo.tsx b/components/dashboard/ModelInfo.tsx deleted file mode 100644 index 149f53d..0000000 --- a/components/dashboard/ModelInfo.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { useState } from "react"; -import { Badge } from "@/components/ui/badge"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { modelData } from "@/src/app/dashboard/lib/data"; -import { cn } from "@/lib/utils"; - -export function ModelInfo() { - const [selectedModel, setSelectedModel] = - useState("optimized"); - - const currentModel = modelData[selectedModel]; - - return ( -
-
- - - - Active - -
- -

- {currentModel.description} -

- -
- {currentModel.metrics.map((metric) => ( -
-
- -
-
-

{metric.label}

-

{metric.value}

-
-
- ))} -
- -
-
- Preprocessing - - Case Folding, Stopwords, Stemming - -
-
- Feature Extraction - TF-IDF Vectorization -
-
- Training Data - 3.445 ulasan -
-
-
- ); -} diff --git a/components/dashboard/BrandFilter.tsx b/components/dashboards/BrandFilter.tsx similarity index 100% rename from components/dashboard/BrandFilter.tsx rename to components/dashboards/BrandFilter.tsx diff --git a/components/dashboard/Header.tsx b/components/dashboards/Header.tsx similarity index 100% rename from components/dashboard/Header.tsx rename to components/dashboards/Header.tsx diff --git a/components/dashboards/ModelInfo.tsx b/components/dashboards/ModelInfo.tsx new file mode 100644 index 0000000..5d0b177 --- /dev/null +++ b/components/dashboards/ModelInfo.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { useState } from "react"; +import { Badge } from "@/components/ui/badge"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { ModelDB } from "@/src/types"; +import { useModelInfo } from "@/src/hooks/useModelInfo"; + +export function ModelInfo({ data }: { data: ModelDB[] }) { + const { selectedIndex, metrics, setSelectedIndex, currentModel } = + useModelInfo({ data }); + + if (!data || data.length === 0) { + return ( +
+

+ Data model tidak tersedia. +

+
+ ); + } + + return ( +
+
+ + + + Active + +
+ +

+ {currentModel.description} +

+ +
+ {metrics.map((metric) => ( +
+
+ +
+
+

{metric.label}

+

+ {typeof metric.value === "number" + ? `${(metric.value * 100).toFixed(1)}%` + : metric.value} +

+
+
+ ))} +
+ +
+
+ Preprocessing + + Case Folding, Stopwords, Stemming + +
+
+ Feature Extraction + TF-IDF Vectorization +
+
+ Training Data + 3.445 Ulasan +
+
+
+ ); +} diff --git a/components/dashboard/ReviewTable.tsx b/components/dashboards/ReviewTable.tsx similarity index 100% rename from components/dashboard/ReviewTable.tsx rename to components/dashboards/ReviewTable.tsx diff --git a/components/dashboard/SentimentAnalyzer.tsx b/components/dashboards/SentimentAnalyzer.tsx similarity index 100% rename from components/dashboard/SentimentAnalyzer.tsx rename to components/dashboards/SentimentAnalyzer.tsx diff --git a/components/dashboard/SentimentChart.tsx b/components/dashboards/SentimentChart.tsx similarity index 100% rename from components/dashboard/SentimentChart.tsx rename to components/dashboards/SentimentChart.tsx diff --git a/components/dashboard/StatCard.tsx b/components/dashboards/StatCard.tsx similarity index 100% rename from components/dashboard/StatCard.tsx rename to components/dashboards/StatCard.tsx diff --git a/components/dashboard/TrendChart.tsx b/components/dashboards/TrendChart.tsx similarity index 89% rename from components/dashboard/TrendChart.tsx rename to components/dashboards/TrendChart.tsx index 064404a..1252bcc 100644 --- a/components/dashboard/TrendChart.tsx +++ b/components/dashboards/TrendChart.tsx @@ -1,3 +1,5 @@ +"use client"; +import { useEffect, useState } from "react"; import { AreaChart, Area, @@ -21,6 +23,11 @@ interface TrendChartProps { } export function TrendChart({ data }: TrendChartProps) { + const [isMounted, setIsMounted] = useState(false); + useEffect(() => { + setIsMounted(true); + }, []); + const CustomTooltip = ({ active, payload, label }: any) => { if (active && payload && payload.length) { return ( @@ -42,8 +49,12 @@ export function TrendChart({ data }: TrendChartProps) { return null; }; + if (!isMounted) { + return
; + } + return ( -
+
- + ( - {value} + + {value} + )} /> +
+
+
+ +
+
+ +
+
+ +
+
+
+
+ +
+ {[1, 2, 3, 4].map((i) => ( +
+
+
+
+ +
+
+
+
+
+ ))} +
+ + {/* Footer info */} +
+ {[1, 2, 3].map((i) => ( +
+ {/* label */} +
+ {/* value */} +
+
+ ))} +
+
+ ); +} diff --git a/package-lock.json b/package-lock.json index 5b41450..92cc641 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,10 @@ "react": "19.2.3", "react-dom": "19.2.3", "recharts": "^3.7.0", - "tailwind-merge": "^3.4.0" + "tailwind-merge": "^3.4.0", + "tailwindcss-animate": "^1.0.7", + "ts-node": "^10.9.2", + "tsx": "^4.21.0" }, "devDependencies": { "@prisma/adapter-neon": "^7.3.0", @@ -395,6 +398,28 @@ "devOptional": true, "license": "Apache-2.0" }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, "node_modules/@electric-sql/pglite": { "version": "0.3.15", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", @@ -458,6 +483,422 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -1197,7 +1638,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1207,7 +1647,6 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { @@ -5056,6 +5495,30 @@ "tailwindcss": "4.1.18" } }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "license": "MIT" + }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -5155,7 +5618,6 @@ "version": "20.19.30", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -5741,7 +6203,6 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5760,6 +6221,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -5793,6 +6266,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -6377,6 +6856,12 @@ "dev": true, "license": "MIT" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -6698,6 +7183,15 @@ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -6974,6 +7468,47 @@ "benchmarks" ] }, + "node_modules/esbuild": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -7642,6 +8177,20 @@ } } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -7790,7 +8339,6 @@ "version": "4.13.1", "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz", "integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==", - "dev": true, "license": "MIT", "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -9066,6 +9614,12 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "license": "ISC" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -10391,7 +10945,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" @@ -11006,9 +11559,17 @@ "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", - "dev": true, "license": "MIT" }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, "node_modules/tapable": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", @@ -11113,6 +11674,49 @@ "typescript": ">=4.8.4" } }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, "node_modules/tsconfig-paths": { "version": "3.15.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", @@ -11145,6 +11749,25 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tw-animate-css": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", @@ -11250,7 +11873,6 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -11307,7 +11929,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -11438,6 +12059,12 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "license": "MIT" + }, "node_modules/valibot": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", @@ -11606,6 +12233,15 @@ "dev": true, "license": "ISC" }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 7a36730..abc1c11 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "seed": "prisma db seed" }, "dependencies": { "@base-ui/react": "^1.1.0", @@ -26,7 +27,10 @@ "react": "19.2.3", "react-dom": "19.2.3", "recharts": "^3.7.0", - "tailwind-merge": "^3.4.0" + "tailwind-merge": "^3.4.0", + "tailwindcss-animate": "^1.0.7", + "ts-node": "^10.9.2", + "tsx": "^4.21.0" }, "devDependencies": { "@prisma/adapter-neon": "^7.3.0", diff --git a/prisma.config.ts b/prisma.config.ts index 831a20f..daed51a 100644 --- a/prisma.config.ts +++ b/prisma.config.ts @@ -1,5 +1,3 @@ -// This file was generated by Prisma, and assumes you have installed the following: -// npm install --save-dev prisma dotenv import "dotenv/config"; import { defineConfig } from "prisma/config"; @@ -7,6 +5,7 @@ export default defineConfig({ schema: "prisma/schema.prisma", migrations: { path: "prisma/migrations", + seed: "tsx prisma/seed.ts", }, datasource: { url: process.env["DATABASE_URL"], diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6eb2dfc..8fb2352 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -5,3 +5,82 @@ generator client { datasource db { provider = "postgresql" } + +enum UserGender { + male + female + other +} + +enum Sentiment { + positive + negative + neutral +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + gender UserGender? + productReference String? + password String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + analysis Analysis[] +} + +model Analysis { + id Int @id @default(autoincrement()) + userId Int + reviewId Int + productId Int + modelId Int + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + user User @relation(fields: [userId], references: [id]) + review Review @relation(fields: [reviewId], references: [id]) + product Product @relation(fields: [productId], references: [id]) + model Model @relation(fields: [modelId], references: [id]) +} + +model Product { + id Int @id @default(autoincrement()) + name String + brand String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + reviews Review[] + analysis Analysis[] +} + +model Review { + id Int @id @default(autoincrement()) + productId Int + content String + keywords String[] + sentiment Sentiment + confidenceScore Float + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + product Product @relation(fields: [productId], references: [id]) + analysis Analysis[] +} + +model Model { + id Int @id @default(autoincrement()) + modelName String + description String + accuracy Float + macroF1 Float + f1Negative Float + f1Neutral Float + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + analysis Analysis[] +} diff --git a/prisma/seed.ts b/prisma/seed.ts new file mode 100644 index 0000000..4711529 --- /dev/null +++ b/prisma/seed.ts @@ -0,0 +1,53 @@ +import prisma from "@/lib/prisma"; + +async function main() { + console.log("Sedang memulai proses seeding..."); + + const modelData = [ + { + modelName: "Model XGBoost (Baseline)", + description: + "Model awal menggunakan parameter default XGBoost (learning_rate=0.3, max_depth=6) pada dataset yang tidak seimbang.", + accuracy: 0.8, + macroF1: 0.56, + f1Negative: 0.61, + f1Neutral: 0.16, + }, + { + modelName: "Model XGBoost (Tuned)", + description: + "Model dengan optimasi Hyperparameter menggunakan Grid Search untuk mencari kombinasi learning_rate dan max_depth terbaik.", + accuracy: 0.81, + macroF1: 0.58, + f1Negative: 0.65, + f1Neutral: 0.17, + }, + { + modelName: "Model XGBoost (Optimized)", + description: + "Model final menggunakan teknik SMOTE untuk menyeimbangkan kelas, seleksi fitur Chi-Square, dan optimasi Grid Search.", + accuracy: 0.82, + macroF1: 0.61, + f1Negative: 0.65, + f1Neutral: 0.27, + }, + ]; + + for (const data of modelData) { + const model = await prisma.model.create({ + data: data, + }); + console.log(`Berhasil membuat model: ${model.modelName}`); + } + + console.log("Proses seeding selesai."); +} + +main() + .catch((e) => { + console.error(e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); diff --git a/src/app/dashboard/lib/actions.ts b/src/app/dashboard/lib/actions.ts new file mode 100644 index 0000000..69dc054 --- /dev/null +++ b/src/app/dashboard/lib/actions.ts @@ -0,0 +1,30 @@ +"use server"; +import prisma from "@/lib/prisma"; +import { notFound } from "next/navigation"; + +export const getClassificationReport = async () => { + try { + const response = await prisma.model.findMany({ + select: { + modelName: true, + description: true, + accuracy: true, + macroF1: true, + f1Negative: true, + f1Neutral: true, + }, + orderBy: { + createdAt: "asc", + }, + }); + + if (!response || response.length === 0) { + return notFound(); + } + + return response; + } catch (error) { + console.error("Error fetching classification report:", error); + throw error; + } +}; diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index 1575d86..085b1f2 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -1,5 +1,5 @@ "use client"; -import { useState } from "react"; +import { Suspense, useEffect, useState } from "react"; import { brandData, reviewData, @@ -7,7 +7,7 @@ import { trendData, wordCloudData, } from "./lib/data"; -import { Header } from "@/components/dashboard/Header"; +import { Header } from "@/components/dashboards/Header"; import { MessageSquareText, Minus, @@ -15,18 +15,22 @@ import { ThumbsUp, TrendingUp, } from "lucide-react"; -import { StatCard } from "@/components/dashboard/StatCard"; -import { TrendChart } from "@/components/dashboard/TrendChart"; -import { SentimentChart } from "@/components/dashboard/SentimentChart"; -import { WordCloud } from "@/components/dashboard/WordCloud"; -import { ModelInfo } from "@/components/dashboard/ModelInfo"; -import { SentimentAnalyzer } from "@/components/dashboard/SentimentAnalyzer"; -import { BrandFilter } from "@/components/dashboard/BrandFilter"; -import { ReviewTable } from "@/components/dashboard/ReviewTable"; +import { StatCard } from "@/components/dashboards/StatCard"; +import { TrendChart } from "@/components/dashboards/TrendChart"; +import { SentimentChart } from "@/components/dashboards/SentimentChart"; +import { WordCloud } from "@/components/dashboards/WordCloud"; +import { ModelInfo } from "@/components/dashboards/ModelInfo"; +import { SentimentAnalyzer } from "@/components/dashboards/SentimentAnalyzer"; +import { BrandFilter } from "@/components/dashboards/BrandFilter"; +import { ReviewTable } from "@/components/dashboards/ReviewTable"; +import { getClassificationReport } from "./lib/actions"; +import { ModelDB } from "@/src/types"; +import { ModelInfoSkeleton } from "@/components/skeletons/ModelInfoSkeleton"; export default function DashboardPage() { const [selectedBrand, setSelectedBrand] = useState(null); - + const [loading, setLoading] = useState(true); + const [modelData, setModelData] = useState([]); const totalReviews = sentimentDistribution.reduce( (sum, s) => sum + s.value, 0, @@ -42,6 +46,20 @@ export default function DashboardPage() { ? reviewData.filter((r) => r.brand === selectedBrand) : reviewData; + useEffect(() => { + async function fetchData() { + try { + const data = await getClassificationReport(); + setModelData(data); + } catch (error) { + console.error("Failed to fetch model data", error); + } finally { + setLoading(false); + } + } + fetchData(); + }, []); + return (
@@ -122,6 +140,7 @@ export default function DashboardPage() { {/* Word Cloud & Model Info */}
+ {/* Slot Kata Kunci */}

Kata Kunci Populer

@@ -130,7 +149,16 @@ export default function DashboardPage() {

- + + {loading ? ( + + ) : modelData.length > 0 ? ( + + ) : ( +
+ Data model tidak tersedia. +
+ )}
{/* Sentiment Analyzer */} diff --git a/src/app/globals.css b/src/app/globals.css index 49f637b..b5665e7 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -115,4 +115,10 @@ @apply backdrop-blur-lg; background: hsl(var(--card) / 0.8); } + + @keyframes shimmer { + 100% { + transform: translateX(100%); + } + } } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ac26897..301a097 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -27,7 +27,7 @@ export default function RootLayout({ }>) { return ( - {children} + {children} ); } diff --git a/src/app/page.tsx b/src/app/page.tsx index ac65120..e03eb4b 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,3 +1,4 @@ +'use client";'; import { LoginPage } from "./auth/login/page"; export default function Home() { diff --git a/src/hooks/useModelInfo.ts b/src/hooks/useModelInfo.ts new file mode 100644 index 0000000..a97559b --- /dev/null +++ b/src/hooks/useModelInfo.ts @@ -0,0 +1,34 @@ +import { useState } from "react"; +import { ModelDB } from "../types"; +import { BarChart3, CircleDot, Target, TrendingDown } from "lucide-react"; + +export const useModelInfo = ({ data }: { data: ModelDB[] }) => { + const [selectedIndex, setSelectedIndex] = useState(0); + + const currentModel = data[selectedIndex]; + + const metrics = [ + { + label: "Accuracy", + value: currentModel?.accuracy ?? 0, + icon: Target, + }, + { + label: "Macro F1", + value: currentModel?.macroF1 ?? 0, + icon: BarChart3, + }, + { + label: "F1 Negative", + value: currentModel?.f1Negative ?? 0, + icon: TrendingDown, + }, + { + label: "F1 Neutral", + value: currentModel?.f1Neutral ?? 0, + icon: CircleDot, + }, + ]; + + return { selectedIndex, metrics, setSelectedIndex, currentModel }; +}; diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..d48c600 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,8 @@ +export interface ModelDB { + modelName: string; + description: string; + accuracy: number; + macroF1: number; + f1Negative: number; + f1Neutral: number; +}