okay bidirectional saving done
This commit is contained in:
parent
1ba3899669
commit
c1f6f25c7d
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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({
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user