This commit is contained in:
zadit biasa aja
2025-07-01 02:00:55 +00:00
parent 1592525dd2
commit a967475cc3
5 changed files with 116 additions and 126 deletions

9
package-lock.json generated
View File

@@ -14101,15 +14101,6 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
<<<<<<< HEAD
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"optional": true
},
=======
>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
"node_modules/set-cookie-parser": { "node_modules/set-cookie-parser": {
"version": "2.7.1", "version": "2.7.1",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",

View File

@@ -1,16 +1,10 @@
<<<<<<< HEAD
import { Routes, Route } from "react-router-dom";
=======
import "./App.css"; import "./App.css";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import Dashboard from "./Dashboard"; import Dashboard from "./Dashboard";
import Login from "./Login"; import Login from "./Login";
>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
import CameraKtp from "./KTPScanner"; import CameraKtp from "./KTPScanner";
import Dashboard from "./Dashboard";
import "./App.css"; import "./App.css";
@@ -23,23 +17,11 @@ const ProtectedRoute = ({ element }) => {
function App() { function App() {
return ( return (
<div className="App"> <div className="App">
<<<<<<< HEAD
<Routes>
<Route path="/" element={<Dashboard />} />
<Route path="/scan" element={<CameraKtp />} />
</Routes>
=======
<BrowserRouter>
<Routes> <Routes>
<Route path="/login" element={<Login />} /> <Route path="/login" element={<Login />} />
<Route path="/" element={<CameraKtp />} /> <Route path="/scan" element={<CameraKtp />} />
<Route <Route path="/" element={<ProtectedRoute element={<Dashboard />} />} />
path="/dashboard"
element={<ProtectedRoute element={<Dashboard />} />}
/>
</Routes> </Routes>
</BrowserRouter>
>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
</div> </div>
); );
} }

View File

@@ -1,24 +1,3 @@
<<<<<<< HEAD
import React from "react";
import styles from "./Dashboard.module.css";
import Header from "./components/Header";
import Sidebar from "./components/Sidebar";
import RoleCard from "./components/RoleCard";
import Chart from "./components/Chart";
const Dashboard = () => {
return (
<div className={styles.dashboard}>
<Sidebar />
<div className={styles.mainContent}>
<Header />
<div className={styles.cards}>
<RoleCard title="Officer" value="$4644" code="#df3422f" />
<RoleCard title="Medtion" value="$8421" />
</div>
<Chart />
</div>
=======
import React, { useState, useRef, useEffect } from "react"; import React, { useState, useRef, useEffect } from "react";
import styles from "./Dashboard.module.css"; import styles from "./Dashboard.module.css";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
@@ -88,7 +67,6 @@ const Dashboard = () => {
if (data[0].payload.officerPerformance) { if (data[0].payload.officerPerformance) {
setOfficerPerformanceData(data[0].payload.officerPerformance); setOfficerPerformanceData(data[0].payload.officerPerformance);
} }
} catch (error) { } catch (error) {
console.error("Token tidak valid:", error.message); console.error("Token tidak valid:", error.message);
localStorage.removeItem("token"); localStorage.removeItem("token");
@@ -102,7 +80,6 @@ const Dashboard = () => {
const handleLogout = () => { const handleLogout = () => {
localStorage.removeItem("token"); localStorage.removeItem("token");
localStorage.removeItem("user"); localStorage.removeItem("user");
localStorage.removeItem("role");
window.location.reload(); window.location.reload();
}; };
@@ -165,7 +142,9 @@ const Dashboard = () => {
</div> </div>
<div className={styles.dropdownContainer} ref={menuRef}> <div className={styles.dropdownContainer} ref={menuRef}>
<span className={styles.userDisplayName}>{user.username || "Guest"}</span> <span className={styles.userDisplayName}>
{user.username || "Guest"}
</span>
<button <button
onClick={() => setIsMenuOpen(!isMenuOpen)} onClick={() => setIsMenuOpen(!isMenuOpen)}
className={styles.dropdownToggle} className={styles.dropdownToggle}
@@ -177,13 +156,28 @@ const Dashboard = () => {
{isMenuOpen && ( {isMenuOpen && (
<div className={styles.dropdownMenu}> <div className={styles.dropdownMenu}>
<button <button
onClick={() => { navigate("/profile"); setIsMenuOpen(false); }} /* Tutup menu setelah klik */ onClick={() => {
navigate("/profile");
setIsMenuOpen(false);
}} /* Tutup menu setelah klik */
className={styles.dropdownItem} className={styles.dropdownItem}
> >
Profile Profile
</button> </button>
<button <button
onClick={() => { handleLogout(); setIsMenuOpen(false); }} /* Tutup menu setelah klik */ onClick={() => {
navigate("/scan");
setIsMenuOpen(false);
}} /* Tutup menu setelah klik */
className={styles.dropdownItem}
>
Scan
</button>
<button
onClick={() => {
handleLogout();
setIsMenuOpen(false);
}} /* Tutup menu setelah klik */
className={styles.dropdownItem} className={styles.dropdownItem}
> >
Logout Logout
@@ -212,7 +206,7 @@ const Dashboard = () => {
{/* Grid for Form (Admin) and Chart (Admin & Officer) */} {/* Grid for Form (Admin) and Chart (Admin & Officer) */}
<div className={styles.dashboardGrid}> <div className={styles.dashboardGrid}>
{user.role === "admin" && ( /* Render form hanya jika admin */ {user.role === "admin" /* Render form hanya jika admin */ && (
<div className={styles.formSection}> <div className={styles.formSection}>
<h2>Tambah Officer Baru</h2> <h2>Tambah Officer Baru</h2>
<form onSubmit={handleAddOfficer} className={styles.form}> <form onSubmit={handleAddOfficer} className={styles.form}>
@@ -250,7 +244,9 @@ const Dashboard = () => {
</button> </button>
</form> </form>
{successMessage && <p className={styles.success}>{successMessage}</p>} {successMessage && (
<p className={styles.success}>{successMessage}</p>
)}
{errorMessage && <p className={styles.error}>{errorMessage}</p>} {errorMessage && <p className={styles.error}>{errorMessage}</p>}
</div> </div>
)} )}
@@ -274,24 +270,21 @@ const Dashboard = () => {
</ResponsiveContainer> </ResponsiveContainer>
*/ */
<div className={styles.chartPlaceholder}> <div className={styles.chartPlaceholder}>
Grafik performa petugas akan ditampilkan di sini. Grafik performa petugas akan ditampilkan di sini. (Integrasikan
(Integrasikan library grafik seperti Recharts/Chart.js) library grafik seperti Recharts/Chart.js)
</div> </div>
) : ( ) : (
<p className={styles.warning}>Tidak ada data performa petugas untuk ditampilkan.</p> <p className={styles.warning}>
Tidak ada data performa petugas untuk ditampilkan.
</p>
)} )}
</div> </div>
</div> </div>
</div> </div>
<div className={styles.footer}>&copy; 2025 Kediri Technopark</div> <div className={styles.footer}>&copy; 2025 Kediri Technopark</div>
>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
</div> </div>
); );
}; };
<<<<<<< HEAD
export default Dashboard; export default Dashboard;
=======
export default Dashboard;
>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04

View File

@@ -1,42 +1,14 @@
<<<<<<< HEAD
.dashboard {
display: flex;
width: 100%;
max-width: 1200px;
background: white;
border-radius: 20px;
overflow: hidden;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
}
.mainContent {
flex: 1;
padding: 1rem;
}
.cards {
display: flex;
flex-wrap: wrap;
gap: 1rem;
}
@media (max-width: 768px) {
.dashboard {
flex-direction: column;
}
}
=======
/* Variabel Warna */ /* Variabel Warna */
:root { :root {
--primary-red: #E53935; --primary-red: #e53935;
--secondary-red: #EF5350; --secondary-red: #ef5350;
--dark-red: #C62828; --dark-red: #c62828;
--light-gray: #EEEEEE; --light-gray: #eeeeee;
--dark-gray: #424242; --dark-gray: #424242;
--white: #FFFFFF; --white: #ffffff;
--success-green: #4CAF50; --success-green: #4caf50;
--warning-yellow: #FFC107; --warning-yellow: #ffc107;
--text-color-light: #F5F5F5; /* Teks terang di latar belakang gelap */ --text-color-light: #f5f5f5; /* Teks terang di latar belakang gelap */
--text-color-dark: #212121; /* Teks gelap di latar belakang terang */ --text-color-dark: #212121; /* Teks gelap di latar belakang terang */
} }
@@ -47,7 +19,7 @@
body { body {
margin: 0; margin: 0;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; /* Font lebih modern */ font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif; /* Font lebih modern */
line-height: 1.6; line-height: 1.6;
color: var(--text-color-dark); color: var(--text-color-dark);
} }
@@ -481,8 +453,8 @@ body {
/* Jika hanya officer, pastikan chart mengambil seluruh lebar kolom */ /* Jika hanya officer, pastikan chart mengambil seluruh lebar kolom */
/* Ini sebenarnya sudah ditangani oleh grid secara default, tapi bisa diperjelas */ /* Ini sebenarnya sudah ditangani oleh grid secara default, tapi bisa diperjelas */
@media (min-width: 768px) { @media (min-width: 768px) {
.dashboardGrid > *:only-child { /* Jika hanya ada satu anak elemen di dalam dashboardGrid */ .dashboardGrid > *:only-child {
/* Jika hanya ada satu anak elemen di dalam dashboardGrid */
grid-column: 1 / -1; /* Ambil seluruh lebar dari kolom pertama hingga terakhir */ grid-column: 1 / -1; /* Ambil seluruh lebar dari kolom pertama hingga terakhir */
} }
} }
>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04

View File

@@ -7,11 +7,14 @@ const formatDate = (iso) => {
return date.toISOString().split("T")[0]; // YYYY-MM-DD return date.toISOString().split("T")[0]; // YYYY-MM-DD
}; };
const isDateField = (value) =>
value && typeof value === "object" && value.type === "dateTime";
const PaginatedFormEditable = ({ data }) => { const PaginatedFormEditable = ({ data }) => {
const [formData, setFormData] = useState(() => { const [formData, setFormData] = useState(() => {
const flat = {}; const flat = {};
Object.entries(data[0]).forEach(([key, value]) => { Object.entries(data[0]).forEach(([key, value]) => {
if (value && typeof value === "object" && "value" in value) { if (isDateField(value)) {
flat[key] = formatDate(value.value); flat[key] = formatDate(value.value);
} else { } else {
flat[key] = value; flat[key] = value;
@@ -20,7 +23,9 @@ const PaginatedFormEditable = ({ data }) => {
return flat; return flat;
}); });
const fields = Object.keys(formData); const fields = Object.keys(formData).filter(
(key) => key.toLowerCase() !== "total"
);
const fieldsPerPage = 3; const fieldsPerPage = 3;
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
const totalPages = Math.ceil(fields.length / fieldsPerPage); const totalPages = Math.ceil(fields.length / fieldsPerPage);
@@ -44,10 +49,56 @@ const PaginatedFormEditable = ({ data }) => {
fontWeight: "bold", fontWeight: "bold",
textTransform: "capitalize", textTransform: "capitalize",
textAlign: "left", textAlign: "left",
display: "block",
marginBottom: 5,
}} }}
> >
{key} {key}
</label> </label>
{/* Tanggal */}
{["Tanggal Lahir", "Berlaku Hingga", "Tanggal Dibuat"].includes(
key
) ? (
formData[key] ? (
<input
type="date"
value={formData[key]}
onChange={(e) => handleChange(key, e.target.value)}
style={{
width: "100%",
padding: 8,
borderRadius: 5,
border: "1px solid #ddd",
}}
/>
) : (
<div
style={{
padding: 8,
border: "1px solid #ddd",
borderRadius: 5,
backgroundColor: "#f9f9f9",
}}
>
Seumur Hidup{" "}
<button
onClick={() =>
handleChange(key, new Date().toISOString().split("T")[0])
}
style={{
marginLeft: 10,
padding: "4px 8px",
fontSize: 12,
cursor: "pointer",
}}
>
Isi Tanggal
</button>
</div>
)
) : (
// Input biasa
<input <input
type="text" type="text"
value={formData[key] || ""} value={formData[key] || ""}
@@ -59,6 +110,7 @@ const PaginatedFormEditable = ({ data }) => {
border: "1px solid #ddd", border: "1px solid #ddd",
}} }}
/> />
)}
</div> </div>
))} ))}