diff --git a/public/js/utils/api.js b/public/js/utils/api.js
index 42ffc39..47235a4 100644
--- a/public/js/utils/api.js
+++ b/public/js/utils/api.js
@@ -73,7 +73,12 @@ const API = {
body: JSON.stringify({ status })
}).then(r => r.json()),
getPdf: (id) => window.open(`/api/invoices/${id}/pdf`, '_blank'),
- getHtml: (id) => window.open(`/api/invoices/${id}/html`, '_blank')
+ getHtml: (id) => window.open(`/api/invoices/${id}/html`, '_blank'),
+ updateSentDates: (id, dates) => fetch(`/api/invoices/${id}/sent-dates`, {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ sent_dates: dates })
+ }).then(r => r.json())
},
// Payment API
diff --git a/public/js/views/invoice-view.js b/public/js/views/invoice-view.js
index 109959b..d6cca3c 100644
--- a/public/js/views/invoice-view.js
+++ b/public/js/views/invoice-view.js
@@ -218,14 +218,28 @@ function renderInvoiceRow(invoice) {
statusBadge = `Open`;
}
- // Send Date
+ // Send Date — show actual sent dates if available, otherwise scheduled
let sendDateDisplay = '—';
- if (invoice.scheduled_send_date) {
+ const sentDates = invoice.sent_dates || [];
+
+ if (sentDates.length > 0) {
+ // Show most recent sent date
+ const lastSent = sentDates[sentDates.length - 1];
+ sendDateDisplay = formatDate(lastSent);
+
+ if (sentDates.length > 1) {
+ // Tooltip with all dates
+ const allDates = sentDates.map(d => formatDate(d)).join('
');
+ sendDateDisplay = `${formatDate(lastSent)}`;
+ sendDateDisplay += ` (${sentDates.length}x)`;
+ }
+ } else if (invoice.scheduled_send_date) {
+ // No actual sends yet — show scheduled date with indicators
const sendDate = parseLocalDate(invoice.scheduled_send_date);
const today = new Date(); today.setHours(0, 0, 0, 0);
const daysUntil = Math.floor((sendDate - today) / 86400000);
sendDateDisplay = formatDate(invoice.scheduled_send_date);
- if (!paid && invoice.email_status !== 'sent') {
+ if (!paid && invoice.email_status !== 'sent' && !overdue) {
if (daysUntil < 0) sendDateDisplay += ` (${Math.abs(daysUntil)}d ago)`;
else if (daysUntil === 0) sendDateDisplay += ` (today)`;
else if (daysUntil <= 3) sendDateDisplay += ` (in ${daysUntil}d)`;
@@ -287,8 +301,11 @@ function renderInvoiceRow(invoice) {
${invNumDisplay} ${statusBadge} |
${invoice.customer_name || 'N/A'} |
${formatDate(invoice.invoice_date)} |
- ${sendDateDisplay} |
- ${invoice.terms} |
+
+
+ ${sendDateDisplay}
+
+ | ${invoice.terms} |
${amountDisplay} |
${editBtn} ${qboBtn} ${pdfBtn} ${htmlBtn} ${sendBtn} ${stripeEmailBtn} ${stripeCheckBtn} ${paidBtn} ${delBtn}
@@ -570,11 +587,59 @@ async function checkStripePayment(invoiceId) {
if (typeof hideSpinner === 'function') hideSpinner();
}
}
+
+async function editSentDates(invoiceId) {
+ // Load current invoice data
+ const res = await fetch(`/api/invoices/${invoiceId}`);
+ const data = await res.json();
+ const invoice = data.invoice;
+ const sentDates = invoice.sent_dates || [];
+
+ // Build a simple prompt-based editor
+ let datesStr = sentDates.join('\n');
+ const input = prompt(
+ 'Edit sent dates (one per line, YYYY-MM-DD format):\n\n' +
+ 'Add a new line to add a date.\nRemove a line to delete a date.\nLeave empty to clear all.',
+ datesStr
+ );
+
+ if (input === null) return; // Cancelled
+
+ // Parse and validate
+ const newDates = input.trim()
+ ? input.trim().split('\n').map(d => d.trim()).filter(d => d)
+ : [];
+
+ for (const d of newDates) {
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(d)) {
+ alert(`Invalid date: "${d}"\nPlease use YYYY-MM-DD format.`);
+ return;
+ }
+ }
+
+ try {
+ const response = await fetch(`/api/invoices/${invoiceId}/sent-dates`, {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ sent_dates: newDates })
+ });
+
+ if (response.ok) {
+ loadInvoices();
+ } else {
+ const err = await response.json();
+ alert(`Error: ${err.error}`);
+ }
+ } catch (e) {
+ console.error('Error updating sent dates:', e);
+ alert('Network error updating sent dates.');
+ }
+}
// ============================================================
// Expose
// ============================================================
window.invoiceView = {
viewPDF, viewHTML, syncFromQBO, resetQbo, markPaid, setEmailStatus, edit, remove,
- loadInvoices, renderInvoiceView, setStatus, checkStripePayment
+ loadInvoices, renderInvoiceView, setStatus, checkStripePayment, editSentDates
};
\ No newline at end of file
diff --git a/src/routes/invoices.js b/src/routes/invoices.js
index 192941e..492e2d1 100644
--- a/src/routes/invoices.js
+++ b/src/routes/invoices.js
@@ -894,8 +894,15 @@ router.post('/:id/send-email', async (req, res) => {
const stripeLink = invoice.stripe_payment_link_url || null;
const info = await sendInvoiceEmail(invoice, recipientEmail, customText, stripeLink, pdfBuffer);
- // 4. (Optional) Status in der DB aktualisieren
- await pool.query('UPDATE invoices SET email_status = $1 WHERE id = $2', ['sent', id]);
+ // 4. Status in der DB aktualisieren
+ await pool.query(
+ `UPDATE invoices
+ SET email_status = 'sent',
+ sent_dates = array_append(COALESCE(sent_dates, '{}'), CURRENT_DATE),
+ updated_at = CURRENT_TIMESTAMP
+ WHERE id = $1`,
+ [id]
+ );
res.json({ success: true, messageId: info.messageId });
@@ -1184,4 +1191,35 @@ async function recordStripePaymentInQbo(invoice, amount, methodLabel, stripeFee,
feeBooked: stripeFee > 0
};
}
+// PATCH update sent dates only
+router.patch('/:id/sent-dates', async (req, res) => {
+ const { id } = req.params;
+ const { sent_dates } = req.body;
+
+ if (!Array.isArray(sent_dates)) {
+ return res.status(400).json({ error: 'sent_dates must be an array of date strings.' });
+ }
+
+ // Validate each date
+ for (const d of sent_dates) {
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(d)) {
+ return res.status(400).json({ error: `Invalid date format: ${d}. Expected YYYY-MM-DD.` });
+ }
+ }
+
+ try {
+ // Sort chronologically
+ const sorted = [...sent_dates].sort();
+
+ await pool.query(
+ 'UPDATE invoices SET sent_dates = $1, updated_at = CURRENT_TIMESTAMP WHERE id = $2',
+ [sorted, id]
+ );
+
+ res.json({ success: true, sent_dates: sorted });
+ } catch (error) {
+ console.error('Error updating sent dates:', error);
+ res.status(500).json({ error: 'Failed to update sent dates.' });
+ }
+});
module.exports = router;
|