fix: reformat eslint issues

This commit is contained in:
Mahen 2026-02-11 08:46:28 +07:00
parent 126772beca
commit 9029319096
16 changed files with 93 additions and 77 deletions

View File

@ -1,9 +1,10 @@
import prisma from "@/lib/prisma";
import { Prisma } from "@prisma/client";
import { NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export async function POST(request: Request) {
export async function POST(_request: Request) {
try {
const products = [
{ name: "ZenBook 14", brand: "ASUS" },
@ -22,8 +23,13 @@ export async function POST(request: Request) {
},
{ status: 201 },
);
} catch (error: any) {
console.error("Create product Error:", error);
} catch (error: unknown) {
console.error("Create product error:", error);
if (error instanceof Prisma.PrismaClientKnownRequestError) {
return NextResponse.json({ error: error.message }, { status: 400 });
}
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },

View File

@ -1,10 +1,10 @@
import prisma from "@/lib/prisma";
import { Sentiment } from "@prisma/client";
import { Prisma, Sentiment } from "@prisma/client";
import { NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export async function POST(request: Request) {
export async function POST(_request: Request) {
try {
const reviews = [
{
@ -40,13 +40,18 @@ export async function POST(request: Request) {
return NextResponse.json(
{
message: "Booking successful",
message: "Analysis successful",
data: result,
},
{ status: 201 },
);
} catch (error: any) {
console.error("Create product Error:", error);
} catch (error: unknown) {
console.error("Create analysis error:", error);
if (error instanceof Prisma.PrismaClientKnownRequestError) {
return NextResponse.json({ error: error.message }, { status: 400 });
}
return NextResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },

View File

@ -1,25 +1,14 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono, Inter } from "next/font/google";
import { Inter } from "next/font/google";
import "./globals.css";
import Providers from "./providers";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
// lalu di body: <body className={`${inter.variable} font-sans`}>
export default function RootLayout({
children,

View File

@ -1,3 +1,5 @@
import { CLabelProps } from "@/src/types";
const renderCustomLabel = ({
cx,
cy,
@ -5,7 +7,7 @@ const renderCustomLabel = ({
innerRadius,
outerRadius,
percent,
}: any) => {
}: CLabelProps) => {
if (percent < 0.05) return null;
const RADIAN = Math.PI / 180;
const radius = innerRadius + (outerRadius - innerRadius) * 0.5;

View File

@ -1,16 +1,7 @@
"use client";
import { Header } from "./Header";
import {
Frown,
Meh,
MessageSquareText,
Minus,
Smile,
ThumbsDown,
ThumbsUp,
TrendingUp,
} from "lucide-react";
import { Frown, Meh, MessageSquareText, Smile, TrendingUp } from "lucide-react";
import { StatCard } from "./StatCard";
import {
brandData,

View File

@ -121,7 +121,7 @@ export function ReviewTable() {
</TableCell>
<TableCell className="align-top">
{getSentimentBadge(review.sentiment as any)}
{getSentimentBadge(review.sentiment ?? null)}
</TableCell>
<TableCell className="align-top">

View File

@ -49,7 +49,7 @@ export default function SentimentForm() {
model XGBoost
</p>
<form onSubmit={(e) => e.preventDefault()}>
<form onSubmit={analyzeText}>
<div className="space-y-4">
{error && (
<div className="p-3 text-sm text-red-600 bg-red-50 border border-red-200 rounded-md flex items-center gap-2">
@ -76,7 +76,7 @@ export default function SentimentForm() {
<ComboboxContent className="bg-card border-border shadow-lg animate-in fade-in zoom-in-95 duration-200 z-50">
{filteredItems.length === 0 && (
<ComboboxEmpty className="text-muted-foreground py-3 px-4 text-sm text-center">
Model "{searchQuery}" tidak ditemukan.
{` Model "${searchQuery}" tidak ditemukan.`}
</ComboboxEmpty>
)}
<ComboboxList className="p-1">
@ -119,7 +119,7 @@ export default function SentimentForm() {
/>
<Button
onClick={analyzeText}
type="submit"
disabled={!isFormValid || isAnalyzing}
className="w-full gap-2"
>
@ -152,11 +152,9 @@ export default function SentimentForm() {
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
{(() => {
const {
icon: Icon,
bgClass,
textClass,
} = getSentimentDisplay(result.sentiment);
const { icon: Icon, textClass } = getSentimentDisplay(
result.sentiment,
);
return (
<div
className={cn(

View File

@ -1,6 +1,5 @@
"use client";
import { useTrendChart } from "@/src/hooks/useTrendChart";
import { TrendChartProps } from "@/src/types";
import {
Area,
@ -15,7 +14,7 @@ import {
import TrendChartTooltip from "./TrendChartToolTip";
export function TrendChart({ data }: TrendChartProps) {
const { isMounted } = useTrendChart();
const isMounted = true;
if (!isMounted) {
return <div className="h-87.5 w-full bg-transparent" />;

View File

@ -1,16 +1,15 @@
import { TrendChartTooltipProps } from "@/src/types";
import React from "react";
import { LegendPayloadItem, TrendChartTooltipProps } from "@/src/types";
const TrendChartTooltip: React.FC<TrendChartTooltipProps> = ({
const TrendChartTooltip = ({
active,
payload,
label,
}) => {
}: TrendChartTooltipProps) => {
if (active && payload && payload.length) {
return (
<div className="rounded-lg border bg-card px-4 py-3 shadow-lg">
<p className="mb-2 font-semibold text-foreground">{label}</p>
{payload.map((item: any, index: number) => (
{payload.map((item: LegendPayloadItem, index: number) => (
<div key={index} className="flex items-center gap-2 text-sm">
<div
className="h-3 w-3 rounded-full"

View File

@ -4,7 +4,8 @@ import { useWordCloud } from "@/src/hooks/useWordCloud";
import WordCloudItem from "./WordCloudItem";
export function WordCloud() {
const { mounted, maxValue, minValue, shuffledWords } = useWordCloud();
const mounted = true;
const { maxValue, minValue, shuffledWords } = useWordCloud();
if (!mounted) {
return (

View File

@ -22,7 +22,7 @@ export const useSentimentForm = () => {
const isFormValid = selectedModel && laptopName.trim() && text.trim();
const analyzeText = async (e: any) => {
const analyzeText = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (!isFormValid) return;
@ -54,9 +54,14 @@ export const useSentimentForm = () => {
confidence: data.confidenceScore,
keywords: data.keywords || [],
});
} catch (err) {
} catch (err: unknown) {
console.error("Failed to analyze:", err);
setError("Gagal menghubungi server. Pastikan API berjalan.");
if (err instanceof Error) {
setError(err.message);
} else {
setError("Gagal menghubungi server. Pastikan API berjalan.");
}
} finally {
setIsAnalyzing(false);
}

View File

@ -1,9 +1,9 @@
import { useEffect, useState } from "react";
// import { useEffect, useState } from "react";
export const useTrendChart = () => {
const [isMounted, setIsMounted] = useState(false);
useEffect(() => {
setIsMounted(true);
}, []);
return { isMounted, setIsMounted };
};
// export const useTrendChart = () => {
// const isMounted = true;
// useEffect(() => {
// setIsMounted(true);
// }, []);
// return { isMounted, setIsMounted };
// };

View File

@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react";
import { useEffect, useState } from "react";
import {
KeywordStats,
Review,
@ -9,12 +9,11 @@ import {
import { WORD_LIMIT } from "../utils/datas";
export const useWordCloud = () => {
const [mounted, setMounted] = useState(false);
const [words, setWords] = useState<WordItem[]>([]);
const [shuffledWords, setShuffledWords] = useState<WordItem[]>([]);
const [ready, setReady] = useState(false);
useEffect(() => {
setMounted(true);
const fetchWords = async () => {
try {
const res = await fetch("/api/review");
@ -90,14 +89,26 @@ export const useWordCloud = () => {
const maxValue = Math.max(...words.map((w) => w.value), 1);
const minValue = Math.min(...words.map((w) => w.value), 0);
const shuffledWords = useMemo(() => {
return [...words].sort(() => Math.random() - 0.5);
useEffect(() => {
const id = setTimeout(() => {
const result = [...words];
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
setShuffledWords(result);
setReady(true);
}, 0);
return () => clearTimeout(id);
}, [words]);
return {
mounted,
maxValue,
minValue,
shuffledWords,
ready,
};
};

View File

@ -62,7 +62,7 @@ export interface SentimentChartProps {
export interface CustomTooltipProps {
active?: boolean;
payload?: any[];
payload?: string[];
total: number;
}
@ -92,16 +92,10 @@ export interface TrendChartProps {
export interface TrendChartTooltipProps {
active?: boolean;
payload?: any[];
payload?: LegendPayloadItem[];
label?: string;
}
// export type WordItem = {
// text: string;
// value: number;
// sentiment: "positive" | "negative" | "neutral";
// };
export interface WordCloudProps {
words: WordItem[];
}
@ -128,7 +122,7 @@ export interface UseStatCardProps {
export interface ReviewItem {
id: number;
content: string;
sentiment: string;
sentiment: Sentiment;
confidenceScore: number;
createdAt: string;
keywords: string[];
@ -164,3 +158,18 @@ export type WordCloudConfig = {
minValue: number;
maxValue: number;
};
export interface CLabelProps {
cx: number;
cy: number;
midAngle: number;
innerRadius: number;
outerRadius: number;
percent: number;
}
export type LegendPayloadItem = {
color?: string;
name?: string;
value?: string;
};

View File

@ -1,5 +1,5 @@
import { Frown, Meh, Smile } from "lucide-react";
import { WordCloudConfig, WordCloudItemProps, WordItem } from "../types";
import { WordCloudConfig, WordItem } from "../types";
export const MODEL_OPTIONS = [
{

View File

@ -1,4 +1,5 @@
import type { Config } from "tailwindcss";
import animate from "tailwindcss-animate";
const config: Config = {
content: [
@ -107,7 +108,7 @@ const config: Config = {
},
},
},
plugins: [require("tailwindcss-animate")],
plugins: [animate],
};
export default config;