This commit is contained in:
Rizqika 2024-11-11 18:45:07 +07:00
parent bd3a3d42ce
commit 68878187e2
2 changed files with 624 additions and 677 deletions

View File

@ -33,6 +33,306 @@ const Enroll = () => {
const [options, setOptions] = useState([]); const [options, setOptions] = useState([]);
const [isMobile, setIsMobile] = useState(false); const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const fetchApplicationIds = async () => {
setIsLoading(true);
const url = `${BASE_URL}/application/list`;
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
const data = await response.json();
if (data.status_code === 200) {
setApplicationIds(data.details.data);
} else {
console.error('Failed to fetch data:', data.details.message);
}
} catch (error) {
console.error('Error fetching application IDs:', error);
} finally {
setIsLoading(false);
}
};
fetchApplicationIds();
setOptions(subjectIds.map(id => ({ value: id, label: id })));
const handleResize = () => {
setIsMobile(window.innerWidth <= 768); // Deteksi apakah layar kecil (mobile)
};
window.addEventListener('resize', handleResize);
handleResize(); // Initial check
return () => window.removeEventListener('resize', handleResize);
}, [subjectIds]);
const handleApplicationChange = async (selectedOption) => {
const selectedId = selectedOption.value;
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
if (!selectedOption) {
console.error("Selected option is undefined");
return;
}
if (selectedApp) {
setSelectedQuota(selectedApp.quota);
}
setApplicationId(selectedId);
// Fetch subjects related to the application
await fetchSubjectIds(selectedId);
};
const handleInputChangeApplication = (newInputValue) => {
// Limit input to 15 characters for Application ID
if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue);
}
};
const fetchSubjectIds = async (appId) => {
setIsLoading(true);
try {
const response = await fetch(`${BASE_URL}/trx_face/list/subject?application_id=${appId}&search=${subjectId}&limit=10`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
const data = await response.json();
console.log("Fetched Subject IDs:", data); // Log data fetched from API
if (data.status_code === 200) {
setSubjectIds(data.details.data);
} else {
console.error('Failed to fetch subject IDs:', data.details.message);
}
} catch (error) {
console.error('Error fetching subject IDs:', error);
} finally {
setIsLoading(false);
}
};
const handleImageUpload = (file) => {
// Ensure the file is not undefined or null before accessing its properties
if (file && file.name) {
const fileExtension = file.name.split('.').pop().toUpperCase();
if (fileTypes.includes(fileExtension)) {
setSelectedImageName(file.name);
setFile(file);
setImageError(''); // Clear any previous errors
} else {
alert('Image format is not supported');
setImageError('Image format is not supported');
setFile(null);
}
} else {
console.error('No file selected or invalid file object.');
}
};
const handleImageCancel = () => {
setSelectedImageName('');
setFile(null);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
const handleEnrollClick = async () => {
let hasError = false; // Track if there are any errors
// Validate inputs and set corresponding errors
const validationErrors = {
imageError: !selectedImageName ? 'Please upload a face photo before enrolling.' : '',
applicationError: !applicationId ? 'Please select an Application ID before enrolling.' : '',
subjectError: !subjectId ? 'Please enter a Subject ID before enrolling.' : '',
};
// Update state with errors
if (validationErrors.imageError) {
setImageError(validationErrors.imageError);
hasError = true;
} else {
setImageError(''); // Clear error if valid
}
if (validationErrors.applicationError) {
setApplicationError(validationErrors.applicationError);
hasError = true;
} else {
setApplicationError(''); // Clear error if valid
}
if (validationErrors.subjectError) {
setSubjectError(validationErrors.subjectError);
hasError = true;
} else {
setSubjectError(''); // Clear error if valid
}
// If there are errors, return early
if (hasError) return;
if (!file) {
setImageError('No file selected. Please upload a valid image file.');
return;
}
const formData = new FormData();
formData.append('application_id', String(applicationId));
formData.append('subject_id', subjectId);
formData.append('file', file);
console.log('Inputs:', {
applicationId,
subjectId,
file: file.name,
});
setIsLoading(true);
setErrorMessage(''); // Clear previous error message
try {
const response = await fetch(`${BASE_URL}/face_recognition/enroll`, {
method: 'POST',
body: formData,
headers: {
'accept': 'application/json',
'x-api-key': `${API_KEY}`,
}
});
if (!response.ok) {
const errorDetails = await response.json();
console.error('Response error details:', errorDetails);
// Periksa jika detail error terkait dengan Subject ID
if (errorDetails.detail && errorDetails.detail.includes('Subject ID')) {
setSubjectError(errorDetails.detail); // Tampilkan error di bawah input Subject ID
} else {
setErrorMessage(errorDetails.detail || 'Failed to enroll, please try again');
}
return;
}
const result = await response.json();
console.log('Enrollment response:', result);
if (result.details && result.details.data && result.details.data.image_url) {
const imageFileName = result.details.data.image_url.split('/').pop();
console.log('Image URL:', result.details.data.image_url);
await fetchImage(imageFileName);
} else {
console.error('Image URL not found in response:', result);
setErrorMessage('Image URL not found in response. Please try again.');
}
setShowResult(true);
console.log('Enrollment successful:', result);
} catch (error) {
console.error('Error during API call:', error);
setErrorMessage('An unexpected error occurred. Please try again.');
} finally {
setIsLoading(false);
}
};
const fetchImage = async (imageFileName) => {
setIsLoading(true);
try {
const response = await fetch(`${BASE_URL}/preview/image/${imageFileName}`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
}
});
if (!response.ok) {
const errorDetails = await response.json();
console.error('Image fetch error details:', errorDetails);
setErrorMessage('Failed to fetch image, please try again.');
return;
}
const imageBlob = await response.blob();
const imageData = URL.createObjectURL(imageBlob);
console.log('Fetched image URL:', imageData);
setImageUrl(imageData);
} catch (error) {
console.error('Error fetching image:', error);
setErrorMessage(error.message);
} finally {
setIsLoading(false);
}
};
const CustomLabel = ({ overRide, children, ...props }) => {
// We intentionally don't pass `overRide` to the label
return (
<label {...props}>
{children}
</label>
);
};
const applicationOptions = applicationIds.map(app => ({
value: app.id,
label: app.name
}));
const handleSubjectIdChange = async (e) => {
const id = e.target.value;
setSubjectId(id);
console.log("Current Subject ID Input:", id); // Debugging: Log input
if (id) {
const exists = subjectIds.includes(id);
console.log("Subject IDs:", subjectIds); // Debugging: Log existing Subject IDs
if (exists) {
setSubjectAvailabilityMessage('Subject already exists.'); // Error message
setSubjectError(''); // Clear any subject error
} else {
setSubjectAvailabilityMessage('This subject ID is available.'); // Success message
setSubjectError('');
}
} else {
setSubjectAvailabilityMessage(''); // Clear message if input is empty
}
};
// Fungsi untuk mengonversi ukuran file dari byte ke KB/MB
const formatFileSize = (sizeInBytes) => {
if (sizeInBytes < 1024) {
return `${sizeInBytes} bytes`; // Jika ukuran lebih kecil dari 1 KB
} else if (sizeInBytes < 1048576) {
return `${(sizeInBytes / 1024).toFixed(2)} KB`; // Jika ukuran lebih kecil dari 1 MB
} else {
return `${(sizeInBytes / 1048576).toFixed(2)} MB`; // Jika ukuran lebih besar dari 1 MB
}
};
const styles = { const styles = {
// Existing styles // Existing styles
formGroup: { formGroup: {
@ -346,307 +646,6 @@ const Enroll = () => {
}, },
} }
useEffect(() => {
const fetchApplicationIds = async () => {
setIsLoading(true);
const url = `${BASE_URL}/application/list`;
try {
const response = await fetch(url, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
const data = await response.json();
if (data.status_code === 200) {
setApplicationIds(data.details.data);
} else {
console.error('Failed to fetch data:', data.details.message);
}
} catch (error) {
console.error('Error fetching application IDs:', error);
} finally {
setIsLoading(false);
}
};
fetchApplicationIds();
setOptions(subjectIds.map(id => ({ value: id, label: id })));
const handleResize = () => {
setIsMobile(window.innerWidth <= 768); // Deteksi apakah layar kecil (mobile)
};
window.addEventListener('resize', handleResize);
handleResize(); // Initial check
return () => window.removeEventListener('resize', handleResize);
}, [subjectIds]);
const handleApplicationChange = async (selectedOption) => {
const selectedId = selectedOption.value;
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
if (!selectedOption) {
console.error("Selected option is undefined");
return;
}
if (selectedApp) {
setSelectedQuota(selectedApp.quota);
}
setApplicationId(selectedId);
// Fetch subjects related to the application
await fetchSubjectIds(selectedId);
};
const handleInputChangeApplication = (newInputValue) => {
// Limit input to 15 characters for Application ID
if (newInputValue.length <= 15) {
setInputValueApplication(newInputValue);
}
};
const fetchSubjectIds = async (appId) => {
setIsLoading(true);
try {
const response = await fetch(`${BASE_URL}/trx_face/list/subject?application_id=${appId}&search=${subjectId}&limit=10`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
});
const data = await response.json();
console.log("Fetched Subject IDs:", data); // Log data fetched from API
if (data.status_code === 200) {
setSubjectIds(data.details.data);
} else {
console.error('Failed to fetch subject IDs:', data.details.message);
}
} catch (error) {
console.error('Error fetching subject IDs:', error);
} finally {
setIsLoading(false);
}
};
const handleImageUpload = (file) => {
// Ensure the file is not undefined or null before accessing its properties
if (file && file.name) {
const fileExtension = file.name.split('.').pop().toUpperCase();
if (fileTypes.includes(fileExtension)) {
setSelectedImageName(file.name);
setFile(file);
setImageError(''); // Clear any previous errors
} else {
alert('Image format is not supported');
setImageError('Image format is not supported');
setFile(null);
}
} else {
console.error('No file selected or invalid file object.');
}
};
const handleImageCancel = () => {
setSelectedImageName('');
setFile(null);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
};
const handleEnrollClick = async () => {
let hasError = false; // Track if there are any errors
// Validate inputs and set corresponding errors
const validationErrors = {
imageError: !selectedImageName ? 'Please upload a face photo before enrolling.' : '',
applicationError: !applicationId ? 'Please select an Application ID before enrolling.' : '',
subjectError: !subjectId ? 'Please enter a Subject ID before enrolling.' : '',
};
// Update state with errors
if (validationErrors.imageError) {
setImageError(validationErrors.imageError);
hasError = true;
} else {
setImageError(''); // Clear error if valid
}
if (validationErrors.applicationError) {
setApplicationError(validationErrors.applicationError);
hasError = true;
} else {
setApplicationError(''); // Clear error if valid
}
if (validationErrors.subjectError) {
setSubjectError(validationErrors.subjectError);
hasError = true;
} else {
setSubjectError(''); // Clear error if valid
}
// If there are errors, return early
if (hasError) return;
if (!file) {
setImageError('No file selected. Please upload a valid image file.');
return;
}
const formData = new FormData();
formData.append('application_id', String(applicationId));
formData.append('subject_id', subjectId);
formData.append('file', file);
console.log('Inputs:', {
applicationId,
subjectId,
file: file.name,
});
setIsLoading(true);
setErrorMessage(''); // Clear previous error message
try {
const response = await fetch(`${BASE_URL}/face_recognition/enroll`, {
method: 'POST',
body: formData,
headers: {
'accept': 'application/json',
'x-api-key': `${API_KEY}`,
}
});
if (!response.ok) {
const errorDetails = await response.json();
console.error('Response error details:', errorDetails);
// Periksa jika detail error terkait dengan Subject ID
if (errorDetails.detail && errorDetails.detail.includes('Subject ID')) {
setSubjectError(errorDetails.detail); // Tampilkan error di bawah input Subject ID
} else {
setErrorMessage(errorDetails.detail || 'Failed to enroll, please try again');
}
return;
}
const result = await response.json();
console.log('Enrollment response:', result);
if (result.details && result.details.data && result.details.data.image_url) {
const imageFileName = result.details.data.image_url.split('/').pop();
console.log('Image URL:', result.details.data.image_url);
await fetchImage(imageFileName);
} else {
console.error('Image URL not found in response:', result);
setErrorMessage('Image URL not found in response. Please try again.');
}
setShowResult(true);
console.log('Enrollment successful:', result);
} catch (error) {
console.error('Error during API call:', error);
setErrorMessage('An unexpected error occurred. Please try again.');
} finally {
setIsLoading(false);
}
};
const fetchImage = async (imageFileName) => {
setIsLoading(true);
try {
const response = await fetch(`${BASE_URL}/preview/image/${imageFileName}`, {
method: 'GET',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
}
});
if (!response.ok) {
const errorDetails = await response.json();
console.error('Image fetch error details:', errorDetails);
setErrorMessage('Failed to fetch image, please try again.');
return;
}
const imageBlob = await response.blob();
const imageData = URL.createObjectURL(imageBlob);
console.log('Fetched image URL:', imageData);
setImageUrl(imageData);
} catch (error) {
console.error('Error fetching image:', error);
setErrorMessage(error.message);
} finally {
setIsLoading(false);
}
};
const CustomLabel = ({ overRide, children, ...props }) => {
// We intentionally don't pass `overRide` to the label
return (
<label {...props}>
{children}
</label>
);
};
const applicationOptions = applicationIds.map(app => ({
value: app.id,
label: app.name
}));
const handleSubjectIdChange = async (e) => {
const id = e.target.value;
setSubjectId(id);
console.log("Current Subject ID Input:", id); // Debugging: Log input
if (id) {
const exists = subjectIds.includes(id);
console.log("Subject IDs:", subjectIds); // Debugging: Log existing Subject IDs
if (exists) {
setSubjectAvailabilityMessage('Subject already exists.'); // Error message
setSubjectError(''); // Clear any subject error
} else {
setSubjectAvailabilityMessage('This subject ID is available.'); // Success message
setSubjectError('');
}
} else {
setSubjectAvailabilityMessage(''); // Clear message if input is empty
}
};
// Fungsi untuk mengonversi ukuran file dari byte ke KB/MB
const formatFileSize = (sizeInBytes) => {
if (sizeInBytes < 1024) {
return `${sizeInBytes} bytes`; // Jika ukuran lebih kecil dari 1 KB
} else if (sizeInBytes < 1048576) {
return `${(sizeInBytes / 1024).toFixed(2)} KB`; // Jika ukuran lebih kecil dari 1 MB
} else {
return `${(sizeInBytes / 1048576).toFixed(2)} MB`; // Jika ukuran lebih besar dari 1 MB
}
};
return ( return (
<div> <div>
{/* Inject keyframes for the spinner */} {/* Inject keyframes for the spinner */}

View File

@ -1,124 +1,189 @@
import React, { useState, useRef, useEffect } from 'react' import React, { useState, useEffect, useRef } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronLeft, faChevronDown, faTimes, faImage } from '@fortawesome/free-solid-svg-icons';
import { DummyKtp } from '../../../assets/images'
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronDown, faChevronLeft, faImage, faTimes } from '@fortawesome/free-solid-svg-icons';
const Verify = () => { const Verify = () => {
const BASE_URL = process.env.REACT_APP_BASE_URL;
const BASE_URL = process.env.REACT_APP_BASE_URL const API_KEY = process.env.REACT_APP_API_KEY;
const API_KEY = process.env.REACT_APP_API_KEY const fileTypes = ["image/jpeg", "image/png"]; // Use MIME types for better file validation
const fileInputRef = useRef(null);
const [isSelectOpen, setIsSelectOpen] = useState(false); const [isSelectOpen, setIsSelectOpen] = useState(false);
const [errorMessage, setErrorMessage] = useState(''); const [errorMessage, setErrorMessage] = useState('');
const [selectedImageName, setSelectedImageName] = useState(''); const [selectedImageName, setSelectedImageName] = useState('');
const fileInputRef = useRef(null); const [file, setFile] = useState(null);
const [showResult, setShowResult] = useState(false);
const [applicationId, setApplicationId] = useState(''); const [applicationId, setApplicationId] = useState('');
const [imageUrl, setImageUrl] = useState('');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [applicationIds, setApplicationIds] = useState([]); const [applicationIds, setApplicationIds] = useState([]);
const [imageError, setImageError] = useState('');
const [data, setData] = useState(null);
const [showResult, setShowResult] = useState(false);
// Example usage: // Fetch Application IDs
const data = {
nik: "21710121748901",
district: "BATAM KOTA",
name: "HANDOKO",
city: "KOTA BATAM",
dob: "BANJARMASIN, 12-12-1974",
state: "PROVINSI KEPULAUAN RIAU",
gender: "LAKI-LAKI",
religion: "KRISTEN",
bloodType: "A",
maritalStatus: "KAWIN",
address: "GOLDEN LAND BLOK FN NO.39",
occupation: "WIRASWASTA",
rtRw: "002/013",
nationality: "WNI",
village: "TAMAN BALOI",
imageUrl: DummyKtp, // Replace this with the actual image path
dark: false,
blur: false,
grayscale: false,
flashlight: false,
};
useEffect(() => { useEffect(() => {
const fetchApplicationIds = async () => { const fetchApplicationIds = async () => {
try { try {
setIsLoading(true) setIsLoading(true);
const response = await fetch(`${BASE_URL}/application/list`, {
const url = `${BASE_URL}/application/list`;
console.log('Fetching URL:', url);
const response = await fetch(url, {
method: 'GET', method: 'GET',
headers: { headers: {
'accept': 'application/json', 'accept': 'application/json',
'x-api-key': `${API_KEY}`, 'x-api-key': API_KEY,
}, },
}); });
const data = await response.json(); if (!response.ok) {
throw new Error('Failed to fetch application IDs');
}
const data = await response.json();
if (data.status_code === 200) { if (data.status_code === 200) {
const ids = data.details.data.map(app => app.id);
console.log('Application Id: ' + ids);
setApplicationIds(data.details.data); setApplicationIds(data.details.data);
} else { } else {
console.error('Failed to fetch data:', data.details.message); throw new Error('Failed to fetch application IDs');
} }
} catch (error) { } catch (error) {
console.error('Error fetching application IDs:', error); setErrorMessage(error.message || 'Error fetching application IDs');
} finally { } finally {
setIsLoading(false) setIsLoading(false);
} }
}; };
fetchApplicationIds(); fetchApplicationIds();
}, []); }, []);
const handleFocus = () => { const handleFocus = () => setIsSelectOpen(true);
setIsSelectOpen(true); const handleBlur = () => setIsSelectOpen(false);
};
const handleBlur = () => { // Handle file upload
setIsSelectOpen(false); const handleImageUpload = (e) => {
}; const file = e.target.files[0];
if (!file) return;
const handleImageUpload = (event) => { const fileType = file.type; // Use MIME type instead of file extension
const file = event.target.files[0]; if (!fileTypes.includes(fileType)) {
setImageError('Image format is not supported');
if (file && (file.type === 'image/jpeg' || file.type === 'image/jpg')) { setFile(null);
setSelectedImageName(file.name); return;
setErrorMessage('');
} else {
alert('Please upload a valid image file (JPG, JPEG).');
} }
if (file.size > 2 * 1024 * 1024) { // 2MB check
setImageError('File size exceeds 2MB');
setFile(null);
return;
}
setSelectedImageName(file.name);
setFile(file);
setImageError('');
}; };
// Cancel file upload
const handleImageCancel = () => { const handleImageCancel = () => {
setSelectedImageName(''); setSelectedImageName('');
if (fileInputRef.current) { setFile(null);
setImageError('');
fileInputRef.current.value = ''; fileInputRef.current.value = '';
};
// Submit form and trigger OCR API
const handleCheckClick = async () => {
if (!file || !applicationId) {
setErrorMessage('Please select an application and upload a file.');
return;
}
setIsLoading(true);
const formData = new FormData();
formData.append('application_id', applicationId);
formData.append('file', file);
console.log(`id: ${applicationId}, file: ${file}`)
try {
const response = await fetch(`${BASE_URL}/ocr-ktp`, {
method: 'POST',
headers: {
'accept': 'application/json',
'x-api-key': API_KEY,
},
body: formData,
});
if (!response.ok) {
throw new Error('OCR processing failed');
}
const result = await response.json();
console.log('Full response:', result); // Log full response to inspect the structure
if (result.status_code === 201) {
console.log('Success');
// Correct the path to access the data-ktp object
const responseData = result.details.data?.['data-ktp'] || {};
// Log each field to inspect the data
console.log('NIK:', responseData.nik);
console.log('District:', responseData.kecamatan);
console.log('Name:', responseData.nama);
console.log('City:', responseData.kabkot);
console.log('Date of Birth:', responseData.tanggal_lahir);
console.log('State:', responseData.provinsi);
console.log('Gender:', responseData.jenis_kelamin);
console.log('Religion:', responseData.agama);
console.log('Blood Type:', responseData.golongan_darah);
console.log('Marital Status:', responseData.status_perkawinan);
console.log('Address:', responseData.alamat);
console.log('Occupation:', responseData.pekerjaan);
console.log('RT/RW:', `${responseData.rt}/${responseData.rw}`);
console.log('Nationality:', responseData.kewarganegaraan);
console.log('Image URL:', result.details.image_url);
console.log('Dark:', responseData.dark);
console.log('Blur:', responseData.blur);
console.log('Grayscale:', responseData.grayscale);
console.log('Flashlight:', responseData.flashlight);
// Map the response data to a new object with default values if the property doesn't exist
const data = {
nik: responseData.nik || 'N/A',
district: responseData.kecamatan || 'N/A',
name: responseData.nama || 'N/A',
city: responseData.kabkot || 'N/A',
dob: responseData.tanggal_lahir || 'N/A',
state: responseData.provinsi || 'N/A',
gender: responseData.jenis_kelamin || 'N/A',
religion: responseData.agama || 'N/A',
bloodType: responseData.golongan_darah || 'N/A',
maritalStatus: responseData.status_perkawinan || 'N/A',
address: responseData.alamat || 'N/A',
occupation: responseData.pekerjaan || 'N/A',
rtRw: `${responseData.rt || 'N/A'}/${responseData.rw || 'N/A'}`,
nationality: responseData.kewarganegaraan || 'N/A',
imageUrl: result.details.image_url || '',
dark: responseData.dark || 'N/A',
blur: responseData.blur || 'N/A',
grayscale: responseData.grayscale || 'N/A',
flashlight: responseData.flashlight || 'N/A',
};
setData(data);
setShowResult(true);
setErrorMessage('');
setSelectedImageName('');
} else {
setErrorMessage('OCR processing failed');
}
} catch (error) {
setErrorMessage(error.message || 'Error during OCR processing');
} finally {
setIsLoading(false);
} }
}; };
const handleCheckClick = async () => {
console.log('Verify - OCR Ktp')
setShowResult(true)
};
const getValueStyle = (value) => ({
color: value ? 'green' : 'red',
});
return ( return (
<div className='container' style={{ marginTop: '3%' }}> <div className="container" style={{ marginTop: '3%' }}>
{/* Inject keyframes for the spinner */}
<style> <style>
{` {`
@keyframes spin { @keyframes spin {
@ -127,26 +192,22 @@ const Verify = () => {
} }
`} `}
</style> </style>
{isLoading && ( {isLoading && (
<div style={styles.loadingOverlay}> <div style={styles.loadingOverlay}>
<div style={styles.spinner}></div> <div style={styles.spinner}></div>
<p style={styles.loadingText}>Loading...</p> <p style={styles.loadingText}>Loading...</p>
</div> </div>
)} )}
{/* Welcome Message */}
<div className="row-card border-left border-primary shadow mb-4" style={{ backgroundColor: '#E2FBEA' }}> <div className="row-card border-left border-primary shadow mb-4" style={{ backgroundColor: '#E2FBEA' }}>
<div className="d-flex flex-column justify-content-start align-items-start p-4"> <div className="d-flex flex-column justify-content-start align-items-start p-4">
<div> <div>
<h4 className="mb-3 text-start"> <h4 className="mb-3 text-start"><i className="fas fa-warning fa-bold me-3"></i>Alert</h4>
<i className="fas fa-warning fa-bold me-3"></i>Alert
</h4>
<p className="mb-0 text-start"> <p className="mb-0 text-start">
Get started now by creating an Application ID and explore all the demo services available on the dashboard. Get started now by creating an Application ID and explore all the demo services available on the dashboard.
Experience the ease and flexibility of trying out all our features firsthand. Experience the ease and flexibility of trying out all our features firsthand.
</p> </p>
</div> </div>
{/* Tombol di bawah teks */}
<div className="d-flex flex-row mt-3"> <div className="d-flex flex-row mt-3">
<Link to="/createApps" style={{ textDecoration: 'none' }}> <Link to="/createApps" style={{ textDecoration: 'none' }}>
<button className="btn d-flex justify-content-center align-items-center me-2" style={{ backgroundColor: '#0542CC' }}> <button className="btn d-flex justify-content-center align-items-center me-2" style={{ backgroundColor: '#0542CC' }}>
@ -158,18 +219,8 @@ const Verify = () => {
</div> </div>
</div> </div>
{/* Section */} <div style={{ padding: '20px', border: '0.1px solid rgba(0, 0, 0, 0.2)', borderLeft: '4px solid #0542cc', borderRadius: '10px', width: '100%' }}>
<div style={ <div className="form-group row align-items-center">
{
padding: '20px',
border: '0.1px solid rgba(0, 0, 0, 0.2)',
borderLeft: '4px solid #0542cc',
borderRadius: '10px',
width: '100%',
}
}>
{/* Application ID Selection */}
<div className="form-group row align-items-center"> {/* Added align-items-center for vertical alignment */}
<div className="col-md-6"> <div className="col-md-6">
<div style={styles.selectWrapper}> <div style={styles.selectWrapper}>
<select <select
@ -194,9 +245,8 @@ const Verify = () => {
/> />
</div> </div>
</div> </div>
<div className="col-md-6"> <div className="col-md-6">
<p className="text-secondary" style={{ fontSize: '16px', fontWeight: '400', margin: '0', marginTop: '8px' }}> {/* Adjusted margins */} <p className="text-secondary" style={{ fontSize: '16px', fontWeight: '400', marginTop: '8px' }}>
Remaining Quota Remaining Quota
</p> </p>
<div style={styles.remainingQuota}> <div style={styles.remainingQuota}>
@ -206,70 +256,53 @@ const Verify = () => {
</div> </div>
</div> </div>
{/* Upload Section */} <div className="col-md-6">
<div className='col-md-6'>
<div className="form-group mt-4"> <div className="form-group mt-4">
<label htmlFor="uploadPhoto" style={{ fontWeight: 600, fontSize: '14px', color: '#212529' }}>Upload your e-KTP Photo</label> <label htmlFor="imageInput" className="form-label">Upload Image (KTP)</label>
<div <div style={styles.uploadWrapper}>
style={styles.uploadArea}
onClick={() => document.getElementById('fileUpload').click()}
>
<i className="fas fa-cloud-upload-alt" style={styles.uploadIcon}></i>
<p style={styles.uploadText}>Drag and Drop Here</p>
<p>Or</p>
<a href="#">Browse</a>
<p className="text-muted">Recommended size: 300x300 (Max File Size: 2MB)</p>
<p className="text-muted">Supported file types: JPG, JPEG</p>
</div>
<input <input
type="file"
id="fileUpload"
ref={fileInputRef} ref={fileInputRef}
style={{ display: 'none' }} type="file"
accept="image/jpeg, image/png, image/jpg" id="imageInput"
className="form-control"
accept="image/jpeg, image/png"
onChange={handleImageUpload} onChange={handleImageUpload}
/> />
{errorMessage && (
<small style={styles.uploadError}>{errorMessage}</small>
)}
</div>
</div>
{/* Display uploaded image name */}
{selectedImageName && ( {selectedImageName && (
<div className="col-md-6 mt-4"> <div className="mt-3">
<div style={styles.fileWrapper}> <p><strong>File:</strong> {selectedImageName}</p>
<FontAwesomeIcon icon={faImage} style={styles.imageIcon} /> <button className="btn btn-danger" onClick={handleImageCancel}>
<div style={{ marginRight: '18rem', marginTop: '0.2rem' }}> <FontAwesomeIcon icon={faTimes} className="me-2" />Cancel
<h5>Uploaded File:</h5> </button>
<p>{selectedImageName}</p>
</div>
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
<FontAwesomeIcon
icon={faTimes}
style={styles.closeIcon}
onClick={handleImageCancel}
/>
</div>
</div>
</div> </div>
)} )}
{imageError && <p style={{ color: 'red' }}>{imageError}</p>}
</div>
</div>
</div>
{/* Submit Button */} <div className="col-md-12 d-flex justify-content-end mt-4">
<div style={styles.submitButton}> <button
<button onClick={handleCheckClick} className="btn d-flex justify-content-center align-items-center me-2" style={{ backgroundColor: '#0542CC' }}> className="btn btn-primary"
<p className="text-white mb-0">Check Now</p> onClick={handleCheckClick}
disabled={isLoading || !file || !applicationId}
>
<FontAwesomeIcon icon={faImage} className="me-2" />
Check KTP
</button> </button>
</div> </div>
{/* Result Section */} {errorMessage && (
{showResult && ( <div style={styles.errorContainer}>
<div style={{ display: 'flex', flexDirection: 'row', marginTop: '20px' }}> <p style={styles.errorText}>{errorMessage}</p>
<div style={{ flex: 1, marginRight: '10px' }}> </div>
<h1 style={{ color: '#0542cc' }}>Results</h1> )}
<div style={styles.resultContainer}> </div>
<div style={{ display: 'flex', flexDirection: 'row' }}>
<table style={{ ...styles.tableStyle, marginRight: '10px' }}> {showResult && data && (
<div className="mt-5">
<h4>OCR Result</h4>
<table style={styles.tableStyle}>
<tbody> <tbody>
<tr> <tr>
<td style={styles.tableCell}>NIK</td> <td style={styles.tableCell}>NIK</td>
@ -299,10 +332,6 @@ const Verify = () => {
<td style={styles.tableCell}>Gender</td> <td style={styles.tableCell}>Gender</td>
<td style={styles.tableCell}>{data.gender}</td> <td style={styles.tableCell}>{data.gender}</td>
</tr> </tr>
</tbody>
</table>
<table style={styles.tableStyle}>
<tbody>
<tr> <tr>
<td style={styles.tableCell}>Religion</td> <td style={styles.tableCell}>Religion</td>
<td style={styles.tableCell}>{data.religion}</td> <td style={styles.tableCell}>{data.religion}</td>
@ -331,181 +360,100 @@ const Verify = () => {
<td style={styles.tableCell}>Nationality</td> <td style={styles.tableCell}>Nationality</td>
<td style={styles.tableCell}>{data.nationality}</td> <td style={styles.tableCell}>{data.nationality}</td>
</tr> </tr>
<tr>
<td style={styles.tableCell}>Image</td>
<td style={styles.tableCell}><img src={data.imageUrl} alt="KTP" width="150" /></td>
</tr>
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
</div>
<div style={{ flex: 1, textAlign: 'center', marginTop: '3rem' }}>
<img
src={data.imageUrl}
alt="KTP"
style={{ width: '292px', height: '172px', borderRadius: '8px', marginBottom: '10px' }}
/>
<div style={{ width: '292px', display: 'flex', flexDirection: 'column', alignItems: 'flex-start', marginLeft: '8.2rem' }}>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', width: '100%' }}>
<p style={getValueStyle(data.dark)}>Dark: {data.dark.toString()}</p>
<p style={getValueStyle(data.blur)}>Blur: {data.blur.toString()}</p>
</div>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between', width: '100%' }}>
<p style={getValueStyle(data.grayscale)}>Grayscale: {data.grayscale.toString()}</p>
<p style={getValueStyle(data.flashlight)}>Flashlight: {data.flashlight.toString()}</p>
</div>
</div>
</div>
</div>
)} )}
</div> </div>
</div> );
) };
}
export default Verify
const styles = { const styles = {
formGroup: {
marginTop: '-45px',
},
selectWrapper: { selectWrapper: {
position: 'relative', position: 'relative',
marginTop: '0', // Adjusted to remove excessive spacing display: 'inline-block',
width: '100%',
}, },
select: { select: {
fontSize: '16px',
padding: '10px',
width: '100%', width: '100%',
paddingRight: '30px', // Ensures padding for the icon borderRadius: '4px',
border: '1px solid #ccc',
}, },
chevronIcon: { chevronIcon: {
position: 'absolute', position: 'absolute',
right: '10px',
top: '50%', top: '50%',
right: '10px',
transform: 'translateY(-50%)', transform: 'translateY(-50%)',
pointerEvents: 'none', color: '#0542cc',
}, },
remainingQuota: { remainingQuota: {
display: 'flex', // Ensures the text aligns properly display: 'flex',
flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
marginTop: '4px', // Adjust spacing from the label
}, },
quotaText: { quotaText: {
fontSize: '40px', fontSize: '24px',
color: '#0542cc',
fontWeight: '600', fontWeight: '600',
}, },
timesText: { timesText: {
marginLeft: '8px',
verticalAlign: 'super',
fontSize: '20px', // Adjust font size if necessary
},
uploadArea: {
backgroundColor: '#e6f2ff',
height: '250px',
cursor: 'pointer',
marginTop: '1rem',
paddingTop: '22px',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
border: '1px solid #ced4da',
borderRadius: '0.25rem',
},
uploadIcon: {
fontSize: '40px',
color: '#0542cc',
marginBottom: '7px',
},
uploadText: {
color: '#1f2d3d',
fontWeight: '400',
fontSize: '16px', fontSize: '16px',
lineHeight: '13px', fontWeight: '300',
}, },
fileWrapper: { uploadWrapper: {
backgroundColor: '#fff',
border: '0.2px solid gray',
padding: '15px 0 0 17px',
borderRadius: '5px',
position: 'relative', position: 'relative',
display: 'flex',
alignItems: 'center',
gap: '10px',
justifyContent: 'space-between',
}, },
fileInfo: { errorContainer: {
marginTop: '4rem', marginTop: '10px',
display: 'flex',
alignItems: 'center',
},
imageIcon: {
color: '#0542cc',
fontSize: '24px',
marginBottom: '1rem'
},
closeIcon: {
color: 'red',
cursor: 'pointer',
fontSize: '26px',
marginRight: '1rem',
marginBottom: '1rem'
},
submitButton: {
marginLeft: 'auto',
marginTop: '4rem',
textAlign: 'start',
position: 'relative', // Menambahkan posisi relative
zIndex: 1, // Menambah z-index jika ada elemen yang menutupi
},
uploadError: {
color: 'red',
fontSize: '12px',
marginTop: '5px',
},
containerResultStyle: {
padding: '20px',
border: '1px solid #ccc',
borderRadius: '8px',
backgroundColor: '#f9f9f9',
marginTop: '20px',
},
resultContainer: {
overflowX: 'auto', // Allows horizontal scrolling if the table is too wide
},
tableStyle: {
width: '100%',
borderCollapse: 'collapse', // Ensures that table borders are merged
},
tableCell: {
padding: '10px', padding: '10px',
border: '1px solid #ddd', // Light gray border around each cell backgroundColor: '#f8d7da',
textAlign: 'left', border: '1px solid #f5c6cb',
borderRadius: '4px',
},
errorText: {
color: '#721c24',
fontSize: '14px',
margin: '0',
}, },
loadingOverlay: { loadingOverlay: {
position: 'fixed', position: 'absolute',
top: 0, top: 0,
left: 0, left: 0,
right: 0, right: 0,
bottom: 0, bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.2)',
display: 'flex', display: 'flex',
flexDirection: 'column',
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
color: 'white',
fontSize: '24px',
zIndex: 1000, zIndex: 1000,
}, },
spinner: { spinner: {
border: '4px solid rgba(0, 0, 0, 0.1)', border: '4px solid #f3f3f3',
borderLeftColor: '#0542cc', borderTop: '4px solid #3498db',
borderRadius: '50%', borderRadius: '50%',
width: '90px', width: '50px',
height: '90px', height: '50px',
animation: 'spin 1s ease-in-out infinite', animation: 'spin 2s linear infinite',
}, },
loadingText: { loadingText: {
marginTop: '10px', marginLeft: '20px',
fontSize: '1.2rem', },
color: '#fff', tableStyle: {
textAlign: 'center', width: '100%',
borderCollapse: 'collapse',
marginTop: '20px',
},
tableCell: {
padding: '8px 15px',
border: '1px solid #ccc',
textAlign: 'left',
}, },
}; };
export default Verify;