Refine help UX and monthly close layout
This commit is contained in:
+41
-19
@@ -69,6 +69,19 @@ import { TimerPage } from "./views/TimerPage";
|
||||
import type { AuthUser, PeriodType } from "./types";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
|
||||
type NavMenuEntry =
|
||||
| {
|
||||
type: "item";
|
||||
href: string;
|
||||
label: string;
|
||||
icon: typeof Timer;
|
||||
active: boolean;
|
||||
}
|
||||
| {
|
||||
type: "separator";
|
||||
key: string;
|
||||
};
|
||||
|
||||
function routeFromPath(pathname: string) {
|
||||
const periodTicketMatch = pathname.match(/^\/analysis\/(month|day)\/([^/]+)\/tickets\/(\d+)$/);
|
||||
|
||||
@@ -518,17 +531,20 @@ export function App() {
|
||||
return <LoginPage onLogin={setCurrentUser} />;
|
||||
}
|
||||
|
||||
const navItems = [
|
||||
{ href: "/timer", label: "Timer", icon: Timer, active: path.startsWith("/timer") || path === "/" },
|
||||
{ href: "/analysis", label: "Auswertung", icon: BarChart3, active: path.startsWith("/analysis") },
|
||||
{ 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") },
|
||||
const navItems: NavMenuEntry[] = [
|
||||
{ type: "item", href: "/timer", label: "Timer", icon: Timer, active: path.startsWith("/timer") || path === "/" },
|
||||
{ type: "item", href: "/analysis", label: "Auswertung", icon: BarChart3, active: path.startsWith("/analysis") },
|
||||
{ type: "separator", key: "after-analysis" },
|
||||
{ type: "item", href: "/monthly-close", label: "Monatsabschluss", icon: ClipboardCheck, active: path.startsWith("/monthly-close") },
|
||||
{ type: "separator", key: "after-monthly-close" },
|
||||
{ type: "item", href: "/statistics", label: "Statistiken", icon: ChartNoAxesCombined, active: path.startsWith("/statistics") },
|
||||
{ type: "item", href: "/recurring", label: "Fixe Abrechnung", icon: Repeat, active: path.startsWith("/recurring") },
|
||||
{ type: "item", 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") }]
|
||||
? [{ type: "item" as const, href: "/admin/users", label: "Benutzer", icon: Shield, active: path.startsWith("/admin/users") }]
|
||||
: []),
|
||||
{ type: "separator", key: "before-faq" },
|
||||
{ type: "item", href: "/faq", label: "FAQ", icon: CircleHelp, active: path.startsWith("/faq") },
|
||||
];
|
||||
const runningTimer = timers.find((timer) => timer.phase === "running") ?? null;
|
||||
const runningElapsedSeconds = runningTimer ? Math.floor(activeElapsedMs(runningTimer, tick) / 1000) : 0;
|
||||
@@ -589,16 +605,22 @@ export function App() {
|
||||
<SidebarGroupLabel>Workflows</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{navItems.map((item) => (
|
||||
<SidebarMenuItem key={item.href}>
|
||||
<SidebarMenuButton asChild tooltip={item.label} isActive={item.active}>
|
||||
<a href={item.href} onClick={navHandler(item.href)}>
|
||||
<item.icon />
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
))}
|
||||
{navItems.map((item) =>
|
||||
item.type === "separator" ? (
|
||||
<SidebarMenuItem key={item.key} className="py-1 group-data-[collapsible=icon]:hidden">
|
||||
<Separator />
|
||||
</SidebarMenuItem>
|
||||
) : (
|
||||
<SidebarMenuItem key={item.href}>
|
||||
<SidebarMenuButton asChild tooltip={item.label} isActive={item.active}>
|
||||
<a href={item.href} onClick={navHandler(item.href)}>
|
||||
<item.icon />
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
)}
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { CircleHelp } from "lucide-react";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog";
|
||||
import { getHelpSection } from "../help-content";
|
||||
|
||||
type HelpLinkProps = {
|
||||
anchor: string;
|
||||
@@ -9,11 +11,34 @@ type HelpLinkProps = {
|
||||
};
|
||||
|
||||
export function HelpLink({ anchor, label = "Hilfe öffnen", className }: HelpLinkProps) {
|
||||
const section = getHelpSection(anchor);
|
||||
|
||||
return (
|
||||
<Button asChild variant="ghost" size="icon-sm" className={className} title={label}>
|
||||
<a href={`/faq#${anchor}`} aria-label={label}>
|
||||
<CircleHelp className="size-4" />
|
||||
</a>
|
||||
</Button>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<Button type="button" variant="ghost" size="icon-sm" className={className} title={label} aria-label={label}>
|
||||
<CircleHelp className="size-4" />
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-lg">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{section.title}</DialogTitle>
|
||||
<DialogDescription>{section.description}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
{section.quickItems.map((item) => (
|
||||
<li key={item} className="flex gap-2">
|
||||
<span className="mt-2 size-1.5 shrink-0 rounded-full bg-primary" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<DialogFooter>
|
||||
<Button asChild variant="secondary">
|
||||
<a href={`/faq#${section.id}`}>Ausführliche FAQ öffnen</a>
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,277 @@
|
||||
export type HelpSection = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
quickItems: string[];
|
||||
details: string[];
|
||||
};
|
||||
|
||||
export const helpSections: HelpSection[] = [
|
||||
{
|
||||
id: "grundprinzip",
|
||||
title: "Grundprinzip",
|
||||
description: "TicketTracker ist die Arbeitszeiterfassung neben Ticketsystem und CRM. Die App misst Arbeit, bewertet Sessions und macht Monatsabschlüsse kontrollierbar.",
|
||||
quickItems: [
|
||||
"Sessions sind die getrackten oder nachgetragenen Arbeitszeiten.",
|
||||
"Teamspace-Werte sind Tageswerte pro Ticket, nicht einzelne Sessions.",
|
||||
"Abgeschlossen wird nur der Monat."
|
||||
],
|
||||
details: [
|
||||
"TicketTracker ersetzt nicht das Ticketsystem. Ticketnummern dienen als Bezug zur Arbeit, die eigentliche Dokumentation bleibt im vorhandenen Ticketsystem.",
|
||||
"TicketTracker ersetzt auch nicht das CRM. Die CRM- beziehungsweise Teamspace-Zeit wird als Kontrollwert gepflegt, damit getrackte Zeiten und eingetragene Abrechnung gegenübergestellt werden können.",
|
||||
"Eine Session ist immer einem Benutzer zugeordnet. In den normalen Auswertungen sieht jeder Benutzer nur seine eigenen Sessions.",
|
||||
"Ticketdaten wie Organisation und Art können aus bestehenden Tickets übernommen werden, auch wenn ein anderer Benutzer das Ticket schon bearbeitet hat.",
|
||||
"Tickets werden nicht abgeschlossen. Sie gruppieren Sessions. Relevant für den Abschluss ist nur, ob alle Sessions eines Monats bewertet sind.",
|
||||
"Der Monatsabschluss sperrt den Monat gegen versehentliche Änderungen. Muss doch noch etwas korrigiert werden, kann der Monat wieder geöffnet werden."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "timer",
|
||||
title: "Timer",
|
||||
description: "Timer erfassen laufende Arbeit an Tickets. Es können mehrere Timer vorbereitet sein, aber immer nur einer läuft aktiv.",
|
||||
quickItems: [
|
||||
"Ticketnummer eingeben und Timer starten.",
|
||||
"Aktivieren eines Timers pausiert alle anderen Timer automatisch.",
|
||||
"Beim Beenden stoppt die Zeit sofort; Details werden danach gespeichert."
|
||||
],
|
||||
details: [
|
||||
"Neue Timer werden mit einer Ticketnummer im Format Ticket#XXXXXX gestartet.",
|
||||
"Alle laufenden und pausierten Timer bleiben in der Seitenleiste sichtbar, damit du auf jeder Unterseite sehen kannst, was vorbereitet oder aktiv ist.",
|
||||
"Wenn ein anderer Timer aktiviert wird, pausiert TicketTracker die übrigen Timer automatisch. Dadurch kann nie versehentlich auf zwei Tickets gleichzeitig Zeit laufen.",
|
||||
"Beim Klick auf Beenden wird die Stoppuhr sofort angehalten. Das Speichern der Tätigkeit findet danach statt und verlängert die gemessene Zeit nicht.",
|
||||
"Existiert das Ticket bereits, übernimmt TicketTracker Organisation und Art. Dann muss beim Beenden nur noch die Tätigkeit eingetragen werden.",
|
||||
"Existiert das Ticket noch nicht, müssen Organisation, Tätigkeit und Art angegeben werden.",
|
||||
"Pausen werden von der Gesamtdauer abgezogen. Die gespeicherte Zeit wird auf volle Minuten gerundet.",
|
||||
"Der Schnellstart im Kopfbereich funktioniert auf allen Unterseiten und nutzt dieselbe Timerlogik."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "session-nachtragen",
|
||||
title: "Session Nachtragen",
|
||||
description: "Nachtragen ist für vergessene Zeiten gedacht, wenn kein Timer gelaufen ist.",
|
||||
quickItems: [
|
||||
"Datum, Von, Bis und Tätigkeit eintragen.",
|
||||
"Benutzer tragen eigene Sessions nach; Admins können Benutzer auswählen.",
|
||||
"Tätigkeiten sind mehrzeilig."
|
||||
],
|
||||
details: [
|
||||
"Beim Nachtragen wird aus Datum, Startzeit und Endzeit automatisch die Dauer berechnet.",
|
||||
"Das Ende muss nach dem Beginn liegen. Übernacht-Sessions sind aktuell nicht vorgesehen und sollten als getrennte Einträge gepflegt werden.",
|
||||
"Die Organisation muss aus der synchronisierten Organisationsliste gewählt werden. Freitext ist absichtlich gesperrt.",
|
||||
"Die Tätigkeit ist eine Textarea. Zeilenumbrüche bleiben erhalten und werden in der Ticketdetailansicht wieder ausgegeben.",
|
||||
"Nachgetragene Sessions starten immer offen. Sie müssen später als Abgerechnet oder Nicht abrechenbar bewertet werden.",
|
||||
"Admins können im Adminbereich zusätzlich bestimmen, welchem Benutzer die nachgetragene Session gehört.",
|
||||
"In der Ticketdetailansicht kann eine Session direkt zum geöffneten Ticket nachgetragen werden. Ticketnummer und Stammdaten sind dort schon vorbelegt beziehungsweise aus dem Ticket abgeleitet."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "ticketdaten",
|
||||
title: "Ticketdaten",
|
||||
description: "Ticketdaten sind Stammdaten für ein Ticket und können nachträglich korrigiert werden.",
|
||||
quickItems: [
|
||||
"Ticketnummer, Organisation und Art sind bearbeitbar.",
|
||||
"Organisationen kommen aus der lokalen Zammad-Synchronisation.",
|
||||
"Die Art wiederkehrender Sessions kommt aus der Regel."
|
||||
],
|
||||
details: [
|
||||
"Ticketnummern können korrigiert werden, falls beim Starten oder Nachtragen ein falscher Bezug gewählt wurde.",
|
||||
"Die Organisation wird immer über die Suchliste gewählt. Dadurch sind spätere Auswertungen pro Organisation zuverlässig.",
|
||||
"Änderungen an Organisation und Art werden auf vorhandene manuelle Sessions dieses Tickets übernommen.",
|
||||
"Bei wiederkehrenden Sessions ist die Art nicht direkt an der Session änderbar. Sie wird ausschließlich über die fixe Abrechnungsregel gesteuert.",
|
||||
"Wenn ein Ticket bereits durch einen anderen Benutzer bekannt ist, können dessen Ticketdaten beim Beenden eines Timers übernommen werden. Die Session selbst bleibt trotzdem deinem Benutzer zugeordnet."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "organisationen",
|
||||
title: "Organisationen",
|
||||
description: "Organisationen kommen aus Zammad und bilden die Grundlage für saubere Kunden- und Monatsauswertungen.",
|
||||
quickItems: [
|
||||
"Nur Admins synchronisieren Organisationen.",
|
||||
"Freitext ist danach nicht mehr möglich.",
|
||||
"Gespeichert werden Zammad-ID, Name und Zeitstempel."
|
||||
],
|
||||
details: [
|
||||
"Im Adminbereich werden Zammad-URL und API-Key gespeichert. Der API-Key wird danach nicht mehr im Klartext angezeigt.",
|
||||
"Beim Sync fragt TicketTracker die Organisationen aus Zammad ab und schreibt sie in die lokale Datenbank.",
|
||||
"Neue Organisationen werden angelegt, geänderte Namen werden aktualisiert.",
|
||||
"Organisationen, die in Zammad nicht mehr vorhanden sind, werden aus der lokalen Liste entfernt beziehungsweise von vorhandenen Tickets und Sessions gelöst.",
|
||||
"Alte Sessions oder Tickets mit Freitext-Organisationen müssen manuell korrigiert werden, indem eine Organisation aus der Liste gewählt wird.",
|
||||
"Die bewusst schlanke Organisationstabelle enthält interne ID, Zammad-ID, Name sowie Erstell- und Änderungszeitpunkte."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "auswertung",
|
||||
title: "Auswertung",
|
||||
description: "Die Auswertung ist die Arbeitsliste für Monat und Tag: Tickets öffnen, Sessions prüfen, sortieren und bewerten.",
|
||||
quickItems: [
|
||||
"Monat oder Tag wählen; die Ansicht aktualisiert automatisch.",
|
||||
"Tickets können gefiltert, gruppiert und sortiert werden.",
|
||||
"Die Einstellungen bleiben nach Refresh und Browserneustart erhalten."
|
||||
],
|
||||
details: [
|
||||
"Die Monatsansicht zeigt alle Tickets, die im gewählten Monat mindestens eine Session haben.",
|
||||
"Die Tagesansicht zeigt dieselben Datenlogiken auf einen einzelnen Tag gefiltert. Es gibt keinen Tagesabschluss.",
|
||||
"Die Pfeile neben der Zeitraum-Eingabe springen jeweils einen Monat oder einen Tag vor beziehungsweise zurück.",
|
||||
"Der Gesamtaufwand-Graph zeigt den getrackten Aufwand im Zeitraum. In der Monatsansicht pro Tag, in der Tagesansicht pro Stunde.",
|
||||
"Die Ticketliste kann nach Typ, Organisation oder Status gruppiert werden. Gruppenüberschriften zeigen Anzahl, Zeit und offene Bewertungen.",
|
||||
"Filter und Sortierungen bleiben im Browser gespeichert und können über Zurücksetzen wieder auf Standard gestellt werden.",
|
||||
"Das Copy-Icon neben der Ticketnummer kopiert die Nummer direkt in die Zwischenablage."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "ticketdetail",
|
||||
title: "Ticketdetailansicht",
|
||||
description: "Die Ticketdetailansicht ist die Bearbeitungsfläche für Sessions, Bewertungen und Teamspace-Tageswerte eines Tickets.",
|
||||
quickItems: [
|
||||
"Sessions sind nach Tag gruppiert.",
|
||||
"Teamspace wird pro Ticket und Tag gespeichert.",
|
||||
"Monats- und Tagesansicht verwenden denselben Teamspace-Wert."
|
||||
],
|
||||
details: [
|
||||
"Oben steht die Ticketnummer mit Copy-Icon, damit sie schnell in andere Systeme übernommen werden kann.",
|
||||
"Die Session-Einträge sind nach Kalendertagen gruppiert. Pro Tagesgruppe steht die Summe der getrackten Zeit.",
|
||||
"Das Teamspace-Feld wird als Stundenwert gepflegt, zum Beispiel 1, 1,5 oder 2.25.",
|
||||
"Während der Eingabe wird der Teamspace-Wert nicht sofort gespeichert. Sobald eine Änderung vorliegt, erscheint ein Speichern-Button.",
|
||||
"Ein leeres Teamspace-Feld entfernt den gespeicherten Teamspace-Wert für diesen Ticket-Tag.",
|
||||
"Der Teamspace-Wert hängt am Ticket, Benutzer und Tag. Deshalb ist er in Monats- und Tagesansicht identisch.",
|
||||
"Sessions können bewertet, bearbeitet oder gelöscht werden. Gelöschte letzte Sessions können dazu führen, dass ein leeres Ticket entfernt wird."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "sessions-bewerten",
|
||||
title: "Sessions Bewerten",
|
||||
description: "Bewertungen bestimmen, ob eine Session abgerechnet wurde oder bewusst nicht abrechenbar ist.",
|
||||
quickItems: [
|
||||
"Jede Session braucht genau eine Bewertung.",
|
||||
"Abgerechnet und Nicht abrechenbar können durch erneuten Klick zurückgenommen werden.",
|
||||
"Offene Bewertungen blockieren den Monatsabschluss."
|
||||
],
|
||||
details: [
|
||||
"Abgerechnet bedeutet: Die Session wurde für die Abrechnung beziehungsweise im CRM berücksichtigt.",
|
||||
"Nicht abrechenbar bedeutet: Die Session gehört zum Arbeitsnachweis, wird aber nicht abgerechnet.",
|
||||
"Ein erneuter Klick auf den aktiven Bewertungsbutton entfernt die Bewertung und macht die Session wieder offen.",
|
||||
"Nicht abrechenbare Sessions zählen als bewertet. Sie blockieren den Monatsabschluss nicht.",
|
||||
"Wenn ein Monat abgeschlossen ist, sind Bewertungen gesperrt. Öffne den Monat zuerst wieder, wenn du etwas korrigieren musst.",
|
||||
"Die offenen Bewertungen findest du gebündelt im Monatsabschluss."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "sessions-bearbeiten",
|
||||
title: "Sessions Bearbeiten und Löschen",
|
||||
description: "Sessions können korrigiert, gelöscht und bei wiederkehrenden Einträgen als Ausnahme behandelt werden.",
|
||||
quickItems: [
|
||||
"Bearbeiten läuft über das Stift-Icon.",
|
||||
"Löschen läuft über das Papierkorb-Icon.",
|
||||
"Wiederkehrende gelöschte Sessions werden nicht neu erzeugt."
|
||||
],
|
||||
details: [
|
||||
"Beim Bearbeiten sind Datum, Von-Uhrzeit, Bis-Uhrzeit, Organisation und Tätigkeit änderbar.",
|
||||
"Die Art ist bei manuellen Sessions änderbar. Bei wiederkehrenden Sessions ist sie gesperrt und kommt aus der Regel.",
|
||||
"Die Dauer wird aus Von und Bis neu berechnet und wieder auf Minuten gerundet.",
|
||||
"Beim Löschen wird geprüft, ob dadurch ein Ticket für dich oder komplett leer wird. Leere Tickets werden entfernt.",
|
||||
"Wenn die letzte Session eines Tickets gelöscht wird, springt die Ansicht zurück zur Auswertung.",
|
||||
"Wiederkehrend erzeugte Sessions werden beim Löschen als Ausnahme gespeichert. Dadurch erscheinen sie beim nächsten Öffnen des Monats nicht wieder automatisch.",
|
||||
"Wenn eine wiederkehrende Leistung nur an einem anderen Tag erledigt wurde, wird die erzeugte Session bearbeitet beziehungsweise verschoben."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "monatsabschluss",
|
||||
title: "Monatsabschluss",
|
||||
description: "Der Monatsabschluss ist die Kontrollseite für offene Bewertungen und Teamspace-Punkte vor dem Abschluss.",
|
||||
quickItems: [
|
||||
"Bewertungen sind Pflicht für den Abschluss.",
|
||||
"Tage ohne Teamspace-Wert können zur Kenntnis genommen werden.",
|
||||
"Der Monat kann wieder geöffnet werden."
|
||||
],
|
||||
details: [
|
||||
"Ein Monat kann abgeschlossen werden, sobald alle Sessions dieses Monats bewertet sind.",
|
||||
"Tage ohne Teamspace-Wert blockieren den Abschluss nicht automatisch. Sie sind eine Prüfliste für CRM-Abgleich und Nacharbeit.",
|
||||
"Wenn eine Session bewusst nicht in Teamspace eingetragen wird, kann der fehlende Tageswert zur Kenntnis genommen werden.",
|
||||
"Zur Kenntnis genommene Einträge können über die Checkbox Zur Kenntnis anzeigen wieder eingeblendet werden.",
|
||||
"Die Kenntnisnahme kann pro Eintrag über Zurücknehmen rückgängig gemacht werden.",
|
||||
"Die Einstellung, ob zur Kenntnis genommene Einträge angezeigt werden, bleibt im Browser gespeichert.",
|
||||
"Offene Bewertungen werden nach Ticket gruppiert. Ein Klick auf Ticket öffnen führt zur Bewertung im Ticket.",
|
||||
"Es gibt keinen Tagesabschluss. Tagesansichten dienen nur der Kontrolle."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "fixe-abrechnungen",
|
||||
title: "Fixe Abrechnungen",
|
||||
description: "Fixe Abrechnungen erzeugen wiederkehrende Sessions für planbare regelmäßige Leistungen.",
|
||||
quickItems: [
|
||||
"Jeder Benutzer verwaltet eigene fixe Abrechnungen.",
|
||||
"Ticket ist optional; ohne Ticket entsteht Fix#ID.",
|
||||
"Feiertage in Bayern werden übersprungen."
|
||||
],
|
||||
details: [
|
||||
"Fixe Abrechnungen sind für regelmäßige Leistungen gedacht, zum Beispiel jeden Donnerstag vier Stunden oder mehrere Wochentage mit unterschiedlichen Dauern.",
|
||||
"Das Ticketfeld darf leer bleiben. Dann gruppiert TicketTracker die erzeugten Sessions automatisch unter Fix#ID.",
|
||||
"Die Organisation, Art und Tätigkeit kommen aus der Regel und werden auf erzeugte Sessions angewendet.",
|
||||
"Beim Muster Wochentage werden einzelne Slots mit Wochentag, Startzeit und Dauer gepflegt.",
|
||||
"Beim Muster Alle X Wochen zählt das Gültig-von-Datum als Startpunkt. Das Intervall läuft wochenweise.",
|
||||
"Gültig bis ist optional. Wird es später gesetzt oder geändert, werden erzeugte Sessions außerhalb der Gültigkeit entfernt.",
|
||||
"Bayerische Feiertage werden bei der automatischen Anlage ausgelassen.",
|
||||
"Einzelne erzeugte Sessions können wegen Urlaub, Krankheit oder Feiertagsverschiebung gelöscht oder verschoben werden."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "statistiken",
|
||||
title: "Statistiken",
|
||||
description: "Statistiken vergleichen getrackte Arbeit, bewertete Sessions und Teamspace-Werte.",
|
||||
quickItems: [
|
||||
"Sessions und Teamspace haben getrennte Farben.",
|
||||
"Positive oder gleiche Differenz ist grün.",
|
||||
"Negative Differenz ist rot."
|
||||
],
|
||||
details: [
|
||||
"Die Statistikseite betrachtet immer einen Monat.",
|
||||
"Getrackt zeigt die Summe aller Session-Zeiten und die Anzahl der Sessions und Tickets.",
|
||||
"Teamspace zeigt, wie viel Zeit für dieselben Ticket-Tage im CRM eingetragen wurde.",
|
||||
"Eine positive Differenz bedeutet: In Teamspace steht mehr Zeit als in den Sessions getrackt wurde.",
|
||||
"Eine negative Differenz bedeutet: In Teamspace steht weniger Zeit als getrackt wurde.",
|
||||
"Bewertet umfasst sowohl Abgerechnet als auch Nicht abrechenbar. Offen sind nur Sessions ohne Bewertung.",
|
||||
"Der Monatsverlauf zeigt getrackte Session-Zeit als Balken und Teamspace als Linie.",
|
||||
"Organisationen, Typen und Ticket-Hotspots helfen dabei, Auffälligkeiten im Monat schnell zu finden."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "profil",
|
||||
title: "Profil",
|
||||
description: "Im Profil pflegt jeder Benutzer die eigenen Zugangsdaten.",
|
||||
quickItems: [
|
||||
"Benutzername und voller Name sind änderbar.",
|
||||
"Passwortänderung benötigt das aktuelle Passwort.",
|
||||
"Der volle Name wird in der Oberfläche angezeigt."
|
||||
],
|
||||
details: [
|
||||
"Der Benutzername wird beim Login verwendet und muss eindeutig sein.",
|
||||
"Der volle Name erscheint in der App, zum Beispiel im Kopfbereich oder im Adminbereich.",
|
||||
"Wenn du dein Passwort ändern willst, musst du das aktuelle Passwort sowie das neue Passwort zweimal eingeben.",
|
||||
"Bleiben die Passwortfelder leer, werden nur Benutzername und voller Name gespeichert.",
|
||||
"Admins können Benutzer ebenfalls verwalten, aber jeder Benutzer kann die eigenen Daten jederzeit selbst pflegen."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "admin",
|
||||
title: "Adminbereich",
|
||||
description: "Der Adminbereich verwaltet Benutzer, Session-Zuordnung, manuelle Admin-Nachträge und Zammad-Sync.",
|
||||
quickItems: [
|
||||
"Admins legen Benutzer an und bearbeiten Rollen.",
|
||||
"Sessions können anderen Benutzern zugewiesen werden.",
|
||||
"Zammad-Organisationen werden über Data-Sync aktualisiert."
|
||||
],
|
||||
details: [
|
||||
"Neue Benutzer bekommen Benutzername, vollen Namen, Passwort, Rolle und Aktivstatus.",
|
||||
"Admins können bestehende Benutzer bearbeiten, Passwörter setzen und Benutzer deaktivieren.",
|
||||
"Der eigene Admin-Zugang kann nicht versehentlich deaktiviert oder zur normalen User-Rolle herabgestuft werden.",
|
||||
"In der Session-Zuordnung sehen Admins die letzten Sessions inklusive Besitzer und können einzelne Sessions umverteilen.",
|
||||
"Beim Umverteilen werden die betroffenen Monatsabschlüsse für alten und neuen Besitzer wieder geöffnet, damit Bewertungen und Auswertungen konsistent bleiben.",
|
||||
"Admins können Sessions für andere Benutzer nachtragen, wenn Zeiten vergessen wurden.",
|
||||
"Der Data-Sync speichert Zammad-Zugangsdaten und synchronisiert Organisationen in die lokale Datenbank."
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export function getHelpSection(anchor: string) {
|
||||
return helpSections.find((section) => section.id === anchor) ?? helpSections[0];
|
||||
}
|
||||
+30
-172
@@ -3,163 +3,7 @@ import { ArrowUp, BookOpenCheck } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
|
||||
type FaqSection = {
|
||||
id: string;
|
||||
title: string;
|
||||
description: string;
|
||||
items: string[];
|
||||
};
|
||||
|
||||
const faqSections: FaqSection[] = [
|
||||
{
|
||||
id: "grundprinzip",
|
||||
title: "Grundprinzip",
|
||||
description: "TicketTracker ersetzt weder Ticketsystem noch CRM. Die App misst Zeiten, bewertet Sessions und bereitet den Monatsabschluss vor.",
|
||||
items: [
|
||||
"Session-Zeit ist die gemessene oder manuell erfasste Arbeitszeit einer Tätigkeit.",
|
||||
"Teamspace-Zeit ist der Tageswert, der für ein Ticket wirklich im CRM eingetragen wurde.",
|
||||
"Abgeschlossen wird nur ein Monat. Tickets werden nicht geschlossen, sondern nur über ihre Sessions bewertet."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "timer",
|
||||
title: "Timer",
|
||||
description: "Timer sind für laufende Arbeit an Tickets gedacht.",
|
||||
items: [
|
||||
"Ticketnummer im Format Ticket#XXXXXX eingeben und Timer starten.",
|
||||
"Es können mehrere Timer vorbereitet sein, aber immer nur einer läuft aktiv.",
|
||||
"Wird ein Timer aktiviert, werden alle anderen automatisch pausiert.",
|
||||
"Beim Klick auf Beenden stoppt die Zeit sofort. Danach werden Tätigkeit und gegebenenfalls Organisation und Art gespeichert."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "session-nachtragen",
|
||||
title: "Session Nachtragen",
|
||||
description: "Vergessene Zeiten können nachträglich als Session angelegt werden.",
|
||||
items: [
|
||||
"Benutzer tragen eigene Sessions nach. Admins können Sessions auch anderen Benutzern zuweisen.",
|
||||
"Pflichtangaben sind Ticket, Organisation, Art, Datum, Von, Bis und Tätigkeit.",
|
||||
"Das Tätigkeitsfeld ist mehrzeilig. Zeilenumbrüche bleiben in den Auswertungen erhalten."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "ticketdaten",
|
||||
title: "Ticketdaten",
|
||||
description: "Ticketdaten können in der Ticketdetailansicht korrigiert werden.",
|
||||
items: [
|
||||
"Bearbeitbar sind Ticketnummer, Organisation und Art.",
|
||||
"Änderungen an Organisation und Art werden auf vorhandene manuelle Sessions des Tickets übernommen.",
|
||||
"Bei wiederkehrenden Sessions kommt die Art aus der Regel der fixen Abrechnung."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "organisationen",
|
||||
title: "Organisationen",
|
||||
description: "Organisationen werden aus Zammad synchronisiert und danach aus der lokalen Liste gewählt.",
|
||||
items: [
|
||||
"Freitext ist nicht mehr erlaubt, damit Auswertungen je Organisation sauber möglich sind.",
|
||||
"Alte Freitexte müssen manuell korrigiert werden.",
|
||||
"Der Zammad-Sync speichert nur interne ID, Zammad-ID, Name und Zeitstempel."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "auswertung",
|
||||
title: "Auswertung",
|
||||
description: "Die Auswertung zeigt Monats- oder Tageswerte und listet Tickets im gewählten Zeitraum.",
|
||||
items: [
|
||||
"Mit den Pfeilen neben Monat oder Datum wechselst du den Zeitraum. Die Daten aktualisieren sich automatisch.",
|
||||
"Tickets können gefiltert, gruppiert und sortiert werden. Diese Einstellungen bleiben gespeichert.",
|
||||
"Der Graph zeigt den Gesamtaufwand des gewählten Zeitraums."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "ticketdetail",
|
||||
title: "Ticketdetailansicht",
|
||||
description: "Hier werden alle Sessions eines Tickets im gewählten Monat oder Tag bearbeitet.",
|
||||
items: [
|
||||
"Sessions sind nach Tagen gruppiert.",
|
||||
"Pro Tag wird die getrackte Zeit angezeigt.",
|
||||
"Das Teamspace-Feld speichert die Stunden, die für diesen Ticket-Tag wirklich im CRM eingetragen wurden.",
|
||||
"Der Teamspace-Wert ist in Monats- und Tagesansicht derselbe."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "sessions-bewerten",
|
||||
title: "Sessions Bewerten",
|
||||
description: "Jede Session braucht eine Bewertung für die Abrechnung.",
|
||||
items: [
|
||||
"Möglich sind Abgerechnet und Nicht abrechenbar.",
|
||||
"Ein erneuter Klick auf die gewählte Bewertung nimmt sie zurück.",
|
||||
"Bewertungen können bei abgeschlossenem Monat erst geändert werden, nachdem der Monat wieder geöffnet wurde."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "sessions-bearbeiten",
|
||||
title: "Sessions Bearbeiten und Löschen",
|
||||
description: "Sessions können korrigiert oder gelöscht werden.",
|
||||
items: [
|
||||
"Bearbeitbar sind Datum, Von, Bis, Organisation, Tätigkeit und Art.",
|
||||
"Bei wiederkehrenden Sessions ist die Art gesperrt und wird über die Regel geändert.",
|
||||
"Wird die letzte Session eines Tickets gelöscht, wird das leere Ticket entfernt.",
|
||||
"Gelöschte wiederkehrende Sessions werden als Ausnahme gespeichert und nicht automatisch neu erzeugt."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "monatsabschluss",
|
||||
title: "Monatsabschluss",
|
||||
description: "Der Monatsabschluss bündelt offene Bewertungen und Teamspace-Prüfpunkte.",
|
||||
items: [
|
||||
"Ein Monat kann abgeschlossen werden, sobald alle Sessions des Monats bewertet sind.",
|
||||
"Tage ohne Teamspace-Wert können zur Kenntnis genommen werden, wenn sie bewusst nicht ins CRM gehören.",
|
||||
"Zur Kenntnis genommene Teamspace-Punkte können eingeblendet und zurückgenommen werden.",
|
||||
"Einen Tagesabschluss gibt es nicht."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "fixe-abrechnungen",
|
||||
title: "Fixe Abrechnungen",
|
||||
description: "Fixe Abrechnungen erzeugen wiederkehrende Sessions für den eigenen Account.",
|
||||
items: [
|
||||
"Ticket ist optional. Ohne Ticket wird automatisch Fix#ID verwendet.",
|
||||
"Muster können Wochentage oder alle X Wochen sein.",
|
||||
"Gültig bis ist optional. Wird es später gesetzt, werden nicht mehr gültige erzeugte Sessions entfernt.",
|
||||
"Bayerische Feiertage werden übersprungen."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "statistiken",
|
||||
title: "Statistiken",
|
||||
description: "Die Statistikseite vergleicht getrackte Sessions mit Teamspace-Zeiten.",
|
||||
items: [
|
||||
"Sessions und Teamspace haben eigene Farben.",
|
||||
"Eine positive oder ausgeglichene Differenz ist grün.",
|
||||
"Eine negative Differenz ist rot und bedeutet: in Teamspace ist weniger eingetragen als getrackt.",
|
||||
"Organisationen, Typen und Ticket-Hotspots helfen bei der Monatskontrolle."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "profil",
|
||||
title: "Profil",
|
||||
description: "Jeder Benutzer kann eigene Accountdaten pflegen.",
|
||||
items: [
|
||||
"Benutzername und voller Name können geändert werden.",
|
||||
"Passwortänderungen benötigen das aktuelle Passwort.",
|
||||
"Der volle Name wird in der App angezeigt."
|
||||
]
|
||||
},
|
||||
{
|
||||
id: "admin",
|
||||
title: "Adminbereich",
|
||||
description: "Admins verwalten Benutzer, Sessions und Zammad-Organisationen.",
|
||||
items: [
|
||||
"Admins legen Benutzer an und können deren Daten bearbeiten.",
|
||||
"Sessions können zwischen Benutzern umverteilt werden.",
|
||||
"Admins können Sessions für andere Benutzer nachtragen.",
|
||||
"Der Zammad-Zugang wird gespeichert und der Organisationssync aktualisiert die lokale Organisationsliste."
|
||||
]
|
||||
}
|
||||
];
|
||||
import { helpSections } from "../help-content";
|
||||
|
||||
export function FaqPage() {
|
||||
return (
|
||||
@@ -167,9 +11,9 @@ export function FaqPage() {
|
||||
<div className="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl font-semibold tracking-normal">FAQ und Anleitung</h2>
|
||||
<p className="text-sm text-muted-foreground">Kurzanleitung für Timer, Auswertung, Teamspace, fixe Abrechnungen und Adminfunktionen.</p>
|
||||
<p className="text-sm text-muted-foreground">Ausführliche Anleitung für Timer, Auswertung, Teamspace, fixe Abrechnungen, Monatsabschluss und Adminfunktionen.</p>
|
||||
</div>
|
||||
<Badge variant="outline">{faqSections.length} Abschnitt(e)</Badge>
|
||||
<Badge variant="outline">{helpSections.length} Abschnitt(e)</Badge>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
@@ -178,10 +22,10 @@ export function FaqPage() {
|
||||
<BookOpenCheck className="size-5 text-muted-foreground" />
|
||||
<CardTitle>Schnellnavigation</CardTitle>
|
||||
</div>
|
||||
<CardDescription>Die Fragezeichen in der App springen direkt in diese Abschnitte.</CardDescription>
|
||||
<CardDescription>Die Fragezeichen in der App öffnen eine Kurzfassung. Von dort gelangst du direkt zum passenden ausführlichen Abschnitt.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-wrap gap-2 px-4 pb-4">
|
||||
{faqSections.map((section) => (
|
||||
{helpSections.map((section) => (
|
||||
<Button key={section.id} asChild variant="secondary" size="sm">
|
||||
<a href={`#${section.id}`}>{section.title}</a>
|
||||
</Button>
|
||||
@@ -190,22 +34,36 @@ export function FaqPage() {
|
||||
</Card>
|
||||
|
||||
<div className="grid gap-3 xl:grid-cols-2">
|
||||
{faqSections.map((section) => (
|
||||
{helpSections.map((section) => (
|
||||
<Card key={section.id} id={section.id} className="scroll-mt-16">
|
||||
<CardHeader className="p-4">
|
||||
<CardTitle>{section.title}</CardTitle>
|
||||
<CardDescription>{section.description}</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
{section.items.map((item) => (
|
||||
<li key={item} className="flex gap-2">
|
||||
<span className="mt-2 size-1.5 shrink-0 rounded-full bg-primary" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="mt-4 flex justify-end">
|
||||
<CardContent className="space-y-4 px-4 pb-4">
|
||||
<div className="rounded-md border bg-muted/20 p-3">
|
||||
<p className="mb-2 text-sm font-medium">Kurzfassung</p>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
{section.quickItems.map((item) => (
|
||||
<li key={item} className="flex gap-2">
|
||||
<span className="mt-2 size-1.5 shrink-0 rounded-full bg-primary" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p className="mb-2 text-sm font-medium">Details</p>
|
||||
<ul className="space-y-2 text-sm text-muted-foreground">
|
||||
{section.details.map((item) => (
|
||||
<li key={item} className="flex gap-2">
|
||||
<span className="mt-2 size-1.5 shrink-0 rounded-full bg-primary/70" />
|
||||
<span>{item}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<a href="#top">
|
||||
<ArrowUp className="size-4" />
|
||||
|
||||
@@ -92,7 +92,7 @@ function groupOpenSessions(sessions: StatisticsOpenSession[]) {
|
||||
|
||||
function OpenTicketItem({ group, month, onNavigate }: { group: OpenTicketGroup; month: string; onNavigate: (to: string) => void }) {
|
||||
return (
|
||||
<div className="flex min-h-32 flex-col justify-between gap-3 rounded-md border bg-background p-3">
|
||||
<div className="flex min-h-36 flex-col justify-between gap-3 rounded-md border bg-background p-3">
|
||||
<div className="min-w-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="font-medium">{group.ticketNumber}</span>
|
||||
@@ -126,7 +126,7 @@ function CrmDayItem({
|
||||
const isAcknowledged = Boolean(item.acknowledged);
|
||||
|
||||
return (
|
||||
<div className={`flex min-h-32 flex-col justify-between gap-3 rounded-md border bg-background p-3 ${isAcknowledged ? "opacity-75" : ""}`}>
|
||||
<div className={`flex min-h-36 flex-col justify-between gap-3 rounded-md border bg-background p-3 ${isAcknowledged ? "opacity-75" : ""}`}>
|
||||
<div className="min-w-0">
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="font-medium">{item.ticket_number}</span>
|
||||
@@ -363,17 +363,27 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
|
||||
|
||||
<Card>
|
||||
<CardHeader className="p-4">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<CardTitle>Aufräumen</CardTitle>
|
||||
<CardDescription>Alles, was für diesen Monat noch Aufmerksamkeit braucht.</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="monatsabschluss" label="Hilfe zu offenen Bewertungen und Teamspace-Prüfpunkten" />
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<div className="inline-flex h-8 items-center gap-2 rounded-md border bg-background px-3 text-sm">
|
||||
<Checkbox
|
||||
checked={showAcknowledgedMissingCrm}
|
||||
aria-label="Zur Kenntnis genommene Teamspace-Tage anzeigen"
|
||||
onCheckedChange={(checked) => setShowAcknowledgedMissingCrm(checked === true)}
|
||||
/>
|
||||
<span>Zur Kenntnis anzeigen ({acknowledgedMissingCrmDays.length})</span>
|
||||
</div>
|
||||
<HelpLink anchor="monatsabschluss" label="Hilfe zu offenen Bewertungen und Teamspace-Prüfpunkten" />
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4 px-4 pb-4 xl:grid-cols-2">
|
||||
<section className="space-y-2">
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<div className="flex min-h-8 items-center gap-2 text-sm font-medium">
|
||||
<FileWarning className="size-4 text-muted-foreground" />
|
||||
Offene Bewertungen
|
||||
</div>
|
||||
@@ -384,19 +394,9 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
|
||||
</section>
|
||||
|
||||
<section className="space-y-2">
|
||||
<div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex items-center gap-2 text-sm font-medium">
|
||||
<Sparkles className="size-4 text-muted-foreground" />
|
||||
Tage ohne Teamspace-Wert
|
||||
</div>
|
||||
<div className="inline-flex items-center gap-2 rounded-md border bg-background px-3 py-2 text-sm">
|
||||
<Checkbox
|
||||
checked={showAcknowledgedMissingCrm}
|
||||
aria-label="Zur Kenntnis genommene Teamspace-Tage anzeigen"
|
||||
onCheckedChange={(checked) => setShowAcknowledgedMissingCrm(checked === true)}
|
||||
/>
|
||||
<span>Zur Kenntnis anzeigen ({acknowledgedMissingCrmDays.length})</span>
|
||||
</div>
|
||||
<div className="flex min-h-8 items-center gap-2 text-sm font-medium">
|
||||
<Sparkles className="size-4 text-muted-foreground" />
|
||||
Tage ohne Teamspace-Wert
|
||||
</div>
|
||||
{visibleMissingCrmDays.map((item) => (
|
||||
<CrmDayItem
|
||||
|
||||
Reference in New Issue
Block a user