Add FAQ guide and contextual help links
This commit is contained in:
@@ -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 }) {
|
||||
|
||||
<Card>
|
||||
<CardHeader className="p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<DatabaseZap className="size-5 text-muted-foreground" />
|
||||
<CardTitle>Data-Sync</CardTitle>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<DatabaseZap className="size-5 text-muted-foreground" />
|
||||
<CardTitle>Data-Sync</CardTitle>
|
||||
</div>
|
||||
<HelpLink anchor="organisationen" label="Hilfe zum Zammad-Sync" />
|
||||
</div>
|
||||
<CardDescription>Zammad-Zugang speichern und Organisationen in die lokale TicketTracker-Datenbank übernehmen.</CardDescription>
|
||||
</CardHeader>
|
||||
@@ -378,8 +382,13 @@ export function AdminUsersPage({ currentUser }: { currentUser: AuthUser }) {
|
||||
|
||||
<Card>
|
||||
<CardHeader className="p-4">
|
||||
<CardTitle>Neuer Benutzer</CardTitle>
|
||||
<CardDescription>Neue Benutzer sehen später nur ihre eigenen Sessions und Abschlüsse.</CardDescription>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<CardTitle>Neuer Benutzer</CardTitle>
|
||||
<CardDescription>Neue Benutzer sehen später nur ihre eigenen Sessions und Abschlüsse.</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="admin" label="Hilfe zur Benutzerverwaltung" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<form className="grid gap-3 lg:grid-cols-[160px_minmax(180px,1fr)_160px_130px_auto_auto] lg:items-end" onSubmit={createUser}>
|
||||
@@ -421,9 +430,12 @@ export function AdminUsersPage({ currentUser }: { currentUser: AuthUser }) {
|
||||
|
||||
<Card>
|
||||
<CardHeader className="p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<CalendarPlus className="size-5 text-muted-foreground" />
|
||||
<CardTitle>Session nachtragen</CardTitle>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<CalendarPlus className="size-5 text-muted-foreground" />
|
||||
<CardTitle>Session nachtragen</CardTitle>
|
||||
</div>
|
||||
<HelpLink anchor="session-nachtragen" label="Hilfe zum Nachtragen für Benutzer" />
|
||||
</div>
|
||||
<CardDescription>Vergessene Zeiten manuell erfassen und direkt einem Benutzer zuweisen.</CardDescription>
|
||||
</CardHeader>
|
||||
@@ -541,7 +553,10 @@ export function AdminUsersPage({ currentUser }: { currentUser: AuthUser }) {
|
||||
<CardTitle>Vorhandene Benutzer</CardTitle>
|
||||
<CardDescription>{loading ? "Lädt..." : `${users.length} Account(s)`}</CardDescription>
|
||||
</div>
|
||||
<Badge variant="outline">{currentUser.display_name}</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline">{currentUser.display_name}</Badge>
|
||||
<HelpLink anchor="admin" label="Hilfe zu vorhandenen Benutzern" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<div className="hidden xl:block">
|
||||
@@ -658,6 +673,7 @@ export function AdminUsersPage({ currentUser }: { currentUser: AuthUser }) {
|
||||
<CardTitle>Session-Zuordnung</CardTitle>
|
||||
<CardDescription>{loading ? "Lädt..." : `${sessions.length} letzte Session(s), inklusive Besitzer.`}</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="admin" label="Hilfe zur Session-Zuordnung" />
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<div className="hidden xl:block">
|
||||
|
||||
@@ -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."}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Badge variant="outline">{formatMinutes(totals?.minutes ?? 0)}</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline">{formatMinutes(totals?.minutes ?? 0)}</Badge>
|
||||
<HelpLink anchor="auswertung" label="Hilfe zur Auswertung" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<div className="h-44 w-full overflow-hidden rounded-md border bg-background">
|
||||
@@ -605,6 +609,7 @@ export function AnalysisPage({ onNavigate }: AnalysisPageProps) {
|
||||
{periodType === "month" ? "Sessions prüfen, bewerten und bei Bedarf Tickets öffnen." : "Tagesansicht ohne eigenen Abschluss."}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="auswertung" label="Hilfe zu Tickets im Zeitraum" />
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<div className="mb-4 rounded-md border bg-muted/20 p-3">
|
||||
@@ -618,6 +623,7 @@ export function AnalysisPage({ onNavigate }: AnalysisPageProps) {
|
||||
</p>
|
||||
</div>
|
||||
{activeFilterCount > 0 ? <Badge variant="outline">{activeFilterCount} aktiv</Badge> : null}
|
||||
<HelpLink anchor="auswertung" label="Hilfe zu Filtern und Sortierung" />
|
||||
</div>
|
||||
<Button
|
||||
type="button"
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
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."
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export function FaqPage() {
|
||||
return (
|
||||
<div id="top" className="space-y-5">
|
||||
<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>
|
||||
</div>
|
||||
<Badge variant="outline">{faqSections.length} Abschnitt(e)</Badge>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpenCheck className="size-5 text-muted-foreground" />
|
||||
<CardTitle>Schnellnavigation</CardTitle>
|
||||
</div>
|
||||
<CardDescription>Die Fragezeichen in der App springen direkt in diese Abschnitte.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-wrap gap-2 px-4 pb-4">
|
||||
{faqSections.map((section) => (
|
||||
<Button key={section.id} asChild variant="secondary" size="sm">
|
||||
<a href={`#${section.id}`}>{section.title}</a>
|
||||
</Button>
|
||||
))}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div className="grid gap-3 xl:grid-cols-2">
|
||||
{faqSections.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">
|
||||
<Button asChild variant="ghost" size="sm">
|
||||
<a href="#top">
|
||||
<ArrowUp className="size-4" />
|
||||
Nach oben
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
<Card>
|
||||
<CardHeader className="p-4">
|
||||
<CardTitle>Checkliste</CardTitle>
|
||||
<CardDescription>Bewertungen sind Pflicht. Teamspace-Punkte helfen beim sauberen CRM-Abgleich.</CardDescription>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<CardTitle>Checkliste</CardTitle>
|
||||
<CardDescription>Bewertungen sind Pflicht. Teamspace-Punkte helfen beim sauberen CRM-Abgleich.</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="monatsabschluss" label="Hilfe zum Monatsabschluss" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-2 px-4 pb-4 md:grid-cols-2">
|
||||
{checklist.map((item) => (
|
||||
@@ -357,8 +363,13 @@ export function MonthlyClosePage({ onNavigate }: MonthlyClosePageProps) {
|
||||
|
||||
<Card>
|
||||
<CardHeader className="p-4">
|
||||
<CardTitle>Aufräumen</CardTitle>
|
||||
<CardDescription>Alles, was für diesen Monat noch Aufmerksamkeit braucht.</CardDescription>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<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>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4 px-4 pb-4 xl:grid-cols-2">
|
||||
<section className="space-y-2">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
<Card>
|
||||
<CardHeader className="p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<UserCircle className="size-5 text-muted-foreground" />
|
||||
<CardTitle>Accountdaten</CardTitle>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<UserCircle className="size-5 text-muted-foreground" />
|
||||
<CardTitle>Accountdaten</CardTitle>
|
||||
</div>
|
||||
<HelpLink anchor="profil" label="Hilfe zum Profil" />
|
||||
</div>
|
||||
<CardDescription>Der Benutzername wird beim Login verwendet. Dein voller Name wird in der App angezeigt.</CardDescription>
|
||||
</CardHeader>
|
||||
|
||||
@@ -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() {
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<CardHeader className="border-b bg-muted/30 p-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Repeat className="size-5 text-muted-foreground" />
|
||||
<CardTitle>{editingBillingId ? "Regel bearbeiten" : "Neue Regel"}</CardTitle>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Repeat className="size-5 text-muted-foreground" />
|
||||
<CardTitle>{editingBillingId ? "Regel bearbeiten" : "Neue Regel"}</CardTitle>
|
||||
</div>
|
||||
<HelpLink anchor="fixe-abrechnungen" label="Hilfe zu fixen Abrechnungen" />
|
||||
</div>
|
||||
<CardDescription>Ticket ist optional. Leer bedeutet automatische Gruppierung als Fix#ID.</CardDescription>
|
||||
</CardHeader>
|
||||
@@ -447,8 +451,13 @@ export function RecurringBillingsPage() {
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<CardHeader className="border-b bg-muted/30 p-4">
|
||||
<CardTitle>Regeln</CardTitle>
|
||||
<CardDescription>Erzeugte Einträge können in der Auswertung verschoben oder gelöscht werden.</CardDescription>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<CardTitle>Regeln</CardTitle>
|
||||
<CardDescription>Erzeugte Einträge können in der Auswertung verschoben oder gelöscht werden.</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="fixe-abrechnungen" label="Hilfe zu erzeugten fixen Sessions" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 p-4">
|
||||
{recurringBillings.map((billing) => (
|
||||
|
||||
@@ -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) {
|
||||
<CardTitle>Monatsverlauf und Abrechnungsqualität</CardTitle>
|
||||
<CardDescription>Getrackter Aufwand pro Tag, Teamspace-Linie und Bewertungsstand in einem Überblick.</CardDescription>
|
||||
</div>
|
||||
<Badge variant="outline">{crmCoverage}% Teamspace-Abdeckung</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline">{crmCoverage}% Teamspace-Abdeckung</Badge>
|
||||
<HelpLink anchor="statistiken" label="Hilfe zu Statistiken" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-4 px-4 pb-4 xl:grid-cols-[1.55fr_1fr]">
|
||||
<div className="space-y-3">
|
||||
@@ -613,9 +617,12 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) {
|
||||
<CardTitle>Statistik pro Organisation</CardTitle>
|
||||
<CardDescription>Kundenaufwand inklusive Teamspace-Differenz und Anteil am getrackten Monatsaufwand.</CardDescription>
|
||||
</div>
|
||||
<Badge variant="outline">
|
||||
{organizationRangeStart}-{organizationRangeEnd} von {stats?.organizations.length ?? 0}
|
||||
</Badge>
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant="outline">
|
||||
{organizationRangeStart}-{organizationRangeEnd} von {stats?.organizations.length ?? 0}
|
||||
</Badge>
|
||||
<HelpLink anchor="statistiken" label="Hilfe zur Organisationsstatistik" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<div className="hidden md:block">
|
||||
@@ -702,8 +709,13 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) {
|
||||
|
||||
<Card>
|
||||
<CardHeader className="p-4">
|
||||
<CardTitle>Typen</CardTitle>
|
||||
<CardDescription>Support und Consulting im direkten Vergleich.</CardDescription>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<CardTitle>Typen</CardTitle>
|
||||
<CardDescription>Support und Consulting im direkten Vergleich.</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="statistiken" label="Hilfe zu Typenstatistiken" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 px-4 pb-4">
|
||||
{(stats?.workTypes ?? []).map((group) => (
|
||||
@@ -738,10 +750,13 @@ export function StatisticsPage({ onNavigate }: StatisticsPageProps) {
|
||||
<CardTitle>Ticket-Hotspots</CardTitle>
|
||||
<CardDescription>Die größten Zeitblöcke im Monat.</CardDescription>
|
||||
</div>
|
||||
<Button variant="secondary" size="sm" onClick={() => onNavigate("/analysis")}>
|
||||
Auswertung öffnen
|
||||
<ExternalLink className="size-4" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<HelpLink anchor="statistiken" label="Hilfe zu Ticket-Hotspots" />
|
||||
<Button variant="secondary" size="sm" onClick={() => onNavigate("/analysis")}>
|
||||
Auswertung öffnen
|
||||
<ExternalLink className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2 px-4 pb-4">
|
||||
{(stats?.tickets ?? []).map((ticket) => (
|
||||
|
||||
@@ -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
|
||||
|
||||
<Card>
|
||||
<CardHeader className="p-4">
|
||||
<CardTitle>Ticketdaten</CardTitle>
|
||||
<CardDescription>Änderungen an Organisation und Art werden auf alle vorhandenen Sessions dieses Tickets übernommen.</CardDescription>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<CardTitle>Ticketdaten</CardTitle>
|
||||
<CardDescription>Änderungen an Organisation und Art werden auf alle vorhandenen Sessions dieses Tickets übernommen.</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="ticketdaten" label="Hilfe zu Ticketdaten" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<form className="grid gap-3 lg:grid-cols-[180px_minmax(220px,1fr)_180px_auto] lg:items-end" onSubmit={saveTicketData}>
|
||||
@@ -544,6 +550,7 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate }: T
|
||||
<CardDescription>Wähle für jede Session genau eine Bewertung aus oder lösche falsche Einträge.</CardDescription>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<HelpLink anchor="ticketdetail" label="Hilfe zur Ticketdetailansicht" />
|
||||
<div className="flex items-center gap-2 rounded-md border bg-muted/30 px-3 py-2 text-sm">
|
||||
<span className="text-muted-foreground">{periodLabel} gesamt</span>
|
||||
<span className="font-semibold">{formatMinutes(periodTotalMinutes)}</span>
|
||||
@@ -657,8 +664,13 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate }: T
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader className="p-4">
|
||||
<CardTitle>Session nachtragen</CardTitle>
|
||||
<CardDescription>Neue Zeit direkt diesem Ticket zuordnen.</CardDescription>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<CardTitle>Session nachtragen</CardTitle>
|
||||
<CardDescription>Neue Zeit direkt diesem Ticket zuordnen.</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="session-nachtragen" label="Hilfe zum Nachtragen von Sessions" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<form className="space-y-4" onSubmit={submitTicketSession}>
|
||||
|
||||
@@ -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
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<CardHeader className="border-b bg-muted/30 p-4">
|
||||
<CardTitle>Neuen Timer starten</CardTitle>
|
||||
<CardDescription>Ein neuer Timer startet sofort und pausiert alle anderen Timer automatisch.</CardDescription>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<CardTitle>Neuen Timer starten</CardTitle>
|
||||
<CardDescription>Ein neuer Timer startet sofort und pausiert alle anderen Timer automatisch.</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="timer" label="Hilfe zu Timern" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="grid gap-3 p-4 sm:grid-cols-[minmax(220px,320px)_auto] sm:items-end">
|
||||
<div className="space-y-2">
|
||||
@@ -330,14 +336,19 @@ export function TimerPage({ timers, setTimers, selectedTimerId, setSelectedTimer
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<CardHeader className="border-b bg-muted/30 p-4">
|
||||
<CardTitle>{selectedTimer ? selectedTimer.ticketNumber : "Keine Session ausgewählt"}</CardTitle>
|
||||
<CardDescription>
|
||||
{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."}
|
||||
</CardDescription>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<CardTitle>{selectedTimer ? selectedTimer.ticketNumber : "Keine Session ausgewählt"}</CardTitle>
|
||||
<CardDescription>
|
||||
{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."}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="timer" label="Hilfe zum Timerstatus" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 p-4">
|
||||
<div className="rounded-lg border bg-background p-4 sm:p-5">
|
||||
@@ -374,8 +385,13 @@ export function TimerPage({ timers, setTimers, selectedTimerId, setSelectedTimer
|
||||
|
||||
<Card className="overflow-hidden">
|
||||
<CardHeader className="border-b bg-muted/30 p-4">
|
||||
<CardTitle>Session nachtragen</CardTitle>
|
||||
<CardDescription>Vergessene Zeiten für deinen eigenen Account manuell erfassen.</CardDescription>
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<CardTitle>Session nachtragen</CardTitle>
|
||||
<CardDescription>Vergessene Zeiten für deinen eigenen Account manuell erfassen.</CardDescription>
|
||||
</div>
|
||||
<HelpLink anchor="session-nachtragen" label="Hilfe zum Nachtragen von Sessions" />
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="p-4">
|
||||
<form className="space-y-4" onSubmit={submitManualSession}>
|
||||
|
||||
Reference in New Issue
Block a user