okay bidirectional saving done

This commit is contained in:
admin 2025-07-06 22:50:07 -06:00
parent 1ba3899669
commit c1f6f25c7d
3 changed files with 227 additions and 103 deletions

View File

@ -277,20 +277,19 @@
} }
/* Walk Sheet Preview */ /* Walk Sheet Preview */
.walk-sheet-preview { .walk-sheet-preview {
background-color: #f5f5f5; background-color: #f5f5f5;
padding: 20px; padding: 30px;
border-radius: var(--border-radius); border-radius: var(--border-radius);
box-shadow: 0 4px 24px rgba(0,0,0,0.10); box-shadow: 0 4px 24px rgba(0,0,0,0.10);
min-width: 350px; min-width: 350px;
max-width: 600px; max-width: 100%;
margin: 0 auto; margin: 0 auto;
height: 700px; min-height: 950px; /* Ensure container is tall enough */
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
overflow-x: auto; overflow: auto;
} }
.walk-sheet-preview h3 { .walk-sheet-preview h3 {
@ -311,19 +310,21 @@
color: #666; color: #666;
} }
/* Walk Sheet Page (8.5 x 11 preview) */ /* Walk Sheet Page (8.5 x 11 actual size) */
.walk-sheet-page { .walk-sheet-page {
width: 100%; /* Actual paper size in pixels at 96 DPI (standard screen resolution) */
max-width: 425px; /* Half of 8.5 inches at 100dpi */ width: 816px; /* 8.5 inches * 96 DPI */
aspect-ratio: 8.5 / 11; height: 1056px; /* 11 inches * 96 DPI */
background: white; background: white;
box-shadow: 0 2px 10px rgba(0,0,0,0.1); box-shadow: 0 4px 20px rgba(0,0,0,0.15);
padding: 40px; /* Match print padding */ padding: 48px; /* 0.5 inch margins at 96 DPI */
margin: 0 auto; margin: 0 auto;
overflow: auto; overflow: hidden;
font-size: 12px; /* Match print font size */ font-size: 12pt; /* Standard document font size */
line-height: 1.4; line-height: 1.6;
position: relative; position: relative;
transform: none; /* No scaling by default */
box-sizing: border-box;
} }
/* Walk Sheet Print Styles */ /* 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 { .walk-sheet-preview .walk-sheet-page {
transform: scale(0.75); transform: none;
transform-origin: top center; transform-origin: top center;
margin-bottom: -25%; margin-bottom: 0;
box-shadow: 0 2px 10px rgba(0,0,0,0.12); border-radius: 0; /* Remove rounded corners to look more like paper */
border-radius: 8px;
} }
/* Walk Sheet Content Styles */ /* Walk Sheet Content Styles - adjusted for actual size */
.ws-header { .ws-header {
text-align: center; text-align: center;
margin-bottom: 20px; margin-bottom: 30px; /* Reduced from 40px */
border-bottom: 2px solid #333; border-bottom: 3px solid #333;
padding-bottom: 10px; padding-bottom: 15px; /* Reduced from 20px */
} }
.ws-title { .ws-title {
font-size: 18px; font-size: 24pt; /* Reduced from 28pt */
font-weight: bold; font-weight: bold;
margin: 0; margin: 0;
color: #000;
} }
.ws-subtitle { .ws-subtitle {
font-size: 14px; font-size: 12pt; /* Reduced from 14pt */
color: #666; color: #444;
margin: 5px 0 0 0; margin: 8px 0 0 0; /* Reduced from 10px */
} }
.ws-qr-section { .ws-qr-section {
display: flex; display: flex;
justify-content: space-around; justify-content: space-around;
margin: 20px 0; margin: 30px 0; /* Reduced from 40px */
padding: 15px; padding: 20px; /* Reduced from 30px */
background-color: #f9f9f9; background-color: #f5f5f5;
border-radius: 5px; border-radius: 8px;
border: 1px solid #ddd;
} }
.ws-qr-item { .ws-qr-item {
@ -408,6 +410,8 @@
.ws-qr-code img { .ws-qr-code img {
display: block; display: block;
margin: 0 auto; margin: 0 auto;
width: 120px; /* Reduced from 140px */
height: 120px;
image-rendering: crisp-edges; image-rendering: crisp-edges;
image-rendering: -webkit-crisp-edges; image-rendering: -webkit-crisp-edges;
image-rendering: pixelated; image-rendering: pixelated;
@ -424,65 +428,105 @@
} }
.ws-qr-label { .ws-qr-label {
font-size: 10px; font-size: 11pt;
font-weight: bold; font-weight: bold;
margin-top: 10px;
color: #333;
} }
.ws-form-section { .ws-form-section {
margin-top: 20px; margin-top: 30px; /* Reduced from 40px */
} }
.ws-form-row { .ws-form-row {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 10px; gap: 20px;
margin-bottom: 10px; margin-bottom: 18px; /* Reduced from 20px */
} }
.ws-form-group { .ws-form-group {
border-bottom: 1px solid #ccc; border-bottom: 2px solid #ccc;
padding-bottom: 5px; padding-bottom: 8px;
} }
.ws-form-label { .ws-form-label {
font-size: 9px; font-size: 10pt;
color: #666; color: #555;
display: block; display: block;
margin-bottom: 2px; margin-bottom: 5px;
font-weight: 500;
} }
.ws-form-field { .ws-form-field {
height: 20px; height: 30px;
background-color: #f9f9f9; 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 { .ws-notes-section {
margin-top: 20px; margin-top: 30px; /* Reduced from 40px */
} }
.ws-notes-label { .ws-notes-label {
font-size: 10px; font-size: 11pt;
font-weight: bold; font-weight: bold;
margin-bottom: 5px; margin-bottom: 10px;
color: #333;
} }
.ws-notes-area { .ws-notes-area {
width: 100%; width: 100%;
height: 60px; height: 80px; /* Reduced from 100px */
border: 1px solid #ccc; border: 2px solid #ccc;
background-color: #f9f9f9; background-color: #fafafa;
border-radius: 4px;
} }
.ws-footer { .ws-footer {
position: absolute; position: absolute;
bottom: 20px; bottom: 48px; /* Match padding */
left: 20px; left: 48px;
right: 20px; right: 48px;
text-align: center; text-align: center;
font-size: 9px; font-size: 10pt;
color: #666; color: #666;
padding-top: 10px; padding-top: 15px; /* Reduced from 20px */
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
max-height: 80px; /* Ensure footer has enough space */
overflow: hidden;
line-height: 1.4;
} }
/* QR Code Generation Status */ /* QR Code Generation Status */
@ -520,7 +564,17 @@
color: var(--warning-color); 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) { @media (max-width: 1200px) {
.walk-sheet-container { .walk-sheet-container {
grid-template-columns: 1fr; grid-template-columns: 1fr;
@ -528,11 +582,20 @@
.walk-sheet-preview { .walk-sheet-preview {
order: -1; order: -1;
max-width: 100vw; 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 { .walk-sheet-preview .walk-sheet-page {
transform: scale(0.65); transform: scale(0.65);
} }
.walk-sheet-preview {
min-height: 700px;
}
} }
@media (max-width: 768px) { @media (max-width: 768px) {
@ -573,13 +636,12 @@
.walk-sheet-preview { .walk-sheet-preview {
min-width: 0; min-width: 0;
max-width: 100vw; max-width: 100vw;
height: 350px; min-height: 500px;
padding: 8px; padding: 10px;
} }
.walk-sheet-preview .walk-sheet-page { .walk-sheet-preview .walk-sheet-page {
transform: scale(0.48); transform: scale(0.45);
min-width: 320px; margin: 0 auto;
margin-bottom: 0;
} }
.walk-sheet-page { .walk-sheet-page {
font-size: 8px; 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) */ /* CSS Variables (define these in style.css if not already defined) */
:root { :root {
--primary-color: #4CAF50; --primary-color: #4CAF50;

View File

@ -489,14 +489,19 @@ function generateWalkSheetPreview() {
<div class="ws-form-row"> <div class="ws-form-row">
<div class="ws-form-group"> <div class="ws-form-group">
<label class="ws-form-label">Support Level (1-4)</label> <label class="ws-form-label">Support Level</label>
<div class="ws-form-field"></div> <div class="ws-form-field circles">
<span class="ws-circle-option"><span class="ws-circle">1</span></span>
<span class="ws-circle-option"><span class="ws-circle">2</span></span>
<span class="ws-circle-option"><span class="ws-circle">3</span></span>
<span class="ws-circle-option"><span class="ws-circle">4</span></span>
</div>
</div> </div>
<div class="ws-form-group"> <div class="ws-form-group">
<label class="ws-form-label">Sign Request</label> <label class="ws-form-label">Sign Request</label>
<div class="ws-form-field" style="display: flex; gap: 20px;"> <div class="ws-form-field circles">
<span> Yes</span> <span class="ws-circle-option"><span class="ws-circle">Y</span> Yes</span>
<span> No</span> <span class="ws-circle-option"><span class="ws-circle">N</span> No</span>
</div> </div>
</div> </div>
</div> </div>
@ -504,7 +509,11 @@ function generateWalkSheetPreview() {
<div class="ws-form-row"> <div class="ws-form-row">
<div class="ws-form-group"> <div class="ws-form-group">
<label class="ws-form-label">Sign Size</label> <label class="ws-form-label">Sign Size</label>
<div class="ws-form-field"></div> <div class="ws-form-field circles">
<span class="ws-circle-option"><span class="ws-circle">S</span></span>
<span class="ws-circle-option"><span class="ws-circle">M</span></span>
<span class="ws-circle-option"><span class="ws-circle">L</span></span>
</div>
</div> </div>
<div class="ws-form-group"> <div class="ws-form-group">
<label class="ws-form-label">Visited Date</label> <label class="ws-form-label">Visited Date</label>
@ -542,7 +551,7 @@ function generateWalkSheetPreview() {
} }
} }
// Generate QR codes for preview // Update the generatePreviewQRCodes function to use smaller size
async function generatePreviewQRCodes() { async function generatePreviewQRCodes() {
for (let i = 1; i <= 3; i++) { for (let i = 1; i <= 3; i++) {
const urlInput = document.getElementById(`qr-code-${i}-url`); const urlInput = document.getElementById(`qr-code-${i}-url`);
@ -551,12 +560,12 @@ async function generatePreviewQRCodes() {
if (url && qrContainer) { if (url && qrContainer) {
try { try {
// Use our local QR code generation endpoint // Use our local QR code generation endpoint with size matching display
const qrImageUrl = `/api/qr?text=${encodeURIComponent(url)}&size=100`; const qrImageUrl = `/api/qr?text=${encodeURIComponent(url)}&size=200`; // Generate at higher res
qrContainer.innerHTML = `<img src="${qrImageUrl}" alt="QR Code ${i}" style="width: 100px; height: 100px;">`; qrContainer.innerHTML = `<img src="${qrImageUrl}" alt="QR Code ${i}" style="width: 120px; height: 120px;">`; // Display smaller
} catch (error) { } catch (error) {
console.error(`Failed to display QR code ${i}:`, error); console.error(`Failed to display QR code ${i}:`, error);
qrContainer.innerHTML = '<div style="width: 100px; height: 100px; border: 1px dashed #ccc; display: flex; align-items: center; justify-content: center; font-size: 10px; color: #999;">QR Error</div>'; qrContainer.innerHTML = '<div style="width: 120px; height: 120px; border: 2px dashed #ccc; display: flex; align-items: center; justify-content: center; font-size: 14px; color: #999;">QR Error</div>';
} }
} else if (qrContainer) { } else if (qrContainer) {
// Clear empty QR containers // Clear empty QR containers
@ -565,6 +574,7 @@ async function generatePreviewQRCodes() {
} }
} }
// Print walk sheet // Print walk sheet
function printWalkSheet() { function printWalkSheet() {
// First generate fresh preview to ensure QR codes are generated // First generate fresh preview to ensure QR codes are generated

View File

@ -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 = {}; let currentConfig = {};
try { try {
const getUrl = `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`; const response = await axios.get(
const currentResponse = await axios.get(getUrl, { `${process.env.NOCODB_API_URL}/db/data/v1/${process.env.NOCODB_PROJECT_ID}/${SETTINGS_SHEET_ID}`,
{
headers: { headers: {
'xc-token': process.env.NOCODB_API_TOKEN 'xc-token': process.env.NOCODB_API_TOKEN,
'Content-Type': 'application/json'
}, },
params: { params: {
sort: '-created_at', sort: '-created_at',
limit: 1 limit: 1
} }
}); }
);
if (currentResponse.data?.list?.length > 0) { if (response.data?.list && response.data.list.length > 0) {
currentConfig = currentResponse.data.list[0]; currentConfig = response.data.list[0];
logger.info('Loaded existing settings for preservation');
} }
} catch (e) { } 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 = { const settingData = {
// System fields
created_at: new Date().toISOString(), created_at: new Date().toISOString(),
created_by: req.session.userEmail, created_by: req.session.userEmail,
// Map location fields (what we're updating)
// Location fields
'Geo-Location': `${lat};${lng}`, 'Geo-Location': `${lat};${lng}`,
latitude: lat, latitude: lat,
longitude: lng, longitude: lng,
zoom: mapZoom, zoom: mapZoom,
// Preserve all walk sheet fields
// Preserve walk sheet fields
walk_sheet_title: currentConfig.walk_sheet_title || 'Campaign Walk Sheet', walk_sheet_title: currentConfig.walk_sheet_title || 'Campaign Walk Sheet',
walk_sheet_subtitle: currentConfig.walk_sheet_subtitle || 'Door-to-Door Canvassing Form', walk_sheet_subtitle: currentConfig.walk_sheet_subtitle || 'Door-to-Door Canvassing Form',
walk_sheet_footer: currentConfig.walk_sheet_footer || 'Thank you for your support!', 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) => { app.post('/api/admin/walk-sheet-config', requireAdmin, async (req, res) => {
try { try {
if (!SETTINGS_SHEET_ID) { if (!SETTINGS_SHEET_ID) {
return res.status(400).json({ return res.status(500).json({
success: false, 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 userEmail = req.session.userEmail;
const timestamp = new Date().toISOString(); const timestamp = new Date().toISOString();
// Prepare data for saving - only include walk sheet fields // Prepare data for saving - include ALL fields
const walkSheetData = { const walkSheetData = {
// System fields
created_at: timestamp, created_at: timestamp,
created_by: userEmail, created_by: userEmail,
// Preserve map location fields from last saved config
// Walk sheet fields in underscore format '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_title: (config.walk_sheet_title || '').toString().trim(),
walk_sheet_subtitle: (config.walk_sheet_subtitle || '').toString().trim(), walk_sheet_subtitle: (config.walk_sheet_subtitle || '').toString().trim(),
walk_sheet_footer: (config.walk_sheet_footer || '').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 Title': (config.walk_sheet_title || '').toString().trim(),
'Walk Sheet Subtitle': (config.walk_sheet_subtitle || '').toString().trim(), 'Walk Sheet Subtitle': (config.walk_sheet_subtitle || '').toString().trim(),
'Walk Sheet Footer': (config.walk_sheet_footer || '').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_url: validateUrl(config.qr_code_1_url),
qr_code_1_label: (config.qr_code_1_label || '').toString().trim(), qr_code_1_label: (config.qr_code_1_label || '').toString().trim(),
qr_code_2_url: validateUrl(config.qr_code_2_url), qr_code_2_url: validateUrl(config.qr_code_2_url),
qr_code_2_label: (config.qr_code_2_label || '').toString().trim(), qr_code_2_label: (config.qr_code_2_label || '').toString().trim(),
qr_code_3_url: validateUrl(config.qr_code_3_url), qr_code_3_url: validateUrl(config.qr_code_3_url),
qr_code_3_label: (config.qr_code_3_label || '').toString().trim(), 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 URL': validateUrl(config.qr_code_1_url),
'QR Code 1 Label': (config.qr_code_1_label || '').toString().trim(), 'QR Code 1 Label': (config.qr_code_1_label || '').toString().trim(),
'QR Code 2 URL': validateUrl(config.qr_code_2_url), '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; let errorDetails = null;
if (error.response?.data) { if (error.response?.data) {
errorDetails = error.response.data;
if (error.response.data.message) { if (error.response.data.message) {
errorMessage = error.response.data.message; errorMessage = error.response.data.message;
} }
if (error.response.data.errors) {
errorDetails = error.response.data.errors;
}
} }
res.status(500).json({ res.status(500).json({