This commit is contained in:
Andreas Knuth 2026-01-21 20:02:02 -06:00
parent bbd1d9e1f2
commit 711ed1e3cf
3 changed files with 151 additions and 4 deletions

View File

@ -4,17 +4,29 @@ let quotes = [];
let currentQuoteId = null; let currentQuoteId = null;
let currentCustomerId = null; let currentCustomerId = null;
let itemCounter = 0; let itemCounter = 0;
let currentLogoFile = null;
// Initialize app // Initialize app
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
loadCustomers(); loadCustomers();
loadQuotes(); loadQuotes();
setDefaultDate(); setDefaultDate();
checkCurrentLogo();
// Setup form handlers // Setup form handlers
document.getElementById('customer-form').addEventListener('submit', handleCustomerSubmit); document.getElementById('customer-form').addEventListener('submit', handleCustomerSubmit);
document.getElementById('quote-form').addEventListener('submit', handleQuoteSubmit); document.getElementById('quote-form').addEventListener('submit', handleQuoteSubmit);
document.getElementById('quote-tax-exempt').addEventListener('change', updateTotals); document.getElementById('quote-tax-exempt').addEventListener('change', updateTotals);
// Setup logo upload handler
document.getElementById('logo-upload').addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
currentLogoFile = file;
document.getElementById('logo-filename').textContent = file.name;
document.getElementById('upload-btn').disabled = false;
}
});
}); });
// Tab Management // Tab Management
@ -29,6 +41,61 @@ function showTab(tabName) {
loadQuotes(); loadQuotes();
} else if (tabName === 'customers') { } else if (tabName === 'customers') {
loadCustomers(); loadCustomers();
} else if (tabName === 'settings') {
checkCurrentLogo();
}
}
// Logo Management
async function checkCurrentLogo() {
try {
const response = await fetch('/api/logo-info');
if (response.ok) {
const data = await response.json();
if (data.hasLogo) {
document.getElementById('logo-preview').classList.remove('hidden');
document.getElementById('logo-image').src = data.logoPath + '?t=' + Date.now();
}
}
} catch (error) {
console.error('Error checking logo:', error);
}
}
async function uploadLogo() {
if (!currentLogoFile) {
alert('Please select a file first');
return;
}
const formData = new FormData();
formData.append('logo', currentLogoFile);
const statusDiv = document.getElementById('upload-status');
statusDiv.innerHTML = '<p class="text-blue-600">Uploading...</p>';
try {
const response = await fetch('/api/upload-logo', {
method: 'POST',
body: formData
});
if (response.ok) {
const data = await response.json();
statusDiv.innerHTML = '<p class="text-green-600">✓ Logo uploaded successfully!</p>';
document.getElementById('logo-preview').classList.remove('hidden');
document.getElementById('logo-image').src = data.path + '?t=' + Date.now();
document.getElementById('upload-btn').disabled = true;
currentLogoFile = null;
document.getElementById('logo-filename').textContent = '';
document.getElementById('logo-upload').value = '';
} else {
const error = await response.json();
statusDiv.innerHTML = `<p class="text-red-600">✗ Error: ${error.error}</p>`;
}
} catch (error) {
console.error('Upload error:', error);
statusDiv.innerHTML = '<p class="text-red-600">✗ Upload failed</p>';
} }
} }

View File

@ -27,6 +27,7 @@
<div class="flex space-x-4"> <div class="flex space-x-4">
<button onclick="showTab('quotes')" id="tab-quotes" class="px-4 py-2 rounded hover:bg-blue-800 tab-btn">Quotes</button> <button onclick="showTab('quotes')" id="tab-quotes" class="px-4 py-2 rounded hover:bg-blue-800 tab-btn">Quotes</button>
<button onclick="showTab('customers')" id="tab-customers" class="px-4 py-2 rounded hover:bg-blue-800 tab-btn">Customers</button> <button onclick="showTab('customers')" id="tab-customers" class="px-4 py-2 rounded hover:bg-blue-800 tab-btn">Customers</button>
<button onclick="showTab('settings')" id="tab-settings" class="px-4 py-2 rounded hover:bg-blue-800 tab-btn">Settings</button>
</div> </div>
</div> </div>
</div> </div>
@ -88,6 +89,38 @@
</table> </table>
</div> </div>
</div> </div>
<!-- Settings Tab -->
<div id="settings-tab" class="tab-content hidden">
<div class="mb-6">
<h2 class="text-3xl font-bold text-gray-800">Settings</h2>
</div>
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="text-xl font-semibold mb-4">Company Logo</h3>
<p class="text-gray-600 mb-4">Upload your company logo to appear on quotes and PDFs. Recommended size: 200x200px (PNG or JPG)</p>
<div class="mb-4">
<div id="logo-preview" class="mb-4 hidden">
<p class="text-sm text-gray-600 mb-2">Current Logo:</p>
<img id="logo-image" src="" alt="Company Logo" class="h-20 border border-gray-300 rounded">
</div>
<input type="file" id="logo-upload" accept="image/png,image/jpeg,image/jpg,image/gif" class="hidden">
<button onclick="document.getElementById('logo-upload').click()"
class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-2 rounded-lg">
Choose Logo
</button>
<span id="logo-filename" class="ml-4 text-gray-600"></span>
</div>
<button onclick="uploadLogo()" id="upload-btn" class="bg-green-600 hover:bg-green-700 text-white px-6 py-2 rounded-lg disabled:bg-gray-400" disabled>
Upload Logo
</button>
<div id="upload-status" class="mt-4"></div>
</div>
</div>
</div> </div>
</div> </div>

View File

@ -277,6 +277,9 @@ app.post('/api/quotes', async (req, res) => {
}); });
app.put('/api/quotes/:id', async (req, res) => { app.put('/api/quotes/:id', async (req, res) => {
console.log('PUT /api/quotes/:id called with id:', req.params.id);
console.log('Request body:', JSON.stringify(req.body, null, 2));
const client = await pool.connect(); const client = await pool.connect();
try { try {
await client.query('BEGIN'); await client.query('BEGIN');
@ -363,12 +366,35 @@ app.post('/api/upload-logo', upload.single('logo'), (req, res) => {
if (!req.file) { if (!req.file) {
return res.status(400).json({ error: 'No file uploaded' }); return res.status(400).json({ error: 'No file uploaded' });
} }
// Save as "current_logo" for easy access
const logoPath = path.join(__dirname, 'uploads', 'current_logo' + path.extname(req.file.filename));
fs.renameSync(req.file.path, logoPath);
res.json({ res.json({
filename: req.file.filename, filename: 'current_logo' + path.extname(req.file.filename),
path: `/uploads/${req.file.filename}` path: `/uploads/current_logo${path.extname(req.file.filename)}`
}); });
}); });
// Get logo info
app.get('/api/logo-info', (req, res) => {
const uploadsDir = path.join(__dirname, 'uploads');
const possibleExtensions = ['.png', '.jpg', '.jpeg', '.gif'];
for (const ext of possibleExtensions) {
const logoPath = path.join(uploadsDir, 'current_logo' + ext);
if (fs.existsSync(logoPath)) {
return res.json({
hasLogo: true,
logoPath: `/uploads/current_logo${ext}`
});
}
}
res.json({ hasLogo: false });
});
// Generate PDF // Generate PDF
app.post('/api/quotes/:id/pdf', async (req, res) => { app.post('/api/quotes/:id/pdf', async (req, res) => {
let browser; let browser;
@ -469,6 +495,27 @@ function generateQuoteHTML(quote) {
return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`; return `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()}`;
}; };
// Check for logo file
let logoHTML = '<div style="width: 40px; height: 40px; background-color: #1e40af; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 18px;">BA</div>';
const uploadsDir = path.join(__dirname, 'uploads');
const possibleExtensions = ['.png', '.jpg', '.jpeg', '.gif'];
for (const ext of possibleExtensions) {
const logoPath = path.join(uploadsDir, 'current_logo' + ext);
if (fs.existsSync(logoPath)) {
try {
const logoBuffer = fs.readFileSync(logoPath);
const logoBase64 = logoBuffer.toString('base64');
const mimeType = ext === '.png' ? 'image/png' : 'image/jpeg';
logoHTML = `<img class="logo-size" src="data:${mimeType};base64,${logoBase64}" alt="Logo">`;
} catch (err) {
console.error('Error reading logo file:', err);
}
break;
}
}
let itemsHTML = ''; let itemsHTML = '';
quote.items.forEach(item => { quote.items.forEach(item => {
itemsHTML += ` itemsHTML += `
@ -686,7 +733,7 @@ function generateQuoteHTML(quote) {
<div class="container"> <div class="container">
<div class="header"> <div class="header">
<div class="company-info"> <div class="company-info">
<div style="width: 40px; height: 40px; background-color: #1e40af; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; font-size: 18px;">BA</div> ${logoHTML}
<div class="company-details"> <div class="company-details">
<h1>Bay Area Affiliates, Inc.</h1> <h1>Bay Area Affiliates, Inc.</h1>
<p>1001 Blucher Street<br> <p>1001 Blucher Street<br>