diff --git a/src/app/analyze/page.tsx b/src/app/analyze/page.tsx
index 6fbb8f0..4a99d29 100644
--- a/src/app/analyze/page.tsx
+++ b/src/app/analyze/page.tsx
@@ -1,339 +1,5 @@
-"use client";
-
-import { Button } from "@/src/components/ui/button";
-import { Input } from "@/src/components/ui/input";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/src/components/ui/select";
-import { useAnalyseText } from "@/src/hooks/useAnalyzeText";
-import { CheckCircle2, Sparkles, Star, Trophy } from "lucide-react";
-import { motion } from "framer-motion";
+import AnalysisClient from "@/src/components/dashboards/AnalysisClient";
export default function AnalysisPage() {
- const {
- url1,
- url2,
- profession,
- loading,
- result,
- disabled,
- handleAnalyze,
- setProfession,
- setUrl1,
- setUrl2,
- setDisabled,
- } = useAnalyseText();
-
- const getSentimentTone = (score: number) => {
- if (score >= 80) return "strong";
- if (score >= 60) return "light";
- return "neutral";
- };
-
- return (
-
-
-
-
-
Analisis Sentimen Real-time
-
-
-
-
-
-
-
-
- setUrl1(e.target.value)}
- className="border p-2 rounded-md focus:ring-2 focus:ring-green-500"
- required
- />
-
-
-
-
-
- setUrl2(e.target.value)}
- className="border p-2 rounded-md focus:ring-2 focus:ring-green-500"
- required
- />
-
-
-
-
-
-
-
-
- {result && (
-
- {/* ================= HEADER WINNER ================= */}
-
-
-
-
-
-
-
- Rekomendasi Terbaik
-
-
-
-
- {result.winning_product}
-
-
-
- Pilihan paling tepat untuk{" "}
-
- {result.profession_target}
-
-
-
-
-
- {/* ================= CARDS GRID ================= */}
-
- {result.details.map((item, index) => {
- const isWinner = item.name === result.winning_product;
-
- const getSentimentTone = (score: number) => {
- if (score >= 80) return "strong";
- if (score >= 60) return "light";
- return "neutral";
- };
-
- const sentimentTone = getSentimentTone(
- item.general_sentiment_score,
- );
-
- return (
-
- {/* WINNER BADGE */}
- {isWinner && (
-
-
-
- Top Choice
-
-
- )}
-
-
- {/* PRODUCT NAME */}
-
-
- {/* COMPATIBILITY SCORE */}
-
-
-
- Kecocokan Profesi
-
-
- {item.profession_compatibility_score}%
-
-
-
-
-
-
-
-
- {/* SENTIMENT SCORE */}
-
-
-
- Sentimen Publik
-
-
- {item.general_sentiment_score}% Positif
-
-
-
-
-
-
-
-
- {/* KEYWORDS */}
-
-
- Kata Kunci Dominan
-
-
-
- {item.top_keywords.map((kw, i) => (
-
- #{kw}
-
- ))}
-
-
-
-
- );
- })}
-
-
- )}
-
- );
+ return ;
}
diff --git a/src/components/dashboards/AnalysisClient.tsx b/src/components/dashboards/AnalysisClient.tsx
new file mode 100644
index 0000000..0db9408
--- /dev/null
+++ b/src/components/dashboards/AnalysisClient.tsx
@@ -0,0 +1,165 @@
+"use client";
+
+import { useAnalyseText } from "@/src/hooks/useAnalyzeText";
+import { Sparkles, Trophy } from "lucide-react";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "../ui/select";
+import { Input } from "../ui/input";
+import { Button } from "../ui/button";
+import { motion } from "framer-motion";
+import { useState } from "react";
+import ResultSection from "./ResultSection";
+
+export default function AnalysisClient() {
+ const {
+ url1,
+ url2,
+ url3,
+ profession,
+ loading,
+ result,
+ disabled,
+ handleAnalyze,
+ setProfession,
+ setUrl1,
+ setUrl2,
+ setDisabled,
+ setUrl3,
+ } = useAnalyseText();
+ const [showField, setShowField] = useState(false);
+
+ const getSentimentTone = (score: number) => {
+ if (score >= 80) return "strong";
+ if (score >= 60) return "light";
+ return "neutral";
+ };
+
+ return (
+
+
+
+
+
Analisis Sentimen Real-time
+
+
+
+
+
+
+
+
+ setUrl1(e.target.value)}
+ className="border rounded-md focus:ring-2 focus:ring-green-500"
+ required
+ />
+
+
+
+
+
+
+ setUrl2(e.target.value)}
+ className="border rounded-md focus:ring-2 focus:ring-green-500 w-full"
+ required
+ />
+
+
+ {showField ? (
+
+
+
+ setUrl3(e.target.value)}
+ className="border p-2 rounded-md focus:ring-2 focus:ring-green-500 w-full"
+ required
+ />
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/dashboards/DashboardClient.tsx b/src/components/dashboards/DashboardClient.tsx
index e53cb8c..b389a2e 100644
--- a/src/components/dashboards/DashboardClient.tsx
+++ b/src/components/dashboards/DashboardClient.tsx
@@ -18,6 +18,7 @@ import SentimentForm from "./SentimentAnalyzer";
import { WordCloud } from "./WordCloud";
import AnalysisPage from "@/src/app/analyze/page";
import SentimentAnalyzer from "./SentimentAnalyzer";
+import AnalysisClient from "./AnalysisClient";
export default function DashboardClient() {
const {
@@ -33,7 +34,7 @@ export default function DashboardClient() {
} = useDashboards();
return (
-
+
@@ -133,7 +134,7 @@ export default function DashboardClient() {
diff --git a/src/components/dashboards/Header.tsx b/src/components/dashboards/Header.tsx
index 49011d4..e412c40 100644
--- a/src/components/dashboards/Header.tsx
+++ b/src/components/dashboards/Header.tsx
@@ -27,7 +27,7 @@ export function Header() {
if (!mounted) return null;
return (
-
+
diff --git a/src/components/dashboards/ResultSection.tsx b/src/components/dashboards/ResultSection.tsx
new file mode 100644
index 0000000..273a99c
--- /dev/null
+++ b/src/components/dashboards/ResultSection.tsx
@@ -0,0 +1,212 @@
+import { AnalysisResults, ResultProps } from "@/src/types";
+import { motion } from "framer-motion";
+import { Trophy, ExternalLink, CheckCircle2, TrendingUp } from "lucide-react";
+
+export default function ResultSection({ result }: ResultProps) {
+ if (!result) return null;
+
+ const getGridClass = (count: number) => {
+ if (count === 1) return "max-w-md mx-auto";
+ if (count === 2) return "grid-cols-1 md:grid-cols-2";
+ return "grid-cols-1 md:grid-cols-2 lg:grid-cols-3";
+ };
+
+ return (
+
+ {/* ================= HEADER WINNER ================= */}
+
+ {/* Dekorasi Background Abstrak */}
+
+
+
+
+
+
+
+ Rekomendasi Terbaik
+
+
+
+
+ {result.winning_product}
+
+
+
+ Pilihan paling tepat dan efisien untuk kebutuhan{" "}
+
+ {result.profession_target}
+
+
+
+
+
+ {/* ================= CARDS GRID ================= */}
+
+ {result.details.map((item, index) => {
+ const isWinner = item.name === result.winning_product;
+
+ return (
+
+ {/* WINNER BADGE (Floating) */}
+ {isWinner && (
+
+
+
+ Pemenang
+
+
+ )}
+
+
+ {/* 1. HEADER KARTU */}
+
+
+ {/* 2. STATISTIK UTAMA (Progress Bars) */}
+
+ {/* Kecocokan Profesi */}
+
+
+
+
+ Kecocokan
+
+
+ {item.profession_compatibility_score}%
+
+
+
+
+
+
+
+ {/* Sentimen Publik */}
+
+
+
+
+ Sentimen
+
+
+ {item.general_sentiment_score}% Positif
+
+
+
+
+
+
+
+
+ {/* 3. FOOTER (Keywords) */}
+
+
+ Kata Kunci
+
+
+ {item.top_keywords.map((kw, i) => (
+
+ #{kw}
+
+ ))}
+
+
+
+
+ );
+ })}
+
+
+ );
+}
diff --git a/src/hooks/useAnalyzeText.ts b/src/hooks/useAnalyzeText.ts
index 9c8f996..9cf6cfa 100644
--- a/src/hooks/useAnalyzeText.ts
+++ b/src/hooks/useAnalyzeText.ts
@@ -4,6 +4,7 @@ import { AnalysisResults } from "../types";
export const useAnalyseText = () => {
const [url1, setUrl1] = useState("");
const [url2, setUrl2] = useState("");
+ const [url3, setUrl3] = useState("");
const [profession, setProfession] = useState("");
const [loading, setLoading] = useState(false);
const [result, setResult] = useState
(null);
@@ -14,11 +15,27 @@ export const useAnalyseText = () => {
setResult(null);
try {
- const scrapePromises = [url1, url2].map((u) =>
+ const urlsToScrape = [url1, url2, url3].filter(
+ (url) => url && url.trim() !== "",
+ );
+
+ if (urlsToScrape.length < 2) {
+ alert("Produk Utama dan minimal 1 Produk Pembanding wajib diisi!");
+ setLoading(false);
+ return;
+ }
+
+ const scrapePromises = urlsToScrape.map((u) =>
fetch("/api/scrape", {
method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
body: JSON.stringify({ url: u }),
- }).then((res) => res.json()),
+ }).then((res) => {
+ if (!res.ok) throw new Error(`Gagal scraping: ${u}`);
+ return res.json();
+ }),
);
const scrapeResults = await Promise.all(scrapePromises);
@@ -59,6 +76,7 @@ export const useAnalyseText = () => {
return {
url1,
url2,
+ url3,
profession,
loading,
result,
@@ -67,6 +85,7 @@ export const useAnalyseText = () => {
setProfession,
setUrl1,
setUrl2,
+ setUrl3,
setDisabled,
};
};
diff --git a/src/types/index.ts b/src/types/index.ts
index b97d60c..1c5d66f 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -194,3 +194,7 @@ export interface AnalysisResults {
winning_product: string;
details: ProductDetail[];
}
+
+export interface ResultProps {
+ result: AnalysisResults | null;
+}