ok
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -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",
|
||||||
|
|||||||
22
src/App.js
22
src/App.js
@@ -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>
|
<Routes>
|
||||||
<Route path="/" element={<Dashboard />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route path="/scan" element={<CameraKtp />} />
|
<Route path="/scan" element={<CameraKtp />} />
|
||||||
|
<Route path="/" element={<ProtectedRoute element={<Dashboard />} />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
=======
|
|
||||||
<BrowserRouter>
|
|
||||||
<Routes>
|
|
||||||
<Route path="/login" element={<Login />} />
|
|
||||||
<Route path="/" element={<CameraKtp />} />
|
|
||||||
<Route
|
|
||||||
path="/dashboard"
|
|
||||||
element={<ProtectedRoute element={<Dashboard />} />}
|
|
||||||
/>
|
|
||||||
</Routes>
|
|
||||||
</BrowserRouter>
|
|
||||||
>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}>© 2025 Kediri Technopark</div>
|
<div className={styles.footer}>© 2025 Kediri Technopark</div>
|
||||||
>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
export default Dashboard;
|
export default Dashboard;
|
||||||
=======
|
|
||||||
export default Dashboard;
|
|
||||||
>>>>>>> b9b4e4c859bd8face05c8d89d7f0c914d9e84a04
|
|
||||||
|
|||||||
@@ -1,43 +1,15 @@
|
|||||||
<<<<<<< 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 */
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Base Styles & Reset */
|
/* Base Styles & Reset */
|
||||||
@@ -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);
|
||||||
}
|
}
|
||||||
@@ -100,10 +72,10 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.userDisplayName {
|
.userDisplayName {
|
||||||
color: var(--text-color-light);
|
color: var(--text-color-light);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
white-space: nowrap; /* Pastikan nama pengguna tidak patah baris */
|
white-space: nowrap; /* Pastikan nama pengguna tidak patah baris */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdownToggle {
|
.dropdownToggle {
|
||||||
@@ -170,7 +142,7 @@ body {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background-color: var(--white);
|
background-color: var(--white);
|
||||||
border-radius: 0.6rem;
|
border-radius: 0.6rem;
|
||||||
box-shadow: 0 2px 5px rgba(0,0,0,0.05);
|
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
.summaryCard {
|
.summaryCard {
|
||||||
@@ -347,7 +319,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.userDisplayName {
|
.userDisplayName {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdownToggle {
|
.dropdownToggle {
|
||||||
@@ -356,11 +328,11 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dropdownMenu {
|
.dropdownMenu {
|
||||||
min-width: 10rem;
|
min-width: 10rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dropdownItem {
|
.dropdownItem {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mainContent {
|
.mainContent {
|
||||||
@@ -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
|
|
||||||
|
|||||||
@@ -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,21 +49,68 @@ const PaginatedFormEditable = ({ data }) => {
|
|||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
textTransform: "capitalize",
|
textTransform: "capitalize",
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
|
display: "block",
|
||||||
|
marginBottom: 5,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{key}
|
{key}
|
||||||
</label>
|
</label>
|
||||||
<input
|
|
||||||
type="text"
|
{/* Tanggal */}
|
||||||
value={formData[key] || ""}
|
{["Tanggal Lahir", "Berlaku Hingga", "Tanggal Dibuat"].includes(
|
||||||
onChange={(e) => handleChange(key, e.target.value)}
|
key
|
||||||
style={{
|
) ? (
|
||||||
width: "100%",
|
formData[key] ? (
|
||||||
padding: 8,
|
<input
|
||||||
borderRadius: 5,
|
type="date"
|
||||||
border: "1px solid #ddd",
|
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
|
||||||
|
type="text"
|
||||||
|
value={formData[key] || ""}
|
||||||
|
onChange={(e) => handleChange(key, e.target.value)}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: 8,
|
||||||
|
borderRadius: 5,
|
||||||
|
border: "1px solid #ddd",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user