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 {
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;

View File

@ -489,14 +489,19 @@ function generateWalkSheetPreview() {
<div class="ws-form-row">
<div class="ws-form-group">
<label class="ws-form-label">Support Level (1-4)</label>
<div class="ws-form-field"></div>
<label class="ws-form-label">Support Level</label>
<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 class="ws-form-group">
<label class="ws-form-label">Sign Request</label>
<div class="ws-form-field" style="display: flex; gap: 20px;">
<span> Yes</span>
<span> No</span>
<div class="ws-form-field circles">
<span class="ws-circle-option"><span class="ws-circle">Y</span> Yes</span>
<span class="ws-circle-option"><span class="ws-circle">N</span> No</span>
</div>
</div>
</div>
@ -504,7 +509,11 @@ function generateWalkSheetPreview() {
<div class="ws-form-row">
<div class="ws-form-group">
<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 class="ws-form-group">
<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() {
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 = `<img src="${qrImageUrl}" alt="QR Code ${i}" style="width: 100px; height: 100px;">`;
// 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 = `<img src="${qrImageUrl}" alt="QR Code ${i}" style="width: 120px; height: 120px;">`; // Display smaller
} catch (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) {
// 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

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 = {};
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({