style: change position analyze card & make dark mode color theme.

This commit is contained in:
Mahen 2026-05-24 17:22:09 +07:00
parent de44a74dde
commit 277c9c9b7e
7 changed files with 60 additions and 24 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>
)} )}

View File

@ -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`}
>
&ldquo;{item.description}&rdquo; &ldquo;{item.description}&rdquo;
</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} />

View File

@ -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

View File

@ -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,

View File

@ -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,