fix: reformat eslint issues
This commit is contained in:
parent
126772beca
commit
9029319096
|
|
@ -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 },
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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" />;
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
};
|
// };
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -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 = [
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue