chore(ui): retire the Building2 icon app-wide (#2235)
Founder request from the register walkthrough. Suppliers (nav, command palette, empty state) use Truck; company and company-scoped surfaces (active company badge, invite, home signpost, SIE preview, template scopes, TIC workspace and its manifest) use Briefcase; the two bank contexts use Landmark. The extension icon resolver no longer maps Building2; the generated sector definitions follow the manifest. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -1531,6 +1531,7 @@ One line per decision: `[YYYY-MM-DD] <decision>: <why>`. Appended by agents and
|
||||
[2026-09-03] Kontakter is not a user-facing noun (founder, after the register walkthrough): the registers people see stay Leverantörer and Kunder, the new page is the queue 'Förslag från bokföringen' plus 'Bara i bokföringen', and confirming a suggestion creates the leverantör or kund row directly (promote_parties). A confirmed party with no role never appears in the UI. The party model underneath is unchanged
|
||||
[2026-09-03] AGI redovisningsperiod = the payout month (agiReportingPeriod on payment_date), not salary_runs.period_*: Skatteverket files per the month the pay went out (kontantprincipen), so lön i efterskott (August work paid 25 September) is declared in September. The in-period payment-date guard (dashboard PATCH, lib/salary/update-run.ts, v1 PATCH, RunHeader min/max) is lifted rather than widened: its only stated reason was that the AGI keyed on period_*, and any residual month window would bite the next efterskott variant. Existing agi_declarations rows keep their stored period (no backfill): a declaration already filed under the earned month is a real-world correction with Skatteverket, not a re-key. New AGI_PERIOD_CONFLICT (409) refuses to overwrite a live run's declaration for the same payout month, since one month's AGI must cover every payment that month and the generator cannot merge runs. Issue #2191.
|
||||
[2026-09-03] The cursor:// deeplink is its own allowlist provider (cursor_deeplink) rendered "Din egen dator" and never "Verifierad", after the skeptic, CodeRabbit and Superagent all made the same point: a custom scheme can be claimed by any local app (RFC 8252 section 8.4), so it carries loopback trust, not vendor trust, and the consent page must not say otherwise; https://www.cursor.com/... keeps the verified label. Same pass fixed the consent-page CSP for custom schemes: new URL('cursor://...').origin is the string "null", so form-action became `'self' null` and Chromium would have blocked the post-consent 303 (correctness skeptic refutation); the header now uses the scheme-source (`cursor:`) when the origin is opaque. Not done: rejecting a missing code_challenge at /authorize. A code minted without one is unexchangeable (verifyPkce against an empty challenge is always false, now pinned by a test), so it is fail-closed; making it fail earlier is a separate change touching every client.
|
||||
[2026-09-03] The Lucide Building2 glyph is retired app-wide (founder, from the register walkthrough). Suppliers use Truck, companies and company-scoped things use Briefcase, banks use Landmark; the extension manifest icon name changed with it
|
||||
[2026-09-03] settleInvoicePayment writes the invoice_payments row BEFORE the CAS status update and removes it in both failure branches, instead of inserting after the update: the kontantmetod cut-off reads invoice_payments only, so a paid invoice without a row is the #2019 defect itself; failing closed on the insert (voucher storno + INVOICE_PAID_BOOK_FAILED) keeps GL, sub-ledger and invoice status in step. The #2019 backfill inserts only where exactly one posted payment voucher exists (invoice_paid / invoice_cash_payment with source_id = invoice); zero or several vouchers are reported, never guessed, and every row is tagged backfill:#2019 in notes so one DELETE reverts a run.
|
||||
[2026-09-03] #2019 skeptic round: the invoice_payments row is written by one helper (lib/invoices/invoice-payment-row.ts) from all four transaction-less settlement paths (dashboard, v1, MCP mark-paid, Stripe), with amount = applied amount (new paid_amount minus prior) rather than cash received, so a 3740 öre absorption never produces a negative fordran in the cut-off or a wrong storno restore. A payment row with transaction_id NULL does NOT count as "reconciled to a bank line" in the two duplicate detectors: the bank line for a manual settlement arrives later and the voucher must still be offered as a twin. The backfill dates rows from the voucher entry_date (paid_at was wall-clock before #1332), refuses rows that disagree with the voucher's 1510 credit / settlement debit, reports partially covered invoices (rows_short) instead of patching them, and records each executed run in behandlingshistorik (new event type InvoicePaymentRowBackfilled, migration 20260903180000). Not done here, pre-existing: the bank-match and pending-operations match paths still store cash received as the row amount, and the kontantmetod cut-off ignores ROT/RUT deduction_total (1513 share shows as outstanding); both filed as follow-ups.
|
||||
[2026-09-03] Enable Banking ASPSP_ERROR ladder (#2202): the widest history window a bank has answered is stored per account as accounts_data[].accepted_history_days (no migration; same write-back as dedup_scope and the balance), not as an absolute date, because a bank's limit is a width from today and an absolute known-good date would age into a genuine width rejection. On a rejected window: no wider than accepted = bank unavailable after ONE call; wider = one retry at the accepted width, then unavailable (was up to five calls). AspspUnavailableError maps to 503 BANK_UNAVAILABLE on the web sync route with copy that says the connection does not need renewing; the agent path keeps the contract code BANK_SYNC_FAILED (adding a code touches core contract + structured-errors + v1 docs, left for a follow-up) but no longer persists renewal advice. The envelope's `detail` field is NOT used as a signal: one sample, "Unknown error", identical to a width rejection. A narrowed sync now returns history_from so the UI can say from which date it is complete.
|
||||
|
||||
@@ -12,12 +12,13 @@ import {
|
||||
BookOpen,
|
||||
Receipt,
|
||||
Calculator,
|
||||
Building2,
|
||||
FileText,
|
||||
FileDown,
|
||||
ExternalLink,
|
||||
ChevronDown,
|
||||
ChevronUp,
|
||||
Briefcase,
|
||||
Landmark,
|
||||
} from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { SupportLink } from '@/components/ui/support-link'
|
||||
@@ -214,8 +215,8 @@ const categoryConfig = {
|
||||
moms: { labelKey: 'category_moms', icon: Receipt, color: 'bg-secondary text-muted-foreground' },
|
||||
faktura: { labelKey: 'category_faktura', icon: FileText, color: 'bg-secondary text-muted-foreground' },
|
||||
bokföring: { labelKey: 'category_bokforing', icon: BookOpen, color: 'bg-secondary text-muted-foreground' },
|
||||
bank: { labelKey: 'category_bank', icon: Building2, color: 'bg-secondary text-muted-foreground' },
|
||||
företag: { labelKey: 'category_foretag', icon: Building2, color: 'bg-secondary text-muted-foreground' },
|
||||
bank: { labelKey: 'category_bank', icon: Landmark, color: 'bg-secondary text-muted-foreground' },
|
||||
företag: { labelKey: 'category_foretag', icon: Briefcase, color: 'bg-secondary text-muted-foreground' },
|
||||
}
|
||||
|
||||
function TermCard({ term, isExpanded, onToggle }: { term: GlossaryTerm; isExpanded: boolean; onToggle: () => void }) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { TH_CLASS, TD_CLASS } from '@/components/ui/dry-table'
|
||||
import { ReportExportMenu } from '@/components/reports/ReportExportMenu'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { Plus, Building2, Lock } from 'lucide-react'
|
||||
import { Plus, Lock, Truck } from 'lucide-react'
|
||||
import Link from 'next/link'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
@@ -194,13 +194,13 @@ export default function SuppliersPage() {
|
||||
) : filteredSuppliers.length === 0 ? (
|
||||
searchTerm ? (
|
||||
<EmptyState
|
||||
icon={Building2}
|
||||
icon={Truck}
|
||||
title={t('no_search_results_title')}
|
||||
description={<span data-ph-mask="">{t('no_search_results_description', { term: searchTerm })}</span>}
|
||||
/>
|
||||
) : (
|
||||
<EmptyState
|
||||
icon={Building2}
|
||||
icon={Truck}
|
||||
title={t('empty_title')}
|
||||
description={t('empty_description')}
|
||||
actionLabel={canWrite ? t('new_supplier') : undefined}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTranslations } from 'next-intl'
|
||||
import Link from 'next/link'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Loader2, Building2, AlertCircle } from 'lucide-react'
|
||||
import { Loader2, AlertCircle, Briefcase } from 'lucide-react'
|
||||
import { createClient } from '@/lib/supabase/client'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import { getBranding } from '@/lib/branding/service'
|
||||
@@ -270,7 +270,7 @@ export default function InvitePage() {
|
||||
<Card className="p-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-2.5 rounded-lg bg-muted/50">
|
||||
<Building2 className="h-5 w-5 text-muted-foreground" />
|
||||
<Briefcase className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{invite.companyName}</p>
|
||||
@@ -309,7 +309,7 @@ export default function InvitePage() {
|
||||
<Card className="p-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-2.5 rounded-lg bg-muted/50">
|
||||
<Building2 className="h-5 w-5 text-muted-foreground" />
|
||||
<Briefcase className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{invite.companyName}</p>
|
||||
@@ -337,7 +337,7 @@ export default function InvitePage() {
|
||||
<Card className="p-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-2.5 rounded-lg bg-muted/50">
|
||||
<Building2 className="h-5 w-5 text-muted-foreground" />
|
||||
<Briefcase className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{invite.companyName}</p>
|
||||
@@ -365,7 +365,7 @@ export default function InvitePage() {
|
||||
<Card className="p-6">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="p-2.5 rounded-lg bg-muted/50">
|
||||
<Building2 className="h-5 w-5 text-muted-foreground" />
|
||||
<Briefcase className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium">{invite.companyName}</p>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/components/ui/dialog'
|
||||
import { BookOpen, Search, Building2, Users, Globe } from 'lucide-react'
|
||||
import { BookOpen, Search, Users, Globe, Briefcase } from 'lucide-react'
|
||||
import { TEMPLATE_CATEGORY_LABELS, SCOPE_LABELS, getTemplateScope, applyTemplate } from '@/lib/bookkeeping/template-library'
|
||||
import type { BookingTemplateCategory, EntityType } from '@/types'
|
||||
import type { FormLine } from '@/components/bookkeeping/JournalEntryForm'
|
||||
@@ -28,7 +28,7 @@ interface Props {
|
||||
const SCOPE_ICONS = {
|
||||
system: Globe,
|
||||
team: Users,
|
||||
company: Building2,
|
||||
company: Briefcase,
|
||||
} as const
|
||||
|
||||
export default function BookingTemplatePicker({ onApply, entityType, defaultAmount }: Props) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
ArrowLeftRight,
|
||||
Users,
|
||||
Wallet,
|
||||
Building2,
|
||||
BookOpen,
|
||||
ListTree,
|
||||
BarChart3,
|
||||
@@ -24,6 +23,7 @@ import {
|
||||
ArrowRight,
|
||||
Scale,
|
||||
type LucideIcon,
|
||||
Truck,
|
||||
} from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
@@ -49,7 +49,7 @@ const ACTION_ENTRIES: Entry[] = [
|
||||
|
||||
const PAGE_ENTRIES: Entry[] = [
|
||||
{ id: 'kunder', label: 'Kunder', icon: Users, href: '/customers' },
|
||||
{ id: 'leverantörer', label: 'Leverantörer', icon: Building2, href: '/suppliers' },
|
||||
{ id: 'leverantörer', label: 'Leverantörer', icon: Truck, href: '/suppliers' },
|
||||
{ id: 'kundfakturor', label: 'Kundfakturor', icon: ReceiptText, href: '/invoices', keywords: 'fakturor fakturering invoices kundfaktura' },
|
||||
{ id: 'leverantörsfakturor', label: 'Leverantörsfakturor', icon: Wallet, href: '/supplier-invoices' },
|
||||
{ id: 'bokföring', label: 'Bokföring', icon: BookOpen, href: '/bookkeeping', keywords: 'verifikat journal ledger' },
|
||||
|
||||
@@ -22,7 +22,6 @@ import {
|
||||
Menu,
|
||||
X,
|
||||
HelpCircle,
|
||||
Building2,
|
||||
Wallet,
|
||||
TrendingUp,
|
||||
ClipboardCheck,
|
||||
@@ -51,6 +50,7 @@ import {
|
||||
ShoppingCart,
|
||||
Car,
|
||||
ClipboardList,
|
||||
Truck,
|
||||
} from 'lucide-react'
|
||||
import { getBranding } from '@/lib/branding/service'
|
||||
import { BrandHomeLink } from '@/components/branding/BrandHomeLink'
|
||||
@@ -256,7 +256,7 @@ const navItems: NavItem[] = [
|
||||
// row. Anställda is a register (you edit an employee rarely, you run
|
||||
// payroll monthly), so it lives here while Löner stays in Arbeta.
|
||||
{ href: '/customers', labelKey: 'customers', icon: Users, group: 'data', fold: 'register' },
|
||||
{ href: '/suppliers', labelKey: 'suppliers', icon: Building2, group: 'data', fold: 'register' },
|
||||
{ href: '/suppliers', labelKey: 'suppliers', icon: Truck, group: 'data', fold: 'register' },
|
||||
{ href: '/articles', labelKey: 'articles', icon: Tag, group: 'data', fold: 'register' },
|
||||
{ href: '/salary/employees', labelKey: 'employees', icon: Users, group: 'data', fold: 'register', employerOnly: true },
|
||||
{ href: '/assets', labelKey: 'assets', icon: Package, group: 'data', fold: 'register' },
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useState } from 'react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { ArrowUpRight, Building2, Loader2 } from 'lucide-react'
|
||||
import { ArrowUpRight, Loader2, Briefcase } from 'lucide-react'
|
||||
import { performCompanySwitch } from '@/lib/company/switch-client'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function HomeDomainSignpost({
|
||||
<div className="stagger-enter mx-auto max-w-lg pt-12">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<div className="mb-5 flex h-12 w-12 items-center justify-center rounded-xl bg-muted/60">
|
||||
<Building2 className="h-5 w-5 text-muted-foreground" />
|
||||
<Briefcase className="h-5 w-5 text-muted-foreground" />
|
||||
</div>
|
||||
<h1 className="font-display text-2xl leading-8 tracking-tight">
|
||||
{t('title')}
|
||||
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
import { useExtensionData } from '@/lib/extensions/use-extension-data'
|
||||
import { useToast } from '@/components/ui/use-toast'
|
||||
import {
|
||||
Building2,
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
MapPin,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
ShieldCheck,
|
||||
Users,
|
||||
Receipt,
|
||||
Briefcase,
|
||||
} from 'lucide-react'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import Link from 'next/link'
|
||||
@@ -330,7 +330,7 @@ export default function TicWorkspace({ userId }: WorkspaceComponentProps) {
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2 text-base">
|
||||
<Building2 className="h-4 w-4" />
|
||||
<Briefcase className="h-4 w-4" />
|
||||
{/* data-ph-mask: the looked-up company name is user data */}
|
||||
<span data-ph-mask="">{profile.companyName}</span>
|
||||
</CardTitle>
|
||||
|
||||
@@ -18,8 +18,8 @@ import {
|
||||
FileText,
|
||||
AlertCircle,
|
||||
CheckCircle,
|
||||
Building2,
|
||||
HelpCircle,
|
||||
Landmark,
|
||||
} from 'lucide-react'
|
||||
import type { BankFileFormatId } from '@/lib/import/bank-file/types'
|
||||
|
||||
@@ -187,7 +187,7 @@ export default function BankFileUploadStep({
|
||||
{(selectedFile.size / 1024).toFixed(1)} KB
|
||||
</p>
|
||||
<Badge variant="secondary" className="mt-2">
|
||||
<Building2 className="mr-1 h-3 w-3" />
|
||||
<Landmark className="mr-1 h-3 w-3" />
|
||||
{detectedFormat === 'wise_statement'
|
||||
? t('bank_format_wise_statement')
|
||||
: detectedFormatName || FORMAT_NAMES[detectedFormat] || detectedFormat}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Button } from '@/components/ui/button'
|
||||
import { Checkbox } from '@/components/ui/checkbox'
|
||||
import { formatCurrency } from '@/lib/utils'
|
||||
import {
|
||||
Building2,
|
||||
Calendar,
|
||||
FileText,
|
||||
CheckCircle,
|
||||
@@ -16,6 +15,7 @@ import {
|
||||
ArrowRight,
|
||||
BarChart3,
|
||||
Info,
|
||||
Briefcase,
|
||||
} from 'lucide-react'
|
||||
import type { ImportPreview, ParseIssue } from '@/lib/import/types'
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function SIEPreviewStep({
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Building2 className="h-5 w-5" />
|
||||
<Briefcase className="h-5 w-5" />
|
||||
Företagsinformation
|
||||
</CardTitle>
|
||||
<CardDescription>Information från SIE-filen</CardDescription>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { Building2 } from 'lucide-react'
|
||||
import { Briefcase } from 'lucide-react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useCompany } from '@/contexts/CompanyContext'
|
||||
@@ -33,7 +33,7 @@ export function ActiveCompanyBadge({ className }: { className?: string }) {
|
||||
)}
|
||||
title={`${t('active_company')}: ${company.name}`}
|
||||
>
|
||||
<Building2 className="h-3 w-3 shrink-0" aria-hidden="true" />
|
||||
<Briefcase className="h-3 w-3 shrink-0" aria-hidden="true" />
|
||||
<span className="sr-only">{t('active_company')}: </span>
|
||||
<span className="truncate">{company.name}</span>
|
||||
</Badge>
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTranslations } from 'next-intl'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Search, ChevronDown, ChevronUp, AlertTriangle, Info, Building2, PenLine } from 'lucide-react'
|
||||
import { Search, ChevronDown, ChevronUp, AlertTriangle, Info, PenLine, Briefcase } from 'lucide-react'
|
||||
import {
|
||||
getCommonTemplates,
|
||||
getAdvancedTemplates,
|
||||
@@ -519,7 +519,7 @@ export default function TemplatePicker({
|
||||
{sortedLibraryRaw.length > 0 && (
|
||||
<div>
|
||||
<p className="text-xs font-medium text-muted-foreground mb-2 flex items-center gap-1.5">
|
||||
<Building2 className="h-3 w-3" />
|
||||
<Briefcase className="h-3 w-3" />
|
||||
{t('my_templates')}
|
||||
</p>
|
||||
<div className="space-y-1.5">
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"definition": {
|
||||
"name": "Bolagsuppgifter",
|
||||
"category": "import",
|
||||
"icon": "Building2",
|
||||
"icon": "Briefcase",
|
||||
"dataPattern": "manual",
|
||||
"hasOwnData": true,
|
||||
"description": "Hämta företagsinformation automatiskt vid registrering",
|
||||
@@ -18,7 +18,7 @@
|
||||
"quickAction": {
|
||||
"label": "Företagsprofil",
|
||||
"description": "Visa offentliga uppgifter",
|
||||
"icon": "Building2",
|
||||
"icon": "Briefcase",
|
||||
"href": "/e/general/tic",
|
||||
"order": 10
|
||||
}
|
||||
|
||||
+2
-2
@@ -60,7 +60,7 @@ export const EXTENSION_DEFINITIONS: Record<string, ExtensionDefinition[]> = {
|
||||
"name": "Bolagsuppgifter",
|
||||
"sector": "general",
|
||||
"category": "import",
|
||||
"icon": "Building2",
|
||||
"icon": "Briefcase",
|
||||
"dataPattern": "manual",
|
||||
"description": "Hämta företagsinformation automatiskt vid registrering",
|
||||
"longDescription": "Fyll i företagsuppgifter automatiskt genom att ange organisationsnummer. Hämtar adress, momsregistrering, F-skattestatus och bankuppgifter från offentliga register via TIC.",
|
||||
@@ -68,7 +68,7 @@ export const EXTENSION_DEFINITIONS: Record<string, ExtensionDefinition[]> = {
|
||||
"quickAction": {
|
||||
"label": "Företagsprofil",
|
||||
"description": "Visa offentliga uppgifter",
|
||||
"icon": "Building2",
|
||||
"icon": "Briefcase",
|
||||
"href": "/e/general/tic",
|
||||
"order": 10
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ import {
|
||||
Ship,
|
||||
FileText,
|
||||
Shield,
|
||||
Building2,
|
||||
Briefcase,
|
||||
Truck,
|
||||
ArrowRightLeft,
|
||||
Mail,
|
||||
MessageCircle,
|
||||
@@ -65,7 +66,8 @@ const ICON_MAP: Record<string, LucideIcon> = {
|
||||
Ship,
|
||||
FileText,
|
||||
Shield,
|
||||
Building2,
|
||||
Briefcase,
|
||||
Truck,
|
||||
ArrowRightLeft,
|
||||
Mail,
|
||||
MessageCircle,
|
||||
|
||||
Reference in New Issue
Block a user