Files
Job-Form/frontend/index.html
T

567 lines
27 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Job Creation Form</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#4c51bf',
secondary: '#5b21b6',
}
}
}
}
</script>
</head>
<body class="min-h-screen bg-gradient-to-br from-primary to-secondary p-4 sm:p-8 flex justify-center items-center font-sans">
<!-- Login Container -->
<div id="loginContainer" class="bg-white rounded-2xl shadow-2xl max-w-md w-full p-8">
<div class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-800 mb-2">Cardoza Construction</h1>
<p class="text-gray-600">Job Creation Portal</p>
</div>
<div class="space-y-6">
<p class="text-gray-700 text-center">Sign in with your Microsoft account to access the Job Creation Form</p>
<button type="button" id="loginBtn" class="w-full py-3 px-4 bg-gradient-to-br from-primary to-secondary text-white font-semibold rounded-lg hover:opacity-95 transition-opacity shadow-lg hover:shadow-xl">
Login with Microsoft
</button>
</div>
<div id="loginError" class="hidden mt-4 p-3 bg-red-50 border border-red-200 text-red-700 rounded-lg text-sm"></div>
</div>
<!-- Form Container (hidden until logged in) -->
<div id="formContainer" class="hidden bg-white rounded-2xl shadow-2xl max-w-4xl w-full p-6 sm:p-12">
<div class="flex items-center justify-between mb-2">
<div>
<h1 class="text-3xl sm:text-4xl font-bold text-gray-800">Job Creation Form</h1>
<p class="text-gray-600 text-sm sm:text-base">Please fill out all required fields to submit a new job</p>
</div>
<div class="text-right">
<p id="userEmailDisplay" class="text-sm text-gray-600 mb-2"></p>
<button type="button" id="logoutBtn" class="px-3 py-1 bg-gray-200 text-gray-800 rounded-lg text-sm hover:bg-gray-300 transition-colors">
Logout
</button>
</div>
</div>
<form id="jobForm" class="space-y-6" autocomplete="off">
<!-- Basic Information -->
<div class="bg-gray-50 rounded-xl p-4 sm:p-6">
<div class="text-lg font-semibold text-gray-700 mb-4 pb-2 border-b-2 border-primary">Basic Information</div>
<div class="grid grid-cols-1 gap-6 mb-6">
<div class="flex flex-col">
<label for="Job_Name" class="block text-sm font-medium text-gray-700 mb-2">
Job Name <span class="text-red-600">*</span>
</label>
<input type="text" id="Job_Name" name="Job_Name" required placeholder="Enter job name" autocomplete="off"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all">
</div>
</div>
<div class="grid grid-cols-1 gap-6 mb-6">
<div class="flex flex-col">
<label for="Job_Status" class="block text-sm font-medium text-gray-700 mb-2">Job Status</label>
<select id="Job_Status" name="Job_Status" required autocomplete="off"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all bg-white text-gray-900">
<option value="Estimating" selected>Estimating</option>
<option value="Est Sent">Est Sent</option>
<option value="Est Hold">Est Hold</option>
<option value="Follow Up">Follow Up</option>
<option value="Awarded">Awarded</option>
<option value="In Progress">In Progress</option>
<option value="On Hold">On Hold</option>
<option value="Billed (In Progress)">Billed (In Progress)</option>
<option value="Billed (Closed)">Billed (Closed)</option>
<option value="Archive">Archive</option>
<option value="Not Awarded">Not Awarded</option>
<option value="Not Bidding">Not Bidding</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 gap-6 mb-6">
<div class="flex flex-col">
<label class="block text-sm font-medium text-gray-700 mb-2">Job Type</label>
<div class="flex flex-wrap gap-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="jobTypeFX" name="Job_Type" value="FX" class="w-4 h-4 text-primary focus:ring-primary" autocomplete="off">
<span class="text-sm">FX</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="jobTypeTM" name="Job_Type" value="TM" class="w-4 h-4 text-primary focus:ring-primary">
<span class="text-sm">TM</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="jobTypeUP" name="Job_Type" value="UP" class="w-4 h-4 text-primary focus:ring-primary">
<span class="text-sm">UP</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="jobTypePW" name="Job_Type" value="PW" class="w-4 h-4 text-primary focus:ring-primary">
<span class="text-sm">PW</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="jobTypeFXEX" name="Job_Type" value="FXEX" class="w-4 h-4 text-primary focus:ring-primary">
<span class="text-sm">FXEX</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="jobTypePWEX" name="Job_Type" value="PWEX" class="w-4 h-4 text-primary focus:ring-primary">
<span class="text-sm">PWEX</span>
</label>
</div>
</div>
</div>
<div class="grid grid-cols-1 gap-6">
<div class="flex flex-col">
<label class="block text-sm font-medium text-gray-700 mb-2">Job Division</label>
<div class="flex flex-wrap gap-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="divisionC" name="Job_Division" value="C#" class="w-4 h-4 text-primary focus:ring-primary">
<span class="text-sm">C#</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="divisionR" name="Job_Division" value="R#" class="w-4 h-4 text-primary focus:ring-primary">
<span class="text-sm">R#</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="divisionS" name="Job_Division" value="S#" class="w-4 h-4 text-primary focus:ring-primary">
<span class="text-sm">S#</span>
</label>
</div>
</div>
</div>
</div>
<!-- Company & Contact -->
<div class="bg-gray-50 rounded-xl p-4 sm:p-6">
<div class="text-lg font-semibold text-gray-700 mb-4 pb-2 border-b-2 border-primary">Company & Contact Information</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6 mb-6">
<div class="flex flex-col">
<label for="Company_Client" class="block text-sm font-medium text-gray-700 mb-2">Company / Client</label>
<input type="text" id="Company_Client" name="Company_Client" placeholder="Enter company or client name" autocomplete="organization"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all">
</div>
<div class="flex flex-col">
<label for="Contact_Person" class="block text-sm font-medium text-gray-700 mb-2">Contact Person</label>
<input type="text" id="Contact_Person" name="Contact_Person" placeholder="Enter contact person" autocomplete="name"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all">
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6 mb-6">
<div class="flex flex-col">
<label for="Phone_Number" class="block text-sm font-medium text-gray-700 mb-2">Phone Number</label>
<input type="tel" id="Phone_Number" name="Phone_Number" placeholder="Enter phone number" autocomplete="tel"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all">
</div>
<div class="flex flex-col">
<label for="Email" class="block text-sm font-medium text-gray-700 mb-2">Email</label>
<input type="email" id="Email" name="Email" placeholder="Enter email address" autocomplete="email"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all">
</div>
</div>
<div class="grid grid-cols-1 gap-6">
<div class="flex flex-col">
<label for="Job_Address" class="block text-sm font-medium text-gray-700 mb-2">Job Address</label>
<input type="text" id="Job_Address" name="Job_Address" placeholder="Enter job address" autocomplete="street-address"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all">
</div>
</div>
</div>
<!-- Personnel -->
<div class="bg-gray-50 rounded-xl p-4 sm:p-6">
<div class="text-lg font-semibold text-gray-700 mb-4 pb-2 border-b-2 border-primary">Personnel & Estimator</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6 mb-6">
<div class="flex flex-col">
<label for="Office_Rep" class="block text-sm font-medium text-gray-700 mb-2">
Office Rep <span class="text-red-600">*</span>
</label>
<select id="Office_Rep" name="Office_Rep" required autocomplete="off"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all bg-white text-gray-400" data-placeholder="true">
<option value="" disabled selected>Select office rep</option>
<option value="Beth Cardoza">Beth Cardoza</option>
<option value="Steve Brewer">Steve Brewer</option>
</select>
</div>
<div class="flex flex-col">
<label for="Estimator" class="block text-sm font-medium text-gray-700 mb-2">Estimator</label>
<select id="Estimator" name="Estimator" required autocomplete="off"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all bg-white text-gray-400" data-placeholder="true">
<option value="" disabled selected>Select estimator</option>
<option value="Beth Cardoza">Beth Cardoza</option>
<option value="Steve Brewer">Steve Brewer</option>
<option value="Timothy Cardoza">Timothy Cardoza</option>
</select>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6">
<div class="flex flex-col">
<label for="Project_Manager" class="block text-sm font-medium text-gray-700 mb-2">Project Manager</label>
<select id="Project_Manager" name="Project_Manager" required autocomplete="off"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all bg-white text-gray-400" data-placeholder="true">
<option value="" disabled selected>Select project manager</option>
<option value="Dave">Dave</option>
<option value="Rick">Rick</option>
<option value="Eddie">Eddie</option>
<option value="Steve">Steve</option>
<option value="Timothy">Timothy</option>
</select>
</div>
</div>
</div>
<!-- Schedule & Dates -->
<div class="bg-gray-50 rounded-xl p-4 sm:p-6">
<div class="text-lg font-semibold text-gray-700 mb-4 pb-2 border-b-2 border-primary">Schedule & Dates</div>
<!-- Start Date (moved to top) -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6 mb-6">
<div class="flex flex-col">
<label for="Start_Date" class="block text-sm font-medium text-gray-700 mb-2">Start Date</label>
<input type="text" id="Start_Date" name="Start_Date" class="datepicker w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all" placeholder="Select start date" autocomplete="off">
</div>
</div>
<!-- Schedule Confidence (now second) -->
<div class="grid grid-cols-1 gap-6 mb-6">
<div class="flex flex-col">
<label for="Schedule_Confidence" class="block text-sm font-medium text-gray-700 mb-2">Schedule Confidence</label>
<select id="Schedule_Confidence" name="Schedule_Confidence" required autocomplete="off"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all bg-white text-gray-400" data-placeholder="true">
<option value="" disabled selected>Select confidence level</option>
<option value="1 (Total Guess)">1 (Total Guess)</option>
<option value="2 (Guess)">2 (Guess)</option>
<option value="3 (Start Date)">3 (Start Date)</option>
<option value="4 (Client Verbal Guess)">4 (Client Verbal Guess)</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7 (Written Schedule Within 90 Days)">7 (Written Schedule Within 90 Days)</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10 (Job Starts Within 7 Days)">10 (Job Starts Within 7 Days)</option>
</select>
</div>
</div>
<!-- Due Date and Time (now third) -->
<div class="grid grid-cols-1 sm:grid-cols-2 gap-6 mb-6">
<div class="flex flex-col">
<label for="Due_Date" class="block text-sm font-medium text-gray-700 mb-2">Due Date</label>
<input type="text" id="Due_Date" name="Due_Date" class="datepicker w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all" placeholder="Select due date" autocomplete="off">
</div>
<div class="flex flex-col">
<label for="Due_Time" class="block text-sm font-medium text-gray-700 mb-2">Due Time</label>
<input type="text" id="Due_Time" name="Due_Time" placeholder="Enter time" autocomplete="off"
class="w-full px-4 py-2.5 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent transition-all">
</div>
</div>
<!-- Due Date Source (now last) -->
<div class="grid grid-cols-1 gap-6">
<div class="flex flex-col">
<label class="block text-sm font-medium text-gray-700 mb-2">Due Date Source</label>
<div class="flex flex-wrap gap-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="sourceRequested" name="Due_Date_Source" value="Requested" class="w-4 h-4 text-primary focus:ring-primary" autocomplete="off">
<span class="text-sm">Requested</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="sourceGuess" name="Due_Date_Source" value="Guess" class="w-4 h-4 text-primary focus:ring-primary">
<span class="text-sm">Guess</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="sourceStartDate" name="Due_Date_Source" value="Start Date" class="w-4 h-4 text-primary focus:ring-primary">
<span class="text-sm">Start Date</span>
</label>
</div>
</div>
</div>
</div>
<!-- Financial -->
<div id="financialSection" class="bg-red-100 border border-red-200 rounded-xl p-4 sm:p-6 transition-colors">
<div class="text-lg font-semibold text-gray-700 mb-4 pb-2 border-b-2 border-primary">Financial</div>
<div class="grid grid-cols-1 gap-6">
<div class="flex flex-col">
<label class="block text-sm font-medium text-gray-700 mb-2">
Tax Exempt <span class="text-red-600">*</span>
</label>
<div class="flex flex-wrap gap-4">
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="taxExemptTrue" name="Tax_Exempt" value="true" required class="w-4 h-4 text-primary focus:ring-primary" autocomplete="off">
<span class="text-sm">Yes</span>
</label>
<label class="flex items-center gap-2 cursor-pointer">
<input type="radio" id="taxExemptFalse" name="Tax_Exempt" value="false" required class="w-4 h-4 text-primary focus:ring-primary" autocomplete="off">
<span class="text-sm">No</span>
</label>
</div>
<p id="taxReminder" class="hidden mt-3 text-sm font-semibold text-red-700">Don't forget to file the paperwork!</p>
</div>
</div>
</div>
<button type="submit" id="submitBtn" disabled
class="w-full py-3 bg-gradient-to-br from-primary to-secondary text-white font-semibold rounded-lg hover:opacity-95 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed shadow-lg hover:shadow-xl">
Submit Job
</button>
<div id="message" class="hidden p-4 rounded-lg font-medium"></div>
</form>
</div>
<script src="https://cdn.jsdelivr.net/npm/pocketbase/dist/pocketbase.umd.js"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script>
// Initialize PocketBase client
const pb = new PocketBase('https://pocketbase.ccllc.pro');
// Update auth UI based on login state
function updateAuthUI() {
const loginContainer = document.getElementById('loginContainer');
const formContainer = document.getElementById('formContainer');
const userEmailDisplay = document.getElementById('userEmailDisplay');
if (pb.authStore.isValid) {
// Show form, hide login
loginContainer.classList.add('hidden');
formContainer.classList.remove('hidden');
const userName = pb.authStore.record?.name || pb.authStore.record?.email || 'Unknown User';
userEmailDisplay.textContent = userName;
} else {
// Show login, hide form
loginContainer.classList.remove('hidden');
formContainer.classList.add('hidden');
document.getElementById('loginError').classList.add('hidden');
}
}
// Handle login button click
document.getElementById('loginBtn').addEventListener('click', async (e) => {
e.preventDefault();
const loginBtn = document.getElementById('loginBtn');
const loginError = document.getElementById('loginError');
loginBtn.disabled = true;
loginBtn.textContent = 'Logging in...';
loginError.classList.add('hidden');
try {
const authData = await pb.collection('Users').authWithOAuth2({
provider: 'microsoft',
});
console.log('✓ Logged in with Microsoft');
updateAuthUI();
} catch (error) {
console.error(error);
loginError.textContent = `Login failed: ${error.message}`;
loginError.classList.remove('hidden');
} finally {
loginBtn.disabled = false;
loginBtn.textContent = 'Login with Microsoft';
}
});
// Handle logout button click
document.getElementById('logoutBtn').addEventListener('click', () => {
pb.authStore.clear();
updateAuthUI();
});
// Initial auth UI update
updateAuthUI();
// Initialize date pickers
flatpickr('.datepicker', {
dateFormat: 'm/d/Y',
allowInput: true,
altInput: true,
altFormat: 'm/d/Y',
});
// Function to check if required fields are filled
function checkRequiredFields() {
const jobName = document.getElementById('Job_Name').value.trim();
const officeRep = document.getElementById('Office_Rep').value;
const taxExempt = document.querySelector('input[name="Tax_Exempt"]:checked');
const submitBtn = document.getElementById('submitBtn');
if (jobName && officeRep && taxExempt) {
submitBtn.disabled = false;
} else {
submitBtn.disabled = true;
}
}
// Update Financial section background and reminder based on Tax Exempt selection
function updateFinancialUI() {
const section = document.getElementById('financialSection');
const reminder = document.getElementById('taxReminder');
const selection = document.querySelector('input[name="Tax_Exempt"]:checked');
// Default: light red, hide reminder
section.classList.remove('bg-green-50', 'border-green-200', 'border-red-300');
section.classList.add('bg-red-100', 'border-red-200');
reminder.classList.add('hidden');
if (!selection) {
return;
}
if (selection.value === 'false') {
// No selected: light green
section.classList.remove('bg-red-100', 'border-red-200', 'border-red-300');
section.classList.add('bg-green-50', 'border-green-200');
reminder.classList.add('hidden');
} else {
// Yes selected: stay red and show reminder
section.classList.remove('bg-green-50', 'border-green-200');
section.classList.add('bg-red-100', 'border-red-300');
reminder.classList.remove('hidden');
}
}
// Style selects to show gray placeholder until a real value is chosen
function updateSelectPlaceholders() {
const selects = document.querySelectorAll('select[data-placeholder="true"]');
selects.forEach((sel) => {
if (!sel.value) {
sel.classList.add('text-gray-400');
sel.classList.remove('text-gray-900');
} else {
sel.classList.remove('text-gray-400');
sel.classList.add('text-gray-900');
}
});
}
// Add event listeners to required fields
document.getElementById('Job_Name').addEventListener('input', checkRequiredFields);
document.getElementById('Office_Rep').addEventListener('change', checkRequiredFields);
document.getElementById('Office_Rep').addEventListener('change', updateSelectPlaceholders);
document.querySelectorAll('input[name="Tax_Exempt"]').forEach(radio => {
radio.addEventListener('change', () => {
checkRequiredFields();
updateFinancialUI();
});
});
document.getElementById('Estimator').addEventListener('change', updateSelectPlaceholders);
document.getElementById('Project_Manager').addEventListener('change', updateSelectPlaceholders);
document.getElementById('Schedule_Confidence').addEventListener('change', updateSelectPlaceholders);
// Initial check
checkRequiredFields();
updateFinancialUI();
updateSelectPlaceholders();
// Form submission
document.getElementById('jobForm').addEventListener('submit', async (e) => {
e.preventDefault();
// Check if user is authenticated
if (!pb.authStore.isValid) {
alert('Please log in with your Microsoft account first');
return;
}
const submitBtn = e.target.querySelector('#submitBtn');
const message = document.getElementById('message');
submitBtn.disabled = true;
submitBtn.textContent = 'Submitting...';
message.classList.add('hidden');
const formData = new FormData(e.target);
const data = Object.fromEntries(formData.entries());
// Add user's PocketBase token
data.pbToken = pb.authStore.token;
// Convert dates to MM/dd/yyyy format for PocketBase
function formatDateToShort(dateString) {
if (!dateString) return '';
const parts = dateString.split('/');
if (parts.length === 3) {
const month = parts[0].padStart(2, '0');
const day = parts[1].padStart(2, '0');
const year = parts[2];
return `${month}/${day}/${year}`;
}
return dateString;
}
if (data.Due_Date) {
data.Due_Date = formatDateToShort(data.Due_Date);
}
if (data.Start_Date) {
data.Start_Date = formatDateToShort(data.Start_Date);
}
// Convert Tax_Exempt to boolean
if (data.Tax_Exempt) {
data.Tax_Exempt = data.Tax_Exempt === 'true';
}
try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (result.success) {
message.className = 'p-4 rounded-lg font-medium bg-green-50 text-green-700 border border-green-200';
message.textContent = result.message;
message.classList.remove('hidden');
e.target.reset();
checkRequiredFields();
} else {
throw new Error(result.message);
}
} catch (error) {
message.className = 'p-4 rounded-lg font-medium bg-red-50 text-red-700 border border-red-200';
message.textContent = error.message || 'Failed to submit form. Please try again.';
message.classList.remove('hidden');
} finally {
submitBtn.disabled = false;
submitBtn.textContent = 'Submit Job';
}
});
</script>
<!-- Version Info -->
<div class="fixed bottom-4 right-4 text-xs text-gray-400">
v1.0.0-beta3
</div>
</body>
</html>