Fix/user feedback (#210)
* Add delete policies for provider consent tokens and provider OTC * Add trade name support for companies in settings and documents * Resolved currency selection issue * Enhance invoice line display with foreign currency support and update delivery date schema to allow empty values * Add currency display for journal entries and include currency metadata in transaction creation * Add trade_name column to company_settings for external display
This commit is contained in:
@@ -211,22 +211,39 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{lines.map((line) => (
|
||||
<tr key={line.id} className="border-b last:border-0">
|
||||
<td className="py-2"><AccountNumber number={line.account_number} showName /></td>
|
||||
<td className="py-2 text-muted-foreground">{line.line_description || ''}</td>
|
||||
<td className="py-2 text-right tabular-nums">
|
||||
{Number(line.debit_amount) > 0
|
||||
? Number(line.debit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })
|
||||
: ''}
|
||||
</td>
|
||||
<td className="py-2 text-right tabular-nums">
|
||||
{Number(line.credit_amount) > 0
|
||||
? Number(line.credit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })
|
||||
: ''}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
{lines.map((line) => {
|
||||
const hasForeignCurrency = line.currency && line.currency !== 'SEK' && line.amount_in_currency != null
|
||||
return (
|
||||
<tr key={line.id} className="border-b last:border-0">
|
||||
<td className="py-2"><AccountNumber number={line.account_number} showName /></td>
|
||||
<td className="py-2 text-muted-foreground">{line.line_description || ''}</td>
|
||||
<td className="py-2 text-right tabular-nums">
|
||||
{Number(line.debit_amount) > 0 && (
|
||||
<>
|
||||
{Number(line.debit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })}
|
||||
{hasForeignCurrency && Number(line.debit_amount) > 0 && (
|
||||
<span className="block text-xs text-muted-foreground">
|
||||
{Number(line.amount_in_currency).toLocaleString('sv-SE', { minimumFractionDigits: 2 })} {line.currency}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<td className="py-2 text-right tabular-nums">
|
||||
{Number(line.credit_amount) > 0 && (
|
||||
<>
|
||||
{Number(line.credit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })}
|
||||
{hasForeignCurrency && Number(line.credit_amount) > 0 && (
|
||||
<span className="block text-xs text-muted-foreground">
|
||||
{Number(line.amount_in_currency).toLocaleString('sv-SE', { minimumFractionDigits: 2 })} {line.currency}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr className="font-semibold">
|
||||
@@ -244,24 +261,41 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i
|
||||
|
||||
{/* Mobile cards */}
|
||||
<div className="sm:hidden space-y-2">
|
||||
{lines.map((line) => (
|
||||
<div key={line.id} className="flex items-center justify-between py-2 border-b last:border-0 gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm"><AccountNumber number={line.account_number} showName /></div>
|
||||
{line.line_description && (
|
||||
<p className="text-xs text-muted-foreground truncate">{line.line_description}</p>
|
||||
)}
|
||||
{lines.map((line) => {
|
||||
const hasForeignCurrency = line.currency && line.currency !== 'SEK' && line.amount_in_currency != null
|
||||
return (
|
||||
<div key={line.id} className="flex items-center justify-between py-2 border-b last:border-0 gap-2">
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="text-sm"><AccountNumber number={line.account_number} showName /></div>
|
||||
{line.line_description && (
|
||||
<p className="text-xs text-muted-foreground truncate">{line.line_description}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-right shrink-0 text-sm tabular-nums">
|
||||
{Number(line.debit_amount) > 0 && (
|
||||
<p>
|
||||
{Number(line.debit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })} D
|
||||
{hasForeignCurrency && (
|
||||
<span className="block text-xs text-muted-foreground">
|
||||
{Number(line.amount_in_currency).toLocaleString('sv-SE', { minimumFractionDigits: 2 })} {line.currency}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
{Number(line.credit_amount) > 0 && (
|
||||
<p>
|
||||
{Number(line.credit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })} K
|
||||
{hasForeignCurrency && (
|
||||
<span className="block text-xs text-muted-foreground">
|
||||
{Number(line.amount_in_currency).toLocaleString('sv-SE', { minimumFractionDigits: 2 })} {line.currency}
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right shrink-0 text-sm tabular-nums">
|
||||
{Number(line.debit_amount) > 0 && (
|
||||
<p>{Number(line.debit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })} D</p>
|
||||
)}
|
||||
{Number(line.credit_amount) > 0 && (
|
||||
<p>{Number(line.credit_amount).toLocaleString('sv-SE', { minimumFractionDigits: 2 })} K</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
)
|
||||
})}
|
||||
<div className="flex justify-between font-semibold text-sm pt-1">
|
||||
<span>Summa</span>
|
||||
<div className="flex gap-3 tabular-nums">
|
||||
|
||||
@@ -16,6 +16,7 @@ export default function CompanySettingsPage() {
|
||||
function handleSave(formData: FormData) {
|
||||
const updates: Record<string, unknown> = {
|
||||
...(formData.has('company_name') && { company_name: formData.get('company_name') as string }),
|
||||
trade_name: (formData.get('trade_name') as string) || null,
|
||||
...(formData.has('org_number') && { org_number: formData.get('org_number') as string }),
|
||||
address_line1: formData.get('address_line1') as string,
|
||||
postal_code: formData.get('postal_code') as string,
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Card, CardContent } from '@/components/ui/card'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { ToastAction } from '@/components/ui/toast'
|
||||
import { DestructiveConfirmDialog, useDestructiveConfirm } from '@/components/ui/destructive-confirm-dialog'
|
||||
import { X } from 'lucide-react'
|
||||
import TransactionForm from '@/components/transactions/TransactionForm'
|
||||
import SwipeCategorizationView from '@/components/transactions/SwipeCategorizationView'
|
||||
@@ -88,6 +89,7 @@ export default function TransactionsPage() {
|
||||
const [exitingIds, setExitingIds] = useState<Set<string>>(new Set())
|
||||
|
||||
const { toast } = useToast()
|
||||
const { dialogProps: deleteDialogProps, confirm: confirmDelete } = useDestructiveConfirm()
|
||||
const supabase = createClient()
|
||||
|
||||
// Computed lists
|
||||
@@ -508,6 +510,40 @@ export default function TransactionsPage() {
|
||||
setIsCreating(false)
|
||||
}
|
||||
|
||||
async function handleDeleteTransaction(id: string) {
|
||||
const transaction = transactions.find((t) => t.id === id)
|
||||
if (!transaction) return
|
||||
|
||||
const ok = await confirmDelete({
|
||||
title: 'Ta bort transaktion',
|
||||
description: `Är du säker på att du vill ta bort "${transaction.description}"? Åtgärden kan inte ångras.`,
|
||||
confirmLabel: 'Ta bort',
|
||||
variant: 'destructive',
|
||||
})
|
||||
if (!ok) return
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/transactions/${id}`, { method: 'DELETE' })
|
||||
if (!response.ok) {
|
||||
const result = await response.json()
|
||||
toast({
|
||||
title: 'Kunde inte ta bort',
|
||||
description: result.error || 'Försök igen.',
|
||||
variant: 'destructive',
|
||||
})
|
||||
return
|
||||
}
|
||||
setTransactions((prev) => prev.filter((t) => t.id !== id))
|
||||
toast({ title: 'Borttagen', description: 'Transaktionen har tagits bort' })
|
||||
} catch {
|
||||
toast({
|
||||
title: 'Kunde inte ta bort',
|
||||
description: 'Transaktionen kunde inte tas bort. Försök igen.',
|
||||
variant: 'destructive',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handleTransactionBooked(transactionId: string, journalEntryId: string) {
|
||||
setExitingIds((prev) => new Set(prev).add(transactionId))
|
||||
setTimeout(() => {
|
||||
@@ -786,7 +822,7 @@ export default function TransactionsPage() {
|
||||
onMarkPrivate={handleMarkPrivate}
|
||||
onOpenMatchDialog={openMatchDialog}
|
||||
onOpenCategoryDialog={openCategoryDialog}
|
||||
|
||||
onDelete={handleDeleteTransaction}
|
||||
onOpenQuickReview={handleOpenQuickReview}
|
||||
onOpenTemplateReview={handleOpenTemplateReview}
|
||||
onToggleSelect={toggleBatchSelect}
|
||||
@@ -917,6 +953,8 @@ export default function TransactionsPage() {
|
||||
<TransactionForm onSubmit={handleCreateTransaction} isLoading={isCreating} />
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<DestructiveConfirmDialog {...deleteDialogProps} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user