From 70ea9067c785542d509a56830762a749738a23e5 Mon Sep 17 00:00:00 2001
From: zadit biasa aja <75159257+everythingonblack@users.noreply.github.com>
Date: Sat, 5 Jul 2025 07:19:32 +0000
Subject: [PATCH] ok
---
src/Dashboard.js | 28 +-
src/Dashboard.module.css | 2 +
src/FileListComponent.js | 236 +++++++++++++++
src/FileListComponent.module.css | 483 +++++++++++++++++++++++++++++++
src/FormComponent.js | 11 +-
5 files changed, 737 insertions(+), 23 deletions(-)
create mode 100644 src/FileListComponent.js
create mode 100644 src/FileListComponent.module.css
diff --git a/src/Dashboard.js b/src/Dashboard.js
index 2243644..1f5a300 100644
--- a/src/Dashboard.js
+++ b/src/Dashboard.js
@@ -1,8 +1,9 @@
+// Dashboard.jsx
import React, { useState, useRef, useEffect } from "react";
import styles from "./Dashboard.module.css";
import { useNavigate } from "react-router-dom";
-// Pastikan Anda sudah menginstal Recharts: npm install recharts
-// import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'; // Contoh Recharts
+import FileListComponent from "./FileListComponent";
+// import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
const Dashboard = () => {
const navigate = useNavigate();
@@ -52,10 +53,6 @@ const Dashboard = () => {
setUser(data[0].payload);
- // Pastikan API Anda mengembalikan data ini, contoh:
- // data[0].payload.stats = { today: 120, month: 2500, overall: 15000 };
- // data[0].payload.officerPerformance = [{ name: "Budi", filesSent: 50 }, { name: "Ani", filesSent: 70 }];
-
if (data[0].payload.stats) {
setTotalFilesSentToday(data[0].payload.stats.today);
setTotalFilesSentMonth(data[0].payload.stats.month);
@@ -112,7 +109,6 @@ const Dashboard = () => {
setUsername("");
setPassword("");
setErrorMessage("");
- // Pertimbangkan untuk memuat ulang data performa jika penambahan officer baru mempengaruhi grafik
} catch (error) {
setErrorMessage(error.message || "Gagal menambahkan officer");
setSuccessMessage("");
@@ -251,24 +247,9 @@ const Dashboard = () => {
)}
- {/* Chart Section - Visible to both Admin and Officer */}
diff --git a/src/Dashboard.module.css b/src/Dashboard.module.css
index 0139a58..7db97ca 100644
--- a/src/Dashboard.module.css
+++ b/src/Dashboard.module.css
@@ -1,3 +1,5 @@
+/* Dashboard.module.css - Cleaned Version */
+
/* Modern Color Palette */
:root {
--primary-blue: #3b82f6;
diff --git a/src/FileListComponent.js b/src/FileListComponent.js
new file mode 100644
index 0000000..dfebe2b
--- /dev/null
+++ b/src/FileListComponent.js
@@ -0,0 +1,236 @@
+import React, { useState, useEffect } from "react";
+import styles from "./FileListComponent.module.css";
+
+const FileListComponent = () => {
+ const [files, setFiles] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [selectedFile, setSelectedFile] = useState(null);
+ const [successMessage, setSuccessMessage] = useState("");
+
+ useEffect(() => {
+ const fetchFiles = async () => {
+ try {
+ const response = await fetch(
+ "https://bot.kediritechnopark.com/webhook/api/files"
+ );
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+
+ const text = await response.text();
+ if (!text) {
+ throw new Error("Server membalas kosong.");
+ }
+
+ const data = JSON.parse(text);
+
+ if (!data.success || !Array.isArray(data.data)) {
+ throw new Error("Format respons tidak valid.");
+ }
+
+ setFiles(data.data);
+ setLoading(false);
+ } catch (error) {
+ console.error("Gagal mengambil data dari server:", error.message);
+ setLoading(false);
+ }
+ };
+
+ fetchFiles();
+ }, []);
+
+ const formatPhoneNumber = (phone) =>
+ phone?.replace(/(\d{4})(\d{4})(\d{4})/, "$1-$2-$3");
+
+ const handleRowClick = async (file) => {
+ try {
+ const response = await fetch(
+ `https://bot.kediritechnopark.com/webhook/8a68d17e-c987-468c-853a-1c7d8104b5ba/api/files/${file.nik}`
+ );
+
+ if (!response.ok) {
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+
+ const text = await response.text();
+ if (!text) {
+ throw new Error("Respons kosong dari server.");
+ }
+
+ const data = JSON.parse(text);
+
+ if (data.error) {
+ alert(data.error);
+ return;
+ }
+
+ setSelectedFile(data);
+ } catch (error) {
+ console.error("Gagal mengambil detail:", error.message || error);
+ alert("Gagal mengambil detail. Pastikan data tersedia.");
+ }
+ };
+
+ const closeModal = () => {
+ setSelectedFile(null);
+ };
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
📁 Daftar File
+ {files.length} file tersedia
+
+
+ {successMessage && (
+
+ ✅
+ {successMessage}
+
+ )}
+
+
+ {files.length === 0 ? (
+
+
Belum ada data
+
+ Tidak ada data KTP yang tersedia saat ini.
+
+
+ ) : (
+
+
+
+ | ID |
+ NIK |
+ Nama Lengkap |
+ No. HP |
+ Email |
+
+
+
+ {files.map((file, index) => (
+ handleRowClick(file)}
+ className={styles.tableRow}
+ >
+ | {index + 1} |
+ {file.nik} |
+ {file.nama_lengkap} |
+ {formatPhoneNumber(file.no_hp)} |
+ {file.email} |
+
+ ))}
+
+
+ )}
+
+
+ {/* Modal Detail */}
+ {selectedFile && (
+
+
e.stopPropagation()}
+ >
+
🪪 Detail Data KTP
+
+
+
+ | NIK |
+ {selectedFile.nik} |
+
+
+ | Nama Lengkap |
+ {selectedFile.nama_lengkap} |
+
+
+ | Tempat Lahir |
+ {selectedFile.tempat_lahir} |
+
+
+ | Tanggal Lahir |
+ {selectedFile.tanggal_lahir} |
+
+
+ | Jenis Kelamin |
+ {selectedFile.jenis_kelamin} |
+
+
+ | Alamat |
+ {selectedFile.alamat} |
+
+
+ | RT/RW |
+ {selectedFile.rt_rw} |
+
+
+ | Kelurahan/Desa |
+ {selectedFile.kel_desa} |
+
+
+ | Kecamatan |
+ {selectedFile.kecamatan} |
+
+
+ | Agama |
+ {selectedFile.agama} |
+
+
+ | Status Perkawinan |
+ {selectedFile.status_perkawinan} |
+
+
+ | Pekerjaan |
+ {selectedFile.pekerjaan} |
+
+
+ | Kewarganegaraan |
+ {selectedFile.kewarganegaraan} |
+
+
+ | No HP |
+ {selectedFile.no_hp} |
+
+
+ | Email |
+ {selectedFile.email} |
+
+
+ | Berlaku Hingga |
+ {selectedFile.berlaku_hingga} |
+
+
+ | Tanggal Pembuatan |
+ {selectedFile.pembuatan} |
+
+
+ | Kota Pembuatan |
+ {selectedFile.kota_pembuatan} |
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default FileListComponent;
diff --git a/src/FileListComponent.module.css b/src/FileListComponent.module.css
new file mode 100644
index 0000000..5415ee8
--- /dev/null
+++ b/src/FileListComponent.module.css
@@ -0,0 +1,483 @@
+/* FileListComponent.module.css - Updated to match Dashboard design */
+
+/* Use the same color palette as Dashboard */
+:root {
+ --primary-blue: #3b82f6;
+ --secondary-blue: #60a5fa;
+ --dark-blue: #1e40af;
+ --neutral-50: #fafafa;
+ --neutral-100: #f5f5f5;
+ --neutral-200: #e5e5e5;
+ --neutral-300: #d4d4d4;
+ --neutral-500: #737373;
+ --neutral-700: #404040;
+ --neutral-800: #262626;
+ --neutral-900: #171717;
+ --white: #ffffff;
+ --success-green: #10b981;
+ --warning-amber: #f59e0b;
+ --error-red: #ef4444;
+ --text-primary: #0f172a;
+ --text-secondary: #64748b;
+ --text-light: #ffffff;
+ --border-light: #e2e8f0;
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1),
+ 0 4px 6px -4px rgb(0 0 0 / 0.1);
+}
+
+/* File List Section */
+.fileListSection {
+ background-color: var(--white);
+ padding: 2rem;
+ border-radius: 1rem;
+ border: 1px solid var(--border-light);
+ box-shadow: var(--shadow-sm);
+ margin: 2rem auto;
+ max-width: 1200px;
+ width: 100%;
+ overflow: hidden;
+ max-height: 600px;
+ height: auto;
+ min-height: 0;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ transition: all 0.2s ease;
+}
+
+.fileListSection:hover {
+ box-shadow: var(--shadow-md);
+}
+
+.fileListHeader {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 1.5rem;
+ flex-wrap: wrap;
+ gap: 1rem;
+ flex-shrink: 0;
+ width: 100%;
+}
+
+.fileListTitle {
+ margin: 0;
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ letter-spacing: -0.025em;
+}
+
+.fileCount {
+ font-size: 0.875rem;
+ color: #ffffff;
+ font-weight: 500;
+ background-color: #ef4444;
+ padding: 0.25rem 0.75rem;
+ border-radius: 1rem;
+ border: 1px solid var(--border-light);
+}
+
+.successMessage {
+ background-color: rgb(16 185 129 / 0.1);
+ color: var(--success-green);
+ border: 1px solid rgb(16 185 129 / 0.2);
+ padding: 0.75rem 1rem;
+ border-radius: 0.5rem;
+ margin-bottom: 1rem;
+ font-size: 0.875rem;
+ font-weight: 500;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ flex-shrink: 0;
+ width: 100%;
+}
+
+.tableContainer {
+ flex: 1;
+ overflow: auto;
+ border-radius: 0.75rem;
+ border: 1px solid var(--border-light);
+ background-color: var(--white);
+ width: 100%;
+}
+
+.fileTable {
+ width: 100%;
+ min-width: 600px;
+ table-layout: auto;
+ border-collapse: collapse;
+ font-size: 0.875rem;
+ background-color: var(--white);
+}
+
+.fileTable th {
+ background-color: #ef4444;
+ padding: 0.75rem;
+ text-align: center;
+ font-weight: 600;
+ color: #ffffff;
+ border-bottom: 1px solid var(--border-light);
+ white-space: nowrap;
+ position: sticky;
+ top: 0;
+ z-index: 1;
+ font-size: 0.875rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+
+.fileTable td {
+ padding: 0.75rem;
+ border-bottom: 1px solid var(--border-light);
+ color: var(--text-primary);
+ vertical-align: middle;
+}
+
+.tableRow {
+ cursor: pointer;
+ transition: background-color 0.2s ease;
+}
+
+.tableRow:hover {
+ background-color: var(--neutral-50);
+}
+
+.nameColumn {
+ font-weight: 500;
+ color: var(--text-primary);
+ min-width: 200px;
+}
+
+.emptyState {
+ text-align: center;
+ padding: 3rem 2rem;
+ color: var(--text-secondary);
+}
+
+.emptyStateTitle {
+ font-size: 1.125rem;
+ margin-bottom: 0.5rem;
+ color: var(--text-primary);
+ font-weight: 600;
+}
+
+.emptyStateText {
+ font-size: 0.875rem;
+ margin: 0;
+ color: var(--text-secondary);
+}
+
+.spinner {
+ width: 2rem;
+ height: 2rem;
+ border: 3px solid var(--neutral-300);
+ border-top: 3px solid #ef4444;
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin: 0 auto 1rem;
+}
+
+@keyframes spin {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+/* Custom Scrollbar */
+.tableContainer::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+.tableContainer::-webkit-scrollbar-track {
+ background: var(--neutral-100);
+ border-radius: 4px;
+}
+
+.tableContainer::-webkit-scrollbar-thumb {
+ background: #ef4444;
+ border-radius: 4px;
+ transition: background 0.2s ease;
+}
+
+.tableContainer::-webkit-scrollbar-thumb:hover {
+ background: #dc2626;
+}
+
+.tableContainer::-webkit-scrollbar-corner {
+ background: var(--neutral-100);
+}
+
+.tableContainer {
+ scrollbar-width: thin;
+ scrollbar-color: #ef4444 var(--neutral-100);
+}
+
+/* Modal Styles - Matching Dashboard Design */
+.modalOverlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1000;
+ backdrop-filter: blur(4px);
+}
+
+.modalContent {
+ background: var(--white);
+ padding: 2rem;
+ border-radius: 1rem;
+ max-width: 600px;
+ width: 90%;
+ max-height: 85vh;
+ overflow-y: auto;
+ box-shadow: var(--shadow-lg);
+ border: 1px solid var(--border-light);
+}
+
+.modalContent h3 {
+ margin: 0 0 1.5rem 0;
+ font-size: 1.25rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ letter-spacing: -0.025em;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid var(--border-light);
+}
+
+.detailTable {
+ width: 100%;
+ border-collapse: collapse;
+ margin-bottom: 1.5rem;
+ font-size: 0.875rem;
+}
+
+.detailTable tr:nth-child(even) {
+ background-color: var(--neutral-50);
+}
+
+.detailTable td {
+ padding: 0.75rem;
+ border-bottom: 1px solid var(--border-light);
+ vertical-align: top;
+}
+
+.detailTable td:first-child {
+ font-weight: 600;
+ color: var(--text-secondary);
+ width: 35%;
+ text-transform: uppercase;
+ font-size: 0.75rem;
+ letter-spacing: 0.05em;
+}
+
+.detailTable td:last-child {
+ color: var(--text-primary);
+ font-weight: 500;
+}
+
+.closeButton {
+ background-color: #ef4444;
+ color: var(--text-light);
+ border: none;
+ padding: 0.75rem 1.5rem;
+ border-radius: 0.5rem;
+ cursor: pointer;
+ font-size: 0.875rem;
+ font-weight: 600;
+ width: 100%;
+ transition: all 0.2s ease;
+ letter-spacing: 0.025em;
+}
+
+.closeButton:hover {
+ background-color: #dc2626;
+ transform: translateY(-1px);
+ box-shadow: var(--shadow-md);
+}
+
+.closeButton:active {
+ transform: translateY(0);
+}
+
+/* Responsive Design */
+@media (max-width: 768px) {
+ .fileListSection {
+ padding: 1.5rem;
+ margin: 1rem;
+ max-width: 100%;
+ height: auto;
+ max-height: 70vh;
+ }
+
+ .fileListHeader {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 0.75rem;
+ }
+
+ .fileListTitle {
+ font-size: 1.125rem;
+ }
+
+ /* Mobile: Show only NIK and Name columns */
+ .fileTable {
+ min-width: 100%;
+ }
+
+ .fileTable th:not(:nth-child(2)):not(:nth-child(3)),
+ .fileTable td:not(:nth-child(2)):not(:nth-child(3)) {
+ display: none;
+ }
+
+ .fileTable th,
+ .fileTable td {
+ padding: 0.75rem 0.5rem;
+ font-size: 0.875rem;
+ }
+
+ .fileTable th:nth-child(2) {
+ width: 40%;
+ }
+
+ .fileTable th:nth-child(3) {
+ width: 60%;
+ }
+
+ .nameColumn {
+ min-width: unset;
+ }
+
+ /* Modal responsive */
+ .modalContent {
+ padding: 1.5rem;
+ width: 95%;
+ max-height: 90vh;
+ border-radius: 0.75rem;
+ }
+
+ .modalContent h3 {
+ font-size: 1.125rem;
+ }
+
+ .detailTable {
+ font-size: 0.8125rem;
+ }
+
+ .detailTable td {
+ padding: 0.625rem 0.5rem;
+ }
+
+ .detailTable td:first-child {
+ width: 40%;
+ }
+}
+
+@media (max-width: 480px) {
+ .fileListSection {
+ padding: 1rem;
+ margin: 0.5rem;
+ border-radius: 0.75rem;
+ }
+
+ .fileListTitle {
+ font-size: 1rem;
+ }
+
+ .fileCount {
+ font-size: 0.75rem;
+ padding: 0.25rem 0.5rem;
+ }
+
+ .fileTable th,
+ .fileTable td {
+ padding: 0.5rem 0.375rem;
+ font-size: 0.8125rem;
+ }
+
+ .modalContent {
+ padding: 1rem;
+ width: 98%;
+ border-radius: 0.5rem;
+ }
+
+ .modalContent h3 {
+ font-size: 1rem;
+ margin-bottom: 1rem;
+ }
+
+ .detailTable {
+ font-size: 0.75rem;
+ }
+
+ .detailTable td {
+ padding: 0.5rem 0.375rem;
+ }
+
+ .closeButton {
+ padding: 0.625rem 1rem;
+ font-size: 0.8125rem;
+ }
+}
+
+/* Tablet and Desktop enhancements */
+@media (min-width: 769px) {
+ .fileListSection {
+ padding: 2.5rem;
+ margin: 2.5rem auto;
+ }
+
+ .fileListTitle {
+ font-size: 1.5rem;
+ }
+
+ .fileTable th,
+ .fileTable td {
+ padding: 1rem 0.75rem;
+ }
+
+ .modalContent {
+ padding: 2.5rem;
+ border-radius: 1rem;
+ }
+
+ .modalContent h3 {
+ font-size: 1.5rem;
+ margin-bottom: 2rem;
+ }
+
+ .detailTable {
+ font-size: 0.875rem;
+ }
+
+ .detailTable td {
+ padding: 1rem 0.75rem;
+ }
+
+ .closeButton {
+ padding: 0.875rem 2rem;
+ width: auto;
+ margin-left: auto;
+ display: block;
+ }
+}
+
+@media (min-width: 1024px) {
+ .fileListSection {
+ padding: 3rem;
+ margin: 3rem auto;
+ }
+}
diff --git a/src/FormComponent.js b/src/FormComponent.js
index 452cda9..17e958e 100644
--- a/src/FormComponent.js
+++ b/src/FormComponent.js
@@ -17,6 +17,8 @@ const fieldLabels = {
validUntil: "Berlaku Hingga",
issuedCity: "Kota Terbit",
issuedDate: "Tanggal Terbit",
+ phoneNumber: "No. HP",
+ email: "Email",
};
function Modal({ isOpen, onClose, loading, fileTemp, onSave, onDelete }) {
@@ -88,7 +90,14 @@ function Modal({ isOpen, onClose, loading, fileTemp, onSave, onDelete }) {
["nik", "fullName", "birthPlace", "birthDate"],
["gender", "address", "neighborhoodCode", "village", "subDistrict"],
["religion", "maritalStatus", "occupation"],
- ["nationality", "validUntil", "issuedCity", "issuedDate"],
+ [
+ "nationality",
+ "validUntil",
+ "issuedCity",
+ "issuedDate",
+ "phoneNumber",
+ "email",
+ ],
];
// Filter field yang disable/hide