feat: UX improvements — nav, reports tabs, dashboard alerts, transaction hints, settings layout

- Move Reports to Finans nav group and auto-expand Övrigt on its pages
- Make report tabs horizontally scrollable with gradient fade on mobile
- Surface deadlines and alerts above the fold on dashboard
- Add dismissible categorization hint card on transactions page
- Split settings company form into 4 separate Cards for scannability
- Add monthly breakdown report, document upload zone, journal entry attachments
- Add batch category selector, receipt document linking, invoice form improvements

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-02-20 17:16:22 +01:00
co-authored by Claude Opus 4.6
parent ba94f60d06
commit d0c0d8a7d2
23 changed files with 2065 additions and 302 deletions
+34 -1
View File
@@ -9,6 +9,8 @@ import { useToast } from '@/components/ui/use-toast'
import { Plus, Trash2 } from 'lucide-react'
import { ConfirmationDialog } from '@/components/ui/confirmation-dialog'
import { JournalEntryReviewContent } from '@/components/bookkeeping/JournalEntryReviewContent'
import DocumentUploadZone from '@/components/bookkeeping/DocumentUploadZone'
import type { UploadedFile } from '@/components/bookkeeping/DocumentUploadZone'
import type { CreateJournalEntryLineInput, FiscalPeriod } from '@/types'
interface Props {
@@ -34,6 +36,9 @@ export default function JournalEntryForm({ onCreated }: Props) {
])
const [isSubmitting, setIsSubmitting] = useState(false)
const [showReview, setShowReview] = useState(false)
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([])
const isUploading = uploadedFiles.some((f) => f.status === 'uploading')
useEffect(() => {
fetchPeriods()
@@ -116,6 +121,23 @@ export default function JournalEntryForm({ onCreated }: Props) {
variant: 'destructive',
})
} else {
// Link uploaded documents to the new journal entry (non-blocking)
const journalEntryId = result.data?.id
if (journalEntryId) {
const filesToLink = uploadedFiles.filter((f) => f.status === 'uploaded' && f.id)
for (const file of filesToLink) {
try {
await fetch(`/api/documents/${file.id}/link`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ journal_entry_id: journalEntryId }),
})
} catch (linkErr) {
console.error('[JournalEntryForm] Failed to link document:', linkErr)
}
}
}
toast({
title: 'Verifikation skapad',
description: `Verifikation ${result.data?.voucher_series}${result.data?.voucher_number} har skapats.`,
@@ -123,6 +145,7 @@ export default function JournalEntryForm({ onCreated }: Props) {
setShowReview(false)
// Reset form
setDescription('')
setUploadedFiles([])
setLines([
{ account_number: '', debit_amount: '', credit_amount: '', line_description: '' },
{ account_number: '', debit_amount: '', credit_amount: '', line_description: '' },
@@ -275,6 +298,15 @@ export default function JournalEntryForm({ onCreated }: Props) {
</Button>
</div>
{/* Document attachments */}
<div>
<Label className="mb-2 block">Underlag</Label>
<DocumentUploadZone
files={uploadedFiles}
onFilesChange={setUploadedFiles}
/>
</div>
{!isBalanced && totalDebit > 0 && (
<p className="text-sm text-red-600">
Differens: {Math.abs(totalDebit - totalCredit).toLocaleString('sv-SE', { minimumFractionDigits: 2 })} kr
@@ -284,7 +316,7 @@ export default function JournalEntryForm({ onCreated }: Props) {
<div className="flex justify-end">
<Button
onClick={handleReview}
disabled={!isBalanced || !description || !selectedPeriod || isSubmitting}
disabled={!isBalanced || !description || !selectedPeriod || isSubmitting || isUploading}
>
Granska & skapa
</Button>
@@ -305,6 +337,7 @@ export default function JournalEntryForm({ onCreated }: Props) {
lines={lines}
totalDebit={totalDebit}
totalCredit={totalCredit}
attachmentCount={uploadedFiles.filter((f) => f.status === 'uploaded').length}
/>
</ConfirmationDialog>
</CardContent>