567 lines
27 KiB
HTML
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>
|
|
|
|
|
|
|