From 895433ddee74f51b31342c056f4ce59d44060098 Mon Sep 17 00:00:00 2001 From: Rizqika Date: Fri, 6 Dec 2024 15:55:42 +0700 Subject: [PATCH] Clean Code - Enroll --- .../FaceRecognition/Section/Enroll.jsx | 625 ++++++------------ 1 file changed, 201 insertions(+), 424 deletions(-) diff --git a/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx b/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx index dd0ec6a..5cec571 100644 --- a/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx +++ b/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx @@ -1,60 +1,70 @@ import React, { useState, useRef, useEffect } from 'react' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTimes, faImage } from '@fortawesome/free-solid-svg-icons'; -import { FileUploader } from 'react-drag-drop-files'; +import { faTimes, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons'; import Select from 'react-select' import { ServerDownAnimation } from '../../../../assets/images'; const Enroll = () => { - const BASE_URL = process.env.REACT_APP_BASE_URL - const API_KEY = process.env.REACT_APP_API_KEY + // Environment variables + const BASE_URL = process.env.REACT_APP_BASE_URL; + const API_KEY = process.env.REACT_APP_API_KEY; - const fileTypes = ["JPG", "JPEG"]; + // State hooks const [file, setFile] = useState(null); - - const [errorMessage, setErrorMessage] = useState(''); + const [imageError, setImageError] = useState(''); const [selectedImageName, setSelectedImageName] = useState(''); - const [resultImageLabel, setresultImageLabel] = useState(""); - const fileInputRef = useRef(null); - const [showResult, setShowResult] = useState(false); - const [applicationId, setApplicationId] = useState(''); - const [applicationIds, setApplicationIds] = useState([]); - const [selectedQuota, setSelectedQuota] = useState(0); const [subjectId, setSubjectId] = useState(''); const [subjectIds, setSubjectIds] = useState([]); const [imageUrl, setImageUrl] = useState(''); const [isLoading, setIsLoading] = useState(false); + const [applicationId, setApplicationId] = useState(''); + const [applicationIds, setApplicationIds] = useState([]); + const [selectedQuota, setSelectedQuota] = useState(0); + const [showResult, setShowResult] = useState(false); + const [resultImageLabel, setresultImageLabel] = useState(""); + // Error messages + const [errorMessage, setErrorMessage] = useState(''); const [applicationError, setApplicationError] = useState(''); const [subjectError, setSubjectError] = useState(''); - const [imageError, setImageError] = useState(''); - const [subjectAvailabilityMessage, setSubjectAvailabilityMessage] = useState(''); // Message for subject availability + const [subjectAvailabilityMessage, setSubjectAvailabilityMessage] = useState(''); - const [inputValueApplication, setInputValueApplication] = useState(''); // Controlled input value for Application ID - const [options, setOptions] = useState([]); + // Controlled inputs + const [inputValueApplication, setInputValueApplication] = useState(''); + const [options, setOptions] = useState([]); + + // Mobile check const [isMobile, setIsMobile] = useState(false); + + // Server check const [isServer, setIsServer] = useState(true); + // Validation errors + const [validationErrors, setValidationErrors] = useState({ + applicationId: '', + file: '' + }); + + // Application Options + const applicationOptions = applicationIds.map(app => ({ + value: app.id, + label: app.name + })); + + const fileInputRef = useRef(null); + + // Fetch application IDs and subject options on component mount useEffect(() => { const fetchApplicationIds = async () => { + setIsLoading(true); try { - setIsLoading(true); const response = await fetch(`${BASE_URL}/application/list`, { method: 'GET', - headers: { - 'accept': 'application/json', - 'x-api-key': API_KEY, - }, + headers: { 'accept': 'application/json', 'x-api-key': API_KEY }, }); - - if (!response.ok) { - throw new Error('Failed to fetch application IDs'); - } - const data = await response.json(); - console.log('Response Data:', data); - + if (data.status_code === 200) { setApplicationIds(data.details.data); setIsServer(true); @@ -68,62 +78,50 @@ const Enroll = () => { } finally { setIsLoading(false); } - }; + }; fetchApplicationIds(); setOptions(subjectIds.map(id => ({ value: id, label: id }))); + // Handle mobile responsiveness const handleResize = () => { - setIsMobile(window.innerWidth <= 768); // Deteksi apakah layar kecil (mobile) + setIsMobile(window.innerWidth <= 768); }; - window.addEventListener('resize', handleResize); handleResize(); // Initial check return () => window.removeEventListener('resize', handleResize); - }, [subjectIds]); - + + // Handle application change 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); }; + // Handle input change for Application ID const handleInputChangeApplication = (newInputValue) => { - // Limit input to 15 characters for Application ID if (newInputValue.length <= 15) { setInputValueApplication(newInputValue); } }; - - + + // Fetch subject IDs based on application ID const fetchSubjectIds = async (appId) => { setIsLoading(true); try { const response = await fetch(`${BASE_URL}/trx_face/list/subject?application_id=${appId}&search=${subjectId}&limit=99`, { method: 'GET', - headers: { - 'accept': 'application/json', - 'x-api-key': API_KEY, - }, + 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 { @@ -135,210 +133,176 @@ const Enroll = () => { setIsLoading(false); } }; - + + // Handle Drop File Image + const handleFileDrop = (files) => { + if (files && files[0]) { + handleImageUpload(files[0]); + } else { + console.error('No valid files dropped'); + } + }; + + // Handle image file upload const handleImageUpload = (file) => { if (!file) { setImageError('Please select a file'); return; } - - // Check file size (2MB = 2 * 1024 * 1024 bytes) - const maxSize = 2 * 1024 * 1024; + + const maxSize = 2 * 1024 * 1024; // 2MB if (file.size > maxSize) { setImageError('File size exceeds 2MB limit'); - setFile(null); - setSelectedImageName(''); return; } - - // Check file type using both extension and MIME type + const fileExtension = file.name.split('.').pop().toLowerCase(); const validExtensions = ['jpg', 'jpeg']; const validMimeTypes = ['image/jpeg', 'image/jpg']; - + if (!validExtensions.includes(fileExtension) || !validMimeTypes.includes(file.type)) { setImageError('Only JPG/JPEG files are allowed'); - setFile(null); - setSelectedImageName(''); return; } - - // Check image dimensions + + const previewUrl = URL.createObjectURL(file); const img = new Image(); - const objectUrl = URL.createObjectURL(file); - img.onload = () => { - URL.revokeObjectURL(objectUrl); - - if (img.width > 300 || img.height > 300) { - setImageError('Image dimensions must not exceed 300x300 pixels'); - setFile(null); - setSelectedImageName(''); - return; - } - - // All validations passed - setSelectedImageName(file.name); setFile(file); + setSelectedImageName(file.name); + setImageUrl(previewUrl); setImageError(''); }; - + img.onerror = () => { - URL.revokeObjectURL(objectUrl); + URL.revokeObjectURL(previewUrl); setImageError('Invalid image file'); - setFile(null); - setSelectedImageName(''); }; - - img.src = objectUrl; + + img.src = previewUrl; }; - - + + // Cancel image upload const handleImageCancel = () => { setSelectedImageName(''); setFile(null); - if (fileInputRef.current) { - fileInputRef.current.value = ''; - } + if (fileInputRef.current) fileInputRef.current.value = ''; }; + + // Handle form validation and enrollment submission const handleEnrollClick = async () => { let hasError = false; - - // 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 - + setErrorMessage(''); + try { const response = await fetch(`${BASE_URL}/face_recognition/enroll`, { method: 'POST', body: formData, - headers: { - 'accept': 'application/json', - 'x-api-key': `${API_KEY}`, - } + headers: { 'accept': 'application/json', 'x-api-key': API_KEY }, }); - + if (!response.ok) { const errorDetails = await response.json(); - console.error('Response error details:', errorDetails); - if (errorDetails.detail && errorDetails.detail.includes('Subject ID')) { - setSubjectError(errorDetails.detail); - } else { - setErrorMessage(errorDetails.detail || 'Failed to enroll, please try again'); - } + setErrorMessage(errorDetails.detail || 'Failed to enroll, please try again'); return; } - + const result = await response.json(); - console.log('Enrollment response:', result); - - // Update quota based on the response data - if (result.details && result.details.data && result.details.data.quota !== undefined) { - const updatedQuota = result.details.data.quota; - setSelectedQuota(updatedQuota); // Update the state with the new quota + if (result.details.data.quota !== undefined) { + setSelectedQuota(result.details.data.quota); } - - 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); - - // Set resultImageLabel after successful enrollment - setresultImageLabel(selectedImageName); // Set resultImageLabel after success - } else { - console.error('Image URL not found in response:', result); - setErrorMessage('Image URL not found in response. Please try again.'); + + if (result.details.data.image_url) { + await fetchImage(result.details.data.image_url.split('/').pop()); + setresultImageLabel(selectedImageName); } - + 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); } - }; + }; + // Fetch image preview 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, - } + 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); + setImageUrl(URL.createObjectURL(imageBlob)); } catch (error) { - console.error('Error fetching image:', error); setErrorMessage(error.message); } finally { setIsLoading(false); } }; - + + // Handle subject ID change + const handleSubjectIdChange = (e) => { + const id = e.target.value; + setSubjectId(id); + + if (id) { + const exists = subjectIds.includes(id); + if (exists) { + setSubjectAvailabilityMessage('Subject already exists.'); + setSubjectError(''); + } else { + setSubjectAvailabilityMessage('This subject ID is available.'); + setSubjectError(''); + } + } else { + setSubjectAvailabilityMessage(''); + } + }; + + // handle Labeling const CustomLabel = ({ overRide, children, ...props }) => { // We intentionally don't pass `overRide` to the label return ( @@ -347,53 +311,35 @@ const Enroll = () => { ); }; - - 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 - } - }; + // Handle Server Down + if (!isServer) { + return ( +
+ Server Down Animation +

Server tidak dapat diakses

+

{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}

+ +
+ ); + } + const styles = { - formGroup: { - marginTop: '-45px', - }, - selectWrapper: { - position: 'relative', - marginTop: '0', - }, - select: { - width: '100%', - paddingRight: '30px', - }, - chevronIcon: { - position: 'absolute', - right: '10px', - top: '50%', - transform: 'translateY(-50%)', - pointerEvents: 'none', - }, remainingQuota: { display: 'flex', flexDirection: 'row', @@ -423,20 +369,6 @@ const Enroll = () => { border: '1px solid #ced4da', borderRadius: '0.25rem', }, - uploadAreaMobile: { - backgroundColor: '#e6f2ff', - height: '50svh', - cursor: 'pointer', - marginTop: '1rem', - paddingTop: '18px', - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - border: '1px solid #ced4da', - borderRadius: '0.25rem', - padding: '20px', - }, uploadIcon: { fontSize: '40px', color: '#0542cc', @@ -448,55 +380,10 @@ const Enroll = () => { fontSize: '16px', lineHeight: '13px', }, - wrapper: { - border: '1px solid #ddd', - borderRadius: '6px', - padding: '18px 10px 0 8px', - height: '13svh', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - backgroundColor: '#f9f9f9', - overflow: 'hidden', - }, - fileWrapper: { - display: 'flex', - alignItems: 'center', - flex: '1', - }, - textContainer: { - flex: '1', - fontSize: '16px', - marginLeft: '6px', - overflow: 'hidden', - whiteSpace: 'nowrap', - textOverflow: 'ellipsis', - marginTop: '1rem', - }, - fileSize: { - fontSize: '12px', - color: '#555', - marginBottom: '2rem', - }, - closeButtonContainer: { - display: 'flex', - alignItems: 'center', - marginLeft: 'auto', - }, - closeButton: { - background: 'transparent', - border: 'none', - cursor: 'pointer', - padding: '0', - }, - imageIcon: { - color: '#0542cc', - fontSize: '18px', - marginRight: '6px', - }, - closeIcon: { - color: 'red', - fontSize: '18px', + customLabel: { + fontWeight: 600, + fontSize: '14px', + color: '#212529', }, submitButton: { marginLeft: 'auto', @@ -505,10 +392,11 @@ const Enroll = () => { position: 'relative', zIndex: 1, }, - uploadError: { - color: 'red', + errorText: { + color: '#dc3545', fontSize: '12px', marginTop: '5px', + display: 'block' }, containerResultStyle: { padding: '20px', @@ -553,16 +441,6 @@ const Enroll = () => { width: '100%', marginTop: isMobile ? '10px' : '0', }, - imageStyle: { - width: '300px', - height: '300px', - borderRadius: '5px', - }, - imageStyleMobile: { - width: '100%', - height: 'auto', - borderRadius: '5px', - }, imageDetails: { marginTop: '10px', fontSize: isMobile ? '14px' : '16px', @@ -594,106 +472,8 @@ const Enroll = () => { fontSize: '1.2rem', color: '#fff', textAlign: 'center', - }, - uploadedFileWrapper: { - backgroundColor: '#fff', - border: '0.2px solid gray', - padding: '15px 0 0 17px', - borderRadius: '5px', - display: 'flex', - alignItems: 'center', - gap: '10px', - justifyContent: 'space-between', - }, - uploadedFileInfo: { - marginRight: '18rem', - marginTop: '0.2rem', - }, - uploadedFileText: { - fontSize: '16px', - color: '#1f2d3d', - }, - customLabel: { - fontWeight: 600, - fontSize: '14px', - color: '#212529', - }, - responsiveImageStyle: { - width: '100%', - maxHeight: '250px', - objectFit: 'cover', - marginTop: '20px', - }, - responsiveResultContainer: { - padding: '1rem', - border: '1px solid #ccc', - borderRadius: '8px', - marginTop: '20px', - }, - responsiveImageContainer: { - marginTop: '20px', - textAlign: 'center', - }, - responsiveSubmitButton: { - marginTop: '1rem', - }, - responsiveLoadingOverlay: { - position: 'absolute', - top: '0', - left: '0', - width: '100%', - height: '100%', - backgroundColor: 'rgba(0,0,0,0.5)', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - zIndex: '10', - }, - responsiveSpinner: { - border: '4px solid #f3f3f3', - borderTop: '4px solid #3498db', - borderRadius: '50%', - width: '50px', - height: '50px', - animation: 'spin 2s linear infinite', - }, - responsiveLoadingText: { - color: 'white', - marginTop: '10px', - }, - errorText: { - color: '#dc3545', - fontSize: '12px', - marginTop: '5px', - display: 'block' } }; - - if (!isServer) { - return ( -
- Server Down Animation -

Server tidak dapat diakses

-

{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}

- -
- ); - } return ( <> @@ -769,55 +549,55 @@ const Enroll = () => { -
-
- + {/* Image Upload */} +
+
+ Upload Face Photo - { - setImageError('Only JPG/JPEG files are allowed'); - setFile(null); - setSelectedImageName(''); - }} - onDrop={(files) => { - if (files && files[0]) { - handleImageUpload(files[0]); - } - }} - children={ -
- -

Drag and Drop Here

-

Or

- fileInputRef.current.click()}>Browse -

Recommended size: 300x300 (Max File Size: 2MB)

-

Supported file types: JPG, JPEG

-
- } - /> +
+ {/* Drag and Drop File Input */} +
{ + e.preventDefault(); + handleFileDrop(e.dataTransfer.files); + }} + onDragOver={(e) => e.preventDefault()} + > + +

Drag and Drop Here

+

Or

+ fileInputRef.current.click()} style={styles.browseLink}>Browse +

Recommended size: 320x200 (Max File Size: 2MB)

+

Supported file types: JPG, JPEG

+
- {imageError && ( - - {imageError} - - )} + {/* File Input */} + handleImageUpload(e.target.files[0])} + /> + + {selectedImageName && ( +
+

File: {selectedImageName}

+ +
+ )} + {/* Display validation errors */} + {validationErrors.file &&

{validationErrors.file}

} + {imageError &&

{imageError}

} +
- - {selectedImageName && ( -
-

File: {selectedImageName}

- -
- )} - {errorMessage && {errorMessage}}