diff --git a/map/app/public/css/admin.css b/map/app/public/css/admin.css index 2b4131a..cb68277 100644 --- a/map/app/public/css/admin.css +++ b/map/app/public/css/admin.css @@ -277,20 +277,19 @@ } /* Walk Sheet Preview */ - .walk-sheet-preview { background-color: #f5f5f5; - padding: 20px; + padding: 30px; border-radius: var(--border-radius); box-shadow: 0 4px 24px rgba(0,0,0,0.10); min-width: 350px; - max-width: 600px; + max-width: 100%; margin: 0 auto; - height: 700px; + min-height: 950px; /* Ensure container is tall enough */ display: flex; flex-direction: column; align-items: center; - overflow-x: auto; + overflow: auto; } .walk-sheet-preview h3 { @@ -311,19 +310,21 @@ color: #666; } -/* Walk Sheet Page (8.5 x 11 preview) */ +/* Walk Sheet Page (8.5 x 11 actual size) */ .walk-sheet-page { - width: 100%; - max-width: 425px; /* Half of 8.5 inches at 100dpi */ - aspect-ratio: 8.5 / 11; + /* Actual paper size in pixels at 96 DPI (standard screen resolution) */ + width: 816px; /* 8.5 inches * 96 DPI */ + height: 1056px; /* 11 inches * 96 DPI */ background: white; - box-shadow: 0 2px 10px rgba(0,0,0,0.1); - padding: 40px; /* Match print padding */ + box-shadow: 0 4px 20px rgba(0,0,0,0.15); + padding: 48px; /* 0.5 inch margins at 96 DPI */ margin: 0 auto; - overflow: auto; - font-size: 12px; /* Match print font size */ - line-height: 1.4; + overflow: hidden; + font-size: 12pt; /* Standard document font size */ + line-height: 1.6; position: relative; + transform: none; /* No scaling by default */ + box-sizing: border-box; } /* Walk Sheet Print Styles */ @@ -359,42 +360,43 @@ } } -/* Adjust preview scaling */ +/* Remove the preview scaling - show at actual size */ .walk-sheet-preview .walk-sheet-page { - transform: scale(0.75); + transform: none; transform-origin: top center; - margin-bottom: -25%; - box-shadow: 0 2px 10px rgba(0,0,0,0.12); - border-radius: 8px; + margin-bottom: 0; + border-radius: 0; /* Remove rounded corners to look more like paper */ } -/* Walk Sheet Content Styles */ +/* Walk Sheet Content Styles - adjusted for actual size */ .ws-header { text-align: center; - margin-bottom: 20px; - border-bottom: 2px solid #333; - padding-bottom: 10px; + margin-bottom: 30px; /* Reduced from 40px */ + border-bottom: 3px solid #333; + padding-bottom: 15px; /* Reduced from 20px */ } .ws-title { - font-size: 18px; + font-size: 24pt; /* Reduced from 28pt */ font-weight: bold; margin: 0; + color: #000; } .ws-subtitle { - font-size: 14px; - color: #666; - margin: 5px 0 0 0; + font-size: 12pt; /* Reduced from 14pt */ + color: #444; + margin: 8px 0 0 0; /* Reduced from 10px */ } .ws-qr-section { display: flex; justify-content: space-around; - margin: 20px 0; - padding: 15px; - background-color: #f9f9f9; - border-radius: 5px; + margin: 30px 0; /* Reduced from 40px */ + padding: 20px; /* Reduced from 30px */ + background-color: #f5f5f5; + border-radius: 8px; + border: 1px solid #ddd; } .ws-qr-item { @@ -408,6 +410,8 @@ .ws-qr-code img { display: block; margin: 0 auto; + width: 120px; /* Reduced from 140px */ + height: 120px; image-rendering: crisp-edges; image-rendering: -webkit-crisp-edges; image-rendering: pixelated; @@ -424,65 +428,105 @@ } .ws-qr-label { - font-size: 10px; + font-size: 11pt; font-weight: bold; + margin-top: 10px; + color: #333; } .ws-form-section { - margin-top: 20px; + margin-top: 30px; /* Reduced from 40px */ } .ws-form-row { display: grid; grid-template-columns: repeat(2, 1fr); - gap: 10px; - margin-bottom: 10px; + gap: 20px; + margin-bottom: 18px; /* Reduced from 20px */ } .ws-form-group { - border-bottom: 1px solid #ccc; - padding-bottom: 5px; + border-bottom: 2px solid #ccc; + padding-bottom: 8px; } .ws-form-label { - font-size: 9px; - color: #666; + font-size: 10pt; + color: #555; display: block; - margin-bottom: 2px; + margin-bottom: 5px; + font-weight: 500; } .ws-form-field { - height: 20px; - background-color: #f9f9f9; + height: 30px; + background-color: #fafafa; + border: 1px solid #eee; +} + +/* Special field styles for circles */ +.ws-form-field.circles { + height: auto; + background: none; + border: none; + display: flex; + gap: 15px; + align-items: center; + padding: 5px 0; +} + +.ws-circle-option { + display: flex; + align-items: center; + gap: 5px; + font-size: 11pt; +} + +.ws-circle { + width: 24px; + height: 24px; + border: 2px solid #333; + border-radius: 50%; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: 10pt; + font-weight: 500; + background-color: white; } .ws-notes-section { - margin-top: 20px; + margin-top: 30px; /* Reduced from 40px */ } .ws-notes-label { - font-size: 10px; + font-size: 11pt; font-weight: bold; - margin-bottom: 5px; + margin-bottom: 10px; + color: #333; } .ws-notes-area { width: 100%; - height: 60px; - border: 1px solid #ccc; - background-color: #f9f9f9; + height: 80px; /* Reduced from 100px */ + border: 2px solid #ccc; + background-color: #fafafa; + border-radius: 4px; } .ws-footer { position: absolute; - bottom: 20px; - left: 20px; - right: 20px; + bottom: 48px; /* Match padding */ + left: 48px; + right: 48px; text-align: center; - font-size: 9px; + font-size: 10pt; color: #666; - padding-top: 10px; + padding-top: 15px; /* Reduced from 20px */ border-top: 1px solid #ccc; + max-height: 80px; /* Ensure footer has enough space */ + overflow: hidden; + line-height: 1.4; } /* QR Code Generation Status */ @@ -520,7 +564,17 @@ color: var(--warning-color); } -/* Responsive */ +/* Responsive - Scale down for smaller screens */ +@media (max-width: 1400px) { + .walk-sheet-preview .walk-sheet-page { + transform: scale(0.85); + transform-origin: top center; + } + .walk-sheet-preview { + min-height: 850px; + } +} + @media (max-width: 1200px) { .walk-sheet-container { grid-template-columns: 1fr; @@ -528,11 +582,20 @@ .walk-sheet-preview { order: -1; max-width: 100vw; - height: 500px; + min-height: 750px; } + .walk-sheet-preview .walk-sheet-page { + transform: scale(0.75); + } +} + +@media (max-width: 1000px) { .walk-sheet-preview .walk-sheet-page { transform: scale(0.65); } + .walk-sheet-preview { + min-height: 700px; + } } @media (max-width: 768px) { @@ -573,13 +636,12 @@ .walk-sheet-preview { min-width: 0; max-width: 100vw; - height: 350px; - padding: 8px; + min-height: 500px; + padding: 10px; } .walk-sheet-preview .walk-sheet-page { - transform: scale(0.48); - min-width: 320px; - margin-bottom: 0; + transform: scale(0.45); + margin: 0 auto; } .walk-sheet-page { font-size: 8px; @@ -587,6 +649,33 @@ } } +/* For very large screens, show at full size without scaling */ +@media (min-width: 1600px) { + .walk-sheet-container { + gap: 40px; + grid-template-columns: 1fr 2fr; /* Give more space to preview */ + } + + .walk-sheet-preview { + max-width: 100%; + min-height: 1100px; + padding: 40px; + } + + .walk-sheet-preview .walk-sheet-page { + transform: none; /* Show at actual size */ + } +} + +/* Container adjustments for actual-size preview */ +.walk-sheet-container { + display: grid; + grid-template-columns: minmax(300px, 400px) 1fr; /* Adjust proportions */ + gap: 30px; + margin-top: 20px; + align-items: flex-start; +} + /* CSS Variables (define these in style.css if not already defined) */ :root { --primary-color: #4CAF50; diff --git a/map/app/public/js/admin.js b/map/app/public/js/admin.js index e018cd1..e02f135 100644 --- a/map/app/public/js/admin.js +++ b/map/app/public/js/admin.js @@ -489,14 +489,19 @@ function generateWalkSheetPreview() {
- -
+ +
+ 1 + 2 + 3 + 4 +
-
- ☐ Yes - ☐ No +
+ Y Yes + N No
@@ -504,7 +509,11 @@ function generateWalkSheetPreview() {
-
+
+ S + M + L +
@@ -542,7 +551,7 @@ function generateWalkSheetPreview() { } } -// Generate QR codes for preview +// Update the generatePreviewQRCodes function to use smaller size async function generatePreviewQRCodes() { for (let i = 1; i <= 3; i++) { const urlInput = document.getElementById(`qr-code-${i}-url`); @@ -551,12 +560,12 @@ async function generatePreviewQRCodes() { if (url && qrContainer) { try { - // Use our local QR code generation endpoint - const qrImageUrl = `/api/qr?text=${encodeURIComponent(url)}&size=100`; - qrContainer.innerHTML = `QR Code ${i}`; + // Use our local QR code generation endpoint with size matching display + const qrImageUrl = `/api/qr?text=${encodeURIComponent(url)}&size=200`; // Generate at higher res + qrContainer.innerHTML = `QR Code ${i}`; // Display smaller } catch (error) { console.error(`Failed to display QR code ${i}:`, error); - qrContainer.innerHTML = '
QR Error
'; + qrContainer.innerHTML = '
QR Error
'; } } else if (qrContainer) { // Clear empty QR containers @@ -565,6 +574,7 @@ async function generatePreviewQRCodes() { } } + // Print walk sheet function printWalkSheet() { // First generate fresh preview to ensure QR codes are generated diff --git a/map/app/server.js b/map/app/server.js index 23dffe4..08824d9 100644 --- a/map/app/server.js +++ b/map/app/server.js @@ -471,40 +471,41 @@ app.post('/api/admin/start-location', requireAdmin, async (req, res) => { }); } - // Get current settings to preserve walk sheet config + // Get the most recent settings to preserve ALL fields let currentConfig = {}; try { - const getUrl = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`; - const currentResponse = await axios.get(getUrl, { - headers: { - 'xc-token': process.env.NOCODB_API_TOKEN - }, - params: { - sort: '-created_at', - limit: 1 + const response = await axios.get( + `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`, + { + headers: { + 'xc-token': process.env.NOCODB_API_TOKEN, + 'Content-Type': 'application/json' + }, + params: { + sort: '-created_at', + limit: 1 + } } - }); + ); - if (currentResponse.data?.list?.length > 0) { - currentConfig = currentResponse.data.list[0]; + if (response.data?.list && response.data.list.length > 0) { + currentConfig = response.data.list[0]; + logger.info('Loaded existing settings for preservation'); } } catch (e) { - logger.warn('Could not fetch current settings:', e.message); + logger.warn('Could not load existing settings, using defaults:', e.message); } - // Create new settings row with updated location + // Create new settings row with updated location but preserve everything else const settingData = { - // System fields created_at: new Date().toISOString(), created_by: req.session.userEmail, - - // Location fields + // Map location fields (what we're updating) 'Geo-Location': `${lat};${lng}`, latitude: lat, longitude: lng, zoom: mapZoom, - - // Preserve walk sheet fields + // Preserve all walk sheet fields walk_sheet_title: currentConfig.walk_sheet_title || 'Campaign Walk Sheet', walk_sheet_subtitle: currentConfig.walk_sheet_subtitle || 'Door-to-Door Canvassing Form', walk_sheet_footer: currentConfig.walk_sheet_footer || 'Thank you for your support!', @@ -814,9 +815,9 @@ app.get('/api/admin/walk-sheet-config', requireAdmin, async (req, res) => { app.post('/api/admin/walk-sheet-config', requireAdmin, async (req, res) => { try { if (!SETTINGS_SHEET_ID) { - return res.status(400).json({ + return res.status(500).json({ success: false, - error: 'Settings sheet not configured. Please configure NOCODB_SETTINGS_SHEET environment variable.' + error: 'Settings sheet not configured' }); } @@ -833,34 +834,56 @@ app.post('/api/admin/walk-sheet-config', requireAdmin, async (req, res) => { }); } + // Get the most recent settings to preserve ALL fields + let currentConfig = {}; + try { + const response = await axios.get( + `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`, + { + headers: { + 'xc-token': process.env.NOCODB_API_TOKEN, + 'Content-Type': 'application/json' + }, + params: { + sort: '-created_at', + limit: 1 + } + } + ); + + if (response.data?.list && response.data.list.length > 0) { + currentConfig = response.data.list[0]; + logger.info('Loaded existing settings for preservation'); + } + } catch (e) { + logger.warn('Could not load existing settings, using defaults:', e.message); + } + const userEmail = req.session.userEmail; const timestamp = new Date().toISOString(); - // Prepare data for saving - only include walk sheet fields + // Prepare data for saving - include ALL fields const walkSheetData = { - // System fields created_at: timestamp, created_by: userEmail, - - // Walk sheet fields in underscore format + // Preserve map location fields from last saved config + 'Geo-Location': currentConfig['Geo-Location'] || currentConfig.geodata || '53.5461;-113.4938', + latitude: currentConfig.latitude || 53.5461, + longitude: currentConfig.longitude || -113.4938, + zoom: currentConfig.zoom || 11, + // Walk sheet fields (what we're updating) walk_sheet_title: (config.walk_sheet_title || '').toString().trim(), walk_sheet_subtitle: (config.walk_sheet_subtitle || '').toString().trim(), walk_sheet_footer: (config.walk_sheet_footer || '').toString().trim(), - - // Also save in title case format for compatibility 'Walk Sheet Title': (config.walk_sheet_title || '').toString().trim(), 'Walk Sheet Subtitle': (config.walk_sheet_subtitle || '').toString().trim(), 'Walk Sheet Footer': (config.walk_sheet_footer || '').toString().trim(), - - // QR Code fields in underscore format qr_code_1_url: validateUrl(config.qr_code_1_url), qr_code_1_label: (config.qr_code_1_label || '').toString().trim(), qr_code_2_url: validateUrl(config.qr_code_2_url), qr_code_2_label: (config.qr_code_2_label || '').toString().trim(), qr_code_3_url: validateUrl(config.qr_code_3_url), qr_code_3_label: (config.qr_code_3_label || '').toString().trim(), - - // Also save in title case format 'QR Code 1 URL': validateUrl(config.qr_code_1_url), 'QR Code 1 Label': (config.qr_code_1_label || '').toString().trim(), 'QR Code 2 URL': validateUrl(config.qr_code_2_url), @@ -905,10 +928,12 @@ app.post('/api/admin/walk-sheet-config', requireAdmin, async (req, res) => { let errorDetails = null; if (error.response?.data) { - errorDetails = error.response.data; if (error.response.data.message) { errorMessage = error.response.data.message; } + if (error.response.data.errors) { + errorDetails = error.response.data.errors; + } } res.status(500).json({