All checks were successful
PR Check — Frontend / frontend (pull_request) Successful in 1m46s
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>
60 lines
2.1 KiB
TypeScript
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 };
|
|
}
|