diff --git a/.gitignore b/.gitignore index fd3dbb571..a84107af3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ yarn-debug.log* yarn-error.log* # local env files +.env .env*.local # vercel diff --git a/app/api/getMeasurement/route.js b/app/api/getMeasurement/route.js new file mode 100644 index 000000000..a3a4863d3 --- /dev/null +++ b/app/api/getMeasurement/route.js @@ -0,0 +1,23 @@ +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +export async function GET(req) { + try { + const measurements = await prisma.measurement.findMany({ + orderBy: { + date: 'desc' // Urutkan berdasarkan tanggal terbaru ke terlama + } + }); + return new Response(JSON.stringify(measurements), { + status: 200, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + return new Response(JSON.stringify({ error: "Failed to fetch data" }), { + status: 500, + }); + } +} diff --git a/app/api/getUser/route.js b/app/api/getUser/route.js new file mode 100644 index 000000000..e02d9f3bc --- /dev/null +++ b/app/api/getUser/route.js @@ -0,0 +1,21 @@ +import { PrismaClient } from '@prisma/client'; +import { NextResponse } from 'next/server'; + +const prisma = new PrismaClient(); + +export async function GET() { + try { + // Mengambil semua pengguna dari tabel users + const users = await prisma.user.findMany(); + + // Mengembalikan respon JSON dengan data pengguna + return NextResponse.json(users); + } catch (error) { + console.error(error); + // Mengembalikan respon JSON dengan status error + return NextResponse.json({ error: error.message }, { status: 500 }); + } finally { + // Menutup koneksi Prisma setelah selesai + await prisma.$disconnect(); + } +} diff --git a/app/api/postMeasurement/route.js b/app/api/postMeasurement/route.js new file mode 100644 index 000000000..6c133fcc1 --- /dev/null +++ b/app/api/postMeasurement/route.js @@ -0,0 +1,32 @@ +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +export async function POST(req) { + const { waterLevel, waterTemp, airTemp, airHumidity, airPressure, windSpeed, curahHujan, date } = await req.json(); + + try { + const newMeasurement = await prisma.measurement.create({ + data: { + waterLevel, + windSpeed, + airTemp, + airHumidity, + airPressure, + waterTemp, + curahHujan, + date: date ? new Date(date) : new Date(), + }, + }); + return new Response(JSON.stringify(newMeasurement), { + status: 201, + headers: { + 'Content-Type': 'application/json', + }, + }); + } catch (error) { + return new Response(JSON.stringify({ error: "Failed to save data" }), { + status: 500, + }); + } + } \ No newline at end of file diff --git a/app/api/postUser/route.js b/app/api/postUser/route.js new file mode 100644 index 000000000..faa92d838 --- /dev/null +++ b/app/api/postUser/route.js @@ -0,0 +1,36 @@ +import { PrismaClient } from '@prisma/client'; + +const prisma = new PrismaClient(); + +export async function POST(req) { + const { username, email, password, role, status } = await req.json(); + + try { + // Menambahkan user baru ke tabel users + const newUser = await prisma.user.create({ + data: { + username, + email, + password, // Pastikan untuk meng-hash password sebelum menyimpannya + role, + status, + }, + }); + + // Mengembalikan respon sukses dengan data user yang baru ditambahkan + return new Response(JSON.stringify({ message: 'User added successfully', user: newUser }), { + status: 201, + headers: { 'Content-Type': 'application/json' }, + }); + } catch (error) { + console.error(error); + // Mengembalikan respon error jika terjadi kesalahan + return new Response(JSON.stringify({ error: 'Failed to add user' }), { + status: 500, + headers: { 'Content-Type': 'application/json' }, + }); + } finally { + // Menutup koneksi Prisma setelah operasi selesai + await prisma.$disconnect(); + } +} diff --git a/app/api/update/[userId].js b/app/api/update/[userId].js new file mode 100644 index 000000000..01c68c362 --- /dev/null +++ b/app/api/update/[userId].js @@ -0,0 +1,38 @@ +// pages/api/update/[userId].js +import pool from '@/utils/db'; + +export default async function handler(req, res) { + const { userId } = req.query; + + if (req.method === 'PUT') { + // Ambil data dari request body + const { username, email, password, role, status } = req.body; + + try { + // Buat koneksi ke database + const connection = await pool.getConnection(); + + // Query untuk memperbarui data pengguna (tanpa phone dan address) + const [result] = await connection.execute( + `UPDATE users + SET username = ?, email = ?, password = ?, role = ?, status = ? + WHERE id = ?`, + [username, email, password, role, status, userId] + ); + + // Lepas koneksi setelah selesai + connection.release(); + + if (result.affectedRows === 0) { + return res.status(404).json({ message: 'User not found' }); + } + + res.status(200).json({ message: 'User updated successfully' }); + } catch (error) { + console.error('Error updating user:', error); + res.status(500).json({ message: 'Error updating user', error }); + } + } else { + res.status(405).json({ message: 'Method not allowed' }); + } +} diff --git a/app/dashboard/charts/page.jsx b/app/dashboard/charts/page.jsx new file mode 100644 index 000000000..1c209c461 --- /dev/null +++ b/app/dashboard/charts/page.jsx @@ -0,0 +1,57 @@ +"use client"; +import { useEffect, useState } from "react"; +import styles from "@/app/ui/dashboard/charts/charts.module.css"; +import { LineChart, Line, XAxis, YAxis, Tooltip, Legend, ResponsiveContainer } from "recharts"; + +// Fungsi untuk fetch data dari API +const fetchData = async () => { + const response = await fetch("/api/getMeasurement"); + const result = await response.json(); + return result; +}; + +const ChartsPage = () => { + const [chartData, setChartData] = useState([]); + + useEffect(() => { + fetchData().then((data) => { + // Urutkan data berdasarkan 'date' atau ID dan ambil 10 data terbaru + const sortedData = data + .sort((a, b) => new Date(b.date) - new Date(a.date)) // Mengurutkan berdasarkan tanggal terbaru + .slice(0, 10); // Ambil hanya 10 data terbaru + setChartData(sortedData); + }); + }, []); + + // Fungsi untuk menampilkan tiap chart dengan data dan judul + const renderChart = (title, dataKey) => ( +
+

{title}

+ + + + + + + + + +
+ ); + + return ( +
+
+ {renderChart("Water Level", "waterLevel")} + {renderChart("Water Temperature", "waterTemp")} + {renderChart("Air Temperature", "airTemp")} + {renderChart("Air Humidity", "airHumidity")} + {renderChart("Air Pressure", "airPressure")} + {renderChart("Wind Speed", "windSpeed")} + {renderChart("Rainfall", "curahHujan")} +
+
+ ); +}; + +export default ChartsPage; diff --git a/app/dashboard/layout.jsx b/app/dashboard/layout.jsx new file mode 100644 index 000000000..10b33326d --- /dev/null +++ b/app/dashboard/layout.jsx @@ -0,0 +1,19 @@ +import Navbar from "../ui/dashboard/navbar/navbar" +import Sidebar from "../ui/dashboard/sidebar/sidebar" +import styles from "../ui/dashboard/dashboard.module.css" + +const Layout= ({children}) => { + return ( +
+
+ +
+
+ + {children} +
+
+ ) +} + +export default Layout \ No newline at end of file diff --git a/app/dashboard/map/page.jsx b/app/dashboard/map/page.jsx new file mode 100644 index 000000000..f9ad86b10 --- /dev/null +++ b/app/dashboard/map/page.jsx @@ -0,0 +1,55 @@ +"use client"; // Menandakan sebagai Client Component + +import React, { useEffect, useState } from 'react'; +import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'; +import 'leaflet/dist/leaflet.css'; +import L from 'leaflet'; + +// Configure Leaflet's default icon +delete L.Icon.Default.prototype._getIconUrl; +L.Icon.Default.mergeOptions({ + iconRetinaUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon-2x.png', + iconUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-icon.png', + shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png', +}); + +const MapPage = () => { + const [isClient, setIsClient] = useState(false); + + useEffect(() => { + setIsClient(true); + }, []); + + const awsData = [ + { awsNumber: 1, location: [-6.971200, 107.631100], suhu: '28°C', kelembapan: '70%' }, + { awsNumber: 2, location: [-6.972000, 107.632000], suhu: '27°C', kelembapan: '65%' }, + { awsNumber: 3, location: [-6.973500, 107.629800], suhu: '27°C', kelembapan: '65%' }, + // Tambahkan data AWS lainnya di sini + ]; + + if (!isClient) { + return null; // Render nothing on the server + } + + return ( +
+ + + {awsData.map(({ awsNumber, location, suhu, kelembapan }) => ( + + + AWS Station {awsNumber}
+ Suhu: {suhu}
+ Kelembapan: {kelembapan} +
+
+ ))} +
+
+ ); +}; + +export default MapPage; diff --git a/app/dashboard/page.jsx b/app/dashboard/page.jsx new file mode 100644 index 000000000..c9b4383b2 --- /dev/null +++ b/app/dashboard/page.jsx @@ -0,0 +1,22 @@ +import styles from '../ui/dashboard/dashboard.module.css' +import Card from '../ui/dashboard/card/card' +import Table from '../ui/dashboard/table/table' +import Card2 from '../ui/dashboard/card/card2' +import Card3 from '../ui/dashboard/card/card3' + +const Dashboard = () => { + return ( +
+
+
+ + + +
+ + + + ) +} + +export default Dashboard \ No newline at end of file diff --git a/app/dashboard/setting/page.jsx b/app/dashboard/setting/page.jsx new file mode 100644 index 000000000..66db55b9a --- /dev/null +++ b/app/dashboard/setting/page.jsx @@ -0,0 +1,7 @@ +const SettingPage= () => { + return ( +
Ini pengaturan
+ ) +} + +export default SettingPage diff --git a/app/dashboard/users/[id]/page.jsx b/app/dashboard/users/[id]/page.jsx new file mode 100644 index 000000000..d804414be --- /dev/null +++ b/app/dashboard/users/[id]/page.jsx @@ -0,0 +1,100 @@ +"use client"; +import styles from '@/app/ui/dashboard/users/singleUser/singleUser.module.css'; +import { useState, useEffect } from 'react'; + +const SingleUserPage = ({ userId }) => { + const [userData, setUserData] = useState(null); + + // Mengambil data pengguna berdasarkan userId + useEffect(() => { + const fetchUserData = async () => { + const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/getUser/${userId}`); + const data = await res.json(); + setUserData(data); + }; + fetchUserData(); + }, [userId]); + + const handleUpdate = async (e) => { + e.preventDefault(); + // Mengirim data yang telah diperbarui ke server + try { + const response = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/update/${userId}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(userData), + }); + const result = await response.json(); + console.log('User updated', result); + } catch (error) { + console.error('Error updating user', error); + } + }; + + if (!userData) { + return
Loading...
; // Tampilkan loading jika data belum tersedia + } + + return ( +
+
+ {userData.username} +
+
+
+ + setUserData({ ...userData, username: e.target.value })} + /> + + + setUserData({ ...userData, email: e.target.value })} + /> + + + setUserData({ ...userData, password: e.target.value })} + /> + + + + + + + + + +
+
+ ); +}; + +export default SingleUserPage; diff --git a/app/dashboard/users/add/page.jsx b/app/dashboard/users/add/page.jsx new file mode 100644 index 000000000..995921632 --- /dev/null +++ b/app/dashboard/users/add/page.jsx @@ -0,0 +1,174 @@ +"use client"; +import { useState } from 'react'; +import { FaEye, FaEyeSlash } from 'react-icons/fa'; +import styles from "@/app/ui/dashboard/users/addUser/addUser.module.css"; + +const AddUserPage = () => { + const [showPassword, setShowPassword] = useState(false); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); + const [formData, setFormData] = useState({ + username: '', + email: '', + password: '', + confirm: '', + role: 0, // default role as user + status: 1, // default status as active + address: '' + }); + const [errors, setErrors] = useState({}); + + const togglePasswordVisibility = () => { + setShowPassword(!showPassword); + }; + + const toggleConfirmPasswordVisibility = () => { + setShowConfirmPassword(!showConfirmPassword); + }; + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData((prevFormData) => ({ + ...prevFormData, + [name]: name === 'role' || name === 'status' ? parseInt(value) : value // convert role and status to number + })); + + // Password confirmation validation + if (name === 'password' || name === 'confirm') { + if (formData.password !== formData.confirm) { + setErrors((prevErrors) => ({ + ...prevErrors, + confirm: 'Passwords do not match' + })); + } else { + setErrors((prevErrors) => ({ + ...prevErrors, + confirm: '' + })); + } + } + }; + + const validate = () => { + const newErrors = {}; + if (!formData.username) newErrors.username = 'Username is required'; + if (!formData.email) newErrors.email = 'Email is required'; + if (!formData.password) newErrors.password = 'Password is required'; + if (formData.password !== formData.confirm) newErrors.confirm = 'Passwords do not match'; + return newErrors; + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + const validationErrors = validate(); + if (Object.keys(validationErrors).length > 0) { + setErrors(validationErrors); + } else { + try { + const response = await fetch('/api/postUser', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username: formData.username, + email: formData.email, + password: formData.password, // Pastikan password disimpan sebagai hash di backend + role: formData.role, + status: formData.status, + address: formData.address, + }), + }); + const result = await response.json(); + console.log('Form submitted', result); + } catch (error) { + console.error('Error submitting form', error); + } + } + }; + + return ( +
+
+ + {errors.username &&
{errors.username}
} + + + {errors.email &&
{errors.email}
} + +
+ + + {showPassword ? : } + +
+ {errors.password &&
{errors.password}
} + +
+ + + {showConfirmPassword ? : } + +
+ {errors.confirm &&
{errors.confirm}
} + + + + + +