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:
Krao Hasanee
2026-05-20 21:32:55 -04:00
parent ff159c5937
commit 565d2ed4bc
34 changed files with 3384 additions and 1161 deletions
+1 -1
View File
@@ -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;
}
+8 -1
View File
@@ -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);
+6 -6
View File
@@ -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>
);