diff --git a/src/components/Collaboration/JobFormbuilder.jsx b/src/components/Collaboration/JobFormbuilder.jsx index f7180d8bd4..089dc6fc99 100644 --- a/src/components/Collaboration/JobFormbuilder.jsx +++ b/src/components/Collaboration/JobFormbuilder.jsx @@ -1,10 +1,7 @@ -/* eslint-disable no-alert */ -/* eslint-disable no-console */ -// eslint-disable-next-line no-alert import { useState, useEffect } from 'react'; import axios from 'axios'; import { useSelector } from 'react-redux'; -import { v4 as uuidv4 } from 'uuid'; +import { Prompt } from 'react-router-dom'; import styles from './JobFormBuilder.module.css'; import { ENDPOINTS } from '~/utils/URL'; import OneCommunityImage from './One-Community-Horizontal-Homepage-Header-980x140px-2.png'; @@ -12,241 +9,213 @@ import QuestionSetManager from './QuestionSetManager'; import QuestionFieldActions from './QuestionFieldActions'; import QuestionEditModal from './QuestionEditModal'; +const safeAlert = msg => globalThis.alert(msg); +const safeConfirm = msg => globalThis.confirm(msg); + function JobFormBuilder() { const { role } = useSelector(state => state.auth.user); - const [formFields, setFormFields] = useState([]); const darkMode = useSelector(state => state.theme.darkMode); + + const [formFields, setFormFields] = useState([]); + const [initialFormFields, setInitialFormFields] = useState([]); + + const [templateName, setTemplateName] = useState(''); + const [selectedTemplate, setSelectedTemplate] = useState(''); + + const [currentFormId, setCurrentFormId] = useState(null); + const [newField, setNewField] = useState({ questionText: '', questionType: 'textbox', - options: [], // For checkbox, radio, and dropdown input types + options: [], visible: true, }); - // Dynamic Form ID Management - const [currentFormId, setCurrentFormId] = useState(null); - - const [jobTitle, setJobTitle] = useState('Please Choose an option'); - const jobPositions = [ - 'APPLIED THROUGH SITE - SEEKING SOFTWARE POSITION', - 'APPLIED THROUGH SITE - GENERAL', - 'APPLIED THROUGH SITE - ADMINISTRATIVE ASSISTANT', - 'APPLIED THROUGH SITE - GRAPHIC DESIGNER', - 'SEEKING SOFTWARE POSITION', - 'SEEKING ADMINISTRATIVE ASSISTANT', - 'EARTHBAG 4-DOME CLUSTER PLUMBING DESIGNS', - 'PLUMBING ENGINEER/MEP FOR EARTHBAG VILLAGE', - 'CIVIL ENGINEER FOR COST ANALYSIS OF FOOD PRODUCTION STRUCTURES', - 'CIVIL ENGINEER TO FINALIZE TEST MATERIALS AND EQUIPMENT FOR FOOD PRODUCTION STRUCTURES', - 'MECHANICAL ENGINEER FOR HVAC FOR FOOD PRODUCTION STRUCTURES', - 'CITY CENTER PLUMBING DESIGNS', - 'ELECTRICAL DESIGNER FOR EARTHBAG VILLAGE', - 'ELECTRICAL DESIGNER FOR STRAW BALE CLASSROOM', - 'ELECTRICAL ENGINEER/DESIGNER FOR DUPLICABLE CITY CENTER', - 'CITY CENTER GEODESIC DOME AUTODESK INVENTOR SIMULATIONS', - '4-DOME CLUSTER ELECTRICAL DESIGN', - 'PHOTOSHOP/GRAPHIC DESIGNER', - 'PASSIVE GREENHOUSE DESIGN', - 'LANDSCAPE ARCHITECT FOR AQUAPINI/WALIPINI STRUCTURES', - 'SEEKING VIRTUAL ASSISTANT', - 'FUNDRAISING HELP', - 'STRAW BALE CLASSROOM STRUCTURAL', - 'PERMACULTURALIST FOR SOIL AMENDMENT', - 'NUTRITIONIST FOR NUTRITION CALCULATIONS AND MENU PLANNING', - 'CHEF OR CULINARY PROFESSIONAL FOR MENU IMPLEMENTATION TUTORIALS', - 'CHEF OR CULINARY PROFESSIONAL TO HELP WITH REMOTE/DISASTER KITCHEN SUPPLY AND STORAGE PLAN', - 'LUMION 2024.1.1 OR HIGHER FOR CITY CENTER', - 'GENERAL', - 'VERMICULTURE', - 'MASTER CARPENTER', - 'FINAL CUT PRO VIDEO EDITOR', - 'INDUSTRIAL DESIGNER FOR DORMER', - 'LANDSCAPE ARCHITECT FOR SKETCHUP AND LUMION RENDER HELP', - 'T.A.S.T. - NEED RESUME & WORK SAMPLES', - 'T.A.S.T. - ENGINEER/ARCHITECT OFFERING POSITION', - 'T.A.S.T. - HGN', - 'ADMIN OF PR REVIEW TEAM AND FRONTEND TESTER', - 'ADMIN OF PR REVIEW TEAM AND FRONTEND TESTER - APPLIED THROUGH SITE', - 'DATA ANALYST APPLICATION', - ]; + const initialNewField = { + questionText: '', + questionType: 'textbox', + options: [], + visible: true, + }; const [newOption, setNewOption] = useState(''); + const [editModalOpen, setEditModalOpen] = useState(false); const [editingQuestion, setEditingQuestion] = useState(null); const [editingIndex, setEditingIndex] = useState(null); - // Auto-load existing form on component mount + const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); + + const jobPositions = ['Software Developer', 'Project Manager', 'Analyst']; + + // Prevent refresh while unsaved changes exist useEffect(() => { - const loadFirstAvailableForm = async () => { + const handler = event => { + if (hasUnsavedChanges) { + event.preventDefault(); + event.returnValue = ''; + } + }; + + globalThis.addEventListener('beforeunload', handler); + return () => globalThis.removeEventListener('beforeunload', handler); + }, [hasUnsavedChanges]); + + // Load form initially + useEffect(() => { + const loadForm = async () => { try { const response = await axios.get(ENDPOINTS.GET_ALL_JOB_FORMS); - if (response.data && response.data.length > 0) { - const firstForm = response.data[0]; - const formId = firstForm._id || firstForm.id; + if (response.data?.length > 0) { + const form = response.data[0]; + const id = form._id || form.id; - setCurrentFormId(formId); - setFormFields(firstForm.questions || []); - setJobTitle(firstForm.title || 'Please Choose an option'); + setCurrentFormId(id); + setFormFields(form.questions || []); + setInitialFormFields(form.questions || []); - console.log('Auto-loaded form:', formId); + setNewField(initialNewField); + setHasUnsavedChanges(false); } } catch (error) { - console.error('Error auto-loading form:', error); + // still allowed logging + console.error(error); } }; - loadFirstAvailableForm(); + loadForm(); }, []); - // const ensureFormExists = async () => { - // if (!currentFormId) { - // console.warn('No form ID available for this operation'); - // return false; - // } - // return true; - // }; + // Detect unsaved changes + useEffect(() => { + const changed = + JSON.stringify(formFields) !== JSON.stringify(initialFormFields) || + JSON.stringify(newField) !== JSON.stringify(initialNewField) || + templateName !== '' || + selectedTemplate !== ''; - // CRUD Functions with Dynamic Form ID + setHasUnsavedChanges(changed); + }, [formFields, newField, templateName, selectedTemplate, initialFormFields]); + + // Clone field const cloneField = async (field, index) => { - const clonedField = JSON.parse(JSON.stringify(field)); + const clone = structuredClone(field); - // Update local state immediately - const newFields = [ - ...formFields.slice(0, index + 1), - clonedField, - ...formFields.slice(index + 1), - ]; - setFormFields(newFields); + const updated = [...formFields.slice(0, index + 1), clone, ...formFields.slice(index + 1)]; + setFormFields(updated); - // Sync with backend if form exists if (currentFormId) { try { await axios.post(ENDPOINTS.ADD_QUESTION(currentFormId), { - question: clonedField, + question: clone, position: index + 1, }); } catch (error) { - console.error('Error cloning question on server:', error); + console.error(error); } } }; + // Move field const moveField = async (index, direction) => { const newIndex = direction === 'up' ? index - 1 : index + 1; + if (newIndex < 0 || newIndex >= formFields.length) return; - if ( - (direction === 'up' && index > 0) || - (direction === 'down' && index < formFields.length - 1) - ) { - // Update local state immediately - const newFields = [...formFields]; - [newFields[index], newFields[newIndex]] = [newFields[newIndex], newFields[index]]; - setFormFields(newFields); - - // Sync with backend if form exists - if (currentFormId) { - try { - await axios.put(ENDPOINTS.REORDER_QUESTIONS(currentFormId), { - fromIndex: index, - toIndex: newIndex, - }); - } catch (error) { - console.error('Error reordering questions on server:', error); - } + const updated = [...formFields]; + [updated[index], updated[newIndex]] = [updated[newIndex], updated[index]]; + setFormFields(updated); + + if (currentFormId) { + try { + await axios.put(ENDPOINTS.REORDER_QUESTIONS(currentFormId), { + fromIndex: index, + toIndex: newIndex, + }); + } catch (error) { + console.error(error); } } }; + // Delete field const deleteField = async index => { - // Update local state immediately - const newFields = [...formFields]; - newFields.splice(index, 1); - setFormFields(newFields); + const updated = [...formFields]; + updated.splice(index, 1); + setFormFields(updated); - // Sync with backend if form exists if (currentFormId) { try { await axios.delete(ENDPOINTS.DELETE_QUESTION(currentFormId, index)); - console.log('Question deleted successfully'); } catch (error) { - console.error('Error deleting question on server:', error); + console.error(error); } } }; + // Edit field const editField = (field, index) => { - // Transform the field structure to match what QuestionEditModal expects - const questionForEdit = { + setEditingQuestion({ label: field.questionText, type: field.questionType, options: field.options, required: field.required || false, placeholder: field.placeholder || '', - }; + }); - setEditingQuestion(questionForEdit); setEditingIndex(index); setEditModalOpen(true); }; - const handleSaveEditedQuestion = async editedQuestion => { - const updatedField = { - ...formFields[editingIndex], - questionText: editedQuestion.label, - questionType: editedQuestion.type, - options: editedQuestion.options || [], - required: editedQuestion.required, - placeholder: editedQuestion.placeholder, + const handleSaveEditedQuestion = async edited => { + const updated = [...formFields]; + + updated[editingIndex] = { + ...updated[editingIndex], + questionText: edited.label, + questionType: edited.type, + options: edited.options || [], + required: edited.required, + placeholder: edited.placeholder, }; - // Update local state immediately - const updatedFields = [...formFields]; - updatedFields[editingIndex] = updatedField; - setFormFields(updatedFields); + setFormFields(updated); - // Sync with backend if form exists if (currentFormId) { try { - await axios.put(ENDPOINTS.UPDATE_QUESTION(currentFormId, editingIndex), updatedField); - console.log('Question updated successfully'); + await axios.put( + ENDPOINTS.UPDATE_QUESTION(currentFormId, editingIndex), + updated[editingIndex], + ); } catch (error) { - console.error('Error updating question on server:', error); + console.error(error); } } - // Close the modal - setEditModalOpen(false); - setEditingQuestion(null); - setEditingIndex(null); - }; - - const handleCancelEdit = () => { setEditModalOpen(false); setEditingQuestion(null); setEditingIndex(null); }; - // Import questions from template - const importQuestions = questions => { - setFormFields(questions); - }; - + // Add option const handleAddOption = () => { - if (newOption.trim() === '') { - alert('Option cannot be empty!'); + if (!newOption.trim()) { + safeAlert('Option cannot be empty'); return; } + setNewField(prev => ({ ...prev, options: [...prev.options, newOption], })); + setNewOption(''); }; + // Add field const handleAddField = async () => { - if (newField.questionText.trim() === '') { - alert('Field label is required!'); + if (!newField.questionText.trim()) { + safeAlert('Field label is required'); return; } @@ -254,15 +223,13 @@ function JobFormBuilder() { ['checkbox', 'radio', 'dropdown'].includes(newField.questionType) && newField.options.length === 0 ) { - alert('You must add at least one option for this field!'); + safeAlert('Please add at least one option'); return; } - // Update local state immediately - const updatedFields = [...formFields, newField]; - setFormFields(updatedFields); + const updated = [...formFields, newField]; + setFormFields(updated); - // Sync with backend if form exists if (currentFormId) { try { await axios.post(ENDPOINTS.ADD_QUESTION(currentFormId), { @@ -270,53 +237,37 @@ function JobFormBuilder() { position: formFields.length, }); } catch (error) { - console.error('Error adding question to server:', error); + console.error(error); } } - setNewField({ questionText: '', questionType: 'textbox', options: [], visible: true }); + setNewField(initialNewField); }; - const changeVisiblity = (event, field) => { - const updatedFields = formFields.map( - item => - item.questionText === field.questionText && item.questionType === field.questionType - ? { ...item, visible: event.target.checked } // Return the updated item - : item, // Return the unchanged item + const changeVisibility = (event, field) => { + const updated = formFields.map(item => + item.questionText === field.questionText && item.questionType === field.questionType + ? { ...item, visible: event.target.checked } + : item, ); - setFormFields(updatedFields); - }; - const handleSubmit = async e => { - e.preventDefault(); - - const formIdToUse = currentFormId || '6753982566fcf3275f129eb4'; - - try { - await axios.put(ENDPOINTS.UPDATE_JOB_FORM, { - formId: formIdToUse, - title: jobTitle, - questions: formFields, - description: '', - }); - - console.log('Form updated successfully'); - alert('Form saved successfully!'); - } catch (error) { - console.error('Error updating form:', error); - alert('Failed to save form. Please try again.'); - } + setFormFields(updated); }; return (
Fill the form with questions about a specific position you want to create an ad for. - The default questions will automatically appear and are alredy selected. You can pick + The default questions will automatically appear and are already selected. You can pick and choose them with the checkbox.
+