From 3b6f35e31a0bfbc114b9b7116fdf4239a189bb90 Mon Sep 17 00:00:00 2001 From: mboehmlaender Date: Wed, 5 Aug 2026 15:46:06 +0200 Subject: [PATCH] Add FAQ guide and contextual help links --- frontend/src/App.tsx | 10 + frontend/src/components/HelpLink.tsx | 19 ++ frontend/src/views/AdminUsersPage.tsx | 34 ++- frontend/src/views/AnalysisPage.tsx | 8 +- frontend/src/views/FaqPage.tsx | 222 +++++++++++++++++++ frontend/src/views/MonthlyClosePage.tsx | 19 +- frontend/src/views/ProfilePage.tsx | 10 +- frontend/src/views/RecurringBillingsPage.tsx | 19 +- frontend/src/views/StatisticsPage.tsx | 35 ++- frontend/src/views/TicketDetailPage.tsx | 20 +- frontend/src/views/TimerPage.tsx | 40 +++- 11 files changed, 388 insertions(+), 48 deletions(-) create mode 100644 frontend/src/components/HelpLink.tsx create mode 100644 frontend/src/views/FaqPage.tsx diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4471ba8..55d606f 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -4,6 +4,7 @@ import { ClipboardCheck, Command, ChartNoAxesCombined, + CircleHelp, LayoutGrid, LogOut, Moon, @@ -33,6 +34,7 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Separator } from "@/components/ui/separator"; +import { HelpLink } from "@/components/HelpLink"; import { Sidebar, SidebarContent, @@ -56,6 +58,7 @@ import { formatTimer } from "./format"; import { activeElapsedMs, pauseEntry, readStoredTimers, resumeEntry, storageKeyForUser, ticketPattern, type TimerEntry } from "./timers"; import { AdminUsersPage } from "./views/AdminUsersPage"; import { AnalysisPage } from "./views/AnalysisPage"; +import { FaqPage } from "./views/FaqPage"; import { LoginPage } from "./views/LoginPage"; import { MonthlyClosePage } from "./views/MonthlyClosePage"; import { ProfilePage } from "./views/ProfilePage"; @@ -97,6 +100,10 @@ function routeFromPath(pathname: string) { return { page: "statistics" as const }; } + if (pathname.startsWith("/faq")) { + return { page: "faq" as const }; + } + if (pathname.startsWith("/monthly-close")) { return { page: "monthly-close" as const }; } @@ -202,6 +209,7 @@ function QuickTimerStarter({ className, inputId, onStartTimer }: QuickTimerStart Timer starten Start + ); } @@ -516,6 +524,7 @@ export function App() { { href: "/monthly-close", label: "Monatsabschluss", icon: ClipboardCheck, active: path.startsWith("/monthly-close") }, { href: "/statistics", label: "Statistiken", icon: ChartNoAxesCombined, active: path.startsWith("/statistics") }, { href: "/recurring", label: "Fixe Abrechnung", icon: Repeat, active: path.startsWith("/recurring") }, + { href: "/faq", label: "FAQ", icon: CircleHelp, active: path.startsWith("/faq") }, { href: "/profile", label: "Profil", icon: UserCog, active: path.startsWith("/profile") }, ...(currentUser.role === "admin" ? [{ href: "/admin/users", label: "Benutzer", icon: Shield, active: path.startsWith("/admin/users") }] @@ -702,6 +711,7 @@ export function App() { {route.page === "monthly-close" ? : null} {route.page === "statistics" ? : null} {route.page === "recurring" ? : null} + {route.page === "faq" ? : null} {route.page === "profile" ? : null} {route.page === "admin-users" && currentUser.role === "admin" ? : null} {route.page === "admin-users" && currentUser.role !== "admin" ? ( diff --git a/frontend/src/components/HelpLink.tsx b/frontend/src/components/HelpLink.tsx new file mode 100644 index 0000000..346bd13 --- /dev/null +++ b/frontend/src/components/HelpLink.tsx @@ -0,0 +1,19 @@ +import { CircleHelp } from "lucide-react"; + +import { Button } from "@/components/ui/button"; + +type HelpLinkProps = { + anchor: string; + label?: string; + className?: string; +}; + +export function HelpLink({ anchor, label = "Hilfe öffnen", className }: HelpLinkProps) { + return ( + + ); +} diff --git a/frontend/src/views/AdminUsersPage.tsx b/frontend/src/views/AdminUsersPage.tsx index 17a6e9b..775091f 100644 --- a/frontend/src/views/AdminUsersPage.tsx +++ b/frontend/src/views/AdminUsersPage.tsx @@ -8,6 +8,7 @@ import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Textarea } from "@/components/ui/textarea"; +import { HelpLink } from "@/components/HelpLink"; import { OrganizationSelect } from "@/components/OrganizationSelect"; import { createAdminSession, @@ -335,9 +336,12 @@ export function AdminUsersPage({ currentUser }: { currentUser: AuthUser }) { -
- - Data-Sync +
+
+ + Data-Sync +
+
Zammad-Zugang speichern und Organisationen in die lokale TicketTracker-Datenbank übernehmen. @@ -378,8 +382,13 @@ export function AdminUsersPage({ currentUser }: { currentUser: AuthUser }) { - Neuer Benutzer - Neue Benutzer sehen später nur ihre eigenen Sessions und Abschlüsse. +
+
+ Neuer Benutzer + Neue Benutzer sehen später nur ihre eigenen Sessions und Abschlüsse. +
+ +
@@ -421,9 +430,12 @@ export function AdminUsersPage({ currentUser }: { currentUser: AuthUser }) { -
- - Session nachtragen +
+
+ + Session nachtragen +
+
Vergessene Zeiten manuell erfassen und direkt einem Benutzer zuweisen. @@ -541,7 +553,10 @@ export function AdminUsersPage({ currentUser }: { currentUser: AuthUser }) { Vorhandene Benutzer {loading ? "Lädt..." : `${users.length} Account(s)`}
- {currentUser.display_name} +
+ {currentUser.display_name} + +
@@ -658,6 +673,7 @@ export function AdminUsersPage({ currentUser }: { currentUser: AuthUser }) { Session-Zuordnung {loading ? "Lädt..." : `${sessions.length} letzte Session(s), inklusive Besitzer.`}
+
diff --git a/frontend/src/views/AnalysisPage.tsx b/frontend/src/views/AnalysisPage.tsx index 333b209..ec4c3f8 100644 --- a/frontend/src/views/AnalysisPage.tsx +++ b/frontend/src/views/AnalysisPage.tsx @@ -9,6 +9,7 @@ import { Input } from "@/components/ui/input"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { CopyTicketButton } from "@/components/CopyTicketButton"; +import { HelpLink } from "@/components/HelpLink"; import { getPeriodOverview } from "../api"; import { currentDay, currentMonth, formatMinutes } from "../format"; import type { PeriodOverview, PeriodType, TicketSummary } from "../types"; @@ -519,7 +520,10 @@ export function AnalysisPage({ onNavigate }: AnalysisPageProps) { {periodType === "month" ? "Aufwand pro Tag im ausgewählten Monat." : "Aufwand pro Stunde am ausgewählten Tag."}
- {formatMinutes(totals?.minutes ?? 0)} +
+ {formatMinutes(totals?.minutes ?? 0)} + +
@@ -605,6 +609,7 @@ export function AnalysisPage({ onNavigate }: AnalysisPageProps) { {periodType === "month" ? "Sessions prüfen, bewerten und bei Bedarf Tickets öffnen." : "Tagesansicht ohne eigenen Abschluss."}
+
@@ -618,6 +623,7 @@ export function AnalysisPage({ onNavigate }: AnalysisPageProps) {

{activeFilterCount > 0 ? {activeFilterCount} aktiv : null} +
+ ))} + +
+ +
+ {faqSections.map((section) => ( + + + {section.title} + {section.description} + + +
    + {section.items.map((item) => ( +
  • + + {item} +
  • + ))} +
+ +
+
+ ))} +
+ + ); +} diff --git a/frontend/src/views/MonthlyClosePage.tsx b/frontend/src/views/MonthlyClosePage.tsx index 22c0eb8..6467b17 100644 --- a/frontend/src/views/MonthlyClosePage.tsx +++ b/frontend/src/views/MonthlyClosePage.tsx @@ -19,6 +19,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import { Checkbox } from "@/components/ui/checkbox"; import { Input } from "@/components/ui/input"; import { CopyTicketButton } from "@/components/CopyTicketButton"; +import { HelpLink } from "@/components/HelpLink"; import { acknowledgeMissingTicketDayBilling, closePeriod, getStatisticsOverview, reopenPeriod } from "../api"; import { currentMonth, formatDate, formatDateTime, formatMinutes } from "../format"; import type { StatisticsCrmDay, StatisticsOpenSession, StatisticsOverview } from "../types"; @@ -339,8 +340,13 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) { - Checkliste - Bewertungen sind Pflicht. Teamspace-Punkte helfen beim sauberen CRM-Abgleich. +
+
+ Checkliste + Bewertungen sind Pflicht. Teamspace-Punkte helfen beim sauberen CRM-Abgleich. +
+ +
{checklist.map((item) => ( @@ -357,8 +363,13 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) { - Aufräumen - Alles, was für diesen Monat noch Aufmerksamkeit braucht. +
+
+ Aufräumen + Alles, was für diesen Monat noch Aufmerksamkeit braucht. +
+ +
diff --git a/frontend/src/views/ProfilePage.tsx b/frontend/src/views/ProfilePage.tsx index 587c0eb..9174c95 100644 --- a/frontend/src/views/ProfilePage.tsx +++ b/frontend/src/views/ProfilePage.tsx @@ -4,6 +4,7 @@ import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; +import { HelpLink } from "@/components/HelpLink"; import { updateCurrentUser } from "../api"; import type { AuthUser } from "../types"; @@ -74,9 +75,12 @@ export function ProfilePage({ currentUser, onUserUpdated }: ProfilePageProps) { -
- - Accountdaten +
+
+ + Accountdaten +
+
Der Benutzername wird beim Login verwendet. Dein voller Name wird in der App angezeigt. diff --git a/frontend/src/views/RecurringBillingsPage.tsx b/frontend/src/views/RecurringBillingsPage.tsx index 7df28ee..e399008 100644 --- a/frontend/src/views/RecurringBillingsPage.tsx +++ b/frontend/src/views/RecurringBillingsPage.tsx @@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; +import { HelpLink } from "@/components/HelpLink"; import { OrganizationSelect } from "@/components/OrganizationSelect"; import { createRecurringBilling, deleteRecurringBilling, getRecurringBillings, updateRecurringBilling } from "../api"; import { currentDay, formatMinutes } from "../format"; @@ -248,9 +249,12 @@ export function RecurringBillingsPage() { -
- - {editingBillingId ? "Regel bearbeiten" : "Neue Regel"} +
+
+ + {editingBillingId ? "Regel bearbeiten" : "Neue Regel"} +
+
Ticket ist optional. Leer bedeutet automatische Gruppierung als Fix#ID. @@ -447,8 +451,13 @@ export function RecurringBillingsPage() { - Regeln - Erzeugte Einträge können in der Auswertung verschoben oder gelöscht werden. +
+
+ Regeln + Erzeugte Einträge können in der Auswertung verschoben oder gelöscht werden. +
+ +
{recurringBillings.map((billing) => ( diff --git a/frontend/src/views/StatisticsPage.tsx b/frontend/src/views/StatisticsPage.tsx index 24d9303..6f28b30 100644 --- a/frontend/src/views/StatisticsPage.tsx +++ b/frontend/src/views/StatisticsPage.tsx @@ -21,6 +21,7 @@ import { Input } from "@/components/ui/input"; import { Pagination, PaginationContent, PaginationItem, PaginationNext, PaginationPrevious } from "@/components/ui/pagination"; import { Progress } from "@/components/ui/progress"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; +import { HelpLink } from "@/components/HelpLink"; import { getStatisticsOverview } from "../api"; import { currentMonth, formatDate, formatMinutes } from "../format"; import type { StatisticsDailyBucket, StatisticsGroup, StatisticsOverview, StatisticsTicket, WorkType } from "../types"; @@ -535,7 +536,10 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) { Monatsverlauf und Abrechnungsqualität Getrackter Aufwand pro Tag, Teamspace-Linie und Bewertungsstand in einem Überblick.
- {crmCoverage}% Teamspace-Abdeckung +
+ {crmCoverage}% Teamspace-Abdeckung + +
@@ -613,9 +617,12 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) { Statistik pro Organisation Kundenaufwand inklusive Teamspace-Differenz und Anteil am getrackten Monatsaufwand.
- - {organizationRangeStart}-{organizationRangeEnd} von {stats?.organizations.length ?? 0} - +
+ + {organizationRangeStart}-{organizationRangeEnd} von {stats?.organizations.length ?? 0} + + +
@@ -702,8 +709,13 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) { - Typen - Support und Consulting im direkten Vergleich. +
+
+ Typen + Support und Consulting im direkten Vergleich. +
+ +
{(stats?.workTypes ?? []).map((group) => ( @@ -738,10 +750,13 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) { Ticket-Hotspots Die größten Zeitblöcke im Monat.
- +
+ + +
{(stats?.tickets ?? []).map((ticket) => ( diff --git a/frontend/src/views/TicketDetailPage.tsx b/frontend/src/views/TicketDetailPage.tsx index bf104b4..5f7e9f4 100644 --- a/frontend/src/views/TicketDetailPage.tsx +++ b/frontend/src/views/TicketDetailPage.tsx @@ -8,6 +8,7 @@ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } f import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { CopyTicketButton } from "@/components/CopyTicketButton"; +import { HelpLink } from "@/components/HelpLink"; import { OrganizationSelect } from "@/components/OrganizationSelect"; import { createSession, @@ -493,8 +494,13 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate }: T - Ticketdaten - Änderungen an Organisation und Art werden auf alle vorhandenen Sessions dieses Tickets übernommen. +
+
+ Ticketdaten + Änderungen an Organisation und Art werden auf alle vorhandenen Sessions dieses Tickets übernommen. +
+ +
@@ -544,6 +550,7 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate }: T Wähle für jede Session genau eine Bewertung aus oder lösche falsche Einträge.
+
{periodLabel} gesamt {formatMinutes(periodTotalMinutes)} @@ -657,8 +664,13 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate }: T - Session nachtragen - Neue Zeit direkt diesem Ticket zuordnen. +
+
+ Session nachtragen + Neue Zeit direkt diesem Ticket zuordnen. +
+ +
diff --git a/frontend/src/views/TimerPage.tsx b/frontend/src/views/TimerPage.tsx index 4372d86..9a56cec 100644 --- a/frontend/src/views/TimerPage.tsx +++ b/frontend/src/views/TimerPage.tsx @@ -8,6 +8,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; +import { HelpLink } from "@/components/HelpLink"; import { OrganizationSelect } from "@/components/OrganizationSelect"; import { createSession, lookupTicket } from "../api"; import { formatTimer } from "../format"; @@ -300,8 +301,13 @@ export function TimerPage({ timers, setTimers, selectedTimerId, setSelectedTimer - Neuen Timer starten - Ein neuer Timer startet sofort und pausiert alle anderen Timer automatisch. +
+
+ Neuen Timer starten + Ein neuer Timer startet sofort und pausiert alle anderen Timer automatisch. +
+ +
@@ -330,14 +336,19 @@ export function TimerPage({ timers, setTimers, selectedTimerId, setSelectedTimer - {selectedTimer ? selectedTimer.ticketNumber : "Keine Session ausgewählt"} - - {selectedTimer - ? selectedTimer.organizationName - ? `${selectedTimer.organizationName}${selectedTimer.workType ? ` · ${selectedTimer.workType === "support" ? "Support" : "Consulting"}` : ""}` - : "Beim Umschalten wird dieser Timer aktiviert und alle anderen werden pausiert." - : "Starte einen Timer, um eine Session zu erfassen."} - +
+
+ {selectedTimer ? selectedTimer.ticketNumber : "Keine Session ausgewählt"} + + {selectedTimer + ? selectedTimer.organizationName + ? `${selectedTimer.organizationName}${selectedTimer.workType ? ` · ${selectedTimer.workType === "support" ? "Support" : "Consulting"}` : ""}` + : "Beim Umschalten wird dieser Timer aktiviert und alle anderen werden pausiert." + : "Starte einen Timer, um eine Session zu erfassen."} + +
+ +
@@ -374,8 +385,13 @@ export function TimerPage({ timers, setTimers, selectedTimerId, setSelectedTimer - Session nachtragen - Vergessene Zeiten für deinen eigenen Account manuell erfassen. +
+
+ Session nachtragen + Vergessene Zeiten für deinen eigenen Account manuell erfassen. +
+ +