fr hide default values, app now modular, and easy to add templates + new template
Some checks failed
Deploy static content to Pages / deploy (push) Has been cancelled

This commit is contained in:
2026-03-22 00:24:30 +05:00
parent 5437bcf721
commit e67cc4ca9c
8 changed files with 354 additions and 216 deletions

225
app.js
View File

@@ -1,25 +1,13 @@
// Garage Templates Configuration
const garageTemplates = {
'rs-auto': {
name: 'RS AUTO',
phone: '775 8999',
logo: {
type: 'svg',
content: `
<svg viewBox="0 0 80 80" class="logo-svg">
<circle cx="40" cy="40" r="38" fill="none" stroke="#000" stroke-width="2"/>
<text x="40" y="18" text-anchor="middle" font-size="8" font-weight="bold">RS AUTO</text>
<text x="40" y="45" text-anchor="middle" font-size="24" font-weight="bold">S</text>
<path d="M20 50 Q40 35 60 50" fill="none" stroke="#000" stroke-width="2"/>
<text x="40" y="58" text-anchor="middle" font-size="6">P:0085037</text>
<text x="40" y="70" text-anchor="middle" font-size="8" font-weight="bold">MALDIVES</text>
</svg>
`
}
}
// Add more garage templates here as needed
// Template cache
const templateCache = {
html: {},
css: {}
};
// Current template ID
let currentTemplateId = null;
let currentBarcodeNumber = null;
// DOM Elements
const form = document.getElementById('stickerForm');
const garageSelect = document.getElementById('garage');
@@ -30,26 +18,114 @@ const modelNumberInput = document.getElementById('modelNumber');
const chassisNumberInput = document.getElementById('chassisNumber');
const engineSerialInput = document.getElementById('engineSerial');
const engineCapacityInput = document.getElementById('engineCapacity');
// Preview Elements
const previewRegNumber = document.getElementById('previewRegNumber');
const previewBarcodeNumber = document.getElementById('previewBarcodeNumber');
const previewBarcode = document.getElementById('previewBarcode');
const previewFromDate = document.getElementById('previewFromDate');
const previewToDate = document.getElementById('previewToDate');
const previewModelNumber = document.getElementById('previewModelNumber');
const previewChassisNumber = document.getElementById('previewChassisNumber');
const previewEngineSerial = document.getElementById('previewEngineSerial');
const previewEngineCapacity = document.getElementById('previewEngineCapacity');
const previewGarageInfo = document.getElementById('previewGarageInfo');
const garageLogo = document.getElementById('garageLogo');
const stickerPreview = document.getElementById('stickerPreview');
// Initialize the application
function init() {
async function init() {
setDefaultDates();
await loadTemplateList();
setupEventListeners();
updatePreview();
generateBarcode();
generateBarcodeNumber();
await loadTemplate(garageSelect.value);
}
// Load template list from index.json
async function loadTemplateList() {
try {
const response = await fetch('templates/index.json');
const data = await response.json();
garageSelect.innerHTML = '';
data.templates.forEach(template => {
const option = document.createElement('option');
option.value = template.id;
option.textContent = template.name;
garageSelect.appendChild(option);
});
} catch (error) {
console.error('Failed to load template list:', error);
showSnackbar('Failed to load templates', 'error');
}
}
// Load a specific template (HTML + CSS)
async function loadTemplate(templateId) {
if (currentTemplateId === templateId && templateCache.html[templateId]) {
renderTemplate(templateId);
return;
}
try {
// Load HTML if not cached
if (!templateCache.html[templateId]) {
const htmlResponse = await fetch(`templates/${templateId}.html`);
templateCache.html[templateId] = await htmlResponse.text();
}
// Load CSS if not cached
if (!templateCache.css[templateId]) {
const cssResponse = await fetch(`templates/${templateId}.css`);
templateCache.css[templateId] = await cssResponse.text();
// Inject CSS into head
const styleId = `template-style-${templateId}`;
let styleEl = document.getElementById(styleId);
if (!styleEl) {
styleEl = document.createElement('style');
styleEl.id = styleId;
document.head.appendChild(styleEl);
}
styleEl.textContent = templateCache.css[templateId];
}
currentTemplateId = templateId;
renderTemplate(templateId);
} catch (error) {
console.error('Failed to load template:', error);
showSnackbar('Failed to load template', 'error');
}
}
// Render template with current form values
function renderTemplate(templateId) {
const html = templateCache.html[templateId];
if (!html) return;
// Get current values - show empty placeholders if no input
const values = {
regNumber: regNumberInput.value ? formatRegNumber(regNumberInput.value) : 'X 0 X 0 0 0 0',
barcodeNumber: currentBarcodeNumber || 'XXXXXXXXXX',
fromDate: formatDateForDisplay(fromDateInput.value) || 'XX-XX-XXXX',
toDate: formatDateForDisplay(toDateInput.value) || 'XX-XX-XXXX',
modelNumber: modelNumberInput.value || 'XXX000-000X',
chassisNumber: chassisNumberInput.value || 'XXXXXXXXXXXXXXXXX',
engineSerial: engineSerialInput.value || 'XXX-XXXXXX',
engineCapacity: engineCapacityInput.value || 'XXX.XXX'
};
// Replace placeholders
let rendered = html;
for (const [key, value] of Object.entries(values)) {
rendered = rendered.replace(new RegExp(`{{${key}}}`, 'g'), value);
}
stickerPreview.innerHTML = rendered;
// Generate barcode
const barcodeEl = stickerPreview.querySelector('.barcode');
if (barcodeEl && currentBarcodeNumber) {
try {
JsBarcode(barcodeEl, currentBarcodeNumber, {
format: 'CODE128',
width: 1.5,
height: 25,
displayValue: false,
margin: 0
});
} catch (e) {
console.error('Barcode generation failed:', e);
}
}
}
// Set default dates (today and 1 year from today)
@@ -84,12 +160,19 @@ function formatRegNumber(regNumber) {
// Generate a random barcode number (10 digits)
function generateBarcodeNumber() {
return Math.floor(1000000000 + Math.random() * 9000000000).toString();
currentBarcodeNumber = Math.floor(1000000000 + Math.random() * 9000000000).toString();
}
// Setup event listeners
function setupEventListeners() {
// Template change
garageSelect.addEventListener('change', async () => {
await loadTemplate(garageSelect.value);
});
// Real-time preview updates
const updatePreview = () => renderTemplate(currentTemplateId);
regNumberInput.addEventListener('input', updatePreview);
fromDateInput.addEventListener('change', updatePreview);
toDateInput.addEventListener('change', updatePreview);
@@ -97,7 +180,6 @@ function setupEventListeners() {
chassisNumberInput.addEventListener('input', updatePreview);
engineSerialInput.addEventListener('input', updatePreview);
engineCapacityInput.addEventListener('input', updatePreview);
garageSelect.addEventListener('change', updatePreview);
// Auto-uppercase for certain fields
regNumberInput.addEventListener('input', (e) => {
@@ -117,53 +199,10 @@ function setupEventListeners() {
const toDate = new Date(fromDate);
toDate.setFullYear(toDate.getFullYear() + 1);
toDateInput.value = formatDateForInput(toDate);
updatePreview();
renderTemplate(currentTemplateId);
});
}
// Update the sticker preview
function updatePreview() {
const garage = garageTemplates[garageSelect.value];
// Update registration number
const regNumber = regNumberInput.value || 'X0X0000';
previewRegNumber.textContent = formatRegNumber(regNumber);
// Update dates
previewFromDate.textContent = formatDateForDisplay(fromDateInput.value) || 'DD-MM-YYYY';
previewToDate.textContent = formatDateForDisplay(toDateInput.value) || 'DD-MM-YYYY';
// Update vehicle info
previewModelNumber.textContent = modelNumberInput.value || 'XXXX-XXXXX';
previewChassisNumber.textContent = chassisNumberInput.value || 'XXXXXXXXXXXXXXXXX';
previewEngineSerial.textContent = engineSerialInput.value || 'XXX-XXXXXX';
previewEngineCapacity.textContent = engineCapacityInput.value || '000.000';
// Update garage info
if (garage) {
previewGarageInfo.textContent = `${garage.name}, TEL: ${garage.phone}`;
garageLogo.innerHTML = garage.logo.content;
}
}
// Generate barcode
function generateBarcode() {
const barcodeNumber = generateBarcodeNumber();
previewBarcodeNumber.textContent = barcodeNumber;
try {
JsBarcode(previewBarcode, barcodeNumber, {
format: 'CODE128',
width: 1.5,
height: 30,
displayValue: false,
margin: 0
});
} catch (e) {
console.error('Barcode generation failed:', e);
}
}
// Handle form submission
async function handleFormSubmit(e) {
e.preventDefault();
@@ -173,6 +212,13 @@ async function handleFormSubmit(e) {
submitBtn.querySelector('.material-icons').textContent = 'autorenew';
try {
// Generate new barcode for final PDF
generateBarcodeNumber();
renderTemplate(currentTemplateId);
// Wait for render
await new Promise(resolve => setTimeout(resolve, 100));
await generatePDF();
showSnackbar('Sticker generated successfully!', 'success');
} catch (error) {
@@ -187,17 +233,15 @@ async function handleFormSubmit(e) {
// Generate PDF
async function generatePDF() {
const { jsPDF } = window.jspdf;
const stickerElement = document.querySelector('.sticker');
const stickerElement = stickerPreview.querySelector('.sticker, [class^="sticker-"]');
// Generate a new barcode number for this sticker
generateBarcode();
// Wait for barcode to render
await new Promise(resolve => setTimeout(resolve, 100));
if (!stickerElement) {
throw new Error('No sticker element found');
}
// Capture the sticker as an image
const canvas = await html2canvas(stickerElement, {
scale: 3, // Higher resolution
scale: 3,
backgroundColor: '#ffffff',
useCORS: true,
logging: false
@@ -206,7 +250,6 @@ async function generatePDF() {
// Create PDF with sticker dimensions
const imgData = canvas.toDataURL('image/png');
// Calculate dimensions (sticker is approximately 280px wide)
const pdfWidth = 100; // mm
const pdfHeight = (canvas.height / canvas.width) * pdfWidth;
@@ -216,7 +259,6 @@ async function generatePDF() {
format: [pdfWidth + 10, pdfHeight + 10]
});
// Center the sticker
const x = 5;
const y = 5;
@@ -226,7 +268,6 @@ async function generatePDF() {
const regNumber = regNumberInput.value || 'sticker';
const filename = `roadworthiness-${regNumber.replace(/\s/g, '')}-${Date.now()}.pdf`;
// Save the PDF
pdf.save(filename);
}

View File

@@ -38,7 +38,7 @@
<label class="form-label" for="garage">Select Garage</label>
<div class="select-wrapper">
<select id="garage" class="form-select" required>
<option value="rs-auto">RS AUTO</option>
<!-- Populated dynamically from templates/index.json -->
</select>
<span class="material-icons select-icon">expand_more</span>
</div>
@@ -48,7 +48,7 @@
<div class="form-group">
<label class="form-label" for="regNumber">Vehicle Registration Number</label>
<input type="text" id="regNumber" class="form-input"
placeholder="e.g., A0F7930" required
placeholder="X0X0000" required
pattern="[A-Za-z0-9]+" maxlength="10">
<span class="form-hint">Letters and numbers only</span>
</div>
@@ -69,28 +69,28 @@
<div class="form-group">
<label class="form-label" for="modelNumber">Model Number</label>
<input type="text" id="modelNumber" class="form-input"
placeholder="e.g., 55S400-010C" required>
placeholder="XXX000-000X" required>
</div>
<!-- Chassis Number -->
<div class="form-group">
<label class="form-label" for="chassisNumber">Chassis Number</label>
<input type="text" id="chassisNumber" class="form-input"
placeholder="e.g., MH355S004DK117181" required>
placeholder="XXXXXXXXXXXXXXXXX" required>
</div>
<!-- Engine Serial Number -->
<div class="form-group">
<label class="form-label" for="engineSerial">Engine Serial Number</label>
<input type="text" id="engineSerial" class="form-input"
placeholder="e.g., 55S-118388" required>
placeholder="XXX-XXXXXX" required>
</div>
<!-- Engine Capacity -->
<div class="form-group">
<label class="form-label" for="engineCapacity">Engine Capacity (CC)</label>
<input type="text" id="engineCapacity" class="form-input"
placeholder="e.g., 135.000" required>
placeholder="XXX.XXX" required>
</div>
<!-- Generate Button -->
@@ -109,45 +109,7 @@
</div>
<div class="preview-container">
<div id="stickerPreview" class="sticker-preview">
<!-- Sticker will be rendered here -->
<div class="sticker">
<div class="sticker-header">
<span id="previewRegNumber">X 0 X 0 0 0 0</span>
</div>
<div class="sticker-barcode-row">
<span id="previewBarcodeNumber">0000000000</span>
<svg id="previewBarcode"></svg>
</div>
<div class="sticker-body">
<div class="sticker-info">
<div class="date-row">
<span><strong>From:</strong> <span id="previewFromDate">DD-MM-YYYY</span></span>
</div>
<div class="date-row">
<span><strong>To:</strong> <span id="previewToDate">DD-MM-YYYY</span></span>
</div>
<div id="previewModelNumber">XXXX-XXXXX</div>
<div id="previewChassisNumber">XXXXXXXXXXXXXXXXX</div>
<div id="previewEngineSerial">XXX-XXXXXX</div>
<div id="previewEngineCapacity">000.000</div>
</div>
<div class="sticker-logo">
<div id="garageLogo" class="garage-logo">
<svg viewBox="0 0 80 80" class="logo-svg">
<circle cx="40" cy="40" r="38" fill="none" stroke="#000" stroke-width="2"/>
<text x="40" y="18" text-anchor="middle" font-size="8" font-weight="bold">RS AUTO</text>
<text x="40" y="45" text-anchor="middle" font-size="24" font-weight="bold">S</text>
<path d="M20 50 Q40 35 60 50" fill="none" stroke="#000" stroke-width="2"/>
<text x="40" y="58" text-anchor="middle" font-size="6">P:0085037</text>
<text x="40" y="70" text-anchor="middle" font-size="8" font-weight="bold">MALDIVES</text>
</svg>
</div>
</div>
</div>
<div class="sticker-footer">
<span id="previewGarageInfo">RS AUTO, TEL: 775 8999</span>
</div>
</div>
<!-- Sticker template loaded dynamically -->
</div>
</div>
</div>

View File

@@ -289,87 +289,14 @@ body {
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
}
/* Sticker Preview */
/* Sticker Preview Container */
.sticker-preview {
background-color: var(--md-sys-color-surface-container);
padding: 8px;
box-shadow: var(--md-sys-elevation-2);
}
.sticker {
width: 280px;
border: 2px solid #000;
font-family: Arial, sans-serif;
font-size: 11px;
background: #fff;
color: #000;
}
.sticker-header {
background-color: #000;
color: #fff;
text-align: center;
padding: 6px 8px;
font-size: 18px;
font-weight: bold;
letter-spacing: 4px;
}
.sticker-barcode-row {
display: flex;
align-items: center;
border-bottom: 1px solid #000;
padding: 4px 8px;
gap: 8px;
}
.sticker-barcode-row span {
font-size: 11px;
font-weight: 500;
}
.sticker-barcode-row svg {
height: 30px;
flex: 1;
}
.sticker-body {
display: flex;
border-bottom: 1px solid #000;
}
.sticker-info {
flex: 1;
padding: 6px 8px;
font-size: 10px;
line-height: 1.4;
}
.sticker-info .date-row {
margin-bottom: 2px;
}
.sticker-logo {
width: 80px;
border-left: 1px solid #000;
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
}
.garage-logo .logo-svg {
width: 70px;
height: 70px;
}
.sticker-footer {
text-align: center;
padding: 4px 8px;
font-size: 10px;
font-weight: 500;
border-top: 1px solid #000;
}
/* Sticker styles are loaded dynamically from templates/*.css */
/* Snackbar */
.snackbar {
@@ -481,10 +408,6 @@ body {
.preview-container {
padding: 16px;
}
.sticker {
width: 260px;
}
}
/* Print Styles for PDF Export */

12
templates/index.json Normal file
View File

@@ -0,0 +1,12 @@
{
"templates": [
{
"id": "rs-auto",
"name": "RS AUTO"
},
{
"id": "motca",
"name": "Min. of Transport & Civil Aviation"
}
]
}

73
templates/motca.css Normal file
View File

@@ -0,0 +1,73 @@
/* Min. of Transport & Civil Aviation Template Styles */
.sticker-motca {
width: 280px;
border: 2px solid #000;
font-family: Arial, sans-serif;
font-size: 11px;
background: #fff;
color: #000;
}
.sticker-motca .sticker-header {
background-color: #000;
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
padding: 6px 8px;
font-size: 14px;
font-weight: bold;
}
.sticker-motca .sticker-header .reg-number {
letter-spacing: 1px;
}
.sticker-motca .sticker-header .barcode-number {
font-size: 12px;
}
.sticker-motca .sticker-dates-row {
display: flex;
justify-content: space-between;
padding: 4px 8px;
font-size: 10px;
border-bottom: 1px solid #000;
}
.sticker-motca .sticker-body {
display: flex;
border-bottom: 1px solid #000;
}
.sticker-motca .sticker-info {
flex: 1;
padding: 4px 8px;
font-size: 10px;
line-height: 1.4;
}
.sticker-motca .sticker-info .barcode {
height: 25px;
width: 100%;
margin-top: 4px;
}
.sticker-motca .sticker-values {
width: 80px;
padding: 4px 8px;
font-size: 10px;
font-weight: bold;
text-align: right;
display: flex;
flex-direction: column;
justify-content: flex-start;
gap: 2px;
}
.sticker-motca .sticker-footer {
text-align: center;
padding: 4px 8px;
font-size: 9px;
font-weight: 500;
}

23
templates/motca.html Normal file
View File

@@ -0,0 +1,23 @@
<div class="sticker sticker-motca">
<div class="sticker-header">
<span class="reg-number">{{regNumber}}</span>
<span class="barcode-number">{{barcodeNumber}}</span>
</div>
<div class="sticker-dates-row">
<span><strong>From</strong> {{fromDate}}</span>
<span><strong>To</strong> {{toDate}}</span>
</div>
<div class="sticker-body">
<div class="sticker-info">
<div>{{modelNumber}}</div>
<div>{{chassisNumber}}</div>
<div>{{engineSerial}}</div>
<svg class="barcode"></svg>
</div>
<div class="sticker-values">
<div>0 sc</div>
<div>{{engineCapacity}} cc</div>
</div>
</div>
<div class="sticker-footer">Min. of Transport & Civil Aviation</div>
</div>

74
templates/rs-auto.css Normal file
View File

@@ -0,0 +1,74 @@
/* RS AUTO Template Styles */
.sticker-rs-auto {
width: 280px;
border: 2px solid #000;
font-family: Arial, sans-serif;
font-size: 11px;
background: #fff;
color: #000;
}
.sticker-rs-auto .sticker-header {
background-color: #000;
color: #fff;
text-align: center;
padding: 6px 8px;
font-size: 18px;
font-weight: bold;
letter-spacing: 4px;
}
.sticker-rs-auto .sticker-barcode-row {
display: flex;
align-items: center;
border-bottom: 1px solid #000;
padding: 4px 8px;
gap: 8px;
}
.sticker-rs-auto .sticker-barcode-row .barcode-number {
font-size: 11px;
font-weight: 500;
}
.sticker-rs-auto .sticker-barcode-row .barcode {
height: 30px;
flex: 1;
}
.sticker-rs-auto .sticker-body {
display: flex;
border-bottom: 1px solid #000;
}
.sticker-rs-auto .sticker-info {
flex: 1;
padding: 6px 8px;
font-size: 10px;
line-height: 1.4;
}
.sticker-rs-auto .sticker-info .date-row {
margin-bottom: 2px;
}
.sticker-rs-auto .sticker-logo {
width: 80px;
border-left: 1px solid #000;
display: flex;
align-items: center;
justify-content: center;
padding: 4px;
}
.sticker-rs-auto .sticker-logo .logo-svg {
width: 70px;
height: 70px;
}
.sticker-rs-auto .sticker-footer {
text-align: center;
padding: 4px 8px;
font-size: 10px;
font-weight: 500;
}

30
templates/rs-auto.html Normal file
View File

@@ -0,0 +1,30 @@
<div class="sticker sticker-rs-auto">
<div class="sticker-header">
<span class="reg-number">{{regNumber}}</span>
</div>
<div class="sticker-barcode-row">
<span class="barcode-number">{{barcodeNumber}}</span>
<svg class="barcode"></svg>
</div>
<div class="sticker-body">
<div class="sticker-info">
<div class="date-row"><strong>From:</strong> {{fromDate}}</div>
<div class="date-row"><strong>To:</strong> {{toDate}}</div>
<div>{{modelNumber}}</div>
<div>{{chassisNumber}}</div>
<div>{{engineSerial}}</div>
<div>{{engineCapacity}}</div>
</div>
<div class="sticker-logo">
<svg viewBox="0 0 80 80" class="logo-svg">
<circle cx="40" cy="40" r="38" fill="none" stroke="#000" stroke-width="2"/>
<text x="40" y="18" text-anchor="middle" font-size="8" font-weight="bold">RS AUTO</text>
<text x="40" y="45" text-anchor="middle" font-size="24" font-weight="bold">S</text>
<path d="M20 50 Q40 35 60 50" fill="none" stroke="#000" stroke-width="2"/>
<text x="40" y="58" text-anchor="middle" font-size="6">P:0085037</text>
<text x="40" y="70" text-anchor="middle" font-size="8" font-weight="bold">MALDIVES</text>
</svg>
</div>
</div>
<div class="sticker-footer">RS AUTO, TEL: 775 8999</div>
</div>