diff --git a/package-lock.json b/package-lock.json index 4577617..a7c2c54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-drag-drop-files": "^2.4.0", + "react-icons": "^5.3.0", "react-router-dom": "^6.28.0", "react-scripts": "^5.0.1", "react-select": "^5.8.2", @@ -13472,6 +13473,14 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==" }, + "node_modules/react-icons": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.3.0.tgz", + "integrity": "sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index 7cc0f84..ba400aa 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-drag-drop-files": "^2.4.0", + "react-icons": "^5.3.0", "react-router-dom": "^6.28.0", "react-scripts": "^5.0.1", "react-select": "^5.8.2", diff --git a/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx b/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx index a50fcec..3c7bd55 100644 --- a/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx +++ b/src/screens/Biometric/FaceRecognition/Section/Enroll.jsx @@ -14,6 +14,7 @@ const Enroll = () => { const [errorMessage, setErrorMessage] = useState(''); const [selectedImageName, setSelectedImageName] = useState(''); + const [resultImageLabel, setresultImageLabel] = useState(""); const fileInputRef = useRef(null); const [showResult, setShowResult] = useState(false); const [applicationId, setApplicationId] = useState(''); @@ -102,7 +103,7 @@ const Enroll = () => { const fetchSubjectIds = async (appId) => { setIsLoading(true); try { - const response = await fetch(`${BASE_URL}/trx_face/list/subject?application_id=${appId}&search=${subjectId}&limit=10`, { + const response = await fetch(`${BASE_URL}/trx_face/list/subject?application_id=${appId}&search=${subjectId}&limit=99`, { method: 'GET', headers: { 'accept': 'application/json', @@ -153,15 +154,15 @@ const Enroll = () => { }; const handleEnrollClick = async () => { - let hasError = false; // Track if there are any errors - + 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); @@ -169,43 +170,43 @@ const Enroll = () => { } 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', @@ -215,31 +216,33 @@ const Enroll = () => { '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 + setSubjectError(errorDetails.detail); } 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); + + // 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.'); } - + setShowResult(true); console.log('Enrollment successful:', result); } catch (error) { @@ -248,10 +251,9 @@ const Enroll = () => { } finally { setIsLoading(false); } - }; - - const fetchImage = async (imageFileName) => { + }; + const fetchImage = async (imageFileName) => { setIsLoading(true); try { const response = await fetch(`${BASE_URL}/preview/image/${imageFileName}`, { @@ -261,30 +263,26 @@ const Enroll = () => { '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); + const imageData = URL.createObjectURL(imageBlob); console.log('Fetched image URL:', imageData); - - - - setImageUrl(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 @@ -772,7 +770,7 @@ const Enroll = () => { style={isMobile ? styles.imageStyleMobile : styles.imageStyle} />
- {selectedImageName} + {resultImageLabel} {/* Display resultImageLabel instead of selectedImageName */}
@@ -782,6 +780,7 @@ const Enroll = () => { ); } + export default Enroll; diff --git a/src/screens/Biometric/FaceRecognition/Section/Verify.jsx b/src/screens/Biometric/FaceRecognition/Section/Verify.jsx index 11ec051..43ac7a8 100644 --- a/src/screens/Biometric/FaceRecognition/Section/Verify.jsx +++ b/src/screens/Biometric/FaceRecognition/Section/Verify.jsx @@ -1,6 +1,6 @@ import React, { useState, useRef, useEffect } from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faChevronLeft, faChevronDown, faTimes, faImage } from '@fortawesome/free-solid-svg-icons'; +import { faChevronDown, faTimes } from '@fortawesome/free-solid-svg-icons'; import { FileUploader } from 'react-drag-drop-files'; import Select from 'react-select' @@ -11,13 +11,13 @@ const Verify = () => { const fileTypes = ["JPG", "JPEG", "PNG"]; const [file, setFile] = useState(null); - const [isSelectOpen, setIsSelectOpen] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const [uploadError, setUploadError] = useState(''); const [applicationError, setApplicationError] = useState(''); const [subjectError, setSubjectError] = useState(''); const [thresholdError, setThresholdError] = useState(''); const [selectedImageName, setSelectedImageName] = useState(''); + const [resultImageLabel, setResultImageLabel] = useState(''); const fileInputRef = useRef(null); const [showResult, setShowResult] = useState(false); const [applicationId, setApplicationId] = useState(''); @@ -39,103 +39,105 @@ const Verify = () => { { id: 3, name: 'euclidean_l2', displayName: 'High' }, ]; - const options = subjectIds.map(id => ({ value: id, label: id })); const [inputValue, setInputValue] = useState(''); + + const options = subjectIds.map(id => ({ + value: id, + label: id + })); + const applicationOptions = applicationIds.map(app => ({ value: app.id, label: app.name })); - useEffect(() => { - const fetchApplicationIds = async () => { - try { - setIsLoading(true); - const url = `${BASE_URL}/application/list`; - 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); - } + const fetchApplicationIds = async () => { + try { + const url = `${BASE_URL}/application/list`; + const response = await fetch(url, { + method: 'GET', + headers: { + 'accept': 'application/json', + 'x-api-key': `${API_KEY}`, + }, + }); + const data = await response.json(); + return data.details.data; // assuming the API returns an array of applications + } catch (error) { + console.error('Error fetching application IDs:', error); + return []; + } + }; + + // Sample function to fetch Subject IDs based on applicationId + const fetchSubjectIds = async (appId) => { + try { + const response = await fetch(`${BASE_URL}/trx_face/list/subject?application_id=${appId}&limit=99`, { + method: 'GET', + headers: { + 'accept': 'application/json', + 'x-api-key': `${API_KEY}`, + }, + }); + const data = await response.json(); + return data.details.data; // assuming the API returns an array of subjects + } catch (error) { + console.error('Error fetching subject IDs:', error); + return []; + } + }; + + useEffect(() => { + const fetchData = async () => { + setIsLoading(true); + const data = await fetchApplicationIds(); + setApplicationIds(data); + setIsLoading(false); }; - fetchApplicationIds(); + fetchData(); const handleResize = () => setIsMobile(window.innerWidth <= 768); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); - }, []); + }, []); // Empty dependency array, so this runs only once when the component mounts - const handleApplicationChange = async (selectedOption) => { - if (!selectedOption) { - console.error("Selected option is undefined"); - return; - } - - const selectedId = selectedOption.value; - const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId)); - - if (selectedApp) { - setSelectedQuota(selectedApp.quota); - } - - setApplicationId(selectedId); - - if (selectedId) { - await fetchSubjectIds(selectedId); - } else { - setSubjectIds([]); - setSubjectAvailabilityMessage(''); - } - }; - - - 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, - }, - }); - - 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 { + // Fetch Subject IDs when applicationId changes + useEffect(() => { + const fetchSubjects = async () => { + if (applicationId) { + setIsLoading(true); + const subjects = await fetchSubjectIds(applicationId); + setSubjectIds(subjects); setIsLoading(false); - } + } else { + setSubjectIds([]); // Clear subjects if no applicationId is selected + } + }; + + fetchSubjects(); + }, [applicationId]); // Runs whenever applicationId changes + + // Handler for changing applicationId + const handleApplicationChange = (selectedOption) => { + const selectedId = selectedOption.value; + const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId)); + + if (selectedApp) { + setSelectedQuota(selectedApp.quota); + } + + setApplicationId(selectedOption.value); // Update applicationId when user selects a new option }; + 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); - setUploadError(''); // Clear any previous errors + setUploadError(''); } else { alert('Image format is not supported'); setUploadError('Image format is not supported'); @@ -144,7 +146,7 @@ const Verify = () => { } else { console.error('No file selected or invalid file object.'); } - }; + }; const handleImageCancel = () => { @@ -165,23 +167,26 @@ const Verify = () => { let hasError = false; // Track if any errors occur + // Validate the applicationId if (!applicationId) { setApplicationError('Please select an Application ID before enrolling.'); hasError = true; // Mark that an error occurred } + // Validate the subjectId if (!subjectId) { setSubjectError('Please enter a Subject ID before enrolling.'); hasError = true; // Mark that an error occurred } + // Validate thresholdId const selectedThreshold = thresholdIds.find(threshold => threshold.name === thresholdId)?.name; - if (!selectedThreshold) { setThresholdError('Invalid threshold selected.'); hasError = true; // Mark that an error occurred } + // Validate image upload if (!selectedImageName) { setUploadError('Please upload a face photo before verifying.'); hasError = true; // Mark that an error occurred @@ -192,18 +197,18 @@ const Verify = () => { return; } - // Log the input values + // Log the input values for debugging console.log('Selected Image Name:', selectedImageName); console.log('Application ID:', applicationId); console.log('Subject ID:', subjectId); console.log('Selected Threshold:', selectedThreshold); + // Prepare FormData for the API request const formData = new FormData(); formData.append('application_id', applicationId); formData.append('threshold', selectedThreshold); formData.append('subject_id', subjectId); - // const file = fileInputRef.current.files[0]; if (file) { formData.append('file', file, file.name); } else { @@ -218,7 +223,7 @@ const Verify = () => { method: 'POST', headers: { 'accept': 'application/json', - 'x-api-key': `${API_KEY}`, + 'x-api-key': API_KEY, }, body: formData, }); @@ -233,6 +238,7 @@ const Verify = () => { setShowResult(true); setVerified(data.details.data.result.verified); + setResultImageLabel(selectedImageName); } else { const errorMessage = data.message || data.detail || data.details?.message || 'An unknown error occurred.'; setErrorMessage(errorMessage); @@ -245,7 +251,6 @@ const Verify = () => { } }; - const fetchImage = async (imageFileName) => { setIsLoading(true); try { @@ -603,178 +608,171 @@ const Verify = () => { {isLoading && (Loading...
+ +Loading...
+ Remaining Quota +
+- Remaining Quota -
-File: {selectedImageName}
+ {file && ( ++ Size: {formatFileSize(file.size)} +
+ )} + +Drag and Drop Here
-Or
- fileInputRef.current.click()}>Browse -Recommended size: 300x300 (Max File Size: 2MB)
-Supported file types: JPG, JPEG
-File: {selectedImageName}
- {file && ( -- Size: {formatFileSize(file.size)} -
- )} -