style: change position analyze card & make dark mode color theme.
This commit is contained in:
parent
de44a74dde
commit
277c9c9b7e
|
|
@ -6,7 +6,6 @@ import { Input } from "../ui/input";
|
||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import ResultSection from "./ResultSection";
|
import ResultSection from "./ResultSection";
|
||||||
import UrlInputList from "./UrlInputList";
|
import UrlInputList from "./UrlInputList";
|
||||||
import { useTheme } from "@/src/context/ThemeContext";
|
|
||||||
|
|
||||||
export default function AnalysisClient() {
|
export default function AnalysisClient() {
|
||||||
const {
|
const {
|
||||||
|
|
@ -18,13 +17,13 @@ export default function AnalysisClient() {
|
||||||
progress,
|
progress,
|
||||||
visibleFields,
|
visibleFields,
|
||||||
urlDatas,
|
urlDatas,
|
||||||
|
darkMode,
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
setVisibleFields,
|
setVisibleFields,
|
||||||
} = useAnalyseText();
|
} = useAnalyseText();
|
||||||
const { darkMode } = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full mx-auto">
|
<div className="w-full mx-auto">
|
||||||
|
|
@ -39,7 +38,7 @@ export default function AnalysisClient() {
|
||||||
<h3
|
<h3
|
||||||
className={`text-lg font-semibold ${darkMode ? "text-white" : "text-black"} transition-all duration-500`}
|
className={`text-lg font-semibold ${darkMode ? "text-white" : "text-black"} transition-all duration-500`}
|
||||||
>
|
>
|
||||||
Analisis Sentimen Real-time
|
Analisis Sentimen
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,11 @@ export default function DashboardClient() {
|
||||||
neutralCount,
|
neutralCount,
|
||||||
loading,
|
loading,
|
||||||
modelData,
|
modelData,
|
||||||
|
darkMode,
|
||||||
|
toggleDarkMode,
|
||||||
percentage,
|
percentage,
|
||||||
scrollToResult,
|
scrollToResult,
|
||||||
} = useDashboards();
|
} = useDashboards();
|
||||||
const { darkMode, toggleDarkMode } = useTheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|
@ -64,6 +65,12 @@ export default function DashboardClient() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<section id="analysis-form" className="scroll-mt-60">
|
||||||
|
<div className="mb-8 ">
|
||||||
|
<AnalysisClient />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<div className="mb-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
<div className="mb-8 grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<StatCard
|
<StatCard
|
||||||
title="Total Ulasan"
|
title="Total Ulasan"
|
||||||
|
|
@ -151,12 +158,6 @@ export default function DashboardClient() {
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section id="analysis-form" className="scroll-mt-60">
|
|
||||||
<div className="mb-8 ">
|
|
||||||
<AnalysisClient />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||||
<div>
|
<div>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useDashboards } from "@/src/hooks/useDashboard";
|
||||||
import { RadarProps } from "@/src/types";
|
import { RadarProps } from "@/src/types";
|
||||||
import { radarFormat } from "@/src/utils/datas";
|
import { radarFormat } from "@/src/utils/datas";
|
||||||
import {
|
import {
|
||||||
|
|
@ -13,16 +14,26 @@ import {
|
||||||
|
|
||||||
const RadarComparisonChart = ({ data }: RadarProps) => {
|
const RadarComparisonChart = ({ data }: RadarProps) => {
|
||||||
const { chartData, colors } = radarFormat({ data });
|
const { chartData, colors } = radarFormat({ data });
|
||||||
|
const { darkMode } = useDashboards();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-100 bg-card p-5 rounded-xl border items-center flex flex-col">
|
<div
|
||||||
<h3 className="text-lg font-semibold text-center">
|
className={`h-100 ${darkMode ? "bg-gray-800 border-transparent" : "bg-card"} p-5 rounded-xl border items-center flex flex-col transition-all duration-500`}
|
||||||
|
>
|
||||||
|
<h3 className="text-lg font-semibold text-center transition-all duration-500">
|
||||||
Perbandingan Aspek Produk
|
Perbandingan Aspek Produk
|
||||||
</h3>
|
</h3>
|
||||||
<ResponsiveContainer width="100%" height="100%" className="border-none">
|
<ResponsiveContainer
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
className="border-transparent transition-all duration-500"
|
||||||
|
>
|
||||||
<RadarChart cx="50%" cy="46%" outerRadius="80%" data={chartData}>
|
<RadarChart cx="50%" cy="46%" outerRadius="80%" data={chartData}>
|
||||||
<PolarGrid />
|
<PolarGrid />
|
||||||
<PolarAngleAxis dataKey="subject" className="text-xs" />
|
<PolarAngleAxis
|
||||||
|
dataKey="subject"
|
||||||
|
className={`${darkMode ? "text-gray-400" : "text-gray-500"} text-xs`}
|
||||||
|
/>
|
||||||
<PolarRadiusAxis
|
<PolarRadiusAxis
|
||||||
angle={90}
|
angle={90}
|
||||||
domain={[0, 100]}
|
domain={[0, 100]}
|
||||||
|
|
@ -40,6 +51,7 @@ const RadarComparisonChart = ({ data }: RadarProps) => {
|
||||||
fill={colors[index % colors.length]}
|
fill={colors[index % colors.length]}
|
||||||
fillOpacity={0.15}
|
fillOpacity={0.15}
|
||||||
dot={{ r: 2, fillOpacity: 1 }}
|
dot={{ r: 2, fillOpacity: 1 }}
|
||||||
|
className={`${darkMode ? "animate-in fade-in duration-500 text-card" : "animate-in fade-in duration-500"}`}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|
@ -48,6 +60,8 @@ const RadarComparisonChart = ({ data }: RadarProps) => {
|
||||||
borderRadius: "12px",
|
borderRadius: "12px",
|
||||||
border: "none",
|
border: "none",
|
||||||
boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
|
boxShadow: "0 4px 12px rgba(0,0,0,0.1)",
|
||||||
|
color: darkMode ? "#e0e0e0" : "#333",
|
||||||
|
backgroundColor: darkMode ? "#333" : "#fff",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Legend
|
<Legend
|
||||||
|
|
@ -58,7 +72,9 @@ const RadarComparisonChart = ({ data }: RadarProps) => {
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
}}
|
}}
|
||||||
formatter={(value) => (
|
formatter={(value) => (
|
||||||
<span className="text-[10px] text-gray-500 uppercase tracking-wider">
|
<span
|
||||||
|
className={`${darkMode ? "text-card" : "text-gray-500"} text-[10px] uppercase tracking-wider transition-all duration-500`}
|
||||||
|
>
|
||||||
{value}
|
{value}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useDashboards } from "@/src/hooks/useDashboard";
|
||||||
import { useResultDetails } from "@/src/hooks/useResultDetails";
|
import { useResultDetails } from "@/src/hooks/useResultDetails";
|
||||||
import { ResultProps } from "@/src/types";
|
import { ResultProps } from "@/src/types";
|
||||||
import { getHighlights, toTitleCase } from "@/src/utils/datas";
|
import { getHighlights, toTitleCase } from "@/src/utils/datas";
|
||||||
|
|
@ -16,16 +17,19 @@ export default function ResultDetails({ result }: ResultProps) {
|
||||||
nextProduct,
|
nextProduct,
|
||||||
prevProduct,
|
prevProduct,
|
||||||
} = useResultDetails({ result }) || {};
|
} = useResultDetails({ result }) || {};
|
||||||
|
const { darkMode } = useDashboards();
|
||||||
|
|
||||||
if (!result || !result.details || result.details.length === 0) return null;
|
if (!result || !result.details || result.details.length === 0) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="relative group border p-8 rounded-xl bg-card h-100 overflow-hidden">
|
<div
|
||||||
|
className={`relative group border p-8 rounded-xl ${darkMode ? "bg-gray-800 border-transparent" : "bg-card"} h-100 overflow-hidden transition-all duration-500`}
|
||||||
|
>
|
||||||
{activeProductIndex > 0 && (
|
{activeProductIndex > 0 && (
|
||||||
<button
|
<button
|
||||||
onClick={prevProduct}
|
onClick={prevProduct}
|
||||||
className="absolute left-4 top-1/2 -translate-y-1/2 p-2 rounded-full cursor-pointer bg-secondary text-primary hover:bg-primary hover:text-white transition-all z-2 shadow-md animate-in fade-in zoom-in duration-300"
|
className={`${darkMode ? "absolute left-4 top-1/2 -translate-y-1/2 p-2 rounded-full cursor-pointer bg-gray-800/30 text-card hover:bg-gray-900 hover:text-card transition-all z-2 shadow-md animate-in fade-in zoom-in duration-300" : "absolute left-4 top-1/2 -translate-y-1/2 p-2 rounded-full cursor-pointer bg-secondary text-primary hover:bg-primary hover:text-white transition-all z-2 shadow-md animate-in fade-in zoom-in duration-300"}`}
|
||||||
aria-label="Previous Product"
|
aria-label="Previous Product"
|
||||||
>
|
>
|
||||||
<ChevronLeft size={24} />
|
<ChevronLeft size={24} />
|
||||||
|
|
@ -61,14 +65,16 @@ export default function ResultDetails({ result }: ResultProps) {
|
||||||
<span className="text-[10px] font-bold text-gray-400 uppercase tracking-[0.2em]">
|
<span className="text-[10px] font-bold text-gray-400 uppercase tracking-[0.2em]">
|
||||||
Produk {index + 1} dari {totalProducts}
|
Produk {index + 1} dari {totalProducts}
|
||||||
</span>
|
</span>
|
||||||
<h4 className="font-bold text-2xl text-gray-800 mt-1 line-clamp-2">
|
<h4
|
||||||
|
className={`${darkMode ? "text-card" : "text-gray-800"} font-bold text-2xl mt-1 line-clamp-2 transition-all duration-500`}
|
||||||
|
>
|
||||||
{toTitleCase(item.name)}
|
{toTitleCase(item.name)}
|
||||||
</h4>
|
</h4>
|
||||||
<a
|
<a
|
||||||
href={item.url}
|
href={item.url}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-primary text-xs font-semibold inline-flex items-center mt-2 hover:underline gap-1"
|
className={`${darkMode ? "text-gray-300" : "text-primary"} text-xs font-semibold inline-flex items-center mt-2 hover:underline gap-1 transition-all duration-500`}
|
||||||
>
|
>
|
||||||
Buka di Tokopedia <ExternalLink size={12} />
|
Buka di Tokopedia <ExternalLink size={12} />
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -109,7 +115,9 @@ export default function ResultDetails({ result }: ResultProps) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-secondary/50 p-5 rounded-2xl border border-blue-50 italic text-gray-600 text-sm leading-relaxed mb-4">
|
<div
|
||||||
|
className={`${darkMode ? "bg-gray-900 text-card border-transparent" : "bg-secondary/50 text-gray-600"} p-5 rounded-2xl border border-blue-50 italic text-sm leading-relaxed mb-4 transition-all duration-500`}
|
||||||
|
>
|
||||||
“{item.description}”
|
“{item.description}”
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -119,7 +127,7 @@ export default function ResultDetails({ result }: ResultProps) {
|
||||||
{activeProductIndex < totalProducts - 1 && (
|
{activeProductIndex < totalProducts - 1 && (
|
||||||
<button
|
<button
|
||||||
onClick={nextProduct}
|
onClick={nextProduct}
|
||||||
className="absolute right-4 top-1/2 -translate-y-1/2 p-2 rounded-full cursor-pointer bg-secondary text-primary hover:bg-primary hover:text-white transition-all z-2 shadow-md animate-in fade-in zoom-in duration-300"
|
className={`${darkMode ? "absolute right-4 top-1/2 -translate-y-1/2 p-2 rounded-full cursor-pointer bg-gray-800/30 text-card hover:bg-gray-900 hover:text-card transition-all z-2 shadow-md animate-in fade-in zoom-in duration-300" : "absolute right-4 top-1/2 -translate-y-1/2 p-2 rounded-full cursor-pointer bg-secondary text-primary hover:bg-primary hover:text-white transition-all z-2 shadow-md animate-in fade-in zoom-in duration-300"}`}
|
||||||
aria-label="Next Product"
|
aria-label="Next Product"
|
||||||
>
|
>
|
||||||
<ChevronRight size={24} />
|
<ChevronRight size={24} />
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,10 @@ import { ResultProps } from "@/src/types";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import RadarComparisonChart from "./RadarComparisonChart";
|
import RadarComparisonChart from "./RadarComparisonChart";
|
||||||
import ResultDetails from "./ResultDetails";
|
import ResultDetails from "./ResultDetails";
|
||||||
|
import { useDashboards } from "@/src/hooks/useDashboard";
|
||||||
|
|
||||||
export default function Resultection({ result }: ResultProps) {
|
export default function Resultection({ result }: ResultProps) {
|
||||||
|
const { darkMode } = useDashboards();
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
className="w-full mx-auto"
|
className="w-full mx-auto"
|
||||||
|
|
@ -19,7 +21,9 @@ export default function Resultection({ result }: ResultProps) {
|
||||||
>
|
>
|
||||||
{result && (
|
{result && (
|
||||||
<div className="space-y-8 animate-in fade-in duration-700">
|
<div className="space-y-8 animate-in fade-in duration-700">
|
||||||
<div className="p-8 rounded-xl text-white shadow-xl flex flex-col md:flex-row justify-between items-center gap-4 bg-primary">
|
<div
|
||||||
|
className={`${darkMode ? "bg-gray-800" : "bg-primary"} p-8 rounded-xl text-white shadow-xl flex flex-col md:flex-row justify-between items-center gap-4 transition-all duration-500`}
|
||||||
|
>
|
||||||
<div>
|
<div>
|
||||||
<p className="mx-auto text-lg text-white/80">
|
<p className="mx-auto text-lg text-white/80">
|
||||||
Rekomendasi Terbaik Berdasarkan Analisis
|
Rekomendasi Terbaik Berdasarkan Analisis
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import {
|
||||||
} from "../services/analyze.service";
|
} from "../services/analyze.service";
|
||||||
import { analyzeSchema } from "../app/validation/analyze.schema";
|
import { analyzeSchema } from "../app/validation/analyze.schema";
|
||||||
import { getMetricId } from "../services/metric.service";
|
import { getMetricId } from "../services/metric.service";
|
||||||
import { getBrandId } from "../services/brand.service";
|
import { useTheme } from "../context/ThemeContext";
|
||||||
|
|
||||||
export const useAnalyseText = () => {
|
export const useAnalyseText = () => {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
@ -19,6 +19,7 @@ export const useAnalyseText = () => {
|
||||||
const [progress, setProgress] = useState({ status: "", percent: 0 });
|
const [progress, setProgress] = useState({ status: "", percent: 0 });
|
||||||
const abortControllerRef = useRef<AbortController | null>(null);
|
const abortControllerRef = useRef<AbortController | null>(null);
|
||||||
const [visibleFields, setVisibleFields] = useState(0);
|
const [visibleFields, setVisibleFields] = useState(0);
|
||||||
|
const { darkMode, toggleDarkMode } = useTheme();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
|
|
@ -234,6 +235,8 @@ export const useAnalyseText = () => {
|
||||||
resultRef,
|
resultRef,
|
||||||
progress,
|
progress,
|
||||||
urlDatas,
|
urlDatas,
|
||||||
|
darkMode,
|
||||||
|
toggleDarkMode,
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { useState, useEffect, useMemo } from "react";
|
||||||
import { ModelDB, Review, StatCounts } from "@/src/types";
|
import { ModelDB, Review, StatCounts } from "@/src/types";
|
||||||
import { getClassificationReport } from "../app/dashboard/lib/actions";
|
import { getClassificationReport } from "../app/dashboard/lib/actions";
|
||||||
import { sentimentStatsPath } from "../utils/const";
|
import { sentimentStatsPath } from "../utils/const";
|
||||||
|
import { useTheme } from "../context/ThemeContext";
|
||||||
|
|
||||||
export const useDashboards = () => {
|
export const useDashboards = () => {
|
||||||
const [selectedBrand, setSelectedBrand] = useState<string | null>(null);
|
const [selectedBrand, setSelectedBrand] = useState<string | null>(null);
|
||||||
|
|
@ -16,6 +17,7 @@ export const useDashboards = () => {
|
||||||
negative: 0,
|
negative: 0,
|
||||||
neutral: 0,
|
neutral: 0,
|
||||||
});
|
});
|
||||||
|
const { darkMode, toggleDarkMode } = useTheme();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchStats() {
|
async function fetchStats() {
|
||||||
|
|
@ -60,7 +62,8 @@ export const useDashboards = () => {
|
||||||
|
|
||||||
fetchModelData();
|
fetchModelData();
|
||||||
window.addEventListener("analysis-complete", fetchModelData);
|
window.addEventListener("analysis-complete", fetchModelData);
|
||||||
return () => window.removeEventListener("analysis-complete", fetchModelData);
|
return () =>
|
||||||
|
window.removeEventListener("analysis-complete", fetchModelData);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const filteredReviews = useMemo(() => {
|
const filteredReviews = useMemo(() => {
|
||||||
|
|
@ -88,6 +91,8 @@ export const useDashboards = () => {
|
||||||
selectedBrand,
|
selectedBrand,
|
||||||
loading,
|
loading,
|
||||||
modelData,
|
modelData,
|
||||||
|
darkMode,
|
||||||
|
toggleDarkMode,
|
||||||
setSelectedBrand,
|
setSelectedBrand,
|
||||||
percentage,
|
percentage,
|
||||||
scrollToResult,
|
scrollToResult,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue