feat: add per-page contextual help via CircleHelp icon
Each page now shows a ? icon next to its title that toggles a collapsible help card with page-specific tips. Supports EN/FR via i18n, closes on outside click or X button. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
0adfa5fe5e
commit
3351601ff5
11 changed files with 250 additions and 23 deletions
59
src/components/shared/PageHelp.tsx
Normal file
59
src/components/shared/PageHelp.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { useState, useRef, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CircleHelp, X } from "lucide-react";
|
||||
|
||||
export function PageHelp({ helpKey }: { helpKey: string }) {
|
||||
const { t } = useTranslation();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
// Close on outside click (same pattern as CategoryCombobox)
|
||||
useEffect(() => {
|
||||
if (!isOpen) return;
|
||||
const handler = (e: MouseEvent) => {
|
||||
if (ref.current && !ref.current.contains(e.target as Node)) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("mousedown", handler);
|
||||
return () => document.removeEventListener("mousedown", handler);
|
||||
}, [isOpen]);
|
||||
|
||||
const tips = t(`${helpKey}.help.tips`, { returnObjects: true }) as string[];
|
||||
|
||||
return (
|
||||
<div ref={ref} className="relative">
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className="text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors"
|
||||
aria-label={t(`${helpKey}.help.title`)}
|
||||
>
|
||||
<CircleHelp size={20} />
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div className="absolute left-0 top-full mt-2 z-40 w-[calc(100vw-var(--sidebar-width,16rem)-6rem)] max-w-3xl bg-[var(--card)] border border-[var(--border)] rounded-xl p-5 shadow-lg">
|
||||
<div className="flex items-start justify-between gap-4 mb-3">
|
||||
<h3 className="font-semibold text-[var(--foreground)]">
|
||||
{t(`${helpKey}.help.title`)}
|
||||
</h3>
|
||||
<button
|
||||
onClick={() => setIsOpen(false)}
|
||||
className="text-[var(--muted-foreground)] hover:text-[var(--foreground)] transition-colors shrink-0"
|
||||
>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</div>
|
||||
<ul className="space-y-1.5 text-sm text-[var(--muted-foreground)]">
|
||||
{Array.isArray(tips) &&
|
||||
tips.map((tip, i) => (
|
||||
<li key={i} className="flex gap-2">
|
||||
<span className="shrink-0">•</span>
|
||||
<span>{tip}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -26,6 +26,15 @@
|
|||
"6months": "6 months",
|
||||
"12months": "12 months",
|
||||
"all": "All"
|
||||
},
|
||||
"help": {
|
||||
"title": "How to use the Dashboard",
|
||||
"tips": [
|
||||
"Use the period selector (top right) to view different time ranges",
|
||||
"Summary cards show your balance, income, and expenses for the selected period",
|
||||
"The pie chart breaks down your expenses by category",
|
||||
"Recent transactions are listed at the bottom"
|
||||
]
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
|
|
@ -145,6 +154,15 @@
|
|||
"checkDuplicates": "Check duplicates",
|
||||
"confirm": "Confirm",
|
||||
"import": "Import"
|
||||
},
|
||||
"help": {
|
||||
"title": "How to import bank statements",
|
||||
"tips": [
|
||||
"Set your import folder, then create one subfolder per bank/source with CSV files inside",
|
||||
"Click a source to configure column mapping, delimiter, and date format",
|
||||
"Preview your data before importing to catch formatting issues",
|
||||
"Duplicate detection prevents the same transactions from being imported twice"
|
||||
]
|
||||
}
|
||||
},
|
||||
"transactions": {
|
||||
|
|
@ -184,7 +202,16 @@
|
|||
},
|
||||
"autoCategorize": "Auto-categorize",
|
||||
"autoCategorizeResult": "{{count}} transaction(s) categorized",
|
||||
"autoCategorizeNone": "No new matches found"
|
||||
"autoCategorizeNone": "No new matches found",
|
||||
"help": {
|
||||
"title": "How to use Transactions",
|
||||
"tips": [
|
||||
"Use the filters to search by description, category, source, or date range",
|
||||
"Click a column header to sort transactions",
|
||||
"Assign categories by clicking the category dropdown on each row",
|
||||
"Auto-categorize uses your keyword rules to categorize transactions in bulk"
|
||||
]
|
||||
}
|
||||
},
|
||||
"categories": {
|
||||
"title": "Categories",
|
||||
|
|
@ -207,7 +234,16 @@
|
|||
"keywordCount": "Keywords",
|
||||
"keywordText": "Keyword...",
|
||||
"priority": "Priority",
|
||||
"customColor": "Custom color"
|
||||
"customColor": "Custom color",
|
||||
"help": {
|
||||
"title": "How to manage Categories",
|
||||
"tips": [
|
||||
"Create top-level categories and subcategories to organize your expenses and income",
|
||||
"Add keywords to a category so transactions matching those words are auto-categorized",
|
||||
"Set a priority on keywords to resolve conflicts when multiple categories match",
|
||||
"Click a category in the tree to view its details, edit it, or manage keywords"
|
||||
]
|
||||
}
|
||||
},
|
||||
"adjustments": {
|
||||
"title": "Adjustments",
|
||||
|
|
@ -215,7 +251,15 @@
|
|||
"date": "Date",
|
||||
"description": "Description",
|
||||
"amount": "Amount",
|
||||
"recurring": "Recurring"
|
||||
"recurring": "Recurring",
|
||||
"help": {
|
||||
"title": "How to use Adjustments",
|
||||
"tips": [
|
||||
"Adjustments let you add manual entries that don't come from bank imports",
|
||||
"Use them for expected expenses or income not yet reflected in your statements",
|
||||
"Recurring adjustments repeat automatically each period"
|
||||
]
|
||||
}
|
||||
},
|
||||
"budget": {
|
||||
"title": "Budget",
|
||||
|
|
@ -224,7 +268,15 @@
|
|||
"planned": "Planned",
|
||||
"actual": "Actual",
|
||||
"difference": "Difference",
|
||||
"template": "Template"
|
||||
"template": "Template",
|
||||
"help": {
|
||||
"title": "How to use Budget",
|
||||
"tips": [
|
||||
"Set planned amounts for each category to track your spending goals",
|
||||
"Compare planned vs. actual spending to see where you're over or under budget",
|
||||
"Use templates to quickly apply the same budget across multiple months"
|
||||
]
|
||||
}
|
||||
},
|
||||
"reports": {
|
||||
"title": "Reports",
|
||||
|
|
@ -232,7 +284,16 @@
|
|||
"byCategory": "Expenses by Category",
|
||||
"overTime": "Category Over Time",
|
||||
"trends": "Monthly Trends",
|
||||
"export": "Export"
|
||||
"export": "Export",
|
||||
"help": {
|
||||
"title": "How to use Reports",
|
||||
"tips": [
|
||||
"Switch between Trends, By Category, and Over Time views using the tabs",
|
||||
"Use the period selector to adjust the time range for all charts",
|
||||
"Monthly Trends shows your income and expenses over time",
|
||||
"Category Over Time tracks how spending in each category evolves"
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings",
|
||||
|
|
@ -251,7 +312,15 @@
|
|||
"error": "Update failed",
|
||||
"retryButton": "Retry"
|
||||
},
|
||||
"dataSafeNotice": "Your data is safe — only the app binary is replaced, your database is not modified."
|
||||
"dataSafeNotice": "Your data is safe — only the app binary is replaced, your database is not modified.",
|
||||
"help": {
|
||||
"title": "About Settings",
|
||||
"tips": [
|
||||
"Check for app updates and install them directly from this page",
|
||||
"Your data is stored locally and is never affected by updates",
|
||||
"Change the app language using the language selector in the sidebar"
|
||||
]
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"save": "Save",
|
||||
|
|
|
|||
|
|
@ -26,6 +26,15 @@
|
|||
"6months": "6 mois",
|
||||
"12months": "12 mois",
|
||||
"all": "Tout"
|
||||
},
|
||||
"help": {
|
||||
"title": "Comment utiliser le tableau de bord",
|
||||
"tips": [
|
||||
"Utilisez le sélecteur de période (en haut à droite) pour changer la plage de dates",
|
||||
"Les cartes résumées affichent votre solde, revenus et dépenses pour la période sélectionnée",
|
||||
"Le graphique circulaire détaille vos dépenses par catégorie",
|
||||
"Les transactions récentes sont listées en bas de page"
|
||||
]
|
||||
}
|
||||
},
|
||||
"import": {
|
||||
|
|
@ -145,6 +154,15 @@
|
|||
"checkDuplicates": "Vérifier les doublons",
|
||||
"confirm": "Confirmer",
|
||||
"import": "Importer"
|
||||
},
|
||||
"help": {
|
||||
"title": "Comment importer des relevés bancaires",
|
||||
"tips": [
|
||||
"Configurez votre dossier d'import, puis créez un sous-dossier par banque/source avec des fichiers CSV",
|
||||
"Cliquez sur une source pour configurer le mapping des colonnes, le délimiteur et le format de date",
|
||||
"Prévisualisez vos données avant l'import pour détecter les problèmes de formatage",
|
||||
"La détection des doublons empêche d'importer les mêmes transactions deux fois"
|
||||
]
|
||||
}
|
||||
},
|
||||
"transactions": {
|
||||
|
|
@ -184,7 +202,16 @@
|
|||
},
|
||||
"autoCategorize": "Auto-catégoriser",
|
||||
"autoCategorizeResult": "{{count}} transaction(s) catégorisée(s)",
|
||||
"autoCategorizeNone": "Aucune correspondance trouvée"
|
||||
"autoCategorizeNone": "Aucune correspondance trouvée",
|
||||
"help": {
|
||||
"title": "Comment utiliser les Transactions",
|
||||
"tips": [
|
||||
"Utilisez les filtres pour rechercher par description, catégorie, source ou plage de dates",
|
||||
"Cliquez sur un en-tête de colonne pour trier les transactions",
|
||||
"Assignez une catégorie via le menu déroulant sur chaque ligne",
|
||||
"L'auto-catégorisation utilise vos règles de mots-clés pour catégoriser en masse"
|
||||
]
|
||||
}
|
||||
},
|
||||
"categories": {
|
||||
"title": "Catégories",
|
||||
|
|
@ -207,7 +234,16 @@
|
|||
"keywordCount": "Mots-clés",
|
||||
"keywordText": "Mot-clé...",
|
||||
"priority": "Priorité",
|
||||
"customColor": "Couleur personnalisée"
|
||||
"customColor": "Couleur personnalisée",
|
||||
"help": {
|
||||
"title": "Comment gérer les Catégories",
|
||||
"tips": [
|
||||
"Créez des catégories et sous-catégories pour organiser vos dépenses et revenus",
|
||||
"Ajoutez des mots-clés à une catégorie pour que les transactions correspondantes soient auto-catégorisées",
|
||||
"Définissez une priorité sur les mots-clés pour résoudre les conflits entre catégories",
|
||||
"Cliquez sur une catégorie dans l'arbre pour voir ses détails, la modifier ou gérer ses mots-clés"
|
||||
]
|
||||
}
|
||||
},
|
||||
"adjustments": {
|
||||
"title": "Ajustements",
|
||||
|
|
@ -215,7 +251,15 @@
|
|||
"date": "Date",
|
||||
"description": "Description",
|
||||
"amount": "Montant",
|
||||
"recurring": "Récurrent"
|
||||
"recurring": "Récurrent",
|
||||
"help": {
|
||||
"title": "Comment utiliser les Ajustements",
|
||||
"tips": [
|
||||
"Les ajustements permettent d'ajouter des entrées manuelles non issues de vos relevés bancaires",
|
||||
"Utilisez-les pour des dépenses ou revenus prévus non encore reflétés dans vos relevés",
|
||||
"Les ajustements récurrents se répètent automatiquement à chaque période"
|
||||
]
|
||||
}
|
||||
},
|
||||
"budget": {
|
||||
"title": "Budget",
|
||||
|
|
@ -224,7 +268,15 @@
|
|||
"planned": "Prévu",
|
||||
"actual": "Réel",
|
||||
"difference": "Écart",
|
||||
"template": "Modèle"
|
||||
"template": "Modèle",
|
||||
"help": {
|
||||
"title": "Comment utiliser le Budget",
|
||||
"tips": [
|
||||
"Définissez des montants prévus par catégorie pour suivre vos objectifs de dépenses",
|
||||
"Comparez le prévu et le réel pour voir où vous dépassez ou êtes en dessous du budget",
|
||||
"Utilisez les modèles pour appliquer rapidement le même budget sur plusieurs mois"
|
||||
]
|
||||
}
|
||||
},
|
||||
"reports": {
|
||||
"title": "Rapports",
|
||||
|
|
@ -232,7 +284,16 @@
|
|||
"byCategory": "Dépenses par catégorie",
|
||||
"overTime": "Catégories dans le temps",
|
||||
"trends": "Tendances mensuelles",
|
||||
"export": "Exporter"
|
||||
"export": "Exporter",
|
||||
"help": {
|
||||
"title": "Comment utiliser les Rapports",
|
||||
"tips": [
|
||||
"Basculez entre les vues Tendances, Par catégorie et Dans le temps via les onglets",
|
||||
"Utilisez le sélecteur de période pour ajuster la plage de dates de tous les graphiques",
|
||||
"Les tendances mensuelles montrent vos revenus et dépenses au fil du temps",
|
||||
"Catégories dans le temps suit l'évolution des dépenses par catégorie"
|
||||
]
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "Paramètres",
|
||||
|
|
@ -251,7 +312,15 @@
|
|||
"error": "Erreur lors de la mise à jour",
|
||||
"retryButton": "Réessayer"
|
||||
},
|
||||
"dataSafeNotice": "Vos données sont en sécurité — seul le programme est remplacé, votre base de données n'est pas modifiée."
|
||||
"dataSafeNotice": "Vos données sont en sécurité — seul le programme est remplacé, votre base de données n'est pas modifiée.",
|
||||
"help": {
|
||||
"title": "À propos des Paramètres",
|
||||
"tips": [
|
||||
"Vérifiez les mises à jour de l'application et installez-les directement depuis cette page",
|
||||
"Vos données sont stockées localement et ne sont jamais affectées par les mises à jour",
|
||||
"Changez la langue de l'application via le sélecteur de langue dans la barre latérale"
|
||||
]
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"save": "Enregistrer",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { PageHelp } from "../components/shared/PageHelp";
|
||||
|
||||
export default function AdjustmentsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-6">{t("adjustments.title")}</h1>
|
||||
<div className="relative flex items-center gap-3 mb-6">
|
||||
<h1 className="text-2xl font-bold">{t("adjustments.title")}</h1>
|
||||
<PageHelp helpKey="adjustments" />
|
||||
</div>
|
||||
<div className="bg-[var(--card)] rounded-xl p-8 border border-[var(--border)] text-center text-[var(--muted-foreground)]">
|
||||
<p>{t("common.noResults")}</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { PageHelp } from "../components/shared/PageHelp";
|
||||
|
||||
export default function BudgetPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-6">{t("budget.title")}</h1>
|
||||
<div className="relative flex items-center gap-3 mb-6">
|
||||
<h1 className="text-2xl font-bold">{t("budget.title")}</h1>
|
||||
<PageHelp helpKey="budget" />
|
||||
</div>
|
||||
<div className="bg-[var(--card)] rounded-xl p-8 border border-[var(--border)] text-center text-[var(--muted-foreground)]">
|
||||
<p>{t("common.noResults")}</p>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { Plus } from "lucide-react";
|
||||
import { PageHelp } from "../components/shared/PageHelp";
|
||||
import { useCategories } from "../hooks/useCategories";
|
||||
import CategoryTree from "../components/categories/CategoryTree";
|
||||
import CategoryDetailPanel from "../components/categories/CategoryDetailPanel";
|
||||
|
|
@ -26,8 +27,11 @@ export default function CategoriesPage() {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="relative flex items-center justify-between mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-2xl font-bold">{t("categories.title")}</h1>
|
||||
<PageHelp helpKey="categories" />
|
||||
</div>
|
||||
<button
|
||||
onClick={startCreating}
|
||||
className="flex items-center gap-2 px-4 py-2 rounded-lg bg-[var(--primary)] text-white text-sm font-medium hover:opacity-90"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { Wallet, TrendingUp, TrendingDown } from "lucide-react";
|
||||
import { useDashboard } from "../hooks/useDashboard";
|
||||
import { PageHelp } from "../components/shared/PageHelp";
|
||||
import PeriodSelector from "../components/dashboard/PeriodSelector";
|
||||
import CategoryPieChart from "../components/dashboard/CategoryPieChart";
|
||||
import RecentTransactionsList from "../components/dashboard/RecentTransactionsList";
|
||||
|
|
@ -43,8 +44,11 @@ export default function DashboardPage() {
|
|||
|
||||
return (
|
||||
<div className={isLoading ? "opacity-50 pointer-events-none" : ""}>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||
<div className="relative flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-2xl font-bold">{t("dashboard.title")}</h1>
|
||||
<PageHelp helpKey="dashboard" />
|
||||
</div>
|
||||
<PeriodSelector value={period} onChange={setPeriod} />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import ImportReportPanel from "../components/import/ImportReportPanel";
|
|||
import WizardNavigation from "../components/import/WizardNavigation";
|
||||
import ImportHistoryPanel from "../components/import/ImportHistoryPanel";
|
||||
import { AlertCircle } from "lucide-react";
|
||||
import { PageHelp } from "../components/shared/PageHelp";
|
||||
|
||||
export default function ImportPage() {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -33,7 +34,10 @@ export default function ImportPage() {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold mb-6">{t("import.title")}</h1>
|
||||
<div className="relative flex items-center gap-3 mb-6">
|
||||
<h1 className="text-2xl font-bold">{t("import.title")}</h1>
|
||||
<PageHelp helpKey="import" />
|
||||
</div>
|
||||
|
||||
{/* Error banner */}
|
||||
{state.error && (
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useReports } from "../hooks/useReports";
|
||||
import { PageHelp } from "../components/shared/PageHelp";
|
||||
import type { ReportTab } from "../shared/types";
|
||||
import PeriodSelector from "../components/dashboard/PeriodSelector";
|
||||
import MonthlyTrendsChart from "../components/reports/MonthlyTrendsChart";
|
||||
|
|
@ -14,8 +15,11 @@ export default function ReportsPage() {
|
|||
|
||||
return (
|
||||
<div className={state.isLoading ? "opacity-50 pointer-events-none" : ""}>
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
|
||||
<div className="relative flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<h1 className="text-2xl font-bold">{t("reports.title")}</h1>
|
||||
<PageHelp helpKey="reports" />
|
||||
</div>
|
||||
<PeriodSelector value={state.period} onChange={setPeriod} />
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
import { getVersion } from "@tauri-apps/api/app";
|
||||
import { useUpdater } from "../hooks/useUpdater";
|
||||
import { APP_NAME } from "../shared/constants";
|
||||
import { PageHelp } from "../components/shared/PageHelp";
|
||||
|
||||
export default function SettingsPage() {
|
||||
const { t } = useTranslation();
|
||||
|
|
@ -31,7 +32,10 @@ export default function SettingsPage() {
|
|||
|
||||
return (
|
||||
<div className="p-6 max-w-2xl mx-auto space-y-6">
|
||||
<div className="relative flex items-center gap-3">
|
||||
<h1 className="text-2xl font-bold">{t("settings.title")}</h1>
|
||||
<PageHelp helpKey="settings" />
|
||||
</div>
|
||||
|
||||
{/* About card */}
|
||||
<div className="bg-[var(--card)] border border-[var(--border)] rounded-xl p-6">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Wand2 } from "lucide-react";
|
||||
import { PageHelp } from "../components/shared/PageHelp";
|
||||
import { useTransactions } from "../hooks/useTransactions";
|
||||
import TransactionFilterBar from "../components/transactions/TransactionFilterBar";
|
||||
import TransactionSummaryBar from "../components/transactions/TransactionSummaryBar";
|
||||
|
|
@ -26,8 +27,9 @@ export default function TransactionsPage() {
|
|||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="relative flex items-center gap-3 mb-6">
|
||||
<h1 className="text-2xl font-bold">{t("transactions.title")}</h1>
|
||||
<PageHelp helpKey="transactions" />
|
||||
<button
|
||||
onClick={handleAutoCategorize}
|
||||
disabled={state.isAutoCategorizing}
|
||||
|
|
|
|||
Loading…
Reference in a new issue