ok
This commit is contained in:
@@ -17,6 +17,69 @@ const FileListComponent = ({
|
||||
const [successMessage, setSuccessMessage] = 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(() => {
|
||||
const fetchFiles = async () => {
|
||||
const token = localStorage.getItem("token");
|
||||
@@ -47,7 +110,7 @@ const FileListComponent = ({
|
||||
|
||||
const today = new Date().toISOString().slice(0, 10);
|
||||
const totalToday = fileData.filter((f) =>
|
||||
f.created_at.startsWith(today)
|
||||
f.created_at && f.created_at.startsWith(today)
|
||||
).length;
|
||||
setTotalFilesSentToday(totalToday);
|
||||
|
||||
@@ -55,6 +118,7 @@ const FileListComponent = ({
|
||||
const currentMonth = now.getMonth();
|
||||
const currentYear = now.getFullYear();
|
||||
const totalThisMonth = fileData.filter((f) => {
|
||||
if (!f.created_at) return false;
|
||||
const d = new Date(f.created_at);
|
||||
return (
|
||||
d.getMonth() === currentMonth && d.getFullYear() === currentYear
|
||||
@@ -64,7 +128,10 @@ const FileListComponent = ({
|
||||
|
||||
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) {
|
||||
const minDate = new Date(Math.min(...dateObjects));
|
||||
const maxDate = new Date(Math.max(...dateObjects));
|
||||
@@ -81,7 +148,9 @@ const FileListComponent = ({
|
||||
current.setMonth(current.getMonth() + 1);
|
||||
}
|
||||
|
||||
fileData.forEach((item) => {
|
||||
fileData
|
||||
.filter(item => item.created_at)
|
||||
.forEach((item) => {
|
||||
const d = new Date(item.created_at);
|
||||
const monthKey = `${d.getFullYear()}-${String(
|
||||
d.getMonth() + 1
|
||||
@@ -133,7 +202,7 @@ const FileListComponent = ({
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://bot.kediritechnopark.com/webhook/solid-data/merged?nama_lengkap=${encodeURIComponent(
|
||||
file.nama_lengkap
|
||||
file.nama_lengkap || ''
|
||||
)}`,
|
||||
{
|
||||
method: "GET",
|
||||
@@ -155,6 +224,9 @@ const FileListComponent = ({
|
||||
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]);
|
||||
} catch (error) {
|
||||
console.error("Gagal mengambil detail:", error.message);
|
||||
@@ -177,38 +249,21 @@ const FileListComponent = ({
|
||||
phone?.replace(/(\d{4})(\d{4})(\d{4})/, "$1-$2-$3");
|
||||
|
||||
const exportToExcel = (data) => {
|
||||
const modifiedData = data.map((item) => ({
|
||||
ID: item.id,
|
||||
Petugas_ID: item.petugas_id,
|
||||
Petugas: item.username,
|
||||
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 processedData = processDataForExcel(data);
|
||||
if (processedData.length === 0) {
|
||||
alert("Tidak ada data untuk diekspor.");
|
||||
return;
|
||||
}
|
||||
|
||||
const worksheet = XLSX.utils.json_to_sheet(modifiedData);
|
||||
const worksheet = XLSX.utils.json_to_sheet(processedData);
|
||||
const workbook = XLSX.utils.book_new();
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, "Data");
|
||||
XLSX.writeFile(workbook, "data-export.xlsx");
|
||||
};
|
||||
|
||||
// Get unique document types for dropdown
|
||||
const documentTypes = getUniqueDocumentTypes(files);
|
||||
|
||||
return (
|
||||
<div className={styles.fileListSection}>
|
||||
<div className={styles.fileListHeader}>
|
||||
@@ -220,9 +275,9 @@ const FileListComponent = ({
|
||||
className={styles.fileCount}
|
||||
>
|
||||
<option value="">Semua</option>
|
||||
<option value="ktp">KTP</option>
|
||||
<option value="kk">KK</option>
|
||||
<option value="akta_kelahiran">Akta Kelahiran</option>
|
||||
{documentTypes.map(type => (
|
||||
<option key={type} value={type}>{type}</option>
|
||||
))}
|
||||
</select>
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -250,14 +305,14 @@ const FileListComponent = ({
|
||||
<div className={styles.emptyState}>
|
||||
<div className={styles.emptyStateTitle}>Belum ada data</div>
|
||||
<p className={styles.emptyStateText}>
|
||||
Tidak ada data KK yang tersedia saat ini.
|
||||
Tidak ada data yang tersedia saat ini.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<table className={styles.fileTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>No</th>
|
||||
<th>NIK</th>
|
||||
<th>Jenis</th>
|
||||
<th className={styles.nameColumn}>Nama Lengkap</th>
|
||||
@@ -266,33 +321,32 @@ const FileListComponent = ({
|
||||
<tbody>
|
||||
{filteredFiles.map((file, index) => (
|
||||
<tr
|
||||
key={file.id}
|
||||
key={file.id || index}
|
||||
onClick={() => handleRowClick(file)}
|
||||
className={styles.tableRow}
|
||||
>
|
||||
<td>{index + 1}</td>
|
||||
<td>{file.nik}</td>
|
||||
<td>{file.document_type}</td>
|
||||
<td className={styles.nameColumn}>{file.nama_lengkap}</td>
|
||||
<td>{file.nik || '-'}</td>
|
||||
<td>{file.document_type || '-'}</td>
|
||||
<td className={styles.nameColumn}>{file.nama_lengkap || '-'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
)}
|
||||
</div>
|
||||
{/* Modal dan komponen lainnya tetap seperti sebelumnya */}
|
||||
|
||||
{/* Modal */}
|
||||
{selectedFile && (
|
||||
<div className={styles.modalOverlay} onClick={closeModal}>
|
||||
{" "}
|
||||
<div
|
||||
className={styles.modalContent}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{" "}
|
||||
{selectedFile.data && (
|
||||
<img
|
||||
src={getImageSrc(selectedFile.data)}
|
||||
alt={`Foto KTP - ${selectedFile.nik}`}
|
||||
alt={`Foto Document - ${selectedFile.nik || 'Unknown'}`}
|
||||
style={{
|
||||
width: "100%",
|
||||
maxHeight: "300px",
|
||||
@@ -302,7 +356,7 @@ const FileListComponent = ({
|
||||
boxShadow: "0 2px 6px rgba(0,0,0,0.2)",
|
||||
}}
|
||||
/>
|
||||
)}{" "}
|
||||
)}
|
||||
<h3>🪪 Detail Data Document</h3>
|
||||
<div style={{ marginBottom: "1rem" }}>
|
||||
<PDFDownloadLink
|
||||
@@ -319,7 +373,7 @@ const FileListComponent = ({
|
||||
}}
|
||||
/>
|
||||
}
|
||||
fileName={`KTP_${selectedFile.nik}.pdf`}
|
||||
fileName={`Document_${selectedFile.nik || selectedFile.id || 'unknown'}.pdf`}
|
||||
style={{
|
||||
textDecoration: "none",
|
||||
padding: "8px 16px",
|
||||
@@ -336,46 +390,97 @@ const FileListComponent = ({
|
||||
</div>
|
||||
<table className={styles.detailTable}>
|
||||
<tbody>
|
||||
{[
|
||||
["NIK", selectedFile.nik],
|
||||
["No.Al", selectedFile.no_al],
|
||||
["Nomor Akta Kelahiran", selectedFile.akta_kelahiran_nomor],
|
||||
["Nama Lengkap", selectedFile.nama_lengkap],
|
||||
["Anak Ke", selectedFile.anak_ke],
|
||||
["Tempat Lahir", selectedFile.tempat_lahir],
|
||||
["Tanggal Lahir", selectedFile.tanggal_lahir],
|
||||
["Jenis Kelamin", selectedFile.jenis_kelamin],
|
||||
["Alamat", selectedFile.alamat],
|
||||
["Ayah", selectedFile.ayah],
|
||||
["ibu", selectedFile.ibu],
|
||||
["RT", selectedFile.rt],
|
||||
["RW", selectedFile.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],
|
||||
]
|
||||
.filter(([_, value]) => value !== null && value !== "")
|
||||
.map(([label, value]) => (
|
||||
<tr key={label}>
|
||||
<td>{label}</td>
|
||||
<td>{value}</td>
|
||||
</tr>
|
||||
{selectedFile && (console.log("selectedFile in modal:", selectedFile), true) &&
|
||||
Object.entries(selectedFile)
|
||||
.map(([key, value]) => {
|
||||
console.log(`Processing: ${key} = ${value} (type: ${typeof value})`);
|
||||
return [key, value];
|
||||
})
|
||||
.filter(([key, value]) => {
|
||||
console.log(`Filtering: ${key} = ${value}`);
|
||||
|
||||
// Exclude specific keys that are not part of the display data
|
||||
const excludedKeys = [
|
||||
"id",
|
||||
"document_type",
|
||||
"created_at",
|
||||
"data", // Exclude image data
|
||||
"foto_url", // Exclude image URL
|
||||
];
|
||||
|
||||
if (excludedKeys.includes(key)) {
|
||||
console.log(`Excluded key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value === null) {
|
||||
console.log(`Null value for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
if (value === undefined) {
|
||||
console.log(`Undefined value for key: ${key}`);
|
||||
return false;
|
||||
}
|
||||
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>
|
||||
</table>
|
||||
<button className={styles.closeButton} onClick={closeModal}>
|
||||
{" "}
|
||||
Tutup{" "}
|
||||
</button>{" "}
|
||||
</div>{" "}
|
||||
Tutup
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,276 +1,276 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
// import React, { useEffect, useState } from "react";
|
||||
|
||||
const fieldLabels = {
|
||||
nik: "NIK",
|
||||
fullName: "Nama Lengkap",
|
||||
birthPlace: "Tempat Lahir",
|
||||
birthDate: "Tanggal Lahir",
|
||||
gender: "Jenis Kelamin",
|
||||
address: "Alamat",
|
||||
neighborhoodCode: "RT/RW",
|
||||
village: "Kelurahan/Desa",
|
||||
subDistrict: "Kecamatan",
|
||||
religion: "Agama",
|
||||
maritalStatus: "Status Perkawinan",
|
||||
occupation: "Pekerjaan",
|
||||
nationality: "Kewarganegaraan",
|
||||
validUntil: "Berlaku Hingga",
|
||||
issuedCity: "Kota Terbit",
|
||||
issuedDate: "Tanggal Terbit",
|
||||
phoneNumber: "No. HP",
|
||||
email: "Email",
|
||||
};
|
||||
// const fieldLabels = {
|
||||
// nik: "NIK",
|
||||
// fullName: "Nama Lengkap",
|
||||
// birthPlace: "Tempat Lahir",
|
||||
// birthDate: "Tanggal Lahir",
|
||||
// gender: "Jenis Kelamin",
|
||||
// address: "Alamat",
|
||||
// neighborhoodCode: "RT/RW",
|
||||
// village: "Kelurahan/Desa",
|
||||
// subDistrict: "Kecamatan",
|
||||
// religion: "Agama",
|
||||
// maritalStatus: "Status Perkawinan",
|
||||
// occupation: "Pekerjaan",
|
||||
// nationality: "Kewarganegaraan",
|
||||
// validUntil: "Berlaku Hingga",
|
||||
// issuedCity: "Kota Terbit",
|
||||
// issuedDate: "Tanggal Terbit",
|
||||
// phoneNumber: "No. HP",
|
||||
// email: "Email",
|
||||
// };
|
||||
|
||||
function Modal({ isOpen, onClose, loading, fileTemp, onSave, onDelete }) {
|
||||
const [formData, setFormData] = useState({});
|
||||
const [step, setStep] = useState(0);
|
||||
// function Modal({ isOpen, onClose, loading, fileTemp, onSave, onDelete }) {
|
||||
// const [formData, setFormData] = useState({});
|
||||
// const [step, setStep] = useState(0);
|
||||
|
||||
// ❗️Field yang disembunyikan, bisa diisi sesuai kebutuhan
|
||||
const disabledFields = [];
|
||||
// // ❗️Field yang disembunyikan, bisa diisi sesuai kebutuhan
|
||||
// const disabledFields = [];
|
||||
|
||||
useEffect(() => {
|
||||
if (fileTemp) {
|
||||
setFormData(Array.isArray(fileTemp) ? fileTemp[0] : fileTemp);
|
||||
setStep(0);
|
||||
} else {
|
||||
setFormData({});
|
||||
}
|
||||
}, [fileTemp]);
|
||||
// useEffect(() => {
|
||||
// if (fileTemp) {
|
||||
// setFormData(Array.isArray(fileTemp) ? fileTemp[0] : fileTemp);
|
||||
// setStep(0);
|
||||
// } else {
|
||||
// setFormData({});
|
||||
// }
|
||||
// }, [fileTemp]);
|
||||
|
||||
if (!isOpen) return null;
|
||||
// if (!isOpen) return null;
|
||||
|
||||
const handleChange = (key, newValue, isDate = false) => {
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[key]: isDate ? { ...prev[key], value: newValue } : newValue,
|
||||
}));
|
||||
};
|
||||
// const handleChange = (key, newValue, isDate = false) => {
|
||||
// setFormData((prev) => ({
|
||||
// ...prev,
|
||||
// [key]: isDate ? { ...prev[key], value: newValue } : newValue,
|
||||
// }));
|
||||
// };
|
||||
|
||||
const formatDate = (value) => {
|
||||
if (!value) return "";
|
||||
const d = new Date(value);
|
||||
return isNaN(d) ? "" : d.toISOString().split("T")[0];
|
||||
};
|
||||
// const formatDate = (value) => {
|
||||
// if (!value) return "";
|
||||
// const d = new Date(value);
|
||||
// return isNaN(d) ? "" : d.toISOString().split("T")[0];
|
||||
// };
|
||||
|
||||
const renderInput = (key, value) => {
|
||||
if (value && typeof value === "object" && value.type === "dateTime") {
|
||||
return (
|
||||
<input
|
||||
type="date"
|
||||
value={formatDate(value.value)}
|
||||
onChange={(e) => handleChange(key, e.target.value, true)}
|
||||
style={styles.input}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// const renderInput = (key, value) => {
|
||||
// if (value && typeof value === "object" && value.type === "dateTime") {
|
||||
// return (
|
||||
// <input
|
||||
// type="date"
|
||||
// value={formatDate(value.value)}
|
||||
// onChange={(e) => handleChange(key, e.target.value, true)}
|
||||
// style={styles.input}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
||||
if (key === "address") {
|
||||
return (
|
||||
<textarea
|
||||
rows={2}
|
||||
value={value || ""}
|
||||
onChange={(e) => handleChange(key, e.target.value)}
|
||||
style={{ ...styles.input, resize: "vertical" }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
// if (key === "address") {
|
||||
// return (
|
||||
// <textarea
|
||||
// rows={2}
|
||||
// value={value || ""}
|
||||
// onChange={(e) => handleChange(key, e.target.value)}
|
||||
// style={{ ...styles.input, resize: "vertical" }}
|
||||
// />
|
||||
// );
|
||||
// }
|
||||
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
value={value != null ? value : ""}
|
||||
onChange={(e) => handleChange(key, e.target.value)}
|
||||
style={styles.input}
|
||||
/>
|
||||
);
|
||||
};
|
||||
// return (
|
||||
// <input
|
||||
// type="text"
|
||||
// value={value != null ? value : ""}
|
||||
// onChange={(e) => handleChange(key, e.target.value)}
|
||||
// style={styles.input}
|
||||
// />
|
||||
// );
|
||||
// };
|
||||
|
||||
// Langkah-langkah form (per halaman)
|
||||
const rawSteps = [
|
||||
["nik", "fullName", "birthPlace", "birthDate"],
|
||||
["gender", "address", "neighborhoodCode", "village", "subDistrict"],
|
||||
["religion", "maritalStatus", "occupation"],
|
||||
[
|
||||
"nationality",
|
||||
"validUntil",
|
||||
"issuedCity",
|
||||
"issuedDate",
|
||||
"phoneNumber",
|
||||
"email",
|
||||
],
|
||||
];
|
||||
// // Langkah-langkah form (per halaman)
|
||||
// const rawSteps = [
|
||||
// ["nik", "fullName", "birthPlace", "birthDate"],
|
||||
// ["gender", "address", "neighborhoodCode", "village", "subDistrict"],
|
||||
// ["religion", "maritalStatus", "occupation"],
|
||||
// [
|
||||
// "nationality",
|
||||
// "validUntil",
|
||||
// "issuedCity",
|
||||
// "issuedDate",
|
||||
// "phoneNumber",
|
||||
// "email",
|
||||
// ],
|
||||
// ];
|
||||
|
||||
// Filter field yang disable/hide
|
||||
const steps = rawSteps.map((fields) =>
|
||||
fields.filter((key) => !disabledFields.includes(key))
|
||||
);
|
||||
// // Filter field yang disable/hide
|
||||
// const steps = rawSteps.map((fields) =>
|
||||
// fields.filter((key) => !disabledFields.includes(key))
|
||||
// );
|
||||
|
||||
// Filter langkah kosong
|
||||
const visibleSteps = steps.filter((step) => step.length > 0);
|
||||
// // Filter langkah kosong
|
||||
// const visibleSteps = steps.filter((step) => step.length > 0);
|
||||
|
||||
return (
|
||||
<div style={styles.overlay} onClick={onClose}>
|
||||
<div style={styles.modal} onClick={(e) => e.stopPropagation()}>
|
||||
{loading ? (
|
||||
<div style={styles.spinnerContainer}>
|
||||
<div style={styles.spinner} />
|
||||
<style>{spinnerStyle}</style>
|
||||
</div>
|
||||
) : (
|
||||
Object.keys(formData).length > 0 && (
|
||||
<>
|
||||
<h4>
|
||||
Verifikasi Data (Langkah {step + 1} dari {visibleSteps.length})
|
||||
</h4>
|
||||
<table style={styles.table}>
|
||||
<tbody>
|
||||
{visibleSteps[step].map((key) => (
|
||||
<tr key={key} style={styles.tableRow}>
|
||||
<td style={styles.tableLabel}>
|
||||
{fieldLabels[key] || key}
|
||||
</td>
|
||||
<td style={styles.tableInput}>
|
||||
{renderInput(key, formData[key])}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
// return (
|
||||
// <div style={styles.overlay} onClick={onClose}>
|
||||
// <div style={styles.modal} onClick={(e) => e.stopPropagation()}>
|
||||
// {loading ? (
|
||||
// <div style={styles.spinnerContainer}>
|
||||
// <div style={styles.spinner} />
|
||||
// <style>{spinnerStyle}</style>
|
||||
// </div>
|
||||
// ) : (
|
||||
// Object.keys(formData).length > 0 && (
|
||||
// <>
|
||||
// <h4>
|
||||
// Verifikasi Data (Langkah {step + 1} dari {visibleSteps.length})
|
||||
// </h4>
|
||||
// <table style={styles.table}>
|
||||
// <tbody>
|
||||
// {visibleSteps[step].map((key) => (
|
||||
// <tr key={key} style={styles.tableRow}>
|
||||
// <td style={styles.tableLabel}>
|
||||
// {fieldLabels[key] || key}
|
||||
// </td>
|
||||
// <td style={styles.tableInput}>
|
||||
// {renderInput(key, formData[key])}
|
||||
// </td>
|
||||
// </tr>
|
||||
// ))}
|
||||
// </tbody>
|
||||
// </table>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
marginTop: 10,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
disabled={step === 0}
|
||||
onClick={() => setStep((s) => s - 1)}
|
||||
style={{
|
||||
...styles.saveButton,
|
||||
opacity: step === 0 ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
< Sebelumnya
|
||||
</button>
|
||||
// <div
|
||||
// style={{
|
||||
// display: "flex",
|
||||
// justifyContent: "space-between",
|
||||
// marginTop: 10,
|
||||
// }}
|
||||
// >
|
||||
// <button
|
||||
// disabled={step === 0}
|
||||
// onClick={() => setStep((s) => s - 1)}
|
||||
// style={{
|
||||
// ...styles.saveButton,
|
||||
// opacity: step === 0 ? 0.5 : 1,
|
||||
// }}
|
||||
// >
|
||||
// < Sebelumnya
|
||||
// </button>
|
||||
|
||||
<button
|
||||
disabled={step === visibleSteps.length - 1}
|
||||
onClick={() => setStep((s) => s + 1)}
|
||||
style={{
|
||||
...styles.saveButton,
|
||||
opacity: step === visibleSteps.length - 1 ? 0.5 : 1,
|
||||
}}
|
||||
>
|
||||
Selanjutnya >
|
||||
</button>
|
||||
</div>
|
||||
// <button
|
||||
// disabled={step === visibleSteps.length - 1}
|
||||
// onClick={() => setStep((s) => s + 1)}
|
||||
// style={{
|
||||
// ...styles.saveButton,
|
||||
// opacity: step === visibleSteps.length - 1 ? 0.5 : 1,
|
||||
// }}
|
||||
// >
|
||||
// Selanjutnya >
|
||||
// </button>
|
||||
// </div>
|
||||
|
||||
<div style={styles.actions}>
|
||||
<button
|
||||
onClick={() => onSave(formData)}
|
||||
style={styles.saveButton}
|
||||
>
|
||||
Simpan ke Galeri
|
||||
</button>
|
||||
<button onClick={onDelete} style={styles.deleteButton}>
|
||||
Hapus
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// <div style={styles.actions}>
|
||||
// <button
|
||||
// onClick={() => onSave(formData)}
|
||||
// style={styles.saveButton}
|
||||
// >
|
||||
// Simpan ke Galeri
|
||||
// </button>
|
||||
// <button onClick={onDelete} style={styles.deleteButton}>
|
||||
// Hapus
|
||||
// </button>
|
||||
// </div>
|
||||
// </>
|
||||
// )
|
||||
// )}
|
||||
// </div>
|
||||
// </div>
|
||||
// );
|
||||
// }
|
||||
|
||||
// Styles dan spinner animation
|
||||
const styles = {
|
||||
overlay: {
|
||||
position: "fixed",
|
||||
inset: 0,
|
||||
backgroundColor: "rgba(0,0,0,0.5)",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
zIndex: 1000,
|
||||
},
|
||||
modal: {
|
||||
backgroundColor: "white",
|
||||
borderRadius: 8,
|
||||
padding: 20,
|
||||
minWidth: 350,
|
||||
maxWidth: "90vw",
|
||||
maxHeight: "80vh",
|
||||
overflowY: "auto",
|
||||
boxShadow: "0 2px 10px rgba(0,0,0,0.3)",
|
||||
},
|
||||
spinnerContainer: {
|
||||
textAlign: "center",
|
||||
padding: 40,
|
||||
},
|
||||
spinner: {
|
||||
border: "4px solid #f3f3f3",
|
||||
borderTop: "4px solid #3498db",
|
||||
borderRadius: "50%",
|
||||
width: 40,
|
||||
height: 40,
|
||||
animation: "spin 1s linear infinite",
|
||||
margin: "0 auto",
|
||||
},
|
||||
table: {
|
||||
width: "100%",
|
||||
borderCollapse: "collapse",
|
||||
},
|
||||
tableRow: {
|
||||
borderBottom: "1px solid #eee",
|
||||
},
|
||||
tableLabel: {
|
||||
padding: "8px 10px",
|
||||
fontWeight: "bold",
|
||||
width: "30%",
|
||||
verticalAlign: "top",
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
tableInput: {
|
||||
padding: "8px 10px",
|
||||
},
|
||||
input: {
|
||||
padding: 6,
|
||||
borderRadius: 4,
|
||||
border: "1px solid #ccc",
|
||||
width: "100%",
|
||||
},
|
||||
actions: {
|
||||
marginTop: 20,
|
||||
textAlign: "right",
|
||||
},
|
||||
saveButton: {
|
||||
marginRight: 10,
|
||||
backgroundColor: "green",
|
||||
color: "white",
|
||||
border: "none",
|
||||
padding: "8px 14px",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer",
|
||||
},
|
||||
deleteButton: {
|
||||
backgroundColor: "red",
|
||||
color: "white",
|
||||
border: "none",
|
||||
padding: "8px 14px",
|
||||
borderRadius: 4,
|
||||
cursor: "pointer",
|
||||
},
|
||||
};
|
||||
// // Styles dan spinner animation
|
||||
// const styles = {
|
||||
// overlay: {
|
||||
// position: "fixed",
|
||||
// inset: 0,
|
||||
// backgroundColor: "rgba(0,0,0,0.5)",
|
||||
// display: "flex",
|
||||
// justifyContent: "center",
|
||||
// alignItems: "center",
|
||||
// zIndex: 1000,
|
||||
// },
|
||||
// modal: {
|
||||
// backgroundColor: "white",
|
||||
// borderRadius: 8,
|
||||
// padding: 20,
|
||||
// minWidth: 350,
|
||||
// maxWidth: "90vw",
|
||||
// maxHeight: "80vh",
|
||||
// overflowY: "auto",
|
||||
// boxShadow: "0 2px 10px rgba(0,0,0,0.3)",
|
||||
// },
|
||||
// spinnerContainer: {
|
||||
// textAlign: "center",
|
||||
// padding: 40,
|
||||
// },
|
||||
// spinner: {
|
||||
// border: "4px solid #f3f3f3",
|
||||
// borderTop: "4px solid #3498db",
|
||||
// borderRadius: "50%",
|
||||
// width: 40,
|
||||
// height: 40,
|
||||
// animation: "spin 1s linear infinite",
|
||||
// margin: "0 auto",
|
||||
// },
|
||||
// table: {
|
||||
// width: "100%",
|
||||
// borderCollapse: "collapse",
|
||||
// },
|
||||
// tableRow: {
|
||||
// borderBottom: "1px solid #eee",
|
||||
// },
|
||||
// tableLabel: {
|
||||
// padding: "8px 10px",
|
||||
// fontWeight: "bold",
|
||||
// width: "30%",
|
||||
// verticalAlign: "top",
|
||||
// textTransform: "capitalize",
|
||||
// },
|
||||
// tableInput: {
|
||||
// padding: "8px 10px",
|
||||
// },
|
||||
// input: {
|
||||
// padding: 6,
|
||||
// borderRadius: 4,
|
||||
// border: "1px solid #ccc",
|
||||
// width: "100%",
|
||||
// },
|
||||
// actions: {
|
||||
// marginTop: 20,
|
||||
// textAlign: "right",
|
||||
// },
|
||||
// saveButton: {
|
||||
// marginRight: 10,
|
||||
// backgroundColor: "green",
|
||||
// color: "white",
|
||||
// border: "none",
|
||||
// padding: "8px 14px",
|
||||
// borderRadius: 4,
|
||||
// cursor: "pointer",
|
||||
// },
|
||||
// deleteButton: {
|
||||
// backgroundColor: "red",
|
||||
// color: "white",
|
||||
// border: "none",
|
||||
// padding: "8px 14px",
|
||||
// borderRadius: 4,
|
||||
// cursor: "pointer",
|
||||
// },
|
||||
// };
|
||||
|
||||
const spinnerStyle = `
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
`;
|
||||
// const spinnerStyle = `
|
||||
// @keyframes spin {
|
||||
// 0% { transform: rotate(0deg); }
|
||||
// 100% { transform: rotate(360deg); }
|
||||
// }
|
||||
// `;
|
||||
|
||||
export default Modal;
|
||||
// export default Modal;
|
||||
|
||||
@@ -179,12 +179,76 @@ const CameraCanvas = () => {
|
||||
const [selectedDocumentType, setSelectedDocumentType] = useState(null);
|
||||
const [cameraInitialized, setCameraInitialized] = 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
|
||||
const [isScanned, setIsScanned] = useState(false);
|
||||
const [showSuccessMessage, setShowSuccessMessage] = useState(false);
|
||||
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) => {
|
||||
if (type === "new") {
|
||||
setShowNewDocumentModal(true);
|
||||
@@ -205,9 +269,12 @@ const CameraCanvas = () => {
|
||||
try {
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
const response = await fetch(
|
||||
"https://bot.kediritechnopark.com/webhook/solid-data/newtype",
|
||||
{
|
||||
// Construct the prompt based on fields
|
||||
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",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -217,36 +284,63 @@ const CameraCanvas = () => {
|
||||
document_type: documentName,
|
||||
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 result = await response.json();
|
||||
|
||||
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");
|
||||
}
|
||||
const data = await response.json();
|
||||
const activeDocumentTypes = data.filter(doc => doc.document_type !== "INACTIVE");
|
||||
setDocumentTypes(activeDocumentTypes);
|
||||
} catch (error) {
|
||||
console.error("Error submitting new document type:", error);
|
||||
console.log("Gagal membuat dokumen. Coba lagi.");
|
||||
console.error("Error re-fetching document types:", error);
|
||||
} 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({
|
||||
x: 0,
|
||||
@@ -694,26 +788,23 @@ const CameraCanvas = () => {
|
||||
};
|
||||
|
||||
const getDocumentDisplayInfo = (docType) => {
|
||||
switch (docType) {
|
||||
case "ktp":
|
||||
return { icon: "🆔", name: "KTP", fullName: "Kartu Tanda Penduduk" };
|
||||
case "kk":
|
||||
return { icon: "👨👩👧👦", name: "KK", fullName: "Kartu Keluarga" };
|
||||
case "akta_kelahiran":
|
||||
const foundDoc = documentTypes.find(doc => doc.document_type === docType);
|
||||
if (foundDoc) {
|
||||
return {
|
||||
icon: "👶",
|
||||
name: "Akta Kelahiran",
|
||||
fullName: "Akta Kelahiran",
|
||||
icon: "📄", // Generic icon for fetched types, or could be dynamic if provided by API
|
||||
name: foundDoc.document_type.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||
fullName: foundDoc.document_type.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||
};
|
||||
}
|
||||
|
||||
switch (docType) {
|
||||
case "new":
|
||||
return { icon: "✨", name: "New Document", fullName: "Dokumen Baru" };
|
||||
default:
|
||||
return {
|
||||
icon: "📄",
|
||||
name: docType,
|
||||
fullName: docType
|
||||
.replace(/_/g, " ")
|
||||
.replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||
name: docType.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||
fullName: docType.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()),
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -781,12 +872,27 @@ const CameraCanvas = () => {
|
||||
{showDocumentSelection ? (
|
||||
<div style={styles.selectionContainer}>
|
||||
<div style={styles.selectionContent}>
|
||||
<div style={styles.selectionHeader}> {/* New div for header */}
|
||||
<h2 style={styles.selectionTitle}>Pilih Jenis Dokumen</h2>
|
||||
<button
|
||||
onClick={() => setIsEditMode(!isEditMode)}
|
||||
style={styles.editButton}
|
||||
>
|
||||
{isEditMode ? "Selesai" : "Edit"}
|
||||
</button>
|
||||
</div>
|
||||
<p style={styles.selectionSubtitle}>
|
||||
Silakan pilih jenis dokumen yang akan Anda scan
|
||||
</p>
|
||||
|
||||
<div style={styles.documentGrid}>
|
||||
{loadingDocumentTypes ? (
|
||||
<div style={styles.spinnerContainer}>
|
||||
<div style={styles.spinner} />
|
||||
<style>{spinnerStyle}</style>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
onClick={() => handleDocumentTypeSelection("new")}
|
||||
style={styles.documentCard}
|
||||
@@ -796,9 +902,12 @@ const CameraCanvas = () => {
|
||||
</div>
|
||||
<div style={styles.documentLabel}>new</div>
|
||||
</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
|
||||
onClick={() => handleDocumentTypeSelection("ktp")}
|
||||
onClick={() => handleDocumentTypeSelection(doc.document_type)}
|
||||
style={styles.documentCard}
|
||||
>
|
||||
<div
|
||||
@@ -807,40 +916,23 @@ const CameraCanvas = () => {
|
||||
backgroundColor: "#f0f0f0",
|
||||
}}
|
||||
>
|
||||
<div style={styles.documentIcon}>🆔</div>
|
||||
<div style={styles.documentIcon}>{displayInfo.icon}</div>
|
||||
</div>
|
||||
<div style={styles.documentLabel}>ktp</div>
|
||||
<div style={styles.documentLabel}>{displayInfo.name}</div>
|
||||
</button>
|
||||
|
||||
{isEditMode && (
|
||||
<button
|
||||
onClick={() => handleDocumentTypeSelection("akta_kelahiran")}
|
||||
style={styles.documentCard}
|
||||
style={styles.deleteIcon}
|
||||
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
|
||||
onClick={() => handleDocumentTypeSelection("kk")}
|
||||
style={styles.documentCard}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
...styles.documentIconContainer,
|
||||
backgroundColor: "#f0f0f0",
|
||||
}}
|
||||
>
|
||||
<div style={styles.documentIcon}>👨👩👧👦</div>
|
||||
)}
|
||||
</div>
|
||||
<div style={styles.documentLabel}>kk</div>
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1338,6 +1430,47 @@ const styles = {
|
||||
fontSize: "14px",
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user