parent
0c16c70a9e
commit
003ebd1de1
|
@ -0,0 +1,147 @@
|
||||||
|
from flask import Flask, request, jsonify
|
||||||
|
from flask_cors import CORS
|
||||||
|
from sklearn.neighbors import KNeighborsClassifier
|
||||||
|
import pandas as pd
|
||||||
|
import os
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
CORS(app)
|
||||||
|
|
||||||
|
|
||||||
|
DATASET_FILE = os.path.join(os.path.dirname(__file__), "dataset.xlsx")
|
||||||
|
|
||||||
|
# Mapping angka ke kategori jawaban
|
||||||
|
ANSWER_MAPPING = {
|
||||||
|
0: "Tidak Mengandung",
|
||||||
|
1: "Sangat Sedikit Mengandung",
|
||||||
|
2: "Agak Mengandung",
|
||||||
|
3: "Cukup Mengandung",
|
||||||
|
4: "Banyak Mengandung",
|
||||||
|
5: "Sangat Banyak Mengandung"
|
||||||
|
}
|
||||||
|
|
||||||
|
REVERSE_MAPPING = {v: k for k, v in ANSWER_MAPPING.items()}
|
||||||
|
|
||||||
|
# Fungsi untuk membaca dataset
|
||||||
|
def load_dataset():
|
||||||
|
if os.path.exists(DATASET_FILE):
|
||||||
|
try:
|
||||||
|
data = pd.read_excel(DATASET_FILE)
|
||||||
|
|
||||||
|
if data.empty:
|
||||||
|
data = pd.DataFrame(columns=["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"])
|
||||||
|
data.to_excel(DATASET_FILE, index=False)
|
||||||
|
except Exception as e:
|
||||||
|
print("Error saat membaca dataset:", e)
|
||||||
|
data = pd.DataFrame(columns=["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"])
|
||||||
|
data.to_excel(DATASET_FILE, index=False)
|
||||||
|
else:
|
||||||
|
data = pd.DataFrame(columns=["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"])
|
||||||
|
data.to_excel(DATASET_FILE, index=False)
|
||||||
|
|
||||||
|
print("Kolom dalam dataset:", data.columns.tolist())
|
||||||
|
|
||||||
|
required_columns = ["Nama Game", "Kekerasan", "Bahasa Kasar", "Tema Seksual", "Darah/Gore", "Elemen Perjudian", "Rating"]
|
||||||
|
for col in required_columns:
|
||||||
|
if col not in data.columns:
|
||||||
|
raise ValueError(f"Kolom '{col}' tidak ditemukan dalam dataset!")
|
||||||
|
|
||||||
|
X = data.iloc[:, 1:-1].values
|
||||||
|
y = data.iloc[:, -1].values
|
||||||
|
|
||||||
|
return X, y, data
|
||||||
|
|
||||||
|
# Fungsi untuk menambahkan data ke dataset
|
||||||
|
def add_to_dataset(name, features, rating):
|
||||||
|
data = pd.read_excel(DATASET_FILE)
|
||||||
|
|
||||||
|
if name not in data['Nama Game'].values:
|
||||||
|
new_row = [name] + features + [rating]
|
||||||
|
data.loc[len(data)] = new_row
|
||||||
|
data.to_excel(DATASET_FILE, index=False)
|
||||||
|
|
||||||
|
# Endpoint untuk mengecek apakah game sudah ada dalam dataset
|
||||||
|
@app.route('/check_game', methods=['POST'])
|
||||||
|
def check_game():
|
||||||
|
try:
|
||||||
|
data = request.json
|
||||||
|
game_name = data['game_name']
|
||||||
|
|
||||||
|
_, _, dataset = load_dataset()
|
||||||
|
|
||||||
|
if game_name in dataset["Nama Game"].values:
|
||||||
|
rating = dataset.loc[dataset["Nama Game"] == game_name, "Rating"].values[0]
|
||||||
|
return jsonify({'exists': True, 'rating': rating})
|
||||||
|
|
||||||
|
return jsonify({'exists': False})
|
||||||
|
except Exception as e:
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
# Endpoint untuk menerima jawaban dan memberikan rating
|
||||||
|
@app.route('/submit_answers', methods=['POST'])
|
||||||
|
def submit_answers():
|
||||||
|
try:
|
||||||
|
data = request.json
|
||||||
|
print("=== DATA DITERIMA DARI FRONTEND ===")
|
||||||
|
print(data)
|
||||||
|
|
||||||
|
game_name = data.get('game_name', '').strip()
|
||||||
|
answers = data.get('answers', [])
|
||||||
|
|
||||||
|
if not game_name or not answers:
|
||||||
|
print("❌ ERROR: Data tidak lengkap")
|
||||||
|
return jsonify({"error": "Data tidak lengkap"}), 400
|
||||||
|
|
||||||
|
print("✔ Nama Game:", game_name)
|
||||||
|
print("✔ Jawaban:", answers)
|
||||||
|
|
||||||
|
# Cek apakah jawaban valid
|
||||||
|
features = []
|
||||||
|
for answer in answers:
|
||||||
|
if answer not in REVERSE_MAPPING:
|
||||||
|
print(f"❌ ERROR: Jawaban tidak valid -> {answer}")
|
||||||
|
return jsonify({"error": f"Jawaban tidak valid: {answer}"}), 400
|
||||||
|
features.append(REVERSE_MAPPING[answer])
|
||||||
|
|
||||||
|
print("✔ Features (dikonversi ke angka):", features)
|
||||||
|
|
||||||
|
|
||||||
|
X, y, dataset = load_dataset()
|
||||||
|
|
||||||
|
# Jika game sudah ada di dataset, kembalikan rating yang sudah ada
|
||||||
|
if game_name in dataset["Nama Game"].values:
|
||||||
|
existing_rating = dataset.loc[dataset["Nama Game"] == game_name, "Rating"].values[0]
|
||||||
|
print("✔ Game ditemukan dalam dataset. Rating:", existing_rating)
|
||||||
|
return jsonify({'rating': existing_rating})
|
||||||
|
|
||||||
|
# Prediksi rating dengan KNN jika dataset cukup besar
|
||||||
|
if len(X) > 3:
|
||||||
|
knn = KNeighborsClassifier(n_neighbors=3)
|
||||||
|
knn.fit(X, y)
|
||||||
|
predicted_rating = knn.predict([features])[0]
|
||||||
|
else:
|
||||||
|
total_score = sum(features)
|
||||||
|
if total_score <= 5:
|
||||||
|
predicted_rating = "SU"
|
||||||
|
elif total_score <= 10:
|
||||||
|
predicted_rating = "3+"
|
||||||
|
elif total_score <= 15:
|
||||||
|
predicted_rating = "7+"
|
||||||
|
elif total_score <= 20:
|
||||||
|
predicted_rating = "13+"
|
||||||
|
else:
|
||||||
|
predicted_rating = "18+"
|
||||||
|
|
||||||
|
print("✔ Prediksi Rating:", predicted_rating)
|
||||||
|
|
||||||
|
add_to_dataset(game_name, features, predicted_rating)
|
||||||
|
return jsonify({'rating': predicted_rating})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print("❌ ERROR di backend:", str(e))
|
||||||
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
app.run(debug=True)
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,35 @@
|
||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"main": "script.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"test": "react-scripts test",
|
||||||
|
"eject": "react-scripts eject"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"description": "",
|
||||||
|
"dependencies": {
|
||||||
|
"react-dom": "^19.0.0",
|
||||||
|
"react-scripts": "^5.0.1"
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"postcss": "^8.5.2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Rating Umur Game</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,38 @@
|
||||||
|
document.getElementById("game-form").addEventListener("submit", async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const formData = new FormData(e.target);
|
||||||
|
const name = formData.get("name");
|
||||||
|
const features = [
|
||||||
|
parseInt(formData.get("kekerasan")),
|
||||||
|
parseInt(formData.get("bahasa_kasar")),
|
||||||
|
parseInt(formData.get("tema_seksual")),
|
||||||
|
parseInt(formData.get("darah_gore")),
|
||||||
|
parseInt(formData.get("elemen_perjudian")),
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Kirim data ke backend
|
||||||
|
const response = await fetch("http://127.0.0.1:5000/predict", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ name, features }),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.status === "exists") {
|
||||||
|
document.getElementById("result").textContent =
|
||||||
|
`Game "${name}" sudah ada. Rating: ${result.predicted_rating}`;
|
||||||
|
} else {
|
||||||
|
document.getElementById("result").textContent =
|
||||||
|
`Game "${name}" berhasil diprediksi. Rating: ${result.predicted_rating}`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
alert("Gagal memproses data.");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,230 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import "./index.css";
|
||||||
|
import PopUp from "./components/PopUp";
|
||||||
|
|
||||||
|
const questions = [
|
||||||
|
"Seberapa banyak game ini mengandung kekerasan?",
|
||||||
|
"Seberapa sering bahasa kasar digunakan dalam game ini?",
|
||||||
|
"Seberapa banyak game ini mengandung konten dewasa?",
|
||||||
|
"Seberapa sering game ini menampilkan penggunaan obat-obatan atau alkohol?",
|
||||||
|
"Apakah game ini memiliki unsur simulasi perjudian?",
|
||||||
|
];
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
"Tidak Mengandung",
|
||||||
|
"Sangat Sedikit Mengandung",
|
||||||
|
"Agak Mengandung",
|
||||||
|
"Cukup Mengandung",
|
||||||
|
"Banyak Mengandung",
|
||||||
|
"Sangat Banyak Mengandung"
|
||||||
|
];
|
||||||
|
|
||||||
|
const ratingDescriptions = {
|
||||||
|
"SU": "Semua Umur - Dapat dimainkan oleh semua orang tanpa batasan konten.",
|
||||||
|
"3+": "Usia 3 Tahun Keatas - Tidak ada konten dewasa, perjudian, atau interaksi online.",
|
||||||
|
"7+": "Usia 7 Tahun Keatas - Ada unsur ringan seperti kekerasan kartun atau bahasa ringan.",
|
||||||
|
"13+": "Usia 13 Tahun Keatas - Ada kekerasan moderat, simulasi perjudian, atau tema horor.",
|
||||||
|
"18+": "Usia 18 Tahun Keatas - Mengandung konten dewasa, kekerasan realistis, atau humor kasar."
|
||||||
|
};
|
||||||
|
|
||||||
|
const App = () => {
|
||||||
|
const [gameName, setGameName] = useState("");
|
||||||
|
const [step, setStep] = useState(0);
|
||||||
|
const [answers, setAnswers] = useState([]);
|
||||||
|
const [rating, setRating] = useState(null);
|
||||||
|
const [isPopUpOpen, setIsPopUpOpen] = useState(false);
|
||||||
|
const [isConfirmPopupOpen, setIsConfirmPopupOpen] = useState(false);
|
||||||
|
const [selectedOption, setSelectedOption] = useState("");
|
||||||
|
|
||||||
|
const checkGame = async () => {
|
||||||
|
const response = await fetch("http://127.0.0.1:5000/check_game", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ game_name: gameName }),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.exists) {
|
||||||
|
setRating(data.rating);
|
||||||
|
} else {
|
||||||
|
setStep(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOptionClick = (option) => {
|
||||||
|
setSelectedOption(option);
|
||||||
|
setIsPopUpOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmSelection = () => {
|
||||||
|
setAnswers([...answers, selectedOption]);
|
||||||
|
setStep(step + 1);
|
||||||
|
setIsPopUpOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const submitAnswers = async () => {
|
||||||
|
setIsConfirmPopupOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmSubmit = async () => {
|
||||||
|
setIsConfirmPopupOpen(false);
|
||||||
|
const response = await fetch("http://localhost:5000/submit_answers", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({ game_name: gameName, answers }),
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
setRating(data.rating);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center min-h-screen bg-gray-100 relative">
|
||||||
|
<div className="w-full max-w-lg bg-white p-6 rounded-lg shadow-md text-center relative">
|
||||||
|
<h1 className="text-2xl font-bold">Rating Umur Game</h1>
|
||||||
|
|
||||||
|
{/* Tombol kembali hanya muncul saat berada di pertanyaan */}
|
||||||
|
{step > 0 && step <= questions.length && (
|
||||||
|
<div className="absolute top-4 right-4 flex space-x-2">
|
||||||
|
{step > 1 && (
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setAnswers(answers.slice(0, -1));
|
||||||
|
setStep(step - 1);
|
||||||
|
}}
|
||||||
|
className="w-6 h-6 bg-yellow-500 rounded-full hover:bg-yellow-700"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Tombol merah: Kembali ke input nama game */}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setStep(0);
|
||||||
|
setAnswers([]);
|
||||||
|
setGameName("");
|
||||||
|
}}
|
||||||
|
className="w-6 h-6 bg-red-500 rounded-full hover:bg-red-700"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
{!rating && step === 0 && (
|
||||||
|
<>
|
||||||
|
<p className="mt-4">Masukkan nama game:</p>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={gameName}
|
||||||
|
onChange={(e) => setGameName(e.target.value)}
|
||||||
|
className="border border-gray-600 bg-gray-100 p-2 w-full mt-2 focus:bg-gray-200 focus:border-black rounded mt-6"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
onClick={checkGame}
|
||||||
|
className="bg-blue-500 text-white px-4 py-2 rounded mt-4"
|
||||||
|
>
|
||||||
|
Cek
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!rating && step > 0 && step <= questions.length && (
|
||||||
|
<>
|
||||||
|
<p className="mt-4">{questions[step - 1]}</p>
|
||||||
|
{options.map((option) => (
|
||||||
|
<button
|
||||||
|
key={option}
|
||||||
|
onClick={() => handleOptionClick(option)}
|
||||||
|
className="block bg-gray-300 text-black px-4 py-2 rounded mt-2 hover:bg-gray-400 w-full"
|
||||||
|
>
|
||||||
|
{option}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!rating && step > questions.length && (
|
||||||
|
<>
|
||||||
|
{/* Menampilkan tabel hasil jawaban */}
|
||||||
|
<div className="mt-4">
|
||||||
|
<h2 className="text-lg font-semibold">Hasil Jawaban Anda:</h2>
|
||||||
|
<table className="table-auto w-full border mt-4">
|
||||||
|
<thead>
|
||||||
|
<tr className="bg-gray-200">
|
||||||
|
<th className="border px-4 py-2">Pertanyaan</th>
|
||||||
|
<th className="border px-4 py-2">Jawaban</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{questions.map((question, index) => (
|
||||||
|
<tr key={index}>
|
||||||
|
<td className="border px-4 py-2">{question}</td>
|
||||||
|
<td className="border px-4 py-2">{answers[index]}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Tombol kembali ke input game */}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setStep(0);
|
||||||
|
setAnswers([]);
|
||||||
|
setGameName("");
|
||||||
|
}}
|
||||||
|
className="bg-red-500 text-white px-4 py-2 rounded mt-4 w-full"
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{/* Tombol untuk konfirmasi hasil */}
|
||||||
|
<button
|
||||||
|
onClick={submitAnswers}
|
||||||
|
className="bg-green-500 text-white px-4 py-2 rounded mt-4 w-full"
|
||||||
|
>
|
||||||
|
Cek Hasil
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{rating && (
|
||||||
|
<>
|
||||||
|
<h2 className="text-xl font-semibold mt-4">
|
||||||
|
Game <span className="font-bold text-blue-500">{gameName}</span> memiliki rating
|
||||||
|
<span className="font-bold text-red-500"> {rating}</span>
|
||||||
|
</h2>
|
||||||
|
<p className="mt-2"><strong>Penjelasan:</strong> {ratingDescriptions[rating]}</p>
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setStep(0);
|
||||||
|
setAnswers([]);
|
||||||
|
setRating(null);
|
||||||
|
setGameName("");
|
||||||
|
}}
|
||||||
|
className="bg-red-500 text-white px-4 py-2 rounded mt-4 w-full"
|
||||||
|
>
|
||||||
|
Coba Lagi
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Pop-up konfirmasi pilihan jawaban */}
|
||||||
|
<PopUp
|
||||||
|
isOpen={isPopUpOpen}
|
||||||
|
onClose={() => setIsPopUpOpen(false)}
|
||||||
|
onConfirm={confirmSelection}
|
||||||
|
selectedOption={selectedOption}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Pop-up konfirmasi cek hasil */}
|
||||||
|
<PopUp
|
||||||
|
isOpen={isConfirmPopupOpen}
|
||||||
|
onClose={() => setIsConfirmPopupOpen(false)}
|
||||||
|
onConfirm={confirmSubmit}
|
||||||
|
selectedOption="Apakah Anda yakin ingin melihat hasil rating?"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default App;
|
|
@ -0,0 +1,30 @@
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
|
||||||
|
const PopUp = ({ isOpen, onClose, onConfirm, selectedOption }) => {
|
||||||
|
if (!isOpen) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
|
||||||
|
<div className="bg-white p-6 rounded-lg shadow-lg text-center">
|
||||||
|
<p>Apakah Anda yakin memilih: <strong>{selectedOption}</strong>?</p>
|
||||||
|
<div className="mt-4 flex justify-center gap-4">
|
||||||
|
<button
|
||||||
|
onClick={onClose}
|
||||||
|
className="bg-gray-500 text-white px-4 py-2 rounded hover:bg-gray-700"
|
||||||
|
>
|
||||||
|
Kembali
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={onConfirm}
|
||||||
|
className="bg-blue-500 text-white px-4 py-2 rounded hover:bg-blue-700"
|
||||||
|
>
|
||||||
|
Lanjut
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PopUp;
|
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
|
@ -0,0 +1,15 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
import reportWebVitals from './reportWebVitals';
|
||||||
|
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
|
|
||||||
|
reportWebVitals();
|
|
@ -0,0 +1,6 @@
|
||||||
|
const reportWebVitals = (metric) => {
|
||||||
|
console.log(metric);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default reportWebVitals;
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ["./src/**/*.{js,jsx,ts,tsx,css}"],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
Flask
|
||||||
|
Flask-Cors
|
||||||
|
pandas
|
||||||
|
scikit-learn
|
||||||
|
openpyxl
|
Loading…
Reference in New Issue