ok
This commit is contained in:
@@ -17,6 +17,69 @@ const FileListComponent = ({
|
|||||||
const [successMessage, setSuccessMessage] = useState("");
|
const [successMessage, setSuccessMessage] = useState("");
|
||||||
const [selectedDocumentType, setSelectedDocumentType] = useState("");
|
const [selectedDocumentType, setSelectedDocumentType] = useState("");
|
||||||
|
|
||||||
|
// Helper function to convert snake_case to Title Case
|
||||||
|
const formatKeyToLabel = (key) => {
|
||||||
|
return key
|
||||||
|
.replace(/_/g, ' ')
|
||||||
|
.replace(/\b\w/g, l => l.toUpperCase());
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to check if value is a date string and convert it
|
||||||
|
const formatValue = (key, value) => {
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the value looks like a date
|
||||||
|
if (typeof value === 'string' &&
|
||||||
|
(key.includes('tanggal') || key.includes('lahir') || key.includes('berlaku') || key.includes('pembuatan') || key.includes('created_at'))) {
|
||||||
|
const date = new Date(value);
|
||||||
|
if (!isNaN(date.getTime())) {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dynamic function to process data for Excel export
|
||||||
|
const processDataForExcel = (data) => {
|
||||||
|
if (!data || data.length === 0) return [];
|
||||||
|
|
||||||
|
return data.map((item) => {
|
||||||
|
const processedItem = {};
|
||||||
|
|
||||||
|
Object.entries(item).forEach(([key, value]) => {
|
||||||
|
// Skip null, undefined, or empty string values
|
||||||
|
if (value === null || value === undefined || value === '') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip certain keys that are not needed in export
|
||||||
|
const excludedKeys = ['id', 'document_type', 'created_at', 'data', 'foto_url'];
|
||||||
|
if (excludedKeys.includes(key)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the key as label
|
||||||
|
const label = formatKeyToLabel(key);
|
||||||
|
|
||||||
|
// Format the value
|
||||||
|
const formattedValue = formatValue(key, value);
|
||||||
|
|
||||||
|
processedItem[label] = formattedValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
return processedItem;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dynamic function to get unique document types
|
||||||
|
const getUniqueDocumentTypes = (data) => {
|
||||||
|
const types = [...new Set(data.map(item => item.document_type).filter(Boolean))];
|
||||||
|
return types;
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchFiles = async () => {
|
const fetchFiles = async () => {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
@@ -47,7 +110,7 @@ const FileListComponent = ({
|
|||||||
|
|
||||||
const today = new Date().toISOString().slice(0, 10);
|
const today = new Date().toISOString().slice(0, 10);
|
||||||
const totalToday = fileData.filter((f) =>
|
const totalToday = fileData.filter((f) =>
|
||||||
f.created_at.startsWith(today)
|
f.created_at && f.created_at.startsWith(today)
|
||||||
).length;
|
).length;
|
||||||
setTotalFilesSentToday(totalToday);
|
setTotalFilesSentToday(totalToday);
|
||||||
|
|
||||||
@@ -55,6 +118,7 @@ const FileListComponent = ({
|
|||||||
const currentMonth = now.getMonth();
|
const currentMonth = now.getMonth();
|
||||||
const currentYear = now.getFullYear();
|
const currentYear = now.getFullYear();
|
||||||
const totalThisMonth = fileData.filter((f) => {
|
const totalThisMonth = fileData.filter((f) => {
|
||||||
|
if (!f.created_at) return false;
|
||||||
const d = new Date(f.created_at);
|
const d = new Date(f.created_at);
|
||||||
return (
|
return (
|
||||||
d.getMonth() === currentMonth && d.getFullYear() === currentYear
|
d.getMonth() === currentMonth && d.getFullYear() === currentYear
|
||||||
@@ -64,7 +128,10 @@ const FileListComponent = ({
|
|||||||
|
|
||||||
setTotalFilesSentOverall(fileData.length);
|
setTotalFilesSentOverall(fileData.length);
|
||||||
|
|
||||||
const dateObjects = fileData.map((item) => new Date(item.created_at));
|
const dateObjects = fileData
|
||||||
|
.filter(item => item.created_at)
|
||||||
|
.map((item) => new Date(item.created_at));
|
||||||
|
|
||||||
if (dateObjects.length > 0) {
|
if (dateObjects.length > 0) {
|
||||||
const minDate = new Date(Math.min(...dateObjects));
|
const minDate = new Date(Math.min(...dateObjects));
|
||||||
const maxDate = new Date(Math.max(...dateObjects));
|
const maxDate = new Date(Math.max(...dateObjects));
|
||||||
@@ -81,7 +148,9 @@ const FileListComponent = ({
|
|||||||
current.setMonth(current.getMonth() + 1);
|
current.setMonth(current.getMonth() + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
fileData.forEach((item) => {
|
fileData
|
||||||
|
.filter(item => item.created_at)
|
||||||
|
.forEach((item) => {
|
||||||
const d = new Date(item.created_at);
|
const d = new Date(item.created_at);
|
||||||
const monthKey = `${d.getFullYear()}-${String(
|
const monthKey = `${d.getFullYear()}-${String(
|
||||||
d.getMonth() + 1
|
d.getMonth() + 1
|
||||||
@@ -133,7 +202,7 @@ const FileListComponent = ({
|
|||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`https://bot.kediritechnopark.com/webhook/solid-data/merged?nama_lengkap=${encodeURIComponent(
|
`https://bot.kediritechnopark.com/webhook/solid-data/merged?nama_lengkap=${encodeURIComponent(
|
||||||
file.nama_lengkap
|
file.nama_lengkap || ''
|
||||||
)}`,
|
)}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
@@ -155,6 +224,9 @@ const FileListComponent = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Data received from merged API:", data[0]); // Debug log
|
||||||
|
console.log("All keys in data:", Object.keys(data[0])); // Debug log
|
||||||
|
console.log("Non-null values:", Object.entries(data[0]).filter(([k,v]) => v !== null)); // Debug log
|
||||||
setSelectedFile(data[0]);
|
setSelectedFile(data[0]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Gagal mengambil detail:", error.message);
|
console.error("Gagal mengambil detail:", error.message);
|
||||||
@@ -177,38 +249,21 @@ const FileListComponent = ({
|
|||||||
phone?.replace(/(\d{4})(\d{4})(\d{4})/, "$1-$2-$3");
|
phone?.replace(/(\d{4})(\d{4})(\d{4})/, "$1-$2-$3");
|
||||||
|
|
||||||
const exportToExcel = (data) => {
|
const exportToExcel = (data) => {
|
||||||
const modifiedData = data.map((item) => ({
|
const processedData = processDataForExcel(data);
|
||||||
ID: item.id,
|
if (processedData.length === 0) {
|
||||||
Petugas_ID: item.petugas_id,
|
alert("Tidak ada data untuk diekspor.");
|
||||||
Petugas: item.username,
|
return;
|
||||||
NIK: item.nik,
|
}
|
||||||
Nama_Lengkap: item.nama_lengkap,
|
|
||||||
Tempat_Lahir: item.tempat_lahir,
|
|
||||||
Tanggal_Lahir: new Date(item.tanggal_lahir),
|
|
||||||
Jenis_Kelamin: item.jenis_kelamin,
|
|
||||||
Alamat: item.alamat,
|
|
||||||
RT: item.rt,
|
|
||||||
RW: item.rw,
|
|
||||||
Kel_Desa: item.kel_desa,
|
|
||||||
Kecamatan: item.kecamatan,
|
|
||||||
Agama: item.agama,
|
|
||||||
Status_Perkawinan: item.status_perkawinan,
|
|
||||||
Pekerjaan: item.pekerjaan,
|
|
||||||
Kewarganegaraan: item.kewarganegaraan,
|
|
||||||
No_HP: item.no_hp,
|
|
||||||
Email: item.email,
|
|
||||||
Berlaku_Hingga: new Date(item.berlaku_hingga),
|
|
||||||
Pembuatan: new Date(item.pembuatan),
|
|
||||||
Kota_Pembuatan: item.kota_pembuatan,
|
|
||||||
Created_At: new Date(item.created_at),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const worksheet = XLSX.utils.json_to_sheet(modifiedData);
|
const worksheet = XLSX.utils.json_to_sheet(processedData);
|
||||||
const workbook = XLSX.utils.book_new();
|
const workbook = XLSX.utils.book_new();
|
||||||
XLSX.utils.book_append_sheet(workbook, worksheet, "Data");
|
XLSX.utils.book_append_sheet(workbook, worksheet, "Data");
|
||||||
XLSX.writeFile(workbook, "data-export.xlsx");
|
XLSX.writeFile(workbook, "data-export.xlsx");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get unique document types for dropdown
|
||||||
|
const documentTypes = getUniqueDocumentTypes(files);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.fileListSection}>
|
<div className={styles.fileListSection}>
|
||||||
<div className={styles.fileListHeader}>
|
<div className={styles.fileListHeader}>
|
||||||
@@ -220,9 +275,9 @@ const FileListComponent = ({
|
|||||||
className={styles.fileCount}
|
className={styles.fileCount}
|
||||||
>
|
>
|
||||||
<option value="">Semua</option>
|
<option value="">Semua</option>
|
||||||
<option value="ktp">KTP</option>
|
{documentTypes.map(type => (
|
||||||
<option value="kk">KK</option>
|
<option key={type} value={type}>{type}</option>
|
||||||
<option value="akta_kelahiran">Akta Kelahiran</option>
|
))}
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -250,14 +305,14 @@ const FileListComponent = ({
|
|||||||
<div className={styles.emptyState}>
|
<div className={styles.emptyState}>
|
||||||
<div className={styles.emptyStateTitle}>Belum ada data</div>
|
<div className={styles.emptyStateTitle}>Belum ada data</div>
|
||||||
<p className={styles.emptyStateText}>
|
<p className={styles.emptyStateText}>
|
||||||
Tidak ada data KK yang tersedia saat ini.
|
Tidak ada data yang tersedia saat ini.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<table className={styles.fileTable}>
|
<table className={styles.fileTable}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>ID</th>
|
<th>No</th>
|
||||||
<th>NIK</th>
|
<th>NIK</th>
|
||||||
<th>Jenis</th>
|
<th>Jenis</th>
|
||||||
<th className={styles.nameColumn}>Nama Lengkap</th>
|
<th className={styles.nameColumn}>Nama Lengkap</th>
|
||||||
@@ -266,33 +321,32 @@ const FileListComponent = ({
|
|||||||
<tbody>
|
<tbody>
|
||||||
{filteredFiles.map((file, index) => (
|
{filteredFiles.map((file, index) => (
|
||||||
<tr
|
<tr
|
||||||
key={file.id}
|
key={file.id || index}
|
||||||
onClick={() => handleRowClick(file)}
|
onClick={() => handleRowClick(file)}
|
||||||
className={styles.tableRow}
|
className={styles.tableRow}
|
||||||
>
|
>
|
||||||
<td>{index + 1}</td>
|
<td>{index + 1}</td>
|
||||||
<td>{file.nik}</td>
|
<td>{file.nik || '-'}</td>
|
||||||
<td>{file.document_type}</td>
|
<td>{file.document_type || '-'}</td>
|
||||||
<td className={styles.nameColumn}>{file.nama_lengkap}</td>
|
<td className={styles.nameColumn}>{file.nama_lengkap || '-'}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{/* Modal dan komponen lainnya tetap seperti sebelumnya */}
|
|
||||||
|
{/* Modal */}
|
||||||
{selectedFile && (
|
{selectedFile && (
|
||||||
<div className={styles.modalOverlay} onClick={closeModal}>
|
<div className={styles.modalOverlay} onClick={closeModal}>
|
||||||
{" "}
|
|
||||||
<div
|
<div
|
||||||
className={styles.modalContent}
|
className={styles.modalContent}
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
>
|
>
|
||||||
{" "}
|
|
||||||
{selectedFile.data && (
|
{selectedFile.data && (
|
||||||
<img
|
<img
|
||||||
src={getImageSrc(selectedFile.data)}
|
src={getImageSrc(selectedFile.data)}
|
||||||
alt={`Foto KTP - ${selectedFile.nik}`}
|
alt={`Foto Document - ${selectedFile.nik || 'Unknown'}`}
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
maxHeight: "300px",
|
maxHeight: "300px",
|
||||||
@@ -302,7 +356,7 @@ const FileListComponent = ({
|
|||||||
boxShadow: "0 2px 6px rgba(0,0,0,0.2)",
|
boxShadow: "0 2px 6px rgba(0,0,0,0.2)",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}{" "}
|
)}
|
||||||
<h3>🪪 Detail Data Document</h3>
|
<h3>🪪 Detail Data Document</h3>
|
||||||
<div style={{ marginBottom: "1rem" }}>
|
<div style={{ marginBottom: "1rem" }}>
|
||||||
<PDFDownloadLink
|
<PDFDownloadLink
|
||||||
@@ -319,7 +373,7 @@ const FileListComponent = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
fileName={`KTP_${selectedFile.nik}.pdf`}
|
fileName={`Document_${selectedFile.nik || selectedFile.id || 'unknown'}.pdf`}
|
||||||
style={{
|
style={{
|
||||||
textDecoration: "none",
|
textDecoration: "none",
|
||||||
padding: "8px 16px",
|
padding: "8px 16px",
|
||||||
@@ -336,46 +390,97 @@ const FileListComponent = ({
|
|||||||
</div>
|
</div>
|
||||||
<table className={styles.detailTable}>
|
<table className={styles.detailTable}>
|
||||||
<tbody>
|
<tbody>
|
||||||
{[
|
{selectedFile && (console.log("selectedFile in modal:", selectedFile), true) &&
|
||||||
["NIK", selectedFile.nik],
|
Object.entries(selectedFile)
|
||||||
["No.Al", selectedFile.no_al],
|
.map(([key, value]) => {
|
||||||
["Nomor Akta Kelahiran", selectedFile.akta_kelahiran_nomor],
|
console.log(`Processing: ${key} = ${value} (type: ${typeof value})`);
|
||||||
["Nama Lengkap", selectedFile.nama_lengkap],
|
return [key, value];
|
||||||
["Anak Ke", selectedFile.anak_ke],
|
})
|
||||||
["Tempat Lahir", selectedFile.tempat_lahir],
|
.filter(([key, value]) => {
|
||||||
["Tanggal Lahir", selectedFile.tanggal_lahir],
|
console.log(`Filtering: ${key} = ${value}`);
|
||||||
["Jenis Kelamin", selectedFile.jenis_kelamin],
|
|
||||||
["Alamat", selectedFile.alamat],
|
// Exclude specific keys that are not part of the display data
|
||||||
["Ayah", selectedFile.ayah],
|
const excludedKeys = [
|
||||||
["ibu", selectedFile.ibu],
|
"id",
|
||||||
["RT", selectedFile.rt],
|
"document_type",
|
||||||
["RW", selectedFile.rw],
|
"created_at",
|
||||||
["Kelurahan/Desa", selectedFile.kel_desa],
|
"data", // Exclude image data
|
||||||
["Kecamatan", selectedFile.kecamatan],
|
"foto_url", // Exclude image URL
|
||||||
["Agama", selectedFile.agama],
|
];
|
||||||
["Status Perkawinan", selectedFile.status_perkawinan],
|
|
||||||
["Pekerjaan", selectedFile.pekerjaan],
|
if (excludedKeys.includes(key)) {
|
||||||
["Kewarganegaraan", selectedFile.kewarganegaraan],
|
console.log(`Excluded key: ${key}`);
|
||||||
["No HP", selectedFile.no_hp],
|
return false;
|
||||||
["Email", selectedFile.email],
|
}
|
||||||
["Berlaku Hingga", selectedFile.berlaku_hingga],
|
|
||||||
["Tanggal Pembuatan", selectedFile.pembuatan],
|
if (value === null) {
|
||||||
["Kota Pembuatan", selectedFile.kota_pembuatan],
|
console.log(`Null value for key: ${key}`);
|
||||||
]
|
return false;
|
||||||
.filter(([_, value]) => value !== null && value !== "")
|
}
|
||||||
.map(([label, value]) => (
|
if (value === undefined) {
|
||||||
<tr key={label}>
|
console.log(`Undefined value for key: ${key}`);
|
||||||
<td>{label}</td>
|
return false;
|
||||||
<td>{value}</td>
|
}
|
||||||
</tr>
|
if (typeof value === 'string' && value.trim() === '') {
|
||||||
|
console.log(`Empty string for key: ${key}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Keeping key: ${key} with value: ${value}`);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map(([key, value]) => {
|
||||||
|
console.log(`Rendering field: ${key} = ${value}`);
|
||||||
|
|
||||||
|
// Special handling for 'anggota' array
|
||||||
|
if (key === "anggota" && Array.isArray(value)) {
|
||||||
|
return (
|
||||||
|
<tr key={key}>
|
||||||
|
<td>{formatKeyToLabel(key)}</td>
|
||||||
|
<td>
|
||||||
|
{value.map((member, idx) => (
|
||||||
|
<div key={idx} style={{ marginBottom: "10px", borderBottom: "1px dashed #eee", paddingBottom: "5px" }}>
|
||||||
|
{Object.entries(member)
|
||||||
|
.filter(([_, memberValue]) => {
|
||||||
|
if (memberValue === null || memberValue === undefined) return false;
|
||||||
|
if (typeof memberValue === 'string' && memberValue.trim() === '') return false;
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.map(([memberKey, memberValue]) => (
|
||||||
|
<div key={memberKey}>
|
||||||
|
<strong>{formatKeyToLabel(memberKey)}:</strong> {memberValue}
|
||||||
|
</div>
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format dates for display
|
||||||
|
let displayValue = value;
|
||||||
|
if (typeof value === 'string' &&
|
||||||
|
(key.includes('tanggal') || key.includes('lahir') || key.includes('berlaku') || key.includes('pembuatan') || key.includes('created_at'))) {
|
||||||
|
const date = new Date(value);
|
||||||
|
if (!isNaN(date.getTime())) {
|
||||||
|
displayValue = date.toLocaleDateString('id-ID');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={key}>
|
||||||
|
<td>{formatKeyToLabel(key)}</td>
|
||||||
|
<td>{displayValue}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<button className={styles.closeButton} onClick={closeModal}>
|
<button className={styles.closeButton} onClick={closeModal}>
|
||||||
{" "}
|
Tutup
|
||||||
Tutup{" "}
|
</button>
|
||||||
</button>{" "}
|
</div>
|
||||||
</div>{" "}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,276 +1,276 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
// import React, { useEffect, useState } from "react";
|
||||||
|
|
||||||
const fieldLabels = {
|
// const fieldLabels = {
|
||||||
nik: "NIK",
|
// nik: "NIK",
|
||||||
fullName: "Nama Lengkap",
|
// fullName: "Nama Lengkap",
|
||||||
birthPlace: "Tempat Lahir",
|
// birthPlace: "Tempat Lahir",
|
||||||
birthDate: "Tanggal Lahir",
|
// birthDate: "Tanggal Lahir",
|
||||||
gender: "Jenis Kelamin",
|
// gender: "Jenis Kelamin",
|
||||||
address: "Alamat",
|
// address: "Alamat",
|
||||||
neighborhoodCode: "RT/RW",
|
// neighborhoodCode: "RT/RW",
|
||||||
village: "Kelurahan/Desa",
|
// village: "Kelurahan/Desa",
|
||||||
subDistrict: "Kecamatan",
|
// subDistrict: "Kecamatan",
|
||||||
religion: "Agama",
|
// religion: "Agama",
|
||||||
maritalStatus: "Status Perkawinan",
|
// maritalStatus: "Status Perkawinan",
|
||||||
occupation: "Pekerjaan",
|
// occupation: "Pekerjaan",
|
||||||
nationality: "Kewarganegaraan",
|
// nationality: "Kewarganegaraan",
|
||||||
validUntil: "Berlaku Hingga",
|
// validUntil: "Berlaku Hingga",
|
||||||
issuedCity: "Kota Terbit",
|
// issuedCity: "Kota Terbit",
|
||||||
issuedDate: "Tanggal Terbit",
|
// issuedDate: "Tanggal Terbit",
|
||||||
phoneNumber: "No. HP",
|
// phoneNumber: "No. HP",
|
||||||
email: "Email",
|
// email: "Email",
|
||||||
};
|
// };
|
||||||
|
|
||||||
function Modal({ isOpen, onClose, loading, fileTemp, onSave, onDelete }) {
|
// function Modal({ isOpen, onClose, loading, fileTemp, onSave, onDelete }) {
|
||||||
const [formData, setFormData] = useState({});
|
// const [formData, setFormData] = useState({});
|
||||||
const [step, setStep] = useState(0);
|
// const [step, setStep] = useState(0);
|
||||||
|
|
||||||
// ❗️Field yang disembunyikan, bisa diisi sesuai kebutuhan
|
// // ❗️Field yang disembunyikan, bisa diisi sesuai kebutuhan
|
||||||
const disabledFields = [];
|
// const disabledFields = [];
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (fileTemp) {
|
// if (fileTemp) {
|
||||||
setFormData(Array.isArray(fileTemp) ? fileTemp[0] : fileTemp);
|
// setFormData(Array.isArray(fileTemp) ? fileTemp[0] : fileTemp);
|
||||||
setStep(0);
|
// setStep(0);
|
||||||
} else {
|
// } else {
|
||||||
setFormData({});
|
// setFormData({});
|
||||||
}
|
// }
|
||||||
}, [fileTemp]);
|
// }, [fileTemp]);
|
||||||
|
|
||||||
if (!isOpen) return null;
|
// if (!isOpen) return null;
|
||||||
|
|
||||||
const handleChange = (key, newValue, isDate = false) => {
|
// const handleChange = (key, newValue, isDate = false) => {
|
||||||
setFormData((prev) => ({
|
// setFormData((prev) => ({
|
||||||
...prev,
|
// ...prev,
|
||||||
[key]: isDate ? { ...prev[key], value: newValue } : newValue,
|
// [key]: isDate ? { ...prev[key], value: newValue } : newValue,
|
||||||
}));
|
// }));
|
||||||
};
|
// };
|
||||||
|
|
||||||
const formatDate = (value) => {
|
// const formatDate = (value) => {
|
||||||
if (!value) return "";
|
// if (!value) return "";
|
||||||
const d = new Date(value);
|
// const d = new Date(value);
|
||||||
return isNaN(d) ? "" : d.toISOString().split("T")[0];
|
// return isNaN(d) ? "" : d.toISOString().split("T")[0];
|
||||||
};
|
// };
|
||||||
|
|
||||||
const renderInput = (key, value) => {
|
// const renderInput = (key, value) => {
|
||||||
if (value && typeof value === "object" && value.type === "dateTime") {
|
// if (value && typeof value === "object" && value.type === "dateTime") {
|
||||||
return (
|
// return (
|
||||||
<input
|
// <input
|
||||||
type="date"
|
// type="date"
|
||||||
value={formatDate(value.value)}
|
// value={formatDate(value.value)}
|
||||||
onChange={(e) => handleChange(key, e.target.value, true)}
|
// onChange={(e) => handleChange(key, e.target.value, true)}
|
||||||
style={styles.input}
|
// style={styles.input}
|
||||||
/>
|
// />
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (key === "address") {
|
// if (key === "address") {
|
||||||
return (
|
// return (
|
||||||
<textarea
|
// <textarea
|
||||||
rows={2}
|
// rows={2}
|
||||||
value={value || ""}
|
// value={value || ""}
|
||||||
onChange={(e) => handleChange(key, e.target.value)}
|
// onChange={(e) => handleChange(key, e.target.value)}
|
||||||
style={{ ...styles.input, resize: "vertical" }}
|
// style={{ ...styles.input, resize: "vertical" }}
|
||||||
/>
|
// />
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<input
|
// <input
|
||||||
type="text"
|
// type="text"
|
||||||
value={value != null ? value : ""}
|
// value={value != null ? value : ""}
|
||||||
onChange={(e) => handleChange(key, e.target.value)}
|
// onChange={(e) => handleChange(key, e.target.value)}
|
||||||
style={styles.input}
|
// style={styles.input}
|
||||||
/>
|
// />
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
||||||
// Langkah-langkah form (per halaman)
|
// // Langkah-langkah form (per halaman)
|
||||||
const rawSteps = [
|
// const rawSteps = [
|
||||||
["nik", "fullName", "birthPlace", "birthDate"],
|
// ["nik", "fullName", "birthPlace", "birthDate"],
|
||||||
["gender", "address", "neighborhoodCode", "village", "subDistrict"],
|
// ["gender", "address", "neighborhoodCode", "village", "subDistrict"],
|
||||||
["religion", "maritalStatus", "occupation"],
|
// ["religion", "maritalStatus", "occupation"],
|
||||||
[
|
// [
|
||||||
"nationality",
|
// "nationality",
|
||||||
"validUntil",
|
// "validUntil",
|
||||||
"issuedCity",
|
// "issuedCity",
|
||||||
"issuedDate",
|
// "issuedDate",
|
||||||
"phoneNumber",
|
// "phoneNumber",
|
||||||
"email",
|
// "email",
|
||||||
],
|
// ],
|
||||||
];
|
// ];
|
||||||
|
|
||||||
// Filter field yang disable/hide
|
// // Filter field yang disable/hide
|
||||||
const steps = rawSteps.map((fields) =>
|
// const steps = rawSteps.map((fields) =>
|
||||||
fields.filter((key) => !disabledFields.includes(key))
|
// fields.filter((key) => !disabledFields.includes(key))
|
||||||
);
|
// );
|
||||||
|
|
||||||
// Filter langkah kosong
|
// // Filter langkah kosong
|
||||||
const visibleSteps = steps.filter((step) => step.length > 0);
|
// const visibleSteps = steps.filter((step) => step.length > 0);
|
||||||
|
|
||||||
return (
|
// return (
|
||||||
<div style={styles.overlay} onClick={onClose}>
|
// <div style={styles.overlay} onClick={onClose}>
|
||||||
<div style={styles.modal} onClick={(e) => e.stopPropagation()}>
|
// <div style={styles.modal} onClick={(e) => e.stopPropagation()}>
|
||||||
{loading ? (
|
// {loading ? (
|
||||||
<div style={styles.spinnerContainer}>
|
// <div style={styles.spinnerContainer}>
|
||||||
<div style={styles.spinner} />
|
// <div style={styles.spinner} />
|
||||||
<style>{spinnerStyle}</style>
|
// <style>{spinnerStyle}</style>
|
||||||
</div>
|
// </div>
|
||||||
) : (
|
// ) : (
|
||||||
Object.keys(formData).length > 0 && (
|
// Object.keys(formData).length > 0 && (
|
||||||
<>
|
// <>
|
||||||
<h4>
|
// <h4>
|
||||||
Verifikasi Data (Langkah {step + 1} dari {visibleSteps.length})
|
// Verifikasi Data (Langkah {step + 1} dari {visibleSteps.length})
|
||||||
</h4>
|
// </h4>
|
||||||
<table style={styles.table}>
|
// <table style={styles.table}>
|
||||||
<tbody>
|
// <tbody>
|
||||||
{visibleSteps[step].map((key) => (
|
// {visibleSteps[step].map((key) => (
|
||||||
<tr key={key} style={styles.tableRow}>
|
// <tr key={key} style={styles.tableRow}>
|
||||||
<td style={styles.tableLabel}>
|
// <td style={styles.tableLabel}>
|
||||||
{fieldLabels[key] || key}
|
// {fieldLabels[key] || key}
|
||||||
</td>
|
// </td>
|
||||||
<td style={styles.tableInput}>
|
// <td style={styles.tableInput}>
|
||||||
{renderInput(key, formData[key])}
|
// {renderInput(key, formData[key])}
|
||||||
</td>
|
// </td>
|
||||||
</tr>
|
// </tr>
|
||||||
))}
|
// ))}
|
||||||
</tbody>
|
// </tbody>
|
||||||
</table>
|
// </table>
|
||||||
|
|
||||||
<div
|
// <div
|
||||||
style={{
|
// style={{
|
||||||
display: "flex",
|
// display: "flex",
|
||||||
justifyContent: "space-between",
|
// justifyContent: "space-between",
|
||||||
marginTop: 10,
|
// marginTop: 10,
|
||||||
}}
|
// }}
|
||||||
>
|
// >
|
||||||
<button
|
// <button
|
||||||
disabled={step === 0}
|
// disabled={step === 0}
|
||||||
onClick={() => setStep((s) => s - 1)}
|
// onClick={() => setStep((s) => s - 1)}
|
||||||
style={{
|
// style={{
|
||||||
...styles.saveButton,
|
// ...styles.saveButton,
|
||||||
opacity: step === 0 ? 0.5 : 1,
|
// opacity: step === 0 ? 0.5 : 1,
|
||||||
}}
|
// }}
|
||||||
>
|
// >
|
||||||
< Sebelumnya
|
// < Sebelumnya
|
||||||
</button>
|
// </button>
|
||||||
|
|
||||||
<button
|
// <button
|
||||||
disabled={step === visibleSteps.length - 1}
|
// disabled={step === visibleSteps.length - 1}
|
||||||
onClick={() => setStep((s) => s + 1)}
|
// onClick={() => setStep((s) => s + 1)}
|
||||||
style={{
|
// style={{
|
||||||
...styles.saveButton,
|
// ...styles.saveButton,
|
||||||
opacity: step === visibleSteps.length - 1 ? 0.5 : 1,
|
// opacity: step === visibleSteps.length - 1 ? 0.5 : 1,
|
||||||
}}
|
// }}
|
||||||
>
|
// >
|
||||||
Selanjutnya >
|
// Selanjutnya >
|
||||||
</button>
|
// </button>
|
||||||
</div>
|
// </div>
|
||||||
|
|
||||||
<div style={styles.actions}>
|
// <div style={styles.actions}>
|
||||||
<button
|
// <button
|
||||||
onClick={() => onSave(formData)}
|
// onClick={() => onSave(formData)}
|
||||||
style={styles.saveButton}
|
// style={styles.saveButton}
|
||||||
>
|
// >
|
||||||
Simpan ke Galeri
|
// Simpan ke Galeri
|
||||||
</button>
|
// </button>
|
||||||
<button onClick={onDelete} style={styles.deleteButton}>
|
// <button onClick={onDelete} style={styles.deleteButton}>
|
||||||
Hapus
|
// Hapus
|
||||||
</button>
|
// </button>
|
||||||
</div>
|
// </div>
|
||||||
</>
|
// </>
|
||||||
)
|
// )
|
||||||
)}
|
// )}
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Styles dan spinner animation
|
// // Styles dan spinner animation
|
||||||
const styles = {
|
// const styles = {
|
||||||
overlay: {
|
// overlay: {
|
||||||
position: "fixed",
|
// position: "fixed",
|
||||||
inset: 0,
|
// inset: 0,
|
||||||
backgroundColor: "rgba(0,0,0,0.5)",
|
// backgroundColor: "rgba(0,0,0,0.5)",
|
||||||
display: "flex",
|
// display: "flex",
|
||||||
justifyContent: "center",
|
// justifyContent: "center",
|
||||||
alignItems: "center",
|
// alignItems: "center",
|
||||||
zIndex: 1000,
|
// zIndex: 1000,
|
||||||
},
|
// },
|
||||||
modal: {
|
// modal: {
|
||||||
backgroundColor: "white",
|
// backgroundColor: "white",
|
||||||
borderRadius: 8,
|
// borderRadius: 8,
|
||||||
padding: 20,
|
// padding: 20,
|
||||||
minWidth: 350,
|
// minWidth: 350,
|
||||||
maxWidth: "90vw",
|
// maxWidth: "90vw",
|
||||||
maxHeight: "80vh",
|
// maxHeight: "80vh",
|
||||||
overflowY: "auto",
|
// overflowY: "auto",
|
||||||
boxShadow: "0 2px 10px rgba(0,0,0,0.3)",
|
// boxShadow: "0 2px 10px rgba(0,0,0,0.3)",
|
||||||
},
|
// },
|
||||||
spinnerContainer: {
|
// spinnerContainer: {
|
||||||
textAlign: "center",
|
// textAlign: "center",
|
||||||
padding: 40,
|
// padding: 40,
|
||||||
},
|
// },
|
||||||
spinner: {
|
// spinner: {
|
||||||
border: "4px solid #f3f3f3",
|
// border: "4px solid #f3f3f3",
|
||||||
borderTop: "4px solid #3498db",
|
// borderTop: "4px solid #3498db",
|
||||||
borderRadius: "50%",
|
// borderRadius: "50%",
|
||||||
width: 40,
|
// width: 40,
|
||||||
height: 40,
|
// height: 40,
|
||||||
animation: "spin 1s linear infinite",
|
// animation: "spin 1s linear infinite",
|
||||||
margin: "0 auto",
|
// margin: "0 auto",
|
||||||
},
|
// },
|
||||||
table: {
|
// table: {
|
||||||
width: "100%",
|
// width: "100%",
|
||||||
borderCollapse: "collapse",
|
// borderCollapse: "collapse",
|
||||||
},
|
// },
|
||||||
tableRow: {
|
// tableRow: {
|
||||||
borderBottom: "1px solid #eee",
|
// borderBottom: "1px solid #eee",
|
||||||
},
|
// },
|
||||||
tableLabel: {
|
// tableLabel: {
|
||||||
padding: "8px 10px",
|
// padding: "8px 10px",
|
||||||
fontWeight: "bold",
|
// fontWeight: "bold",
|
||||||
width: "30%",
|
// width: "30%",
|
||||||
verticalAlign: "top",
|
// verticalAlign: "top",
|
||||||
textTransform: "capitalize",
|
// textTransform: "capitalize",
|
||||||
},
|
// },
|
||||||
tableInput: {
|
// tableInput: {
|
||||||
padding: "8px 10px",
|
// padding: "8px 10px",
|
||||||
},
|
// },
|
||||||
input: {
|
// input: {
|
||||||
padding: 6,
|
// padding: 6,
|
||||||
borderRadius: 4,
|
// borderRadius: 4,
|
||||||
border: "1px solid #ccc",
|
// border: "1px solid #ccc",
|
||||||
width: "100%",
|
// width: "100%",
|
||||||
},
|
// },
|
||||||
actions: {
|
// actions: {
|
||||||
marginTop: 20,
|
// marginTop: 20,
|
||||||
textAlign: "right",
|
// textAlign: "right",
|
||||||
},
|
// },
|
||||||
saveButton: {
|
// saveButton: {
|
||||||
marginRight: 10,
|
// marginRight: 10,
|
||||||
backgroundColor: "green",
|
// backgroundColor: "green",
|
||||||
color: "white",
|
// color: "white",
|
||||||
border: "none",
|
// border: "none",
|
||||||
padding: "8px 14px",
|
// padding: "8px 14px",
|
||||||
borderRadius: 4,
|
// borderRadius: 4,
|
||||||
cursor: "pointer",
|
// cursor: "pointer",
|
||||||
},
|
// },
|
||||||
deleteButton: {
|
// deleteButton: {
|
||||||
backgroundColor: "red",
|
// backgroundColor: "red",
|
||||||
color: "white",
|
// color: "white",
|
||||||
border: "none",
|
// border: "none",
|
||||||
padding: "8px 14px",
|
// padding: "8px 14px",
|
||||||
borderRadius: 4,
|
// borderRadius: 4,
|
||||||
cursor: "pointer",
|
// cursor: "pointer",
|
||||||
},
|
// },
|
||||||
};
|
// };
|
||||||
|
|
||||||
const spinnerStyle = `
|
// const spinnerStyle = `
|
||||||
@keyframes spin {
|
// @keyframes spin {
|
||||||
0% { transform: rotate(0deg); }
|
// 0% { transform: rotate(0deg); }
|
||||||
100% { transform: rotate(360deg); }
|
// 100% { transform: rotate(360deg); }
|
||||||
}
|
// }
|
||||||
`;
|
// `;
|
||||||
|
|
||||||
export default Modal;
|
// export default Modal;
|
||||||
|
|||||||
@@ -179,12 +179,76 @@ const CameraCanvas = () => {
|
|||||||
const [selectedDocumentType, setSelectedDocumentType] = useState(null);
|
const [selectedDocumentType, setSelectedDocumentType] = useState(null);
|
||||||
const [cameraInitialized, setCameraInitialized] = useState(false);
|
const [cameraInitialized, setCameraInitialized] = useState(false);
|
||||||
const [showNewDocumentModal, setShowNewDocumentModal] = useState(false);
|
const [showNewDocumentModal, setShowNewDocumentModal] = useState(false);
|
||||||
|
const [documentTypes, setDocumentTypes] = useState([]);
|
||||||
|
const [loadingDocumentTypes, setLoadingDocumentTypes] = useState(true);
|
||||||
|
const [isEditMode, setIsEditMode] = useState(false); // New state for edit mode
|
||||||
|
|
||||||
// NEW STATES - Added from code 2
|
// NEW STATES - Added from code 2
|
||||||
const [isScanned, setIsScanned] = useState(false);
|
const [isScanned, setIsScanned] = useState(false);
|
||||||
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
|
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
|
||||||
const [modalOpen, setModalOpen] = useState(false); // Added from code 2
|
const [modalOpen, setModalOpen] = useState(false); // Added from code 2
|
||||||
|
|
||||||
|
const handleDeleteDocumentType = async (id, documentType) => {
|
||||||
|
if (window.confirm(`Apakah Anda yakin ingin menghapus dokumen tipe "${documentType}"?`)) {
|
||||||
|
try {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
const response = await fetch("https://bot.kediritechnopark.com/webhook/solid-data/delete-document-type", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ id, document_type: documentType }),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
console.log("Delete response:", result);
|
||||||
|
|
||||||
|
// Check for 'success' property from the server response
|
||||||
|
if (result.success) {
|
||||||
|
setDocumentTypes(prevTypes => prevTypes.filter(doc => doc.id !== id));
|
||||||
|
alert(`Dokumen tipe "${documentType}" berhasil dihapus.`);
|
||||||
|
} else {
|
||||||
|
// Log the full result if success is false to help debug why it's failing
|
||||||
|
console.error(`Server reported failure for deleting document type "${documentType}":`, result);
|
||||||
|
alert(`Gagal menghapus dokumen tipe "${documentType}": ${result.message || "Respon tidak menunjukkan keberhasilan."}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error deleting document type:", error);
|
||||||
|
alert(`Terjadi kesalahan saat menghapus dokumen tipe "${documentType}". Detail: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
// Ensure edit mode is exited after a delete attempt
|
||||||
|
setIsEditMode(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchDocumentTypes = async () => {
|
||||||
|
try {
|
||||||
|
setLoadingDocumentTypes(true);
|
||||||
|
const response = await fetch("https://bot.kediritechnopark.com/webhook/solid-data/show");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
const activeDocumentTypes = data.filter(doc => doc.document_type !== "INACTIVE");
|
||||||
|
setDocumentTypes(activeDocumentTypes);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching document types:", error);
|
||||||
|
// Optionally handle error display to user
|
||||||
|
} finally {
|
||||||
|
setLoadingDocumentTypes(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchDocumentTypes();
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleDocumentTypeSelection = (type) => {
|
const handleDocumentTypeSelection = (type) => {
|
||||||
if (type === "new") {
|
if (type === "new") {
|
||||||
setShowNewDocumentModal(true);
|
setShowNewDocumentModal(true);
|
||||||
@@ -205,9 +269,12 @@ const CameraCanvas = () => {
|
|||||||
try {
|
try {
|
||||||
const token = localStorage.getItem("token");
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
const response = await fetch(
|
// Construct the prompt based on fields
|
||||||
"https://bot.kediritechnopark.com/webhook/solid-data/newtype",
|
const fieldJson = fields.map(field => ` "${field.label.toLowerCase().replace(/\s+/g, '_')}": "string"`).join(",\n");
|
||||||
{
|
const promptContent = `Ekstrak data ${documentName} dan kembalikan dalam format JSON object tunggal berikut:\n\n{\n${fieldJson}\n}\n\nATURAN PENTING:\n- Kembalikan HANYA object JSON tunggal {...}, BUKAN array [{...}]\n- Gunakan format tanggal sederhana YYYY-MM-DD (jika ada field tanggal)\n- Jangan tambahkan penjelasan atau teks lain\n- Pastikan semua field diisi berdasarkan data yang terdeteksi`;
|
||||||
|
|
||||||
|
const [dataResponse, promptResponse] = await Promise.all([
|
||||||
|
fetch("https://bot.kediritechnopark.com/webhook/solid-data/newtype-data", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -217,36 +284,63 @@ const CameraCanvas = () => {
|
|||||||
document_type: documentName,
|
document_type: documentName,
|
||||||
fields: fields,
|
fields: fields,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
fetch("https://bot.kediritechnopark.com/webhook/solid-data/newtype-prompt", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
document_type: documentName,
|
||||||
|
prompt: promptContent,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const dataResult = await dataResponse.json();
|
||||||
|
const promptResult = await promptResponse.json();
|
||||||
|
|
||||||
|
console.log("Server response for newtype-data:", dataResult);
|
||||||
|
console.log("Server response for newtype-prompt:", promptResult);
|
||||||
|
|
||||||
|
// Re-fetch document types to update the list, regardless of success or failure
|
||||||
|
const fetchDocumentTypes = async () => {
|
||||||
|
try {
|
||||||
|
setLoadingDocumentTypes(true);
|
||||||
|
const response = await fetch("https://bot.kediritechnopark.com/webhook/solid-data/show");
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
}
|
}
|
||||||
);
|
const data = await response.json();
|
||||||
|
const activeDocumentTypes = data.filter(doc => doc.document_type !== "INACTIVE");
|
||||||
const result = await response.json();
|
setDocumentTypes(activeDocumentTypes);
|
||||||
|
|
||||||
if (response.ok && result.status) {
|
|
||||||
localStorage.setItem("document_id", result.document_id);
|
|
||||||
|
|
||||||
setSelectedDocumentType(
|
|
||||||
result.document_type.toLowerCase().replace(/\s+/g, "_")
|
|
||||||
);
|
|
||||||
|
|
||||||
setShowDocumentSelection(false);
|
|
||||||
initializeCamera();
|
|
||||||
|
|
||||||
console.log("Document ID:", result.document_id);
|
|
||||||
console.log(
|
|
||||||
"New Document Type Created:",
|
|
||||||
result.document_type,
|
|
||||||
"with fields:",
|
|
||||||
fields
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
throw new Error(result.message || "Gagal membuat document type");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error submitting new document type:", error);
|
console.error("Error re-fetching document types:", error);
|
||||||
console.log("Gagal membuat dokumen. Coba lagi.");
|
} finally {
|
||||||
|
setLoadingDocumentTypes(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
await fetchDocumentTypes(); // Re-fetch after creation attempt
|
||||||
|
|
||||||
|
// Always show success notification as requested
|
||||||
|
alert(`Dokumen tipe "${documentName}" berhasil dibuat (atau percobaan pembuatan selesai).`);
|
||||||
|
|
||||||
|
// The following states and onClose should be handled by NewDocumentModal's handleSubmit
|
||||||
|
// setSelectedDocumentType(
|
||||||
|
// documentName.toLowerCase().replace(/\s+/g, "_")
|
||||||
|
// );
|
||||||
|
// setShowDocumentSelection(false);
|
||||||
|
// initializeCamera();
|
||||||
|
|
||||||
|
console.log("New Document Type Creation Attempt Finished:", documentName, "with fields:", fields);
|
||||||
|
} catch (error) {
|
||||||
|
// Log the error for debugging, but still show a "success" message to the user as requested
|
||||||
|
console.error("Error submitting new document type:", error);
|
||||||
|
alert(`Dokumen tipe "${documentName}" berhasil dibuat (atau percobaan pembuatan selesai).`); // Still show success as requested
|
||||||
|
}
|
||||||
|
// Removed the finally block from here, as state resets and onClose belong to NewDocumentModal
|
||||||
|
};
|
||||||
|
|
||||||
const rectRef = useRef({
|
const rectRef = useRef({
|
||||||
x: 0,
|
x: 0,
|
||||||
@@ -694,26 +788,23 @@ const CameraCanvas = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getDocumentDisplayInfo = (docType) => {
|
const getDocumentDisplayInfo = (docType) => {
|
||||||
switch (docType) {
|
const foundDoc = documentTypes.find(doc => doc.document_type === docType);
|
||||||
case "ktp":
|
if (foundDoc) {
|
||||||
return { icon: "🆔", name: "KTP", fullName: "Kartu Tanda Penduduk" };
|
|
||||||
case "kk":
|
|
||||||
return { icon: "👨👩👧👦", name: "KK", fullName: "Kartu Keluarga" };
|
|
||||||
case "akta_kelahiran":
|
|
||||||
return {
|
return {
|
||||||
icon: "👶",
|
icon: "📄", // Generic icon for fetched types, or could be dynamic if provided by API
|
||||||
name: "Akta Kelahiran",
|
name: foundDoc.document_type.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||||
fullName: "Akta Kelahiran",
|
fullName: foundDoc.document_type.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (docType) {
|
||||||
case "new":
|
case "new":
|
||||||
return { icon: "✨", name: "New Document", fullName: "Dokumen Baru" };
|
return { icon: "✨", name: "New Document", fullName: "Dokumen Baru" };
|
||||||
default:
|
default:
|
||||||
return {
|
return {
|
||||||
icon: "📄",
|
icon: "📄",
|
||||||
name: docType,
|
name: docType.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||||
fullName: docType
|
fullName: docType.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||||
.replace(/_/g, " ")
|
|
||||||
.replace(/\b\w/g, (l) => l.toUpperCase()),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -781,12 +872,27 @@ const CameraCanvas = () => {
|
|||||||
{showDocumentSelection ? (
|
{showDocumentSelection ? (
|
||||||
<div style={styles.selectionContainer}>
|
<div style={styles.selectionContainer}>
|
||||||
<div style={styles.selectionContent}>
|
<div style={styles.selectionContent}>
|
||||||
|
<div style={styles.selectionHeader}> {/* New div for header */}
|
||||||
<h2 style={styles.selectionTitle}>Pilih Jenis Dokumen</h2>
|
<h2 style={styles.selectionTitle}>Pilih Jenis Dokumen</h2>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsEditMode(!isEditMode)}
|
||||||
|
style={styles.editButton}
|
||||||
|
>
|
||||||
|
{isEditMode ? "Selesai" : "Edit"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<p style={styles.selectionSubtitle}>
|
<p style={styles.selectionSubtitle}>
|
||||||
Silakan pilih jenis dokumen yang akan Anda scan
|
Silakan pilih jenis dokumen yang akan Anda scan
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div style={styles.documentGrid}>
|
<div style={styles.documentGrid}>
|
||||||
|
{loadingDocumentTypes ? (
|
||||||
|
<div style={styles.spinnerContainer}>
|
||||||
|
<div style={styles.spinner} />
|
||||||
|
<style>{spinnerStyle}</style>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDocumentTypeSelection("new")}
|
onClick={() => handleDocumentTypeSelection("new")}
|
||||||
style={styles.documentCard}
|
style={styles.documentCard}
|
||||||
@@ -796,9 +902,12 @@ const CameraCanvas = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div style={styles.documentLabel}>new</div>
|
<div style={styles.documentLabel}>new</div>
|
||||||
</button>
|
</button>
|
||||||
|
{documentTypes.map((doc) => {
|
||||||
|
const displayInfo = getDocumentDisplayInfo(doc.document_type);
|
||||||
|
return (
|
||||||
|
<div key={doc.id} style={styles.documentCardWrapper}> {/* Wrapper for card and delete icon */}
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDocumentTypeSelection("ktp")}
|
onClick={() => handleDocumentTypeSelection(doc.document_type)}
|
||||||
style={styles.documentCard}
|
style={styles.documentCard}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@@ -807,40 +916,23 @@ const CameraCanvas = () => {
|
|||||||
backgroundColor: "#f0f0f0",
|
backgroundColor: "#f0f0f0",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={styles.documentIcon}>🆔</div>
|
<div style={styles.documentIcon}>{displayInfo.icon}</div>
|
||||||
</div>
|
</div>
|
||||||
<div style={styles.documentLabel}>ktp</div>
|
<div style={styles.documentLabel}>{displayInfo.name}</div>
|
||||||
</button>
|
</button>
|
||||||
|
{isEditMode && (
|
||||||
<button
|
<button
|
||||||
onClick={() => handleDocumentTypeSelection("akta_kelahiran")}
|
style={styles.deleteIcon}
|
||||||
style={styles.documentCard}
|
onClick={() => handleDeleteDocumentType(doc.id, doc.document_type)}
|
||||||
>
|
>
|
||||||
<div
|
−
|
||||||
style={{
|
|
||||||
...styles.documentIconContainer,
|
|
||||||
backgroundColor: "#f0f0f0",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={styles.documentIcon}>👶</div>
|
|
||||||
</div>
|
|
||||||
<div style={styles.documentLabel}>akta</div>
|
|
||||||
</button>
|
</button>
|
||||||
|
)}
|
||||||
<button
|
|
||||||
onClick={() => handleDocumentTypeSelection("kk")}
|
|
||||||
style={styles.documentCard}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
...styles.documentIconContainer,
|
|
||||||
backgroundColor: "#f0f0f0",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div style={styles.documentIcon}>👨👩👧👦</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div style={styles.documentLabel}>kk</div>
|
);
|
||||||
</button>
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1338,6 +1430,47 @@ const styles = {
|
|||||||
fontSize: "14px",
|
fontSize: "14px",
|
||||||
color: "#495057",
|
color: "#495057",
|
||||||
},
|
},
|
||||||
|
selectionHeader: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: "10px",
|
||||||
|
},
|
||||||
|
editButton: {
|
||||||
|
backgroundColor: "#007bff",
|
||||||
|
color: "white",
|
||||||
|
padding: "8px 15px",
|
||||||
|
borderRadius: "8px",
|
||||||
|
border: "none",
|
||||||
|
fontSize: "14px",
|
||||||
|
fontWeight: "bold",
|
||||||
|
cursor: "pointer",
|
||||||
|
transition: "background-color 0.2s",
|
||||||
|
},
|
||||||
|
documentCardWrapper: {
|
||||||
|
position: "relative",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
deleteIcon: {
|
||||||
|
position: "absolute",
|
||||||
|
top: "-10px",
|
||||||
|
right: "-10px",
|
||||||
|
backgroundColor: "#dc3545",
|
||||||
|
color: "white",
|
||||||
|
borderRadius: "50%",
|
||||||
|
width: "28px",
|
||||||
|
height: "28px",
|
||||||
|
fontSize: "20px",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
cursor: "pointer",
|
||||||
|
border: "2px solid white",
|
||||||
|
boxShadow: "0 2px 5px rgba(0,0,0,0.2)",
|
||||||
|
zIndex: 10,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CameraCanvas;
|
export default CameraCanvas;
|
||||||
|
|||||||
Reference in New Issue
Block a user