Files
accounted/components/reports/useRecentReports.ts
T
Jakob Wennberg 6911f657e9 feat(reports): catalog as one dry table with band groups and Senast öppnad (#1147)
The founder-picked Tabellen variant from the rest-of-nav 2 concept:
the report library becomes a single dry table where band rows carry the
accounting taxonomy, each report is one clickable line with its
description in muted ink, and a Senast oppnad column replaces the
recents shelf (RecentReportsShelf deleted). useRecentReports now stores
slug+timestamp pairs (legacy plain-slug entries parse as undated).
FiscalYearSelector swaps to the house FyPicker chip, help moves behind
the ? popover, catalog footnote as pgnote. Entity gating, dimension
gating, route-owning reports and the persisted FY choice all unchanged.

9341 tests pass, lint clean, guards pass. sv+en keys added.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 16:41:09 +02:00

75 lines
2.2 KiB
TypeScript

'use client'
import { useCallback, useEffect, useState } from 'react'
/**
* Tracks the last reports the user opened, per company, in localStorage.
* Mirrors the `Accounted:<key>:<companyId>` convention used by
* FiscalYearSelector (STORAGE_KEY_PREFIX). Powers the "Senast öppnad" column
* in the report catalog table.
*
* Storage format: Array<{ s: slug, at: epoch-ms }>. Legacy entries were plain
* slug strings; those parse without a timestamp and simply show no date.
*/
const STORAGE_KEY_PREFIX = 'Accounted:report-recents:'
const MAX_RECENTS = 12
type StoredRecent = string | { s: string; at: number }
export function useRecentReports(companyId: string | null | undefined) {
const [openedAt, setOpenedAt] = useState<Record<string, number>>({})
useEffect(() => {
let cancelled = false
// Deferred to a microtask so the read isn't a synchronous setState in the
// effect body (and so the first server/client render agree on an empty
// list, avoiding a hydration mismatch).
Promise.resolve().then(() => {
if (cancelled) return
if (!companyId) {
setOpenedAt({})
return
}
try {
const raw = window.localStorage.getItem(STORAGE_KEY_PREFIX + companyId)
const parsed = raw ? (JSON.parse(raw) as StoredRecent[]) : []
const map: Record<string, number> = {}
for (const entry of parsed) {
if (typeof entry === 'object' && entry && entry.s) map[entry.s] = entry.at
}
setOpenedAt(map)
} catch {
setOpenedAt({})
}
})
return () => {
cancelled = true
}
}, [companyId])
const pushRecent = useCallback(
(slug: string) => {
if (!companyId) return
setOpenedAt((prev) => {
const next = { ...prev, [slug]: Date.now() }
try {
const stored = Object.entries(next)
.sort((a, b) => b[1] - a[1])
.slice(0, MAX_RECENTS)
.map(([s, at]) => ({ s, at }))
window.localStorage.setItem(
STORAGE_KEY_PREFIX + companyId,
JSON.stringify(stored),
)
} catch {
/* localStorage unavailable: keep in-memory only */
}
return next
})
},
[companyId],
)
return { openedAt, pushRecent }
}