asdasd
This commit is contained in:
parent
bbd1d9e1f2
commit
711ed1e3cf
|
|
@ -4,17 +4,29 @@ let quotes = [];
|
|||
let currentQuoteId = null;
|
||||
let currentCustomerId = null;
|
||||
let itemCounter = 0;
|
||||
let currentLogoFile = null;
|
||||
|
||||
// Initialize app
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadCustomers();
|
||||
loadQuotes();
|
||||
setDefaultDate();
|
||||
checkCurrentLogo();
|
||||
|
||||
// Setup form handlers
|
||||
document.getElementById('customer-form').addEventListener('submit', handleCustomerSubmit);
|
||||
document.getElementById('quote-form').addEventListener('submit', handleQuoteSubmit);
|
||||
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
|
||||
|
|
@ -29,6 +41,61 @@ function showTab(tabName) {
|
|||
loadQuotes();
|
||||
} else if (tabName === 'customers') {
|
||||
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>';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@
|
|||
<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('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>
|
||||
|
|
@ -88,6 +89,38 @@
|
|||
</table>
|
||||
</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>
|
||||
|
||||
|
|
@ -257,4 +290,4 @@
|
|||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
53
server.js
53
server.js
|
|
@ -277,6 +277,9 @@ app.post('/api/quotes', 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();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
|
|
@ -363,12 +366,35 @@ app.post('/api/upload-logo', upload.single('logo'), (req, res) => {
|
|||
if (!req.file) {
|
||||
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({
|
||||
filename: req.file.filename,
|
||||
path: `/uploads/${req.file.filename}`
|
||||
filename: 'current_logo' + path.extname(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
|
||||
app.post('/api/quotes/:id/pdf', async (req, res) => {
|
||||
let browser;
|
||||
|
|
@ -469,6 +495,27 @@ function generateQuoteHTML(quote) {
|
|||
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 = '';
|
||||
quote.items.forEach(item => {
|
||||
itemsHTML += `
|
||||
|
|
@ -686,7 +733,7 @@ function generateQuoteHTML(quote) {
|
|||
<div class="container">
|
||||
<div class="header">
|
||||
<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">
|
||||
<h1>Bay Area Affiliates, Inc.</h1>
|
||||
<p>1001 Blucher Street<br>
|
||||
|
|
|
|||
Loading…
Reference in New Issue