sent date
This commit is contained in:
@@ -218,14 +218,28 @@ function renderInvoiceRow(invoice) {
|
||||
statusBadge = `<span class="inline-block px-2 py-0.5 text-xs font-semibold rounded-full bg-orange-200 text-orange-800">Open</span>`;
|
||||
}
|
||||
|
||||
// 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 = `<span title="All send dates: ${allDates}" class="cursor-help border-b border-dotted border-gray-400">${formatDate(lastSent)}</span>`;
|
||||
sendDateDisplay += ` <span class="text-xs text-gray-400">(${sentDates.length}x)</span>`;
|
||||
}
|
||||
} 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 += ` <span class="text-xs text-red-500">(${Math.abs(daysUntil)}d ago)</span>`;
|
||||
else if (daysUntil === 0) sendDateDisplay += ` <span class="text-xs text-orange-500 font-semibold">(today)</span>`;
|
||||
else if (daysUntil <= 3) sendDateDisplay += ` <span class="text-xs text-yellow-600">(in ${daysUntil}d)</span>`;
|
||||
@@ -287,8 +301,11 @@ function renderInvoiceRow(invoice) {
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium text-gray-900">${invNumDisplay} ${statusBadge}</td>
|
||||
<td class="px-4 py-3 text-sm text-gray-500">${invoice.customer_name || 'N/A'}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">${formatDate(invoice.invoice_date)}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">${sendDateDisplay}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">${invoice.terms}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-600">
|
||||
<span class="cursor-pointer hover:text-blue-600" onclick="window.invoiceView.editSentDates(${invoice.id})" title="Click to edit sent dates">
|
||||
${sendDateDisplay}
|
||||
</span>
|
||||
</td> <td class="px-4 py-3 whitespace-nowrap text-sm text-gray-500">${invoice.terms}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-gray-900 font-semibold">${amountDisplay}</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium space-x-1">
|
||||
${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
|
||||
};
|
||||
Reference in New Issue
Block a user