feat(pending): queue order toggle, entry date + notes in review, account names everywhere (#1812)

* feat(pending): queue order toggle, entry date + notes in review, account names everywhere

Four review-queue gaps reported by a customer approving bokslut batches:

- Oldest-first toggle: /api/pending-operations accepts order=asc|desc
  (default desc); the queue header gets an Äldst först / Nyast först
  button, remembered per browser (localStorage pending.sortOrder).
- Fiscal year visible: categorize previews now carry the transaction date
  (preview_data.date) and render a Datum row, so two open years are
  distinguishable.
- The agent's `notes` (audit-trail context) is shown in the detail panel
  as Anteckning; before, it was stored in params and never rendered.
- Account names: VoucherLinesTable and PreviewKonteringTable fall back to
  the chart name from AccountNamesContext (6110 Kontorsmateriel · AMAZON
  PRIME instead of the bank text alone); useAccountNamesSource moves to a
  shared hook so the chat ApprovalCard provides the same names.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(agent): call useAccountNamesSource in ApprovalCard

The provider referenced accountNames without the hook call; the core build
(tsc) caught it. Local tsc had not, so this also re-runs the full check.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mattsson
2026-08-23 04:54:23 +02:00
committed by GitHub
co-authored by Claude Fable 5
parent d35c401c0c
commit 2fd58c4125
10 changed files with 177 additions and 45 deletions
@@ -6,7 +6,7 @@ import {
parseJsonResponse,
} from '@/tests/helpers'
const { supabase, enqueue, reset } = createQueuedMockSupabase()
const { supabase, enqueue, reset, findCalls } = createQueuedMockSupabase()
const requireAuthMock = vi.fn()
vi.mock('@/lib/auth/require-auth', () => ({
@@ -79,6 +79,39 @@ describe('GET /api/pending-operations', () => {
expect(supabase.from).toHaveBeenCalledTimes(3)
})
it('orders the pending queue oldest-first when order=asc is given', async () => {
enqueue({ data: [{ id: 'operation-old', status: 'pending' }], count: 1 })
enqueue({ count: 0 })
enqueue({ count: 0 })
const response = await GET(
createMockRequest('/api/pending-operations', { searchParams: { status: 'pending', order: 'asc' } }),
{ params: Promise.resolve({}) },
)
expect(response.status).toBe(200)
const orderCalls = findCalls('pending_operations', 'order')
expect(orderCalls[0]).toEqual(['created_at', { ascending: true, nullsFirst: false }])
})
it('defaults to newest-first and rejects an unknown order with 400', async () => {
enqueue({ data: [], count: 0 })
enqueue({ count: 0 })
enqueue({ count: 0 })
await GET(createMockRequest('/api/pending-operations'), { params: Promise.resolve({}) })
expect(findCalls('pending_operations', 'order')[0]).toEqual([
'created_at',
{ ascending: false, nullsFirst: false },
])
reset()
requireAuthMock.mockResolvedValue({ user: { id: 'user-1' }, supabase })
const bad = await GET(
createMockRequest('/api/pending-operations', { searchParams: { order: 'sideways' } }),
{ params: Promise.resolve({}) },
)
expect(bad.status).toBe(400)
})
it('returns 500 when the list query fails', async () => {
enqueue({ error: { message: 'database unavailable' } })
enqueue({ count: 0 })
+2 -2
View File
@@ -15,7 +15,7 @@ export const GET = withRouteContext(
async (request, { supabase, companyId }) => {
const result = validateQuery(request, PendingOperationsQuerySchema)
if (!result.success) return result.response
const { status, limit, offset } = result.data
const { status, limit, offset, order } = result.data
// Terminal tabs (Godkända/Avvisade) order by when the op was RESOLVED, not
// created: auto-expired ops are ≥30 days old by construction, so a
@@ -36,7 +36,7 @@ export const GET = withRouteContext(
.select('*', { count: 'exact' })
.eq('company_id', companyId)
.in('status', statusesFor(status))
.order(orderColumn, { ascending: false, nullsFirst: false })
.order(orderColumn, { ascending: order === 'asc', nullsFirst: false })
.range(offset, offset + limit - 1)
// Return every tab count with the active list. The browser previously