From 71e82d0a21e0aaecd5e1bb0dae227a3f16782911 Mon Sep 17 00:00:00 2001 From: Devin Wilson Date: Mon, 18 May 2026 15:44:15 -0700 Subject: [PATCH] fix(work-item): separate work order and invoice actions, remove duplicate buttons --- web/src/routes/contractor/WorkItemDetail.tsx | 204 +++++-------------- 1 file changed, 53 insertions(+), 151 deletions(-) diff --git a/web/src/routes/contractor/WorkItemDetail.tsx b/web/src/routes/contractor/WorkItemDetail.tsx index c286a5a..95148bc 100644 --- a/web/src/routes/contractor/WorkItemDetail.tsx +++ b/web/src/routes/contractor/WorkItemDetail.tsx @@ -18,8 +18,6 @@ import { updateInvoiceStatus, unlinkTimeEntriesForLineItem, } from '../../services/firestore'; -import { httpsCallable } from 'firebase/functions'; -import { functions } from '../../lib/firebase'; import { buildChangeOrderPdf } from '../../lib/buildPdf'; import { IconClose } from '../../components/icons'; @@ -198,75 +196,6 @@ export default function WorkItemDetail({ setItem(updated); } - async function handleSendToClient() { - if (!item?.id) return; - - // Use original sender email if available (from forwarded emails), else client email - const recipientEmail = item.senderEmail || client?.email; - const recipientName = item.senderName || client?.name || ''; - if (!recipientEmail) return; - - try { - // Generate a magic link — use client if assigned, otherwise create for sender - const linkEmail = client?.email || recipientEmail; - const linkClientId = client?.id || 'unassigned'; - - const gen = httpsCallable< - { clientId: string; email: string; workItemId: string }, - { token: string } - >(functions, 'generateMagicLink'); - const { data: { token } } = await gen({ - clientId: linkClientId, - email: linkEmail, - workItemId: item.id, - }); - - const portalLink = `${window.location.origin}/portal/auth?token=${token}`; - - const subject = encodeURIComponent( - `Invoice: ${item.subject}` - ); - - const snapshot = buildSnapshotItem(); - const lineItemsBlock = snapshot.lineItems - .map( - (li, i) => - ` ${i + 1}. ${li.description || '(no description)'}\n` + - ` Hours: ${li.hours.toFixed(1)} | Cost: ${formatCurrency(li.cost)}` - ) - .join('\n\n'); - - const billingNote = item.deductFromRetainer - ? `\nBilling: ${snapshot.totalHours.toFixed(1)} hours will be deducted from your retainer balance.\n` - : ''; - - const body = encodeURIComponent( - `Hello ${recipientName || 'there'},\n\n` + - `A new work order is ready for your review.\n\n` + - `————————————————————————————————\n` + - `WORK ORDER SUMMARY\n` + - `————————————————————————————————\n\n` + - `Subject: ${item.subject}\n` + - `Type: ${item.type.charAt(0).toUpperCase() + item.type.slice(1)}\n\n` + - `Line Items:\n\n` + - `${lineItemsBlock}\n\n` + - `————————————————————————————————\n` + - `Total Hours: ${snapshot.totalHours.toFixed(1)} hrs\n` + - `Total Cost: ${formatCurrency(snapshot.totalCost)}\n` + - `————————————————————————————————\n` + - `${billingNote}\n` + - `Review and approve this work order:\n` + - `${portalLink}\n\n` + - `This link expires in 7 days. Reply to this email with any questions.\n\n\n` + - `Thank you for your business.\n\n` + - `Best regards` - ); - window.open(`mailto:${recipientEmail}?subject=${subject}&body=${body}`, '_self'); - } catch (err) { - console.error('Send to client error:', err); - } - } - const labelClass = 'block text-[10px] font-semibold text-[var(--text-secondary)] uppercase tracking-wider mb-1.5'; const inputClass = 'w-full h-10 px-3 rounded-xl border border-[var(--border)] bg-[var(--bg-input)] text-sm text-[var(--text-primary)] outline-none focus:border-[var(--accent)] focus:ring-2 focus:ring-[var(--accent)]/15 transition-all'; @@ -671,10 +600,10 @@ export default function WorkItemDetail({ - {/* Invoice Tracking */} + {/* Invoice Status */}

- Invoice + Invoice Status

{/* Draft / No invoice */} @@ -791,9 +720,13 @@ export default function WorkItemDetail({ )}
- {/* ── Email Actions ── */} + {/* Email */} {client?.email && ( -
+
+

+ Email +

+
{/* Send Work Order */} +
)} - {/* ── Invoice Status Actions ── */} - {(item.invoiceStatus === 'sent' || item.invoiceStatus === 'overdue') && ( -
- - {item.invoiceStatus === 'sent' && ( - - )} -
- )} - - {/* Actions */} + {/* Primary Actions */}
- {item.status !== 'completed' && item.status !== 'archived' && ( - - )} {item.status !== 'completed' && item.status !== 'archived' && ( - )} - - {/* Send to Client */} - {(client?.email || item.senderEmail) && ( - + {/* Work Order Actions */} + {((item.status === 'approved' && !generatingPdf) || (client?.email || item.senderEmail)) && ( +
+

+ Work Order Actions +

+
+ {item.status === 'approved' && !generatingPdf && ( + + )} + {(client?.email || item.senderEmail) && ( + + )} +
+
)} - {/* PDF Preview Modal */} {showPdfPreview && previewUrl && (