Session 2026-05-20: UI fixes, invoice filtering, file browser, request approvals, sub invoice task scope
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -224,7 +224,7 @@ export default function CreateInvoice() {
|
||||
|
||||
const taskIds = [...new Set(validItems.filter(i => i.task_id && !i.submission_id).map(i => i.task_id))];
|
||||
if (taskIds.length > 0) {
|
||||
const { error: taskError } = await supabase.from('tasks').update({ invoiced: true }).in('id', taskIds);
|
||||
const { error: taskError } = await supabase.from('tasks').update({ invoiced: true, status: 'invoiced' }).in('id', taskIds);
|
||||
if (taskError) throw taskError;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,13 @@ export default function InvoiceDetail() {
|
||||
const { error } = await supabase.from('invoices').update(updates).eq('id', id);
|
||||
if (!error) {
|
||||
setInvoice(i => ({ ...i, ...updates }));
|
||||
// Sync task statuses along invoice lifecycle
|
||||
const { data: freshItems } = await supabase.from('invoice_items').select('task_id, submission_id').eq('invoice_id', id);
|
||||
const taskIds = (freshItems || []).filter(i => i.task_id && !i.submission_id).map(i => i.task_id);
|
||||
if (taskIds.length > 0) {
|
||||
const newTaskStatus = status === 'paid' ? 'paid' : status === 'sent' ? 'invoiced' : 'client_approved';
|
||||
await supabase.from('tasks').update({ status: newTaskStatus }).in('id', taskIds);
|
||||
}
|
||||
} else {
|
||||
alert('Failed to update status.');
|
||||
}
|
||||
@@ -195,7 +202,7 @@ export default function InvoiceDetail() {
|
||||
try {
|
||||
const { data: freshItems } = await supabase.from('invoice_items').select('task_id, submission_id').eq('invoice_id', id);
|
||||
const taskIds = (freshItems || []).filter(i => i.task_id && !i.submission_id).map(i => i.task_id);
|
||||
if (taskIds.length > 0) await supabase.from('tasks').update({ invoiced: false }).in('id', taskIds);
|
||||
if (taskIds.length > 0) await supabase.from('tasks').update({ invoiced: false, status: 'client_approved' }).in('id', taskIds);
|
||||
const submissionIds = (freshItems || []).filter(i => i.submission_id).map(i => i.submission_id);
|
||||
if (submissionIds.length > 0) await supabase.from('submissions').update({ invoiced: false }).in('id', submissionIds);
|
||||
const { error } = await supabase.from('invoices').delete().eq('id', id);
|
||||
|
||||
@@ -576,14 +576,14 @@ export default function Invoices() {
|
||||
<tr key={inv.id} onClick={() => navigate(`/invoices/${inv.id}`)} style={{ cursor: 'pointer' }}>
|
||||
<td style={{ fontWeight: 600 }}>{inv.invoice_number}</td>
|
||||
<td style={{ fontWeight: 600 }}>{inv.bill_to || inv.company?.name}</td>
|
||||
<td>{new Date(inv.invoice_date).toLocaleDateString()}</td>
|
||||
<td style={{ color: 'var(--text-muted)' }}>{new Date(inv.invoice_date).toLocaleDateString()}</td>
|
||||
<td>
|
||||
<span style={{ color: inv.status !== 'paid' && new Date(inv.due_date) < new Date() ? 'var(--danger)' : 'inherit' }}>
|
||||
<span style={{ color: inv.status !== 'paid' && new Date(inv.due_date) < new Date() ? 'var(--danger)' : 'var(--text-muted)' }}>
|
||||
{new Date(inv.due_date).toLocaleDateString()}
|
||||
</span>
|
||||
</td>
|
||||
<td><span className={`badge badge-${statusColor[inv.status]}`} style={{ textTransform: 'capitalize' }}>{inv.status}</span></td>
|
||||
<td style={{ fontWeight: 700 }}>${Number(inv.total).toFixed(2)}</td>
|
||||
<td style={{ fontWeight: 700, color: 'var(--accent)' }}>${Number(inv.total).toFixed(2)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
@@ -847,14 +847,14 @@ export default function Invoices() {
|
||||
const subInvoiceStatusColor = { draft: 'not_started', submitted: 'in_progress', paid: 'client_approved' };
|
||||
return (
|
||||
<tr key={inv.id} style={{ cursor: 'pointer' }} onClick={() => navigate(`/sub-invoices/${inv.id}`)}>
|
||||
<td style={{ fontWeight: 700 }}>{inv.invoice_number}</td>
|
||||
<td style={{ fontWeight: 600 }}>{inv.invoice_number}</td>
|
||||
<td>
|
||||
<div style={{ fontWeight: 600 }}>{inv.profile?.name || 'External'}</div>
|
||||
<div style={{ color: 'var(--text-muted)', fontSize: 12 }}>{inv.profile?.email || '—'}</div>
|
||||
</td>
|
||||
<td>{inv.submitted_at ? new Date(inv.submitted_at).toLocaleDateString() : <span style={{ color: 'var(--text-muted)' }}>—</span>}</td>
|
||||
<td style={{ color: 'var(--text-muted)' }}>{inv.submitted_at ? new Date(inv.submitted_at).toLocaleDateString() : '—'}</td>
|
||||
<td><span className={`badge badge-${subInvoiceStatusColor[inv.status] || 'not_started'}`} style={{ textTransform: 'capitalize' }}>{inv.status}</span></td>
|
||||
<td style={{ textAlign: 'right', fontWeight: 700 }}>${total.toFixed(2)}</td>
|
||||
<td style={{ textAlign: 'right', fontWeight: 700, color: 'var(--accent)' }}>${total.toFixed(2)}</td>
|
||||
<td />
|
||||
</tr>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user