Clean Code - Enroll
This commit is contained in:
parent
1ede4324fa
commit
895433ddee
@ -1,59 +1,69 @@
|
|||||||
import React, { useState, useRef, useEffect } from 'react'
|
import React, { useState, useRef, useEffect } from 'react'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faTimes, faImage } from '@fortawesome/free-solid-svg-icons';
|
import { faTimes, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FileUploader } from 'react-drag-drop-files';
|
|
||||||
import Select from 'react-select'
|
import Select from 'react-select'
|
||||||
import { ServerDownAnimation } from '../../../../assets/images';
|
import { ServerDownAnimation } from '../../../../assets/images';
|
||||||
|
|
||||||
const Enroll = () => {
|
const Enroll = () => {
|
||||||
|
|
||||||
const BASE_URL = process.env.REACT_APP_BASE_URL
|
// Environment variables
|
||||||
const API_KEY = process.env.REACT_APP_API_KEY
|
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 [file, setFile] = useState(null);
|
||||||
|
const [imageError, setImageError] = useState('');
|
||||||
const [errorMessage, setErrorMessage] = useState('');
|
|
||||||
const [selectedImageName, setSelectedImageName] = 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 [subjectId, setSubjectId] = useState('');
|
||||||
const [subjectIds, setSubjectIds] = useState([]);
|
const [subjectIds, setSubjectIds] = useState([]);
|
||||||
const [imageUrl, setImageUrl] = useState('');
|
const [imageUrl, setImageUrl] = useState('');
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
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 [applicationError, setApplicationError] = useState('');
|
||||||
const [subjectError, setSubjectError] = useState('');
|
const [subjectError, setSubjectError] = useState('');
|
||||||
const [imageError, setImageError] = useState('');
|
const [subjectAvailabilityMessage, setSubjectAvailabilityMessage] = useState('');
|
||||||
const [subjectAvailabilityMessage, setSubjectAvailabilityMessage] = useState(''); // Message for subject availability
|
|
||||||
|
|
||||||
const [inputValueApplication, setInputValueApplication] = useState(''); // Controlled input value for Application ID
|
// Controlled inputs
|
||||||
|
const [inputValueApplication, setInputValueApplication] = useState('');
|
||||||
const [options, setOptions] = useState([]);
|
const [options, setOptions] = useState([]);
|
||||||
|
|
||||||
|
// Mobile check
|
||||||
const [isMobile, setIsMobile] = useState(false);
|
const [isMobile, setIsMobile] = useState(false);
|
||||||
|
|
||||||
|
// Server check
|
||||||
const [isServer, setIsServer] = useState(true);
|
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(() => {
|
useEffect(() => {
|
||||||
const fetchApplicationIds = async () => {
|
const fetchApplicationIds = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
|
||||||
const response = await fetch(`${BASE_URL}/application/list`, {
|
const response = await fetch(`${BASE_URL}/application/list`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: { 'accept': 'application/json', 'x-api-key': API_KEY },
|
||||||
'accept': 'application/json',
|
|
||||||
'x-api-key': API_KEY,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Failed to fetch application IDs');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log('Response Data:', data);
|
|
||||||
|
|
||||||
if (data.status_code === 200) {
|
if (data.status_code === 200) {
|
||||||
setApplicationIds(data.details.data);
|
setApplicationIds(data.details.data);
|
||||||
@ -73,56 +83,44 @@ const Enroll = () => {
|
|||||||
fetchApplicationIds();
|
fetchApplicationIds();
|
||||||
setOptions(subjectIds.map(id => ({ value: id, label: id })));
|
setOptions(subjectIds.map(id => ({ value: id, label: id })));
|
||||||
|
|
||||||
|
// Handle mobile responsiveness
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
setIsMobile(window.innerWidth <= 768); // Deteksi apakah layar kecil (mobile)
|
setIsMobile(window.innerWidth <= 768);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.addEventListener('resize', handleResize);
|
window.addEventListener('resize', handleResize);
|
||||||
handleResize(); // Initial check
|
handleResize(); // Initial check
|
||||||
|
|
||||||
return () => window.removeEventListener('resize', handleResize);
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
|
||||||
}, [subjectIds]);
|
}, [subjectIds]);
|
||||||
|
|
||||||
|
// Handle application change
|
||||||
const handleApplicationChange = async (selectedOption) => {
|
const handleApplicationChange = async (selectedOption) => {
|
||||||
const selectedId = selectedOption.value;
|
const selectedId = selectedOption.value;
|
||||||
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
|
const selectedApp = applicationIds.find(app => app.id === parseInt(selectedId));
|
||||||
|
|
||||||
if (!selectedOption) {
|
|
||||||
console.error("Selected option is undefined");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (selectedApp) {
|
if (selectedApp) {
|
||||||
setSelectedQuota(selectedApp.quota);
|
setSelectedQuota(selectedApp.quota);
|
||||||
}
|
}
|
||||||
|
|
||||||
setApplicationId(selectedId);
|
setApplicationId(selectedId);
|
||||||
|
|
||||||
// Fetch subjects related to the application
|
|
||||||
await fetchSubjectIds(selectedId);
|
await fetchSubjectIds(selectedId);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Handle input change for Application ID
|
||||||
const handleInputChangeApplication = (newInputValue) => {
|
const handleInputChangeApplication = (newInputValue) => {
|
||||||
// Limit input to 15 characters for Application ID
|
|
||||||
if (newInputValue.length <= 15) {
|
if (newInputValue.length <= 15) {
|
||||||
setInputValueApplication(newInputValue);
|
setInputValueApplication(newInputValue);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fetch subject IDs based on application ID
|
||||||
const fetchSubjectIds = async (appId) => {
|
const fetchSubjectIds = async (appId) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${BASE_URL}/trx_face/list/subject?application_id=${appId}&search=${subjectId}&limit=99`, {
|
const response = await fetch(`${BASE_URL}/trx_face/list/subject?application_id=${appId}&search=${subjectId}&limit=99`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: { 'accept': 'application/json', 'x-api-key': API_KEY },
|
||||||
'accept': 'application/json',
|
|
||||||
'x-api-key': API_KEY,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
console.log("Fetched Subject IDs:", data); // Log data fetched from API
|
|
||||||
|
|
||||||
if (data.status_code === 200) {
|
if (data.status_code === 200) {
|
||||||
setSubjectIds(data.details.data);
|
setSubjectIds(data.details.data);
|
||||||
@ -136,105 +134,86 @@ const Enroll = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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) => {
|
const handleImageUpload = (file) => {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
setImageError('Please select a file');
|
setImageError('Please select a file');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check file size (2MB = 2 * 1024 * 1024 bytes)
|
const maxSize = 2 * 1024 * 1024; // 2MB
|
||||||
const maxSize = 2 * 1024 * 1024;
|
|
||||||
if (file.size > maxSize) {
|
if (file.size > maxSize) {
|
||||||
setImageError('File size exceeds 2MB limit');
|
setImageError('File size exceeds 2MB limit');
|
||||||
setFile(null);
|
|
||||||
setSelectedImageName('');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check file type using both extension and MIME type
|
|
||||||
const fileExtension = file.name.split('.').pop().toLowerCase();
|
const fileExtension = file.name.split('.').pop().toLowerCase();
|
||||||
const validExtensions = ['jpg', 'jpeg'];
|
const validExtensions = ['jpg', 'jpeg'];
|
||||||
const validMimeTypes = ['image/jpeg', 'image/jpg'];
|
const validMimeTypes = ['image/jpeg', 'image/jpg'];
|
||||||
|
|
||||||
if (!validExtensions.includes(fileExtension) || !validMimeTypes.includes(file.type)) {
|
if (!validExtensions.includes(fileExtension) || !validMimeTypes.includes(file.type)) {
|
||||||
setImageError('Only JPG/JPEG files are allowed');
|
setImageError('Only JPG/JPEG files are allowed');
|
||||||
setFile(null);
|
|
||||||
setSelectedImageName('');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check image dimensions
|
const previewUrl = URL.createObjectURL(file);
|
||||||
const img = new Image();
|
const img = new Image();
|
||||||
const objectUrl = URL.createObjectURL(file);
|
|
||||||
|
|
||||||
img.onload = () => {
|
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);
|
setFile(file);
|
||||||
|
setSelectedImageName(file.name);
|
||||||
|
setImageUrl(previewUrl);
|
||||||
setImageError('');
|
setImageError('');
|
||||||
};
|
};
|
||||||
|
|
||||||
img.onerror = () => {
|
img.onerror = () => {
|
||||||
URL.revokeObjectURL(objectUrl);
|
URL.revokeObjectURL(previewUrl);
|
||||||
setImageError('Invalid image file');
|
setImageError('Invalid image file');
|
||||||
setFile(null);
|
|
||||||
setSelectedImageName('');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
img.src = objectUrl;
|
img.src = previewUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cancel image upload
|
||||||
const handleImageCancel = () => {
|
const handleImageCancel = () => {
|
||||||
setSelectedImageName('');
|
setSelectedImageName('');
|
||||||
setFile(null);
|
setFile(null);
|
||||||
if (fileInputRef.current) {
|
if (fileInputRef.current) fileInputRef.current.value = '';
|
||||||
fileInputRef.current.value = '';
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Handle form validation and enrollment submission
|
||||||
const handleEnrollClick = async () => {
|
const handleEnrollClick = async () => {
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
|
|
||||||
// Validate inputs and set corresponding errors
|
|
||||||
const validationErrors = {
|
const validationErrors = {
|
||||||
imageError: !selectedImageName ? 'Please upload a face photo before enrolling.' : '',
|
imageError: !selectedImageName ? 'Please upload a face photo before enrolling.' : '',
|
||||||
applicationError: !applicationId ? 'Please select an Application ID before enrolling.' : '',
|
applicationError: !applicationId ? 'Please select an Application ID before enrolling.' : '',
|
||||||
subjectError: !subjectId ? 'Please enter a Subject ID before enrolling.' : '',
|
subjectError: !subjectId ? 'Please enter a Subject ID before enrolling.' : '',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Update state with errors
|
|
||||||
if (validationErrors.imageError) {
|
if (validationErrors.imageError) {
|
||||||
setImageError(validationErrors.imageError);
|
setImageError(validationErrors.imageError);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
} else {
|
|
||||||
setImageError(''); // Clear error if valid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validationErrors.applicationError) {
|
if (validationErrors.applicationError) {
|
||||||
setApplicationError(validationErrors.applicationError);
|
setApplicationError(validationErrors.applicationError);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
} else {
|
|
||||||
setApplicationError(''); // Clear error if valid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (validationErrors.subjectError) {
|
if (validationErrors.subjectError) {
|
||||||
setSubjectError(validationErrors.subjectError);
|
setSubjectError(validationErrors.subjectError);
|
||||||
hasError = true;
|
hasError = true;
|
||||||
} else {
|
|
||||||
setSubjectError(''); // Clear error if valid
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are errors, return early
|
|
||||||
if (hasError) return;
|
if (hasError) return;
|
||||||
|
|
||||||
if (!file) {
|
if (!file) {
|
||||||
@ -247,98 +226,83 @@ const Enroll = () => {
|
|||||||
formData.append('subject_id', subjectId);
|
formData.append('subject_id', subjectId);
|
||||||
formData.append('file', file);
|
formData.append('file', file);
|
||||||
|
|
||||||
console.log('Inputs:', {
|
|
||||||
applicationId,
|
|
||||||
subjectId,
|
|
||||||
file: file.name,
|
|
||||||
});
|
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setErrorMessage(''); // Clear previous error message
|
setErrorMessage('');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${BASE_URL}/face_recognition/enroll`, {
|
const response = await fetch(`${BASE_URL}/face_recognition/enroll`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
headers: {
|
headers: { 'accept': 'application/json', 'x-api-key': API_KEY },
|
||||||
'accept': 'application/json',
|
|
||||||
'x-api-key': `${API_KEY}`,
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorDetails = await response.json();
|
const errorDetails = await response.json();
|
||||||
console.error('Response error details:', errorDetails);
|
setErrorMessage(errorDetails.detail || 'Failed to enroll, please try again');
|
||||||
if (errorDetails.detail && errorDetails.detail.includes('Subject ID')) {
|
|
||||||
setSubjectError(errorDetails.detail);
|
|
||||||
} else {
|
|
||||||
setErrorMessage(errorDetails.detail || 'Failed to enroll, please try again');
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
console.log('Enrollment response:', result);
|
if (result.details.data.quota !== undefined) {
|
||||||
|
setSelectedQuota(result.details.data.quota);
|
||||||
// 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 && result.details.data && result.details.data.image_url) {
|
if (result.details.data.image_url) {
|
||||||
const imageFileName = result.details.data.image_url.split('/').pop();
|
await fetchImage(result.details.data.image_url.split('/').pop());
|
||||||
console.log('Image URL:', result.details.data.image_url);
|
setresultImageLabel(selectedImageName);
|
||||||
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);
|
setShowResult(true);
|
||||||
console.log('Enrollment successful:', result);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error during API call:', error);
|
|
||||||
setErrorMessage('An unexpected error occurred. Please try again.');
|
setErrorMessage('An unexpected error occurred. Please try again.');
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Fetch image preview
|
||||||
const fetchImage = async (imageFileName) => {
|
const fetchImage = async (imageFileName) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${BASE_URL}/preview/image/${imageFileName}`, {
|
const response = await fetch(`${BASE_URL}/preview/image/${imageFileName}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: { 'accept': 'application/json', 'x-api-key': API_KEY },
|
||||||
'accept': 'application/json',
|
|
||||||
'x-api-key': API_KEY,
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errorDetails = await response.json();
|
|
||||||
console.error('Image fetch error details:', errorDetails);
|
|
||||||
setErrorMessage('Failed to fetch image, please try again.');
|
setErrorMessage('Failed to fetch image, please try again.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageBlob = await response.blob();
|
const imageBlob = await response.blob();
|
||||||
const imageData = URL.createObjectURL(imageBlob);
|
setImageUrl(URL.createObjectURL(imageBlob));
|
||||||
console.log('Fetched image URL:', imageData);
|
|
||||||
|
|
||||||
setImageUrl(imageData);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching image:', error);
|
|
||||||
setErrorMessage(error.message);
|
setErrorMessage(error.message);
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
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 }) => {
|
const CustomLabel = ({ overRide, children, ...props }) => {
|
||||||
// We intentionally don't pass `overRide` to the label
|
// We intentionally don't pass `overRide` to the label
|
||||||
return (
|
return (
|
||||||
@ -348,52 +312,34 @@ const Enroll = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const applicationOptions = applicationIds.map(app => ({
|
// Handle Server Down
|
||||||
value: app.id,
|
if (!isServer) {
|
||||||
label: app.name
|
return (
|
||||||
}));
|
<div style={{ textAlign: 'center', marginTop: '50px' }}>
|
||||||
|
<img
|
||||||
const handleSubjectIdChange = async (e) => {
|
src={ServerDownAnimation}
|
||||||
const id = e.target.value;
|
alt="Server Down Animation"
|
||||||
setSubjectId(id);
|
style={{ width: '18rem', height: '18rem', marginBottom: '20px' }}
|
||||||
|
/>
|
||||||
console.log("Current Subject ID Input:", id); // Debugging: Log input
|
<h2 style={{ color: 'red' }}>Server tidak dapat diakses</h2>
|
||||||
|
<p>{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}</p>
|
||||||
if (id) {
|
<button
|
||||||
const exists = subjectIds.includes(id);
|
onClick={() => window.location.reload()}
|
||||||
console.log("Subject IDs:", subjectIds); // Debugging: Log existing Subject IDs
|
style={{
|
||||||
|
padding: '10px 20px',
|
||||||
if (exists) {
|
backgroundColor: '#0542cc',
|
||||||
setSubjectAvailabilityMessage('Subject already exists.'); // Error message
|
color: '#fff',
|
||||||
setSubjectError(''); // Clear any subject error
|
border: 'none',
|
||||||
} else {
|
borderRadius: '5px',
|
||||||
setSubjectAvailabilityMessage('This subject ID is available.'); // Success message
|
cursor: 'pointer'
|
||||||
setSubjectError('');
|
}}>
|
||||||
}
|
Coba Lagi
|
||||||
} else {
|
</button>
|
||||||
setSubjectAvailabilityMessage(''); // Clear message if input is empty
|
</div>
|
||||||
}
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
const styles = {
|
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: {
|
remainingQuota: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
@ -423,20 +369,6 @@ const Enroll = () => {
|
|||||||
border: '1px solid #ced4da',
|
border: '1px solid #ced4da',
|
||||||
borderRadius: '0.25rem',
|
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: {
|
uploadIcon: {
|
||||||
fontSize: '40px',
|
fontSize: '40px',
|
||||||
color: '#0542cc',
|
color: '#0542cc',
|
||||||
@ -448,55 +380,10 @@ const Enroll = () => {
|
|||||||
fontSize: '16px',
|
fontSize: '16px',
|
||||||
lineHeight: '13px',
|
lineHeight: '13px',
|
||||||
},
|
},
|
||||||
wrapper: {
|
customLabel: {
|
||||||
border: '1px solid #ddd',
|
fontWeight: 600,
|
||||||
borderRadius: '6px',
|
fontSize: '14px',
|
||||||
padding: '18px 10px 0 8px',
|
color: '#212529',
|
||||||
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',
|
|
||||||
},
|
},
|
||||||
submitButton: {
|
submitButton: {
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
@ -505,10 +392,11 @@ const Enroll = () => {
|
|||||||
position: 'relative',
|
position: 'relative',
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
},
|
},
|
||||||
uploadError: {
|
errorText: {
|
||||||
color: 'red',
|
color: '#dc3545',
|
||||||
fontSize: '12px',
|
fontSize: '12px',
|
||||||
marginTop: '5px',
|
marginTop: '5px',
|
||||||
|
display: 'block'
|
||||||
},
|
},
|
||||||
containerResultStyle: {
|
containerResultStyle: {
|
||||||
padding: '20px',
|
padding: '20px',
|
||||||
@ -553,16 +441,6 @@ const Enroll = () => {
|
|||||||
width: '100%',
|
width: '100%',
|
||||||
marginTop: isMobile ? '10px' : '0',
|
marginTop: isMobile ? '10px' : '0',
|
||||||
},
|
},
|
||||||
imageStyle: {
|
|
||||||
width: '300px',
|
|
||||||
height: '300px',
|
|
||||||
borderRadius: '5px',
|
|
||||||
},
|
|
||||||
imageStyleMobile: {
|
|
||||||
width: '100%',
|
|
||||||
height: 'auto',
|
|
||||||
borderRadius: '5px',
|
|
||||||
},
|
|
||||||
imageDetails: {
|
imageDetails: {
|
||||||
marginTop: '10px',
|
marginTop: '10px',
|
||||||
fontSize: isMobile ? '14px' : '16px',
|
fontSize: isMobile ? '14px' : '16px',
|
||||||
@ -594,107 +472,9 @@ const Enroll = () => {
|
|||||||
fontSize: '1.2rem',
|
fontSize: '1.2rem',
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
textAlign: 'center',
|
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 (
|
|
||||||
<div style={{ textAlign: 'center', marginTop: '50px' }}>
|
|
||||||
<img
|
|
||||||
src={ServerDownAnimation}
|
|
||||||
alt="Server Down Animation"
|
|
||||||
style={{ width: '18rem', height: '18rem', marginBottom: '20px' }}
|
|
||||||
/>
|
|
||||||
<h2 style={{ color: 'red' }}>Server tidak dapat diakses</h2>
|
|
||||||
<p>{errorMessage || 'Silakan periksa koneksi internet Anda atau coba lagi nanti.'}</p>
|
|
||||||
<button
|
|
||||||
onClick={() => window.location.reload()}
|
|
||||||
style={{
|
|
||||||
padding: '10px 20px',
|
|
||||||
backgroundColor: '#0542cc',
|
|
||||||
color: '#fff',
|
|
||||||
border: 'none',
|
|
||||||
borderRadius: '5px',
|
|
||||||
cursor: 'pointer'
|
|
||||||
}}>
|
|
||||||
Coba Lagi
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<style>
|
<style>
|
||||||
@ -769,56 +549,56 @@ const Enroll = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='col-md-6'>
|
{/* Image Upload */}
|
||||||
<div className="row form-group mt-4">
|
<div className="col-md-6">
|
||||||
<CustomLabel htmlFor="uploadPhoto" style={styles.customLabel}>
|
<div className="form-group mt-4">
|
||||||
|
<CustomLabel htmlFor="imageInput" style={styles.customLabel}>
|
||||||
Upload Face Photo
|
Upload Face Photo
|
||||||
</CustomLabel>
|
</CustomLabel>
|
||||||
<FileUploader
|
<div style={styles.uploadWrapper}>
|
||||||
handleChange={handleImageUpload}
|
{/* Drag and Drop File Input */}
|
||||||
name="file"
|
<div
|
||||||
types={fileTypes}
|
style={styles.uploadArea}
|
||||||
multiple={false}
|
onDrop={(e) => {
|
||||||
onTypeError={(err) => {
|
e.preventDefault();
|
||||||
setImageError('Only JPG/JPEG files are allowed');
|
handleFileDrop(e.dataTransfer.files);
|
||||||
setFile(null);
|
}}
|
||||||
setSelectedImageName('');
|
onDragOver={(e) => e.preventDefault()}
|
||||||
}}
|
>
|
||||||
onDrop={(files) => {
|
<FontAwesomeIcon icon={faCloudUploadAlt} style={styles.uploadIcon} />
|
||||||
if (files && files[0]) {
|
<p style={styles.uploadText}>Drag and Drop Here</p>
|
||||||
handleImageUpload(files[0]);
|
<p>Or</p>
|
||||||
}
|
<a href="#" onClick={() => fileInputRef.current.click()} style={styles.browseLink}>Browse</a>
|
||||||
}}
|
<p className="text-muted" style={styles.uploadText}>Recommended size: 320x200 (Max File Size: 2MB)</p>
|
||||||
children={
|
<p className="text-muted" style={styles.uploadText}>Supported file types: JPG, JPEG</p>
|
||||||
<div style={isMobile ? styles.uploadAreaMobile : styles.uploadArea}>
|
</div>
|
||||||
<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="#" onClick={() => fileInputRef.current.click()}>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>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{imageError && (
|
{/* File Input */}
|
||||||
<small className="text-danger mt-2" style={{ fontSize: '12px' }}>
|
<input
|
||||||
{imageError}
|
ref={fileInputRef}
|
||||||
</small>
|
type="file"
|
||||||
)}
|
id="imageInput"
|
||||||
|
className="form-control"
|
||||||
|
style={{ display: 'none' }}
|
||||||
|
accept="image/jpeg, image/jpg"
|
||||||
|
onChange={(e) => handleImageUpload(e.target.files[0])}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{selectedImageName && (
|
||||||
|
<div className="mt-3">
|
||||||
|
<p><strong>File:</strong> {selectedImageName}</p>
|
||||||
|
<button className="btn btn-danger" onClick={handleImageCancel}>
|
||||||
|
<FontAwesomeIcon icon={faTimes} className="me-2" />Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* Display validation errors */}
|
||||||
|
{validationErrors.file && <p style={styles.errorText}>{validationErrors.file}</p>}
|
||||||
|
{imageError && <p style={styles.errorText}>{imageError}</p>}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{selectedImageName && (
|
|
||||||
<div className="mt-3">
|
|
||||||
<p><strong>File:</strong> {selectedImageName}</p>
|
|
||||||
<button className="btn btn-danger" onClick={handleImageCancel}>
|
|
||||||
<FontAwesomeIcon icon={faTimes} className="me-2" />Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{errorMessage && <small style={styles.uploadError}>{errorMessage}</small>}
|
|
||||||
|
|
||||||
<div style={styles.submitButton}>
|
<div style={styles.submitButton}>
|
||||||
<button onClick={handleEnrollClick} className="btn d-flex justify-content-center align-items-center me-2" style={{ backgroundColor: '#0542CC' }}>
|
<button onClick={handleEnrollClick} className="btn d-flex justify-content-center align-items-center me-2" style={{ backgroundColor: '#0542CC' }}>
|
||||||
<p className="text-white mb-0">Enroll Now</p>
|
<p className="text-white mb-0">Enroll Now</p>
|
||||||
@ -861,6 +641,3 @@ const Enroll = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default Enroll;
|
export default Enroll;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user