Update
This commit is contained in:
1 parent
fa6db04370
commit
0c14a3acbd
5 files changed
+79
-7
No files matched your search
@@ -1504,6 +1504,8 @@ export function App() {
|
||||
period={route.period}
|
||||
ticketId={route.ticketId}
|
||||
onNavigate={navigate}
|
||||
onStartTimer={startQuickTimer}
|
||||
currentUser={currentUser}
|
||||
onOpenTicketChange={updateOpenTicket}
|
||||
returnPath={ticketReturnPaths[route.ticketId] ?? openTickets.find((ticket) => ticket.ticketId === route.ticketId)?.returnPath ?? "/analysis"}
|
||||
/>
|
||||
|
||||
@@ -146,6 +146,7 @@ export const helpSections: HelpSection[] = [
|
||||
"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.",
|
||||
"Mit Bewertung --- bleibt die Session offen. Abrechenbar oder Nicht abrechenbar werden direkt an der neuen Session gespeichert.",
|
||||
"Die interne Zammad-Notiz kann auch beim Nachtragen gesetzt werden. Bei Zammad-Fehlern wird sie geparkt und kann später im Profil gesendet werden.",
|
||||
"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.",
|
||||
"Bei Consulting-Projekten mit mehreren Teamspace-Einträgen ist der passende Teamspace-Eintrag Pflicht. Bei nur einem Eintrag wird er automatisch gesetzt."
|
||||
]
|
||||
@@ -218,6 +219,7 @@ export const helpSections: HelpSection[] = [
|
||||
],
|
||||
details: [
|
||||
"Oben steht die Ticketnummer mit Copy-Icon, damit sie schnell in andere Systeme übernommen werden kann.",
|
||||
"Über Timer starten kann direkt für dieses Ticket ein Timer gestartet oder ein vorhandener Timer aktiviert werden.",
|
||||
"Die Session-Einträge sind nach Kalendertagen gruppiert. Pro Tagesgruppe steht die Summe der abgerechneten Zeit.",
|
||||
"Abrechenbare, nicht abrechenbare und offene Zeiten werden neben der abgerechneten Tageszeit separat angezeigt.",
|
||||
"Bei Projekt-Tickets mit mehreren Teamspace-Einträgen zeigt jede zugeordnete Session den gewählten Teamspace-Eintrag. Nicht zugeordnete Sessions werden hervorgehoben.",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ArrowLeft, CheckCircle2, ChevronDown, ChevronUp, ClipboardList, Copy, EyeOff, FolderKanban, Pencil, Plus, RotateCcw, Save, Star, Trash2 } from "lucide-react";
|
||||
import { ArrowLeft, CheckCircle2, ChevronDown, ChevronUp, ClipboardList, Copy, EyeOff, FolderKanban, Pencil, Play, Plus, RotateCcw, Save, Star, Trash2 } from "lucide-react";
|
||||
import { FormEvent, useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { Alert } from "@/components/ui/alert";
|
||||
@@ -32,7 +32,7 @@ import {
|
||||
} from "../api";
|
||||
import { currentDay, currentMonth, formatDate, formatDateTime, formatMinutes, formatTimeRange } from "../format";
|
||||
import { ticketHasRecurringBilling } from "../recurring-billings";
|
||||
import type { BillingStatus, PeriodType, ProjectSummary, ProjectTeamspaceEntry, SessionEntry, TicketDetailPeriodType, TicketPeriod, WorkType } from "../types";
|
||||
import type { AuthUser, BillingStatus, PeriodType, ProjectSummary, ProjectTeamspaceEntry, SessionEntry, TicketDetailPeriodType, TicketPeriod, WorkType } from "../types";
|
||||
import { ZammadPriorityBadge, ZammadStateBadge } from "../zammad-meta";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -41,6 +41,8 @@ type TicketDetailPageProps = {
|
||||
period: string;
|
||||
ticketId: string;
|
||||
onNavigate: (to: string) => void;
|
||||
onStartTimer: (ticketNumber: string) => Promise<boolean>;
|
||||
currentUser: AuthUser;
|
||||
onOpenTicketChange?: (ticket: {
|
||||
key: string;
|
||||
ticketId: string;
|
||||
@@ -60,6 +62,7 @@ type TicketSessionFormState = {
|
||||
projectTeamspaceEntryId: string;
|
||||
activity: string;
|
||||
billingStatus: BillingStatus;
|
||||
createZammadArticle: boolean;
|
||||
workType: WorkType;
|
||||
day: string;
|
||||
startTime: string;
|
||||
@@ -92,6 +95,7 @@ function emptyTicketSessionForm(): TicketSessionFormState {
|
||||
projectTeamspaceEntryId: "",
|
||||
activity: "",
|
||||
billingStatus: null,
|
||||
createZammadArticle: false,
|
||||
workType: "support",
|
||||
day: currentDay(),
|
||||
startTime: "09:00",
|
||||
@@ -480,7 +484,7 @@ function ProjectTeamspaceEntriesEditor({
|
||||
);
|
||||
}
|
||||
|
||||
export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onOpenTicketChange, returnPath }: TicketDetailPageProps) {
|
||||
export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onStartTimer, currentUser, onOpenTicketChange, returnPath }: TicketDetailPageProps) {
|
||||
const initialGlobalMonthFilter = periodType === "all" && period !== "all" ? period : currentMonth();
|
||||
const [data, setData] = useState<TicketPeriod | null>(null);
|
||||
const [loadingId, setLoadingId] = useState<string | null>(null);
|
||||
@@ -593,6 +597,19 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
|
||||
}));
|
||||
}, [data?.ticket, data?.project]);
|
||||
|
||||
useEffect(() => {
|
||||
setTicketSession((current) => {
|
||||
if (current.activity || current.billingStatus || current.createZammadArticle === currentUser.zammad_session_note_default_enabled) {
|
||||
return current;
|
||||
}
|
||||
|
||||
return {
|
||||
...current,
|
||||
createZammadArticle: currentUser.zammad_session_note_default_enabled
|
||||
};
|
||||
});
|
||||
}, [currentUser.zammad_session_note_default_enabled]);
|
||||
|
||||
useEffect(() => {
|
||||
setProjectDescriptionExpanded(false);
|
||||
}, [data?.project?.id]);
|
||||
@@ -1023,7 +1040,8 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
|
||||
endedAt: endedAt.toISOString(),
|
||||
durationSeconds,
|
||||
projectTeamspaceEntryId: effectiveWorkType === "consulting" && requiresProjectTeamspaceEntry ? ticketSession.projectTeamspaceEntryId : null,
|
||||
billingStatus: ticketSession.billingStatus
|
||||
billingStatus: ticketSession.billingStatus,
|
||||
createZammadArticle: ticketSession.createZammadArticle
|
||||
});
|
||||
toast.success("Session nachgetragen", {
|
||||
description: "Der Eintrag wurde diesem Ticket zugeordnet. Betroffene Abschlüsse wurden wieder geöffnet."
|
||||
@@ -1035,7 +1053,8 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
|
||||
projectTeamspaceEntryId: "",
|
||||
workType: ticket.work_type ?? "support",
|
||||
day: ticketSession.day,
|
||||
billingStatus: null
|
||||
billingStatus: null,
|
||||
createZammadArticle: currentUser.zammad_session_note_default_enabled
|
||||
});
|
||||
await load();
|
||||
} catch (error) {
|
||||
@@ -1264,6 +1283,12 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{ticket?.ticket_number ? (
|
||||
<Button type="button" size="sm" onClick={() => void onStartTimer(ticket.ticket_number)}>
|
||||
<Play className="size-4" />
|
||||
Timer starten
|
||||
</Button>
|
||||
) : null}
|
||||
{ticket?.ticket_number ? (
|
||||
<Button type="button" size="sm" variant={isFavorite ? "default" : "secondary"} disabled={savingFavorite} onClick={() => void toggleTicketFavorite()}>
|
||||
<Star className={cn("size-4", isFavorite && "fill-current")} />
|
||||
@@ -1974,6 +1999,16 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<label className="flex min-h-10 items-start gap-2 rounded-md border bg-background p-3 text-sm lg:col-span-5">
|
||||
<Checkbox
|
||||
checked={ticketSession.createZammadArticle}
|
||||
onCheckedChange={(checked) => setTicketSession({ ...ticketSession, createZammadArticle: checked === true })}
|
||||
/>
|
||||
<span>
|
||||
<span className="block font-medium">Interne Zammad-Notiz erstellen</span>
|
||||
<span className="block text-xs text-muted-foreground">Überträgt die Tätigkeit inklusive time_unit nach Zammad oder parkt sie bei Fehlern.</span>
|
||||
</span>
|
||||
</label>
|
||||
<Button type="submit" disabled={savingTicketSession}>
|
||||
<Save className="size-4" />
|
||||
{savingTicketSession ? "Speichert..." : "Nachtragen"}
|
||||
|
||||
@@ -24,6 +24,7 @@ type ManualSessionFormState = {
|
||||
projectTeamspaceEntryId: string;
|
||||
activity: string;
|
||||
billingStatus: BillingStatus;
|
||||
createZammadArticle: boolean;
|
||||
workType: WorkType;
|
||||
day: string;
|
||||
startTime: string;
|
||||
@@ -43,6 +44,7 @@ function emptyManualSession(): ManualSessionFormState {
|
||||
projectTeamspaceEntryId: "",
|
||||
activity: "",
|
||||
billingStatus: null,
|
||||
createZammadArticle: false,
|
||||
workType: "support",
|
||||
day: currentDay(),
|
||||
startTime: "09:00",
|
||||
@@ -221,6 +223,19 @@ export function TimerPage({ timers, setTimers, selectedTimerId, setSelectedTimer
|
||||
};
|
||||
}, [manualSession.ticketNumber]);
|
||||
|
||||
useEffect(() => {
|
||||
setManualSession((current) => {
|
||||
if (current.ticketNumber || current.activity || current.organizationId || current.billingStatus || current.createZammadArticle === currentUser.zammad_session_note_default_enabled) {
|
||||
return current;
|
||||
}
|
||||
|
||||
return {
|
||||
...current,
|
||||
createZammadArticle: currentUser.zammad_session_note_default_enabled
|
||||
};
|
||||
});
|
||||
}, [currentUser.zammad_session_note_default_enabled]);
|
||||
|
||||
async function addTimer(source: "local" | "zammad" = "local") {
|
||||
const normalizedTicketNumber = ticketNumber.trim();
|
||||
|
||||
@@ -596,14 +611,16 @@ export function TimerPage({ timers, setTimers, selectedTimerId, setSelectedTimer
|
||||
durationSeconds,
|
||||
zammadLookup: Boolean(manualLookupTicket?.zammad_ticket_id),
|
||||
projectTeamspaceEntryId: requiresTeamspaceEntry(manualLookupTicket) ? manualSession.projectTeamspaceEntryId : null,
|
||||
billingStatus: manualSession.billingStatus
|
||||
billingStatus: manualSession.billingStatus,
|
||||
createZammadArticle: manualSession.createZammadArticle
|
||||
});
|
||||
toast.success("Session nachgetragen", {
|
||||
description: manualLookupTicket?.is_project ? "Der Eintrag wurde dem Projekt zugeordnet." : "Der Eintrag ist in deiner Ticketliste offen."
|
||||
});
|
||||
setManualSession({
|
||||
...emptyManualSession(),
|
||||
day: manualSession.day
|
||||
day: manualSession.day,
|
||||
createZammadArticle: currentUser.zammad_session_note_default_enabled
|
||||
});
|
||||
} catch (error) {
|
||||
toast.error("Session konnte nicht nachgetragen werden", {
|
||||
@@ -908,6 +925,16 @@ export function TimerPage({ timers, setTimers, selectedTimerId, setSelectedTimer
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<label className="flex min-h-10 items-start gap-2 rounded-md border bg-background p-3 text-sm lg:col-span-5">
|
||||
<Checkbox
|
||||
checked={manualSession.createZammadArticle}
|
||||
onCheckedChange={(checked) => setManualSession({ ...manualSession, createZammadArticle: checked === true })}
|
||||
/>
|
||||
<span>
|
||||
<span className="block font-medium">Interne Zammad-Notiz erstellen</span>
|
||||
<span className="block text-xs text-muted-foreground">Überträgt die Tätigkeit inklusive time_unit nach Zammad oder parkt sie bei Fehlern.</span>
|
||||
</span>
|
||||
</label>
|
||||
<Button type="submit" disabled={savingManualSession || manualTicketIsRecurringBilling}>
|
||||
<Upload className="size-4" />
|
||||
{savingManualSession ? "Speichert..." : "Nachtragen"}
|
||||
|
||||
Reference in new issue
Block a user