Simpl-Resultat/src/utils/onboardingChecklist.ts
le king fu f77ce94505
All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m46s
feat(onboarding): add transaction count, onboarding counters and checklist reducer (#351)
Lay the data foundation of the getting-started checklist, without UI:

- transactionService.getTransactionCount(): unfiltered COUNT(*) of transactions
- onboardingService: getOnboardingCounters() reads the five aggregated counts,
  the import folder and the onboarding_checklist_hidden preference in one
  Promise.all; hideChecklist()/showChecklist() persist the preference and log
- utils/onboardingChecklist.computeChecklist(): pure computation of the six
  rows (profile, importFolder, source, import, manualCategory, budget), with
  the budget row locked and excluded from the total when the edition has no
  Budget
- hooks/useOnboardingChecklist: exported reducer + initial state, actions
  refresh/hide/show, composed with useEntitlement("budget"); a load error
  sets status "error" and keeps the hidden flag
- types OnboardingCounters, ChecklistItem, ChecklistItemKey

Tests cover every row in isolation, the budget gating (total 5 vs 6, complete
at 5/5 locked and 6/6 open), the aggregated query and preference reads, and
every reducer action. No DB migration, no CHANGELOG entry (centralised in #357).

Resolves #351

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-03 19:22:18 -04:00

60 lines
2.1 KiB
TypeScript

import type { ChecklistItem, OnboardingCounters } from "../shared/types";
/**
* Pure computation of the getting-started checklist (#351).
*
* Six rows in a fixed order. The `profile` row is always done (the checklist
* is only reachable from an open profile). The `budget` row is locked when the
* edition does not include Budget; a locked row is excluded from `total` and
* from `complete`, and links to the Users settings (license card) instead.
*/
export interface ChecklistOptions {
/** An import folder has been chosen. */
importFolderSet: boolean;
/** The Budget feature is part of the current edition. */
budgetAllowed: boolean;
}
export interface Checklist {
items: ChecklistItem[];
/** Number of unlocked rows that are done. */
done: number;
/** Number of unlocked rows (6, or 5 when Budget is locked). */
total: number;
/** True when every unlocked row is done. */
complete: boolean;
}
export function computeChecklist(
counters: OnboardingCounters,
options: ChecklistOptions
): Checklist {
const budgetLocked = !options.budgetAllowed;
const items: ChecklistItem[] = [
// Always done: the checklist only exists inside an open profile. The link
// is never rendered for a done row (#352); it points at profile management.
{ key: "profile", done: true, locked: false, to: "/settings/users" },
{ key: "importFolder", done: options.importFolderSet, locked: false, to: "/import" },
{ key: "source", done: counters.sources >= 1, locked: false, to: "/import" },
{ key: "import", done: counters.imports >= 1, locked: false, to: "/import" },
{
key: "manualCategory",
done: counters.manualCategorized >= 1,
locked: false,
to: "/transactions",
},
{
key: "budget",
done: counters.budgetEntries >= 1,
locked: budgetLocked,
to: budgetLocked ? "/settings/users" : "/budget",
},
];
const unlocked = items.filter((item) => !item.locked);
const done = unlocked.filter((item) => item.done).length;
const total = unlocked.length;
return { items, done, total, complete: done === total };
}