feat: UI polish, lint fixes, onboarding redesign, help page expansion, and test improvements
Broad update across dashboard pages, components, extensions, and lib code. Includes ESLint config additions, onboarding flow redesign, settings page refactor, help page content expansion, dead code removal, and test mock fixes. Adds dev docs and public assets. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
02ebe50f52
commit
091d043c85
@@ -31,6 +31,7 @@ function mockAuth(userId: string | null) {
|
||||
data: { user: userId ? { id: userId } : null },
|
||||
}),
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any)
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ function mockAuth(userId: string | null) {
|
||||
data: { user: userId ? { id: userId } : null },
|
||||
}),
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any)
|
||||
}
|
||||
|
||||
@@ -45,9 +46,11 @@ describe('GET /api/audit-trail', () => {
|
||||
{ id: '1', action: 'INSERT', table_name: 'journal_entries', created_at: '2024-01-01T00:00:00Z' },
|
||||
{ id: '2', action: 'COMMIT', table_name: 'journal_entries', created_at: '2024-01-02T00:00:00Z' },
|
||||
]
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mockGetAuditLog.mockResolvedValue({ data: entries as any, count: 2 })
|
||||
|
||||
const req = createMockRequest('/api/audit-trail')
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const { status, body } = await parseJsonResponse<{ data: any[]; count: number }>(await GET(req))
|
||||
|
||||
expect(status).toBe(200)
|
||||
|
||||
@@ -60,7 +60,7 @@ export async function PUT(
|
||||
const body: Partial<CreateDeadlineInput> = await request.json()
|
||||
|
||||
// First, get existing deadline to verify ownership
|
||||
const { data: existing, error: fetchError } = await supabase
|
||||
const { data: _existing, error: fetchError } = await supabase
|
||||
.from('deadlines')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
|
||||
@@ -34,7 +34,7 @@ export async function POST(request: Request) {
|
||||
}
|
||||
|
||||
const body: ExecuteRequest = await request.json()
|
||||
const { transactions, format, filename, file_hash, skip_duplicates = true, auto_categorize = true } = body
|
||||
const { transactions, format, filename, file_hash, skip_duplicates: _skip_duplicates = true, auto_categorize: _auto_categorize = true } = body
|
||||
|
||||
if (!transactions || transactions.length === 0) {
|
||||
return NextResponse.json({ error: 'No transactions to import' }, { status: 400 })
|
||||
|
||||
@@ -89,7 +89,7 @@ describe('POST /api/invoices/[id]/mark-paid', () => {
|
||||
|
||||
const request = createMockRequest('/api/invoices/inv-1/mark-paid', { method: 'POST' })
|
||||
const response = await POST(request, createMockRouteParams({ id: 'inv-1' }))
|
||||
const { status, body } = await parseJsonResponse<{ error: string }>(response)
|
||||
const { status, body: _body } = await parseJsonResponse<{ error: string }>(response)
|
||||
|
||||
expect(status).toBe(400)
|
||||
})
|
||||
|
||||
@@ -4,7 +4,7 @@ import { eventBus } from '@/lib/events'
|
||||
import { ensureInitialized } from '@/lib/init'
|
||||
import { CreateInvoiceSchema, CreateCreditNoteSchema } from '@/lib/api/schemas'
|
||||
import type { EntityType, AccountingMethod, Invoice, CreditNote, InvoiceDocumentType } from '@/types'
|
||||
import { getVatRules, calculateVat, calculateTotal, getAvailableVatRates, getVatTreatmentForRate } from '@/lib/invoices/vat-rules'
|
||||
import { getVatRules, getAvailableVatRates } from '@/lib/invoices/vat-rules'
|
||||
import { fetchExchangeRate, convertToSEK } from '@/lib/currency/riksbanken'
|
||||
import {
|
||||
createCreditNoteJournalEntry,
|
||||
|
||||
@@ -23,6 +23,7 @@ function mockAuth(userId: string | null) {
|
||||
data: { user: userId ? { id: userId } : null },
|
||||
}),
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} as any)
|
||||
}
|
||||
|
||||
@@ -67,6 +68,7 @@ describe('GET /api/reports/audit-trail', () => {
|
||||
|
||||
it('returns CSV format with correct headers', async () => {
|
||||
mockAuth('user-1')
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mockGetAuditLog.mockResolvedValue({ data: sampleEntries as any, count: 2 })
|
||||
|
||||
const req = createMockRequest('/api/reports/audit-trail', {
|
||||
@@ -88,6 +90,7 @@ describe('GET /api/reports/audit-trail', () => {
|
||||
|
||||
it('returns JSON format as downloadable file', async () => {
|
||||
mockAuth('user-1')
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
mockGetAuditLog.mockResolvedValue({ data: sampleEntries as any, count: 2 })
|
||||
|
||||
const req = createMockRequest('/api/reports/audit-trail', {
|
||||
@@ -118,7 +121,9 @@ describe('GET /api/reports/audit-trail', () => {
|
||||
}))
|
||||
|
||||
mockGetAuditLog
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.mockResolvedValueOnce({ data: bigPage as any, count: 600 })
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.mockResolvedValueOnce({ data: lastPage as any, count: 600 })
|
||||
|
||||
const req = createMockRequest('/api/reports/audit-trail', {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { findFiscalPeriod } from '@/lib/bookkeeping/engine'
|
||||
import {
|
||||
createSupplierInvoicePaymentEntry,
|
||||
createSupplierInvoiceCashEntry,
|
||||
@@ -9,41 +8,6 @@ import { validateBody } from '@/lib/api/validate'
|
||||
import { MatchSupplierInvoiceSchema } from '@/lib/api/schemas'
|
||||
import type { SupplierInvoice, SupplierInvoiceItem } from '@/types'
|
||||
|
||||
/**
|
||||
* Ensure a fiscal period exists for the given date
|
||||
*/
|
||||
async function ensureFiscalPeriod(
|
||||
supabase: Awaited<ReturnType<typeof createClient>>,
|
||||
userId: string,
|
||||
date: string
|
||||
): Promise<string | null> {
|
||||
const existingPeriodId = await findFiscalPeriod(supabase, userId, date)
|
||||
if (existingPeriodId) return existingPeriodId
|
||||
|
||||
const transactionDate = new Date(date)
|
||||
const year = transactionDate.getFullYear()
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('fiscal_periods')
|
||||
.upsert({
|
||||
user_id: userId,
|
||||
name: `Räkenskapsår ${year}`,
|
||||
period_start: `${year}-01-01`,
|
||||
period_end: `${year}-12-31`,
|
||||
}, {
|
||||
onConflict: 'user_id,period_start,period_end',
|
||||
})
|
||||
.select('id')
|
||||
.single()
|
||||
|
||||
if (error) {
|
||||
console.error('Failed to create fiscal period:', error)
|
||||
return null
|
||||
}
|
||||
|
||||
return data?.id || null
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/transactions/[id]/match-supplier-invoice
|
||||
*
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { getSuggestedCategories, mergeAiSuggestions, getSuggestedTemplates, type SuggestedCategory, type SuggestedTemplate } from '@/lib/transactions/category-suggestions'
|
||||
import type { Transaction, TransactionCategory, EntityType } from '@/types'
|
||||
import type { Transaction, EntityType } from '@/types'
|
||||
|
||||
/**
|
||||
* POST /api/transactions/suggest-categories
|
||||
|
||||
Reference in New Issue
Block a user