style: make night mode visibility
This commit is contained in:
parent
be2b6d7fcd
commit
f367b1c3ad
|
|
@ -7,7 +7,7 @@ import { Button } from "../ui/button";
|
||||||
import ResultSection from "./ResultSection";
|
import ResultSection from "./ResultSection";
|
||||||
import UrlInputList from "./UrlInputList";
|
import UrlInputList from "./UrlInputList";
|
||||||
|
|
||||||
export default function AnalysisClient() {
|
export default function AnalysisClient({ isDark }: { isDark: boolean }) {
|
||||||
const {
|
const {
|
||||||
isValid,
|
isValid,
|
||||||
errors,
|
errors,
|
||||||
|
|
@ -28,23 +28,31 @@ export default function AnalysisClient() {
|
||||||
<div className="w-full mx-auto">
|
<div className="w-full mx-auto">
|
||||||
<form
|
<form
|
||||||
onSubmit={handleSubmit(onSubmit)}
|
onSubmit={handleSubmit(onSubmit)}
|
||||||
className="bg-white p-6 rounded-lg border mb-8"
|
className={`p-6 rounded-lg mb-8 ${isDark ? "bg-gray-800" : "bg-white border border-gray-200"} transition-all duration-500`}
|
||||||
>
|
>
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
<Sparkles className="h-5 w-5 text-primary" />
|
<Sparkles
|
||||||
<h3 className="text-lg font-semibold">Analisis Sentimen Real-time</h3>
|
className={`h-5 w-5 text-primary ${isDark ? "text-white" : "text-black"} transition-all duration-500`}
|
||||||
|
/>
|
||||||
|
<h3
|
||||||
|
className={`text-lg font-semibold ${isDark ? "text-white" : "text-black"} transition-all duration-500`}
|
||||||
|
>
|
||||||
|
Analisis Sentimen Real-time
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label className="block mb-1 text-sm font-medium text-gray-700">
|
<label
|
||||||
|
className={`block mb-1 text-sm font-medium ${isDark ? "text-white" : "text-gray-700"} transition-all duration-500`}
|
||||||
|
>
|
||||||
Tautan Produk 1
|
Tautan Produk 1
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="url"
|
type="url"
|
||||||
placeholder="Contoh: https://tokopedia.com/..."
|
placeholder="Contoh: https://tokopedia.com/..."
|
||||||
className={`${errors.url1 ? "border-sentiment-negative" : "focus:ring-primary"}`}
|
className={`${errors.url1 ? "border-sentiment-negative" : "focus:ring-primary"} ${isDark ? "bg-gray-800 text-white" : "bg-white"} transition-all duration-500 w-full`}
|
||||||
{...register("url1")}
|
{...register("url1")}
|
||||||
/>
|
/>
|
||||||
{errors.url1 && (
|
{errors.url1 && (
|
||||||
|
|
@ -55,13 +63,15 @@ export default function AnalysisClient() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label className="block mb-1 text-sm font-medium text-gray-700">
|
<label
|
||||||
|
className={`block mb-1 text-sm font-medium ${isDark ? "text-white" : "text-gray-700"} transition-all duration-500`}
|
||||||
|
>
|
||||||
Tautan Produk 2
|
Tautan Produk 2
|
||||||
</label>
|
</label>
|
||||||
<Input
|
<Input
|
||||||
type="url"
|
type="url"
|
||||||
placeholder="Contoh: https://tokopedia.com/..."
|
placeholder="Contoh: https://tokopedia.com/..."
|
||||||
className={`w-full ${errors.url2 ? "border-sentiment-negative" : "focus:ring-primary"}`}
|
className={`${errors.url2 ? "border-sentiment-negative" : "focus:ring-primary"} ${isDark ? "bg-gray-800 text-white" : "bg-white"} transition-all duration-500 w-full`}
|
||||||
{...register("url2")}
|
{...register("url2")}
|
||||||
/>
|
/>
|
||||||
{errors.url2 && (
|
{errors.url2 && (
|
||||||
|
|
@ -77,6 +87,7 @@ export default function AnalysisClient() {
|
||||||
urlDatas={urlDatas}
|
urlDatas={urlDatas}
|
||||||
visibleFields={visibleFields}
|
visibleFields={visibleFields}
|
||||||
setVisibleFields={setVisibleFields}
|
setVisibleFields={setVisibleFields}
|
||||||
|
isDark={isDark}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{visibleFields < 2 && (
|
{visibleFields < 2 && (
|
||||||
|
|
@ -90,10 +101,11 @@ export default function AnalysisClient() {
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setVisibleFields((prev) => prev + 1)}
|
onClick={() => setVisibleFields((prev) => prev + 1)}
|
||||||
className={`
|
className={`
|
||||||
h-max bg-card text-primary hover:bg-[#F8FBFF]
|
h-max bg-card text-primary
|
||||||
border-dashed border border-primary/20 shadow-xs
|
border-dashed border border-primary/20 shadow-xs
|
||||||
transition-all duration-500 animate-in fade-in zoom-in-95
|
transition-all duration-500 animate-in fade-in zoom-in-95
|
||||||
${visibleFields === 0 ? "w-full md:w-1/2" : "w-full"}
|
${visibleFields === 0 ? "w-full md:w-1/2" : "w-full"}
|
||||||
|
${isDark ? "bg-gray-800 text-white hover:bg-gray-900 border-dashed border border-white" : "text-black hover:bg-[#F8FBFF] "}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{visibleFields === 0
|
{visibleFields === 0
|
||||||
|
|
@ -136,7 +148,7 @@ export default function AnalysisClient() {
|
||||||
type="submit"
|
type="submit"
|
||||||
hidden={loading}
|
hidden={loading}
|
||||||
disabled={!isValid}
|
disabled={!isValid}
|
||||||
className="w-full md:w-max bg-primary text-white px-6 py-3 mt-6 rounded-md transition-colors disabled:bg-gray-400"
|
className={`w-full md:w-max bg-primary text-white px-6 py-3 mt-6 rounded-md transition-colors disabled:bg-gray-400`}
|
||||||
>
|
>
|
||||||
<Sparkles className="h-4 w-4" />
|
<Sparkles className="h-4 w-4" />
|
||||||
{loading ? "Menganalisis..." : "Analisis Sekarang"}
|
{loading ? "Menganalisis..." : "Analisis Sekarang"}
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ export function BrandFilter() {
|
||||||
<SelectValue placeholder="Pilih Brand" />
|
<SelectValue placeholder="Pilih Brand" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent
|
<SelectContent
|
||||||
className="bg-card border-border shadow-lg"
|
className="bg-card shadow-lg"
|
||||||
position="popper"
|
position="popper"
|
||||||
>
|
>
|
||||||
<SelectItem
|
<SelectItem
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
Frown,
|
Frown,
|
||||||
Meh,
|
Meh,
|
||||||
MessageSquareText,
|
MessageSquareText,
|
||||||
|
Moon,
|
||||||
Smile,
|
Smile,
|
||||||
Sparkles,
|
Sparkles,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
@ -30,13 +31,21 @@ export default function DashboardClient() {
|
||||||
neutralCount,
|
neutralCount,
|
||||||
loading,
|
loading,
|
||||||
modelData,
|
modelData,
|
||||||
|
darkMode,
|
||||||
|
setDarkMode,
|
||||||
percentage,
|
percentage,
|
||||||
scrollToResult,
|
scrollToResult,
|
||||||
} = useDashboards();
|
} = useDashboards();
|
||||||
|
|
||||||
|
const toggleDarkMode = () => {
|
||||||
|
setDarkMode((prevMode) => !prevMode);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-[#F8FBFF]" suppressHydrationWarning>
|
<div
|
||||||
<Header />
|
className={`min-h-screen ${darkMode ? "bg-gray-900 text-white" : "bg-[#F8FBFF]"} suppressHydrationWarning} transition-all duration-500`}
|
||||||
|
>
|
||||||
|
<Header onToggle={toggleDarkMode} isDark={darkMode} />
|
||||||
|
|
||||||
<main className="container mx-auto px-4 py-8">
|
<main className="container mx-auto px-4 py-8">
|
||||||
<div
|
<div
|
||||||
|
|
@ -67,6 +76,7 @@ export default function DashboardClient() {
|
||||||
value={totalReviews}
|
value={totalReviews}
|
||||||
icon={MessageSquareText}
|
icon={MessageSquareText}
|
||||||
delay={0}
|
delay={0}
|
||||||
|
isDark={darkMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StatCard
|
<StatCard
|
||||||
|
|
@ -76,6 +86,7 @@ export default function DashboardClient() {
|
||||||
icon={Smile}
|
icon={Smile}
|
||||||
variant="positive"
|
variant="positive"
|
||||||
delay={100}
|
delay={100}
|
||||||
|
isDark={darkMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StatCard
|
<StatCard
|
||||||
|
|
@ -85,6 +96,7 @@ export default function DashboardClient() {
|
||||||
icon={Frown}
|
icon={Frown}
|
||||||
variant="negative"
|
variant="negative"
|
||||||
delay={200}
|
delay={200}
|
||||||
|
isDark={darkMode}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<StatCard
|
<StatCard
|
||||||
|
|
@ -94,6 +106,7 @@ export default function DashboardClient() {
|
||||||
icon={Meh}
|
icon={Meh}
|
||||||
variant="neutral"
|
variant="neutral"
|
||||||
delay={300}
|
delay={300}
|
||||||
|
isDark={darkMode}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -111,24 +124,34 @@ export default function DashboardClient() {
|
||||||
</div> */}
|
</div> */}
|
||||||
|
|
||||||
<div className="mb-8 grid gap-4 lg:grid-cols-2">
|
<div className="mb-8 grid gap-4 lg:grid-cols-2">
|
||||||
<div className="rounded-xl border bg-card p-6">
|
<div
|
||||||
|
className={`rounded-xl border ${darkMode ? "border-transparent" : "border-gray-200"} bg-card p-6 ${darkMode ? "bg-gray-800" : "bg-white"} transition-all duration-500`}
|
||||||
|
>
|
||||||
<div className="flex items-center gap-2 mb-2">
|
<div className="flex items-center gap-2 mb-2">
|
||||||
<FileLock className="h-5 w-5 text-primary" />
|
<FileLock
|
||||||
<h3 className="text-lg font-semibold">Kata Kunci Populer</h3>
|
className={`h-5 w-5 text-primary ${darkMode ? "text-white" : "text-black"} transition-all duration-500`}
|
||||||
|
/>
|
||||||
|
<h3
|
||||||
|
className={`text-lg font-semibold ${darkMode ? "text-white" : "text-neutral"} transition-all duration-500`}
|
||||||
|
>
|
||||||
|
Kata Kunci Populer
|
||||||
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<p className="mb-4 text-sm text-muted-foreground">
|
<p
|
||||||
|
className={`mb-4 text-sm ${darkMode ? "text-white" : "text-neutral"} transition-all duration-500`}
|
||||||
|
>
|
||||||
Kata-kata yang sering muncul dalam ulasan berdasarkan kategori
|
Kata-kata yang sering muncul dalam ulasan berdasarkan kategori
|
||||||
sentimen
|
sentimen
|
||||||
</p>
|
</p>
|
||||||
<WordCloud />
|
<WordCloud isDark={darkMode} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<ModelInfoSkeleton />
|
<ModelInfoSkeleton />
|
||||||
) : modelData.length > 0 ? (
|
) : modelData.length > 0 ? (
|
||||||
<ModelInfo data={modelData} />
|
<ModelInfo data={modelData} isDark={darkMode} />
|
||||||
) : (
|
) : (
|
||||||
<div className="rounded-xl border bg-card p-6 text-center text-muted-foreground">
|
<div className="rounded-xl border border-gray-200 bg-card p-6 text-center text-muted-foreground">
|
||||||
Data model tidak tersedia.
|
Data model tidak tersedia.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
@ -136,7 +159,7 @@ export default function DashboardClient() {
|
||||||
|
|
||||||
<section id="analysis-form" className="scroll-mt-60">
|
<section id="analysis-form" className="scroll-mt-60">
|
||||||
<div className="mb-8 ">
|
<div className="mb-8 ">
|
||||||
<AnalysisClient />
|
<AnalysisClient isDark={darkMode} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import {
|
||||||
Database,
|
Database,
|
||||||
Laptop,
|
Laptop,
|
||||||
LogOut,
|
LogOut,
|
||||||
|
Moon,
|
||||||
User,
|
User,
|
||||||
UserCircle,
|
UserCircle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
|
@ -19,14 +20,23 @@ import { signOut } from "next-auth/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useHeader } from "@/src/hooks/useHeader";
|
import { useHeader } from "@/src/hooks/useHeader";
|
||||||
import { useDashboards } from "@/src/hooks/useDashboard";
|
import { useDashboards } from "@/src/hooks/useDashboard";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export function Header() {
|
export function Header({
|
||||||
|
onToggle,
|
||||||
|
isDark,
|
||||||
|
}: {
|
||||||
|
onToggle: () => void;
|
||||||
|
isDark: boolean;
|
||||||
|
}) {
|
||||||
const { open, setOpen, session, mounted, productCount } = useHeader();
|
const { open, setOpen, session, mounted, productCount } = useHeader();
|
||||||
const { totalReviews } = useDashboards();
|
const { totalReviews } = useDashboards();
|
||||||
|
|
||||||
if (!mounted) return null;
|
if (!mounted) return null;
|
||||||
return (
|
return (
|
||||||
<header className="border-b bg-[#F8FBFF]/50 backdrop-blur-sm sticky top-0 z-1">
|
<header
|
||||||
|
className={`border-b ${isDark ? "bg-gray-900 text-white" : "bg-[#F8FBFF]/50"} backdrop-blur-sm sticky top-0 z-10 transition-all duration-500`}
|
||||||
|
>
|
||||||
<div className="container mx-auto px-4 py-4">
|
<div className="container mx-auto px-4 py-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Link href="/" className="flex items-center gap-3 cursor-pointer">
|
<Link href="/" className="flex items-center gap-3 cursor-pointer">
|
||||||
|
|
@ -86,6 +96,7 @@ export function Header() {
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
|
<Moon onClick={onToggle} className="h-4 w-4 cursor-pointer" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,13 @@ import {
|
||||||
import { ModelDB } from "@/src/types";
|
import { ModelDB } from "@/src/types";
|
||||||
import { useModelInfo } from "@/src/hooks/useModelInfo";
|
import { useModelInfo } from "@/src/hooks/useModelInfo";
|
||||||
|
|
||||||
export function ModelInfo({ data }: { data: ModelDB[] }) {
|
export function ModelInfo({
|
||||||
|
data,
|
||||||
|
isDark,
|
||||||
|
}: {
|
||||||
|
data: ModelDB[];
|
||||||
|
isDark: boolean;
|
||||||
|
}) {
|
||||||
const { selectedIndex, metrics, setSelectedIndex, currentModel } =
|
const { selectedIndex, metrics, setSelectedIndex, currentModel } =
|
||||||
useModelInfo({ data });
|
useModelInfo({ data });
|
||||||
|
|
||||||
|
|
@ -26,25 +32,29 @@ export function ModelInfo({ data }: { data: ModelDB[] }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-xl border bg-card p-6">
|
<div
|
||||||
|
className={`rounded-xl border ${isDark ? "border-transparent" : "border-gray-200"} bg-card p-6 ${isDark ? "bg-gray-800" : "bg-white"} transition-all duration-500`}
|
||||||
|
>
|
||||||
<div className="mb-4 flex items-center justify-between gap-4">
|
<div className="mb-4 flex items-center justify-between gap-4">
|
||||||
<Select
|
<Select
|
||||||
value={selectedIndex.toString()}
|
value={selectedIndex.toString()}
|
||||||
onValueChange={(val) => setSelectedIndex(parseInt(val))}
|
onValueChange={(val) => setSelectedIndex(parseInt(val))}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-fit justify-start gap-3 text-md font-semibold border-border bg-card shadow-sm">
|
<SelectTrigger
|
||||||
|
className={`w-fit justify-start gap-3 text-md font-semibold border border-gray-200 bg-card ${isDark ? "bg-gray-900" : "bg-white"} transition-all duration-500`}
|
||||||
|
>
|
||||||
<SelectValue placeholder="Pilih Model" />
|
<SelectValue placeholder="Pilih Model" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|
||||||
<SelectContent
|
<SelectContent
|
||||||
className="bg-card border-border shadow-lg"
|
className={`bg-card shadow-lg ${isDark ? "bg-gray-900 text-white" : "bg-white"}`}
|
||||||
position="popper"
|
position="popper"
|
||||||
>
|
>
|
||||||
{data.map((model, index) => (
|
{data.map((model, index) => (
|
||||||
<SelectItem
|
<SelectItem
|
||||||
key={model.modelName + index}
|
key={model.modelName + index}
|
||||||
value={index.toString()}
|
value={index.toString()}
|
||||||
className="cursor-pointer hover:bg-primary hover:text-card focus:bg-primary focus:text-card"
|
className={`cursor-pointer hover:bg-primary hover:text-card focus:bg-primary focus:text-card ${isDark ? "bg-gray-900 text-white" : "bg-white"} transition-all duration-500`}
|
||||||
>
|
>
|
||||||
{model.modelName}
|
{model.modelName}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
|
@ -54,7 +64,7 @@ export function ModelInfo({ data }: { data: ModelDB[] }) {
|
||||||
|
|
||||||
<Badge
|
<Badge
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className={`bg-sentiment-positive-light text-sentiment-positive ${currentModel.isActive ? "bg-sentiment-positive-light text-sentiment-positive" : "bg-primary/10 text-primary"}`}
|
className={`${currentModel.isActive ? `${isDark ? "bg-sentiment-positive/10 text-sentiment-positive" : "bg-sentiment-positive-light text-sentiment-positive"}` : `${isDark ? "bg-gray-900 text-white" : "bg-primary/10 text-primary"}`} transition-all duration-500`}
|
||||||
>
|
>
|
||||||
{currentModel.isActive === true ? "Active" : "Inactive"}
|
{currentModel.isActive === true ? "Active" : "Inactive"}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
@ -70,8 +80,12 @@ export function ModelInfo({ data }: { data: ModelDB[] }) {
|
||||||
key={metric.label}
|
key={metric.label}
|
||||||
className="flex items-center gap-3 rounded-lg p-3 bg-secondary/50 border border-border/40"
|
className="flex items-center gap-3 rounded-lg p-3 bg-secondary/50 border border-border/40"
|
||||||
>
|
>
|
||||||
<div className="rounded-lg bg-primary/10 p-2">
|
<div
|
||||||
<metric.icon className="h-4 w-4 text-primary" />
|
className={`rounded-lg p-2 ${isDark ? "bg-gray-600" : "bg-primary/10 "} transition-all duration-500`}
|
||||||
|
>
|
||||||
|
<metric.icon
|
||||||
|
className={`h-4 w-4 text-primary ${isDark ? "text-white" : "text-black"} transition-all duration-500`}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-muted-foreground">{metric.label}</p>
|
<p className="text-xs text-muted-foreground">{metric.label}</p>
|
||||||
|
|
@ -85,7 +99,9 @@ export function ModelInfo({ data }: { data: ModelDB[] }) {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-6 space-y-2 text-sm text-muted-foreground border-t pt-4">
|
<div
|
||||||
|
className={`mt-6 space-y-2 text-sm text-muted-foreground border-t border-gray-200 pt-4`}
|
||||||
|
>
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<span>Preprocessing</span>
|
<span>Preprocessing</span>
|
||||||
<span className="text-foreground">
|
<span className="text-foreground">
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ export function ReviewTable() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="rounded-xl border bg-card">
|
<div className="rounded-xl border border-gray-200 bg-card">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow className="hover:bg-transparent">
|
<TableRow className="hover:bg-transparent">
|
||||||
|
|
@ -155,7 +155,7 @@ export function ReviewTable() {
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
{totalPages > 1 && (
|
{totalPages > 1 && (
|
||||||
<div className="border-t bg-muted/20 px-6 py-4">
|
<div className="border-t border-t-gray-200 bg-muted/20 px-6 py-4">
|
||||||
<Pagination className="justify-center sm:justify-end">
|
<Pagination className="justify-center sm:justify-end">
|
||||||
<PaginationContent>
|
<PaginationContent>
|
||||||
<PaginationItem>
|
<PaginationItem>
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ export function StatCard({
|
||||||
trend,
|
trend,
|
||||||
variant = "default",
|
variant = "default",
|
||||||
delay = 0,
|
delay = 0,
|
||||||
|
isDark,
|
||||||
}: StatCardProps) {
|
}: StatCardProps) {
|
||||||
const { isVisible, displayValue } = useStatCard({ value, delay });
|
const { isVisible, displayValue } = useStatCard({ value, delay });
|
||||||
|
|
||||||
|
|
@ -20,18 +21,28 @@ export function StatCard({
|
||||||
"relative overflow-hidden rounded-xl border p-6 card-elevated transition-all duration-500",
|
"relative overflow-hidden rounded-xl border p-6 card-elevated transition-all duration-500",
|
||||||
variantStyles[variant],
|
variantStyles[variant],
|
||||||
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4",
|
isVisible ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4",
|
||||||
|
isDark ? "bg-gray-800" : "bg-white",
|
||||||
|
isDark ? "border-transparent" : "border-gray-200",
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between">
|
<div className="flex items-start justify-between">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="text-sm font-medium text-muted-foreground">{title}</p>
|
<p
|
||||||
|
className={`text-sm font-medium text-muted-foreground ${isDark ? "text-white" : "text-neutral"}`}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="flex items-baseline gap-1">
|
<div className="flex items-baseline gap-1">
|
||||||
<span className="text-3xl font-bold tracking-tight">
|
<span
|
||||||
|
className={`text-3xl font-bold tracking-tight ${isDark ? "text-white" : "text-neutral"}`}
|
||||||
|
>
|
||||||
{displayValue.toLocaleString()}
|
{displayValue.toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
{suffix && (
|
{suffix && (
|
||||||
<span className="text-lg font-medium text-muted-foreground">
|
<span
|
||||||
|
className={`text-lg font-medium ${isDark ? "text-white" : "text-neutral"}`}
|
||||||
|
>
|
||||||
{suffix}
|
{suffix}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
|
@ -55,7 +66,7 @@ export function StatCard({
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={cn("rounded-xl p-3", iconStyles[variant])}>
|
<div className={cn("rounded-xl p-3 transition-all duration-500", iconStyles(isDark)[variant])}>
|
||||||
<Icon className="h-6 w-6" />
|
<Icon className="h-6 w-6" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,13 @@ const UrlInputItem = ({
|
||||||
index,
|
index,
|
||||||
visibleFields,
|
visibleFields,
|
||||||
onRemove,
|
onRemove,
|
||||||
|
isDark,
|
||||||
}: UrlInputItemProps) => {
|
}: UrlInputItemProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="animate-in fade-in slide-in-from-bottom-2 duration-300">
|
<div className="animate-in fade-in slide-in-from-bottom-2 duration-300">
|
||||||
<label className="block mb-1 text-sm font-medium text-gray-700">
|
<label
|
||||||
|
className={`block mb-1 text-sm font-medium ${isDark ? "text-white" : "text-gray-700"} transition-all duration-500`}
|
||||||
|
>
|
||||||
{item.labels}
|
{item.labels}
|
||||||
</label>
|
</label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
|
@ -18,16 +21,15 @@ const UrlInputItem = ({
|
||||||
<Input
|
<Input
|
||||||
type="url"
|
type="url"
|
||||||
placeholder="Contoh: https://tokopedia.com/..."
|
placeholder="Contoh: https://tokopedia.com/..."
|
||||||
className={`${item.errors ? "border-sentiment-negative" : "focus:ring-primary"}`}
|
className={`${item.errors ? "border-sentiment-negative" : "focus:ring-primary"} ${isDark ? "bg-gray-800 text-white" : "bg-white"} transition-all duration-500 w-full`}
|
||||||
{...item.title}
|
{...item.title}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{index === visibleFields - 1 && (
|
{index === visibleFields - 1 && (
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
|
||||||
onClick={onRemove}
|
onClick={onRemove}
|
||||||
className="text-sentiment-negative hover:text-sentiment-negative hover:bg-sentiment-negative-light shrink-0"
|
className={`shrink-0 ${isDark ? "text-sentiment-negative bg-transparent hover:text-sentiment-negative hover:bg-sentiment-negative/10" : "text-sentiment-negative bg-card hover:text-sentiment-negative hover:bg-sentiment-negative-light"} transition-all duration-500`}
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ const UrlInputList = ({
|
||||||
urlDatas,
|
urlDatas,
|
||||||
visibleFields,
|
visibleFields,
|
||||||
setVisibleFields,
|
setVisibleFields,
|
||||||
|
isDark,
|
||||||
}: UrlInputListProps) => {
|
}: UrlInputListProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -15,6 +16,7 @@ const UrlInputList = ({
|
||||||
index={index}
|
index={index}
|
||||||
visibleFields={visibleFields}
|
visibleFields={visibleFields}
|
||||||
onRemove={() => setVisibleFields((prev) => prev - 1)}
|
onRemove={() => setVisibleFields((prev) => prev - 1)}
|
||||||
|
isDark={isDark}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { useWordCloud } from "@/src/hooks/useWordCloud";
|
||||||
import WordCloudItem from "./WordCloudItem";
|
import WordCloudItem from "./WordCloudItem";
|
||||||
import { Inbox } from "lucide-react";
|
import { Inbox } from "lucide-react";
|
||||||
|
|
||||||
export function WordCloud() {
|
export function WordCloud({ isDark }: { isDark: boolean }) {
|
||||||
const { maxValue, minValue, shuffledWords, isEmpty } = useWordCloud();
|
const { maxValue, minValue, shuffledWords, isEmpty } = useWordCloud();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
|
||||||
type={type}
|
type={type}
|
||||||
data-slot="input"
|
data-slot="input"
|
||||||
className={cn(
|
className={cn(
|
||||||
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border border-gray-200 bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
||||||
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
||||||
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
className
|
className
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ export const useDashboards = () => {
|
||||||
negative: 0,
|
negative: 0,
|
||||||
neutral: 0,
|
neutral: 0,
|
||||||
});
|
});
|
||||||
|
const [darkMode, setDarkMode] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchStats() {
|
async function fetchStats() {
|
||||||
|
|
@ -84,6 +85,8 @@ export const useDashboards = () => {
|
||||||
selectedBrand,
|
selectedBrand,
|
||||||
loading,
|
loading,
|
||||||
modelData,
|
modelData,
|
||||||
|
darkMode,
|
||||||
|
setDarkMode,
|
||||||
setSelectedBrand,
|
setSelectedBrand,
|
||||||
percentage,
|
percentage,
|
||||||
scrollToResult,
|
scrollToResult,
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ export interface StatCardProps {
|
||||||
};
|
};
|
||||||
variant?: "default" | "positive" | "negative" | "neutral";
|
variant?: "default" | "positive" | "negative" | "neutral";
|
||||||
delay?: number;
|
delay?: number;
|
||||||
|
isDark: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TrendData {
|
interface TrendData {
|
||||||
|
|
@ -385,10 +386,12 @@ export type UrlInputItemProps = {
|
||||||
index: number;
|
index: number;
|
||||||
visibleFields: number;
|
visibleFields: number;
|
||||||
onRemove: () => void;
|
onRemove: () => void;
|
||||||
|
isDark: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UrlInputListProps = {
|
export type UrlInputListProps = {
|
||||||
urlDatas: UrlData[];
|
urlDatas: UrlData[];
|
||||||
visibleFields: number;
|
visibleFields: number;
|
||||||
setVisibleFields: React.Dispatch<React.SetStateAction<number>>;
|
setVisibleFields: React.Dispatch<React.SetStateAction<number>>;
|
||||||
|
isDark: boolean;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ const variantStyles = {
|
||||||
neutral: "bg-sentiment-neutral-light border-sentiment-neutral/20",
|
neutral: "bg-sentiment-neutral-light border-sentiment-neutral/20",
|
||||||
};
|
};
|
||||||
|
|
||||||
const iconStyles = {
|
const iconStyles = (isDark: boolean) => {
|
||||||
default: "bg-primary/10 text-primary",
|
return {
|
||||||
positive: "bg-sentiment-positive/10 text-sentiment-positive",
|
default: `bg-primary/10 text-primary ${isDark ? "text-white" : "text-neutral"} ${isDark ? "bg-gray-900" : "bg-primary/10"}`,
|
||||||
negative: "bg-sentiment-negative/10 text-sentiment-negative",
|
positive: "bg-sentiment-positive/10 text-sentiment-positive",
|
||||||
neutral: "bg-sentiment-neutral/10 text-sentiment-neutral",
|
negative: "bg-sentiment-negative/10 text-sentiment-negative",
|
||||||
|
neutral: "bg-sentiment-neutral/10 text-sentiment-neutral",
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export { variantStyles, iconStyles };
|
export { variantStyles, iconStyles };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue