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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -62,7 +62,7 @@ export interface SentimentChartProps {
export interface CustomTooltipProps { export interface CustomTooltipProps {
active?: boolean; active?: boolean;
payload?: any[]; payload?: string[];
total: number; total: number;
} }
@ -92,16 +92,10 @@ export interface TrendChartProps {
export interface TrendChartTooltipProps { export interface TrendChartTooltipProps {
active?: boolean; active?: boolean;
payload?: any[]; payload?: LegendPayloadItem[];
label?: string; label?: string;
} }
// export type WordItem = {
// text: string;
// value: number;
// sentiment: "positive" | "negative" | "neutral";
// };
export interface WordCloudProps { export interface WordCloudProps {
words: WordItem[]; words: WordItem[];
} }
@ -128,7 +122,7 @@ export interface UseStatCardProps {
export interface ReviewItem { export interface ReviewItem {
id: number; id: number;
content: string; content: string;
sentiment: string; sentiment: Sentiment;
confidenceScore: number; confidenceScore: number;
createdAt: string; createdAt: string;
keywords: string[]; keywords: string[];
@ -164,3 +158,18 @@ export type WordCloudConfig = {
minValue: number; minValue: number;
maxValue: 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 { Frown, Meh, Smile } from "lucide-react";
import { WordCloudConfig, WordCloudItemProps, WordItem } from "../types"; import { WordCloudConfig, WordItem } from "../types";
export const MODEL_OPTIONS = [ export const MODEL_OPTIONS = [
{ {

View File

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