feat: improve UX across reports, navigation, invoicing, and settings
- Reorganize reports page from flat 11-tab bar into 4-column categorized grid (Bokslut, Skatt & moms, Huvudböcker, Avstämning) - Move Import from Övrigt to Finans nav group for better discoverability - Merge standalone Marketplace link into Tillägg section as "Utforska fler..." - Rename abbreviated "Lev.fakturor" to full "Leverantörsfakturor" - Add uncategorized transaction count badge to nav (desktop pill + mobile dot) - Strengthen credit note confirmation with destructive styling and type-to-confirm pattern requiring exact invoice number - Clarify invoice send vs mark-sent with "Skickad manuellt" label, visual hierarchy, and explanatory helper text - Show Banking and Notifications settings tabs always, with placeholder message and link to extensions when not enabled Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
ecd95ccb82
commit
aabe527f68
@@ -5,11 +5,12 @@ import { useRouter } from 'next/navigation'
|
||||
import { createClient } from '@/lib/supabase/client'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Textarea } from '@/components/ui/textarea'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { formatCurrency, formatDate } from '@/lib/utils'
|
||||
import { cn, formatCurrency, formatDate } from '@/lib/utils'
|
||||
import { getVatTreatmentLabel } from '@/lib/invoices/vat-rules'
|
||||
import { Loader2, ArrowLeft, AlertTriangle } from 'lucide-react'
|
||||
import type { Invoice, InvoiceItem, Customer } from '@/types'
|
||||
@@ -29,6 +30,7 @@ export default function CreateCreditNotePage({ params }: { params: Promise<{ id:
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||
const [reason, setReason] = useState('')
|
||||
const [confirmText, setConfirmText] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
fetchInvoice()
|
||||
@@ -157,14 +159,14 @@ export default function CreateCreditNotePage({ params }: { params: Promise<{ id:
|
||||
</div>
|
||||
|
||||
{/* Warning */}
|
||||
<Card className="border-warning/50 bg-warning/5">
|
||||
<Card className="border-destructive/50 bg-destructive/5">
|
||||
<CardContent className="flex items-start gap-4 pt-6">
|
||||
<AlertTriangle className="h-5 w-5 text-warning flex-shrink-0 mt-0.5" />
|
||||
<AlertTriangle className="h-5 w-5 text-destructive flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium">Viktigt om kreditfakturor</p>
|
||||
<p className="font-medium text-destructive">Oåterkallelig åtgärd</p>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
En kreditfaktura makulerar den ursprungliga fakturan helt.
|
||||
Alla belopp blir negativa och den ursprungliga fakturan markeras som krediterad.
|
||||
Alla belopp blir negativa, en bokföringsverifikation skapas, och den ursprungliga fakturan markeras som krediterad.
|
||||
Denna åtgärd kan inte ångras.
|
||||
</p>
|
||||
</div>
|
||||
@@ -295,12 +297,36 @@ export default function CreateCreditNotePage({ params }: { params: Promise<{ id:
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Confirmation */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Bekräfta</CardTitle>
|
||||
<CardDescription>
|
||||
Skriv fakturanumret <span className="font-mono font-semibold text-foreground">{invoice.invoice_number}</span> för att bekräfta
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Input
|
||||
value={confirmText}
|
||||
onChange={(e) => setConfirmText(e.target.value)}
|
||||
placeholder={invoice.invoice_number}
|
||||
className={cn(
|
||||
confirmText && confirmText !== invoice.invoice_number && 'border-destructive'
|
||||
)}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="flex justify-end gap-4">
|
||||
<Button variant="outline" onClick={() => router.back()}>
|
||||
Avbryt
|
||||
</Button>
|
||||
<Button onClick={handleSubmit} disabled={isSubmitting}>
|
||||
<Button
|
||||
variant="destructive"
|
||||
onClick={handleSubmit}
|
||||
disabled={isSubmitting || confirmText !== invoice.invoice_number}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
|
||||
@@ -406,16 +406,16 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st
|
||||
Skicka via e-post
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={() => updateStatus('sent')} disabled={isUpdating}>
|
||||
<Button variant="secondary" onClick={() => updateStatus('sent')} disabled={isUpdating}>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
Markera som skickad
|
||||
Skickad manuellt
|
||||
</Button>
|
||||
)
|
||||
)}
|
||||
{isDeliveryNote && invoice.status === 'draft' && (
|
||||
<Button onClick={() => updateStatus('sent')} disabled={isUpdating}>
|
||||
<Button variant="secondary" onClick={() => updateStatus('sent')} disabled={isUpdating}>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
Markera som skickad
|
||||
Skickad manuellt
|
||||
</Button>
|
||||
)}
|
||||
{(invoice.status === 'sent' || invoice.status === 'overdue') && isRealInvoice && (
|
||||
@@ -824,24 +824,38 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st
|
||||
{!isProforma && invoice.status === 'draft' && (
|
||||
<>
|
||||
{!isDeliveryNote && customerHasEmail ? (
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={sendInvoiceEmail}
|
||||
disabled={isSendingEmail}
|
||||
>
|
||||
{isSendingEmail ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Mail className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
Skicka via e-post
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
className="w-full"
|
||||
onClick={sendInvoiceEmail}
|
||||
disabled={isSendingEmail}
|
||||
>
|
||||
{isSendingEmail ? (
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Mail className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
Skicka via e-post
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full text-muted-foreground"
|
||||
onClick={() => updateStatus('sent')}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
Skickad manuellt
|
||||
</Button>
|
||||
<p className="text-[11px] text-muted-foreground/60 px-1 -mt-1">
|
||||
Använd om du redan skickat fakturan på annat sätt (post, annat system)
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{!isDeliveryNote && (
|
||||
<div className="flex items-start gap-2 p-3 bg-yellow-50 border border-yellow-200 rounded-lg mb-2">
|
||||
<AlertTriangle className="h-4 w-4 text-yellow-600 mt-0.5 flex-shrink-0" />
|
||||
<p className="text-xs text-yellow-700">
|
||||
<div className="flex items-start gap-2 p-3 bg-yellow-50 border border-yellow-200 rounded-lg mb-2 dark:bg-yellow-950/30 dark:border-yellow-800">
|
||||
<AlertTriangle className="h-4 w-4 text-yellow-600 dark:text-yellow-500 mt-0.5 flex-shrink-0" />
|
||||
<p className="text-xs text-yellow-700 dark:text-yellow-400">
|
||||
Kunden saknar e-postadress. Lägg till e-post för att kunna skicka fakturan digitalt.
|
||||
</p>
|
||||
</div>
|
||||
@@ -852,8 +866,11 @@ export default function InvoiceDetailPage({ params }: { params: Promise<{ id: st
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<Send className="mr-2 h-4 w-4" />
|
||||
Markera som skickad
|
||||
Skickad manuellt
|
||||
</Button>
|
||||
<p className="text-[11px] text-muted-foreground/60 px-1 -mt-1">
|
||||
Markerar fakturan som skickad och skapar bokföringsverifikation
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
|
||||
@@ -29,11 +29,18 @@ export default async function DashboardLayout({
|
||||
|
||||
const entityType = (settings.entity_type as EntityType) || 'enskild_firma'
|
||||
|
||||
const { data: enabledToggles } = await supabase
|
||||
.from('extension_toggles')
|
||||
.select('sector_slug, extension_slug')
|
||||
.eq('user_id', user.id)
|
||||
.eq('enabled', true)
|
||||
const [{ data: enabledToggles }, { count: uncategorizedCount }] = await Promise.all([
|
||||
supabase
|
||||
.from('extension_toggles')
|
||||
.select('sector_slug, extension_slug')
|
||||
.eq('user_id', user.id)
|
||||
.eq('enabled', true),
|
||||
supabase
|
||||
.from('transactions')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('user_id', user.id)
|
||||
.is('is_business', null),
|
||||
])
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-background">
|
||||
@@ -48,6 +55,7 @@ export default async function DashboardLayout({
|
||||
companyName={settings.company_name || 'Min verksamhet'}
|
||||
entityType={entityType}
|
||||
enabledExtensions={enabledToggles || []}
|
||||
uncategorizedTransactionCount={uncategorizedCount ?? 0}
|
||||
/>
|
||||
<main id="main-content" className="pb-20 md:pb-0 md:pl-[232px]" role="main">
|
||||
<div className="max-w-5xl mx-auto px-5 py-8 md:px-8 md:py-10">
|
||||
|
||||
@@ -103,54 +103,89 @@ export default function ReportsPage() {
|
||||
|
||||
{selectedPeriod ? (
|
||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||
<TabsList className="h-auto w-full flex-wrap justify-start gap-1 p-1">
|
||||
<TabsTrigger value="trial-balance">
|
||||
<Scale className="h-4 w-4 mr-1" />
|
||||
Saldobalans
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="income-statement">
|
||||
<TrendingUp className="h-4 w-4 mr-1" />
|
||||
Resultaträkning
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="balance-sheet">
|
||||
<FileText className="h-4 w-4 mr-1" />
|
||||
Balansräkning
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="vat-declaration">
|
||||
<Receipt className="h-4 w-4 mr-1" />
|
||||
Momsdeklaration
|
||||
</TabsTrigger>
|
||||
{isEnskildFirma && (
|
||||
<TabsTrigger value="ne-declaration">
|
||||
<Briefcase className="h-4 w-4 mr-1" />
|
||||
NE-bilaga
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="sru-export">
|
||||
<FileDown className="h-4 w-4 mr-1" />
|
||||
SRU-export
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="huvudbok">
|
||||
<BookOpen className="h-4 w-4 mr-1" />
|
||||
Huvudbok
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="grundbok">
|
||||
<List className="h-4 w-4 mr-1" />
|
||||
Grundbok
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="kundreskontra">
|
||||
<Users className="h-4 w-4 mr-1" />
|
||||
Kundreskontra
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="supplier-ledger">
|
||||
<Building2 className="h-4 w-4 mr-1" />
|
||||
Lev.reskontra
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="bank-reconciliation">
|
||||
<ArrowLeftRight className="h-4 w-4 mr-1" />
|
||||
Bankavstämning
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 mb-2">
|
||||
{/* Bokslut (Financial Statements) */}
|
||||
<div className="space-y-1">
|
||||
<p className="text-[10px] font-semibold text-muted-foreground/70 uppercase tracking-[0.08em] px-1">
|
||||
Bokslut
|
||||
</p>
|
||||
<TabsList className="flex flex-col h-auto w-full gap-0.5 p-1">
|
||||
<TabsTrigger value="trial-balance" className="w-full justify-start">
|
||||
<Scale className="h-4 w-4 mr-1.5" />
|
||||
Saldobalans
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="income-statement" className="w-full justify-start">
|
||||
<TrendingUp className="h-4 w-4 mr-1.5" />
|
||||
Resultaträkning
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="balance-sheet" className="w-full justify-start">
|
||||
<FileText className="h-4 w-4 mr-1.5" />
|
||||
Balansräkning
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
{/* Skatt & moms (Tax & VAT) */}
|
||||
<div className="space-y-1">
|
||||
<p className="text-[10px] font-semibold text-muted-foreground/70 uppercase tracking-[0.08em] px-1">
|
||||
Skatt & moms
|
||||
</p>
|
||||
<TabsList className="flex flex-col h-auto w-full gap-0.5 p-1">
|
||||
<TabsTrigger value="vat-declaration" className="w-full justify-start">
|
||||
<Receipt className="h-4 w-4 mr-1.5" />
|
||||
Momsdeklaration
|
||||
</TabsTrigger>
|
||||
{isEnskildFirma && (
|
||||
<TabsTrigger value="ne-declaration" className="w-full justify-start">
|
||||
<Briefcase className="h-4 w-4 mr-1.5" />
|
||||
NE-bilaga
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="sru-export" className="w-full justify-start">
|
||||
<FileDown className="h-4 w-4 mr-1.5" />
|
||||
SRU-export
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
{/* Huvudböcker (Ledgers) */}
|
||||
<div className="space-y-1">
|
||||
<p className="text-[10px] font-semibold text-muted-foreground/70 uppercase tracking-[0.08em] px-1">
|
||||
Huvudböcker
|
||||
</p>
|
||||
<TabsList className="flex flex-col h-auto w-full gap-0.5 p-1">
|
||||
<TabsTrigger value="huvudbok" className="w-full justify-start">
|
||||
<BookOpen className="h-4 w-4 mr-1.5" />
|
||||
Huvudbok
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="grundbok" className="w-full justify-start">
|
||||
<List className="h-4 w-4 mr-1.5" />
|
||||
Grundbok
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="kundreskontra" className="w-full justify-start">
|
||||
<Users className="h-4 w-4 mr-1.5" />
|
||||
Kundreskontra
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="supplier-ledger" className="w-full justify-start">
|
||||
<Building2 className="h-4 w-4 mr-1.5" />
|
||||
Leverantörsreskontra
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
|
||||
{/* Avstämning (Reconciliation) */}
|
||||
<div className="space-y-1">
|
||||
<p className="text-[10px] font-semibold text-muted-foreground/70 uppercase tracking-[0.08em] px-1">
|
||||
Avstämning
|
||||
</p>
|
||||
<TabsList className="flex flex-col h-auto w-full gap-0.5 p-1">
|
||||
<TabsTrigger value="bank-reconciliation" className="w-full justify-start">
|
||||
<ArrowLeftRight className="h-4 w-4 mr-1.5" />
|
||||
Bankavstämning
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TabsContent value="trial-balance">
|
||||
<TrialBalanceView periodId={selectedPeriod} />
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useRouter, useSearchParams } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import { createClient } from '@/lib/supabase/client'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import { Button } from '@/components/ui/button'
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
Moon,
|
||||
Monitor,
|
||||
Palette,
|
||||
ExternalLink,
|
||||
} from 'lucide-react'
|
||||
import { useTheme } from 'next-themes'
|
||||
import type { CompanySettings } from '@/types'
|
||||
@@ -41,6 +43,7 @@ export default function SettingsPage() {
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [settings, setSettings] = useState<CompanySettings | null>(null)
|
||||
const [hasBankingExtension, setHasBankingExtension] = useState(false)
|
||||
const [hasNotificationExtension, setHasNotificationExtension] = useState(false)
|
||||
const { theme, setTheme } = useTheme()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
@@ -93,25 +96,31 @@ export default function SettingsPage() {
|
||||
|
||||
setSettings(settingsData)
|
||||
|
||||
// Check if Enable Banking extension is enabled
|
||||
try {
|
||||
const toggleRes = await fetch('/api/extensions/toggles/general/enable-banking')
|
||||
if (toggleRes.ok) {
|
||||
const { data } = await toggleRes.json()
|
||||
setHasBankingExtension(data?.enabled || false)
|
||||
}
|
||||
} catch {
|
||||
// If toggle check fails, also check for existing connections
|
||||
// Check extension toggles in parallel
|
||||
const [bankingToggleRes, notificationToggleRes] = await Promise.all([
|
||||
fetch('/api/extensions/toggles/general/enable-banking').catch(() => null),
|
||||
fetch('/api/extensions/toggles/general/push-notifications').catch(() => null),
|
||||
])
|
||||
|
||||
if (bankingToggleRes?.ok) {
|
||||
const { data } = await bankingToggleRes.json()
|
||||
setHasBankingExtension(data?.enabled || false)
|
||||
} else {
|
||||
// Fallback: check for existing bank connections
|
||||
const { data: connections } = await supabase
|
||||
.from('bank_connections')
|
||||
.select('id')
|
||||
.eq('user_id', user.id)
|
||||
.eq('status', 'active')
|
||||
.limit(1)
|
||||
|
||||
setHasBankingExtension((connections && connections.length > 0) || false)
|
||||
}
|
||||
|
||||
if (notificationToggleRes?.ok) {
|
||||
const { data } = await notificationToggleRes.json()
|
||||
setHasNotificationExtension(data?.enabled || false)
|
||||
}
|
||||
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
@@ -197,18 +206,14 @@ export default function SettingsPage() {
|
||||
<Building className="mr-2 h-4 w-4" />
|
||||
Företag
|
||||
</TabsTrigger>
|
||||
{hasBankingExtension && BankingPanel && (
|
||||
<TabsTrigger value="banking">
|
||||
<CreditCard className="mr-2 h-4 w-4" />
|
||||
Bank (PSD2)
|
||||
</TabsTrigger>
|
||||
)}
|
||||
{NotificationPanel && (
|
||||
<TabsTrigger value="notifications">
|
||||
<Bell className="mr-2 h-4 w-4" />
|
||||
Aviseringar
|
||||
</TabsTrigger>
|
||||
)}
|
||||
<TabsTrigger value="banking">
|
||||
<CreditCard className="mr-2 h-4 w-4" />
|
||||
Bank (PSD2)
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="notifications">
|
||||
<Bell className="mr-2 h-4 w-4" />
|
||||
Aviseringar
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="calendar">
|
||||
<Calendar className="mr-2 h-4 w-4" />
|
||||
Kalender
|
||||
@@ -436,18 +441,50 @@ export default function SettingsPage() {
|
||||
</TabsContent>
|
||||
|
||||
{/* Banking settings — loaded dynamically from extension */}
|
||||
{hasBankingExtension && BankingPanel && (
|
||||
<TabsContent value="banking" className="space-y-6">
|
||||
<TabsContent value="banking" className="space-y-6">
|
||||
{hasBankingExtension && BankingPanel ? (
|
||||
<BankingPanel />
|
||||
</TabsContent>
|
||||
)}
|
||||
) : (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<CreditCard className="h-10 w-10 text-muted-foreground/40 mb-4" />
|
||||
<p className="font-medium mb-1">Bankintegration (PSD2) är inte aktiverad</p>
|
||||
<p className="text-sm text-muted-foreground mb-4 max-w-md">
|
||||
Aktivera tillägget Enable Banking för att koppla ditt bankkonto och automatiskt hämta transaktioner.
|
||||
</p>
|
||||
<Button variant="outline" asChild>
|
||||
<Link href="/extensions">
|
||||
<ExternalLink className="mr-2 h-4 w-4" />
|
||||
Gå till Tillägg
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* Notification settings — loaded dynamically from extension */}
|
||||
{NotificationPanel && (
|
||||
<TabsContent value="notifications">
|
||||
<TabsContent value="notifications">
|
||||
{hasNotificationExtension && NotificationPanel ? (
|
||||
<NotificationPanel />
|
||||
</TabsContent>
|
||||
)}
|
||||
) : (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center justify-center py-12 text-center">
|
||||
<Bell className="h-10 w-10 text-muted-foreground/40 mb-4" />
|
||||
<p className="font-medium mb-1">Push-notiser är inte aktiverade</p>
|
||||
<p className="text-sm text-muted-foreground mb-4 max-w-md">
|
||||
Aktivera tillägget Push-notiser för att få aviseringar om förfallna fakturor, deadlines och andra händelser.
|
||||
</p>
|
||||
<Button variant="outline" asChild>
|
||||
<Link href="/extensions">
|
||||
<ExternalLink className="mr-2 h-4 w-4" />
|
||||
Gå till Tillägg
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
{/* Calendar feed settings */}
|
||||
<TabsContent value="calendar">
|
||||
|
||||
@@ -33,6 +33,7 @@ interface DashboardNavProps {
|
||||
companyName: string
|
||||
entityType: EntityType
|
||||
enabledExtensions?: { sector_slug: string; extension_slug: string }[]
|
||||
uncategorizedTransactionCount?: number
|
||||
}
|
||||
|
||||
interface NavItem {
|
||||
@@ -50,11 +51,11 @@ const navItems: NavItem[] = [
|
||||
{ href: '/invoices', label: 'Fakturor', icon: Receipt, group: 'finans' },
|
||||
{ href: '/customers', label: 'Kunder', icon: Users, group: 'finans' },
|
||||
{ href: '/suppliers', label: 'Leverantörer', icon: Building2, group: 'finans' },
|
||||
{ href: '/supplier-invoices', label: 'Lev.fakturor', icon: FileInput, group: 'finans' },
|
||||
{ href: '/supplier-invoices', label: 'Leverantörsfakturor', icon: FileInput, group: 'finans' },
|
||||
{ href: '/transactions', label: 'Transaktioner', icon: ArrowLeftRight, group: 'finans' },
|
||||
{ href: '/bookkeeping', label: 'Bokföring', icon: BookOpen, group: 'finans' },
|
||||
{ href: '/reports', label: 'Rapporter', icon: BarChart3, group: 'finans' },
|
||||
{ href: '/import', label: 'Importera', icon: Upload, group: 'övrigt' },
|
||||
{ href: '/import', label: 'Importera', icon: Upload, group: 'finans' },
|
||||
{ href: '/help', label: 'Hjälp', icon: HelpCircle, group: 'övrigt' },
|
||||
{ href: '/settings', label: 'Inställningar', icon: Settings, group: 'övrigt' },
|
||||
]
|
||||
@@ -65,17 +66,17 @@ const groupLabels: Record<string, string> = {
|
||||
övrigt: 'Övrigt',
|
||||
}
|
||||
|
||||
export default function DashboardNav({ companyName, entityType, enabledExtensions }: DashboardNavProps) {
|
||||
export default function DashboardNav({ companyName, entityType, enabledExtensions, uncategorizedTransactionCount = 0 }: DashboardNavProps) {
|
||||
const pathname = usePathname()
|
||||
const router = useRouter()
|
||||
const supabase = createClient()
|
||||
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
|
||||
// Auto-expand Övrigt when the user is on one of its pages, or when manually toggled
|
||||
const isOnOvrigtPage = ['/import', '/help', '/settings'].some(p => pathname.startsWith(p))
|
||||
const isOnOvrigtPage = ['/help', '/settings'].some(p => pathname.startsWith(p))
|
||||
const [manualOvrigtExpanded, setManualOvrigtExpanded] = useState(false)
|
||||
const isOvrigtExpanded = isOnOvrigtPage || manualOvrigtExpanded
|
||||
// Auto-expand Tillägg when on an extension page, or when manually toggled (persisted)
|
||||
const isOnExtensionPage = pathname.startsWith('/e/')
|
||||
// Auto-expand Tillägg when on an extension page or marketplace, or when manually toggled (persisted)
|
||||
const isOnExtensionPage = pathname.startsWith('/e/') || pathname.startsWith('/extensions')
|
||||
const [manualTillaggExpanded, setManualTillaggExpanded] = useState(false)
|
||||
const isTillaggExpanded = isOnExtensionPage || manualTillaggExpanded
|
||||
|
||||
@@ -200,6 +201,9 @@ export default function DashboardNav({ companyName, entityType, enabledExtension
|
||||
{finansItems.map((item) => {
|
||||
const Icon = item.icon
|
||||
const active = isActive(item.href)
|
||||
const badge = item.href === '/transactions' && uncategorizedTransactionCount > 0
|
||||
? uncategorizedTransactionCount
|
||||
: null
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
@@ -215,35 +219,19 @@ export default function DashboardNav({ companyName, entityType, enabledExtension
|
||||
"mr-2.5 h-[15px] w-[15px] flex-shrink-0",
|
||||
active ? "text-primary" : "text-muted-foreground/70 group-hover:text-muted-foreground"
|
||||
)} />
|
||||
{item.label}
|
||||
<span className="flex-1">{item.label}</span>
|
||||
{badge !== null && (
|
||||
<span className="ml-auto min-w-[18px] h-[18px] flex items-center justify-center rounded-full bg-primary/15 text-primary text-[10px] font-semibold px-1">
|
||||
{badge > 99 ? '99+' : badge}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Marketplace - standalone link */}
|
||||
<div className="mb-4">
|
||||
<div className="space-y-px">
|
||||
<Link
|
||||
href="/extensions"
|
||||
className={cn(
|
||||
'group flex items-center px-3 py-[7px] text-[13px] transition-colors duration-150 rounded-lg',
|
||||
isActive('/extensions')
|
||||
? 'bg-primary/8 text-foreground font-medium'
|
||||
: 'text-muted-foreground hover:text-foreground hover:bg-muted/40'
|
||||
)}
|
||||
>
|
||||
<Store className={cn(
|
||||
"mr-2.5 h-[15px] w-[15px] flex-shrink-0",
|
||||
isActive('/extensions') ? "text-primary" : "text-muted-foreground/70 group-hover:text-muted-foreground"
|
||||
)} />
|
||||
Marketplace
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tillägg - collapsible */}
|
||||
{/* Tillägg - collapsible, with marketplace link */}
|
||||
<div className="mb-4">
|
||||
<button
|
||||
onClick={toggleTillagg}
|
||||
@@ -286,6 +274,21 @@ export default function DashboardNav({ companyName, entityType, enabledExtension
|
||||
Inga tillägg aktiverade
|
||||
</p>
|
||||
)}
|
||||
<Link
|
||||
href="/extensions"
|
||||
className={cn(
|
||||
'group flex items-center px-3 py-[7px] text-[13px] transition-colors duration-150 rounded-lg',
|
||||
isActive('/extensions')
|
||||
? 'bg-primary/8 text-foreground font-medium'
|
||||
: 'text-muted-foreground/60 hover:text-muted-foreground hover:bg-muted/40'
|
||||
)}
|
||||
>
|
||||
<Store className={cn(
|
||||
"mr-2.5 h-[15px] w-[15px] flex-shrink-0",
|
||||
isActive('/extensions') ? "text-primary" : "text-muted-foreground/50 group-hover:text-muted-foreground"
|
||||
)} />
|
||||
Utforska fler...
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -352,22 +355,32 @@ export default function DashboardNav({ companyName, entityType, enabledExtension
|
||||
{mobileNavItems.map((item) => {
|
||||
const Icon = item.icon
|
||||
const active = isActive(item.href)
|
||||
const badge = item.href === '/transactions' && uncategorizedTransactionCount > 0
|
||||
? uncategorizedTransactionCount
|
||||
: null
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
className={cn(
|
||||
'flex flex-col items-center justify-center flex-1 h-full text-xs transition-colors duration-200',
|
||||
'relative flex flex-col items-center justify-center flex-1 h-full text-xs transition-colors duration-200',
|
||||
active
|
||||
? 'text-primary'
|
||||
: 'text-muted-foreground'
|
||||
)}
|
||||
>
|
||||
<Icon className={cn(
|
||||
"h-5 w-5 mb-1",
|
||||
active && "text-primary"
|
||||
)} />
|
||||
<div className="relative">
|
||||
<Icon className={cn(
|
||||
"h-5 w-5 mb-1",
|
||||
active && "text-primary"
|
||||
)} />
|
||||
{badge !== null && (
|
||||
<span className="absolute -top-1.5 -right-2.5 min-w-[16px] h-[16px] flex items-center justify-center rounded-full bg-primary text-primary-foreground text-[9px] font-semibold px-0.5">
|
||||
{badge > 99 ? '99+' : badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<span className={cn(
|
||||
"truncate",
|
||||
active && "font-medium"
|
||||
@@ -473,23 +486,6 @@ export default function DashboardNav({ companyName, entityType, enabledExtension
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Marketplace */}
|
||||
<div className="mb-4">
|
||||
<Link
|
||||
href="/extensions"
|
||||
onClick={closeMobileMenu}
|
||||
className={cn(
|
||||
'flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors',
|
||||
isActive('/extensions')
|
||||
? 'bg-primary/10 text-primary font-medium'
|
||||
: 'text-muted-foreground hover:bg-secondary/50 hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Store className="h-5 w-5" />
|
||||
Marketplace
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Tillägg */}
|
||||
<div className="mb-4">
|
||||
<p className="px-3 py-2 text-xs font-medium text-muted-foreground uppercase tracking-wider">
|
||||
@@ -522,6 +518,19 @@ export default function DashboardNav({ companyName, entityType, enabledExtension
|
||||
Inga tillägg aktiverade
|
||||
</p>
|
||||
)}
|
||||
<Link
|
||||
href="/extensions"
|
||||
onClick={closeMobileMenu}
|
||||
className={cn(
|
||||
'flex items-center gap-3 px-3 py-2.5 rounded-lg transition-colors',
|
||||
isActive('/extensions')
|
||||
? 'bg-primary/10 text-primary font-medium'
|
||||
: 'text-muted-foreground/60 hover:bg-secondary/50 hover:text-foreground'
|
||||
)}
|
||||
>
|
||||
<Store className="h-5 w-5" />
|
||||
Utforska fler...
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* Other section */}
|
||||
|
||||
Reference in New Issue
Block a user