6911f657e9
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>
75 lines
2.2 KiB
TypeScript
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 }
|
|
}
|