diff --git a/frontend/index.html b/frontend/index.html
index d93a1f3..5202d10 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -5,6 +5,8 @@
Job Creation Form
+
+
diff --git a/server.ts b/server.ts
index 87fd8a2..1cf1b1b 100644
--- a/server.ts
+++ b/server.ts
@@ -693,6 +693,54 @@ app.post('/api/submit', async (c) => {
if (dueIso) data.Due_Date = dueIso;
if (startIso) data.Start_Date = startIso;
+ // Address validation: require plausible address, coordinates, OR Address_Notes provided
+ const addr = (data.Job_Address || '').toString().trim();
+ const addrNotes = (data.Address_Notes || '').toString().trim();
+
+ // Check if it looks like coordinates: pattern "lat, lon" or "lat,lon"
+ const coordPattern = /^-?\d+\.?\d*\s*,\s*-?\d+\.?\d*$/;
+ const isCoordinates = coordPattern.test(addr);
+
+ // Check if it looks like a real address: has digit(s) and letter(s), at least 5 chars
+ const hasDigit = /\d/.test(addr);
+ const hasAlpha = /[A-Za-z]/.test(addr);
+ const isAddress = addr.length >= 5 && hasDigit && hasAlpha;
+
+ const addressLooksValid = isCoordinates || isAddress;
+ if (!addressLooksValid && addrNotes.length < 5) {
+ return c.json({
+ success: false,
+ message: 'Provide a real address (e.g., 123 Main St, City, State), coordinates (e.g., 34.0522, -118.2437), or enter directions in Address Notes.'
+ }, 400);
+ }
+ data.Job_Address = addr;
+ data.Address_Notes = addrNotes;
+
+// Job name validation: must be at least 4 characters, contain letters, not symbols-only, not "n/a"
+ const jobName = (data.Job_Name || '').toString().trim();
+ const invalidLiterals = ['n/a', 'na', 'n.a.', 'n\\a', 'none'];
+ const onlySymbols = /^[^A-Za-z0-9]+$/.test(jobName);
+ const hasLetters = /[A-Za-z]/.test(jobName);
+ const isInvalidLiteral = invalidLiterals.includes(jobName.toLowerCase());
+ const isLongEnough = jobName.length >= 4;
+ if (!isLongEnough || !hasLetters || onlySymbols || isInvalidLiteral) {
+ return c.json({
+ success: false,
+ message: 'Job name must be at least 4 characters with letters (no symbols-only, no N/A).'
+ }, 400);
+ }
+ data.Job_Name = jobName;
+
+ // Enforce Job_Type and Job_Division selection
+ const jobType = (data.Job_Type || '').toString().trim();
+ const jobDivision = (data.Job_Division || '').toString().trim();
+ if (!jobType || !jobDivision) {
+ return c.json({
+ success: false,
+ message: 'Please select both Job Type and Job Division.'
+ }, 400);
+ }
+
// Get all existing job numbers and find the max
const existingJobs = await pb.collection(process.env.PB_COLLECTION!).getFullList({
fields: 'Job_Number',