This commit is contained in:
michael committed 2026-09-15 14:26:46 +00:00
1 parent c66b5a9aa9
commit 3ebcea0b3d
1 file changed
+99 -97
+99 -97
View File
@@ -1242,108 +1242,110 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
</Alert>
) : null}
{isGlobalTicket ? (
<div className={cn("grid gap-4", isGlobalTicket && "xxl:grid-cols-2")}>
{isGlobalTicket ? (
<Card>
<CardHeader className="p-4">
<CardTitle>Filter</CardTitle>
<CardDescription>Grenzt die Sessionliste dieses Tickets ein. Die Ticketdaten selbst bleiben global.</CardDescription>
</CardHeader>
<CardContent className="grid gap-3 px-4 pb-4 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="ticket-month-filter">Monat</label>
<select
id="ticket-month-filter"
className="h-8 w-full rounded-lg border border-input bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
value={globalMonthFilter}
onChange={(event) => setGlobalMonthFilter(event.currentTarget.value)}
>
<option value="all">Alle Monate</option>
{globalMonthFilter !== "all" && !selectedMonthHasSessions ? <option value={globalMonthFilter}>{globalMonthFilter} (0)</option> : null}
{availableMonths.map((month) => (
<option key={month.month} value={month.month}>
{month.month} ({month.session_count})
</option>
))}
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="ticket-status-filter">Bewertung</label>
<select
id="ticket-status-filter"
className="h-8 w-full rounded-lg border border-input bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
value={globalStatusFilter}
onChange={(event) => setGlobalStatusFilter(event.currentTarget.value as typeof globalStatusFilter)}
>
<option value="all">Alle Bewertungen</option>
<option value="open">Offen</option>
<option value="billed">Abgerechnet</option>
<option value="non_billable">Nicht abrechenbar</option>
</select>
</div>
</CardContent>
</Card>
) : null}
<Card>
<CardHeader className="p-4">
<CardTitle>Filter</CardTitle>
<CardDescription>Grenzt die Sessionliste dieses Tickets ein. Die Ticketdaten selbst bleiben global.</CardDescription>
<div className="flex items-start justify-between gap-3">
<div>
<CardTitle>Ticketdaten</CardTitle>
<CardDescription>
{canEditTicketData
? "Änderungen an Organisation und Art werden auf alle vorhandenen Sessions dieses Tickets übernommen."
: "Dieses Projekt ist geteilt. Nur der Zammad-Owner kann Ticket- und Projektdaten ändern."}
</CardDescription>
</div>
<HelpLink anchor="ticketdaten" label="Hilfe zu Ticketdaten" />
</div>
</CardHeader>
<CardContent className="grid gap-3 px-4 pb-4 md:grid-cols-3">
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="ticket-month-filter">Monat</label>
<select
id="ticket-month-filter"
className="h-8 w-full rounded-lg border border-input bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
value={globalMonthFilter}
onChange={(event) => setGlobalMonthFilter(event.currentTarget.value)}
>
<option value="all">Alle Monate</option>
{globalMonthFilter !== "all" && !selectedMonthHasSessions ? <option value={globalMonthFilter}>{globalMonthFilter} (0)</option> : null}
{availableMonths.map((month) => (
<option key={month.month} value={month.month}>
{month.month} ({month.session_count})
</option>
))}
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="ticket-status-filter">Bewertung</label>
<select
id="ticket-status-filter"
className="h-8 w-full rounded-lg border border-input bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
value={globalStatusFilter}
onChange={(event) => setGlobalStatusFilter(event.currentTarget.value as typeof globalStatusFilter)}
>
<option value="all">Alle Bewertungen</option>
<option value="open">Offen</option>
<option value="billed">Abgerechnet</option>
<option value="non_billable">Nicht abrechenbar</option>
</select>
</div>
<CardContent className="px-4 pb-4">
<form className="grid gap-3 md:grid-cols-2" onSubmit={saveTicketData}>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="edit-ticket-number">Ticketnummer</label>
<Input id="edit-ticket-number" value={editTicketNumber} disabled={!canEditTicketData} onChange={(event) => setEditTicketNumber(event.currentTarget.value)} required />
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="edit-ticket-work-type">Art</label>
<select
id="edit-ticket-work-type"
className="h-8 w-full rounded-lg border border-input bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
value={editWorkType}
disabled={!canEditTicketData}
onChange={(event) => setEditWorkType(event.currentTarget.value as WorkType)}
>
<option value="support">Support</option>
<option value="consulting">Consulting</option>
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="edit-ticket-title">Titel</label>
<Input id="edit-ticket-title" value={editTicketTitle} disabled={!canEditTicketData} onChange={(event) => setEditTicketTitle(event.currentTarget.value)} placeholder="optional" />
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="edit-ticket-organization">Organisation</label>
{ticket?.customer_name && !ticket.organization_id ? (
<p className="text-xs text-muted-foreground">Bisheriger Freitext: {ticket.customer_name}</p>
) : null}
<OrganizationSelect
value={editOrganizationId}
selectedName={editOrganizationName}
disabled={!canEditTicketData}
onChange={(organization) => {
setEditOrganizationId(organization.id);
setEditOrganizationName(organization.name);
}}
required
/>
</div>
<Button type="submit" className="md:col-span-2 md:justify-self-end" disabled={!canEditTicketData || savingTicket}>
<Save className="size-4" />
{savingTicket ? "Speichert..." : "Speichern"}
</Button>
</form>
</CardContent>
</Card>
) : null}
<Card>
<CardHeader className="p-4">
<div className="flex items-start justify-between gap-3">
<div>
<CardTitle>Ticketdaten</CardTitle>
<CardDescription>
{canEditTicketData
? "Änderungen an Organisation und Art werden auf alle vorhandenen Sessions dieses Tickets übernommen."
: "Dieses Projekt ist geteilt. Nur der Zammad-Owner kann Ticket- und Projektdaten ändern."}
</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)_minmax(220px,1fr)_160px_auto] lg:items-end" onSubmit={saveTicketData}>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="edit-ticket-number">Ticketnummer</label>
<Input id="edit-ticket-number" value={editTicketNumber} disabled={!canEditTicketData} onChange={(event) => setEditTicketNumber(event.currentTarget.value)} required />
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="edit-ticket-title">Titel</label>
<Input id="edit-ticket-title" value={editTicketTitle} disabled={!canEditTicketData} onChange={(event) => setEditTicketTitle(event.currentTarget.value)} placeholder="optional" />
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="edit-ticket-organization">Organisation</label>
{ticket?.customer_name && !ticket.organization_id ? (
<p className="text-xs text-muted-foreground">Bisheriger Freitext: {ticket.customer_name}</p>
) : null}
<OrganizationSelect
value={editOrganizationId}
selectedName={editOrganizationName}
disabled={!canEditTicketData}
onChange={(organization) => {
setEditOrganizationId(organization.id);
setEditOrganizationName(organization.name);
}}
required
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="edit-ticket-work-type">Art</label>
<select
id="edit-ticket-work-type"
className="h-8 w-full rounded-lg border border-input bg-background px-2 text-sm focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-ring/50"
value={editWorkType}
disabled={!canEditTicketData}
onChange={(event) => setEditWorkType(event.currentTarget.value as WorkType)}
>
<option value="support">Support</option>
<option value="consulting">Consulting</option>
</select>
</div>
<Button type="submit" disabled={!canEditTicketData || savingTicket}>
<Save className="size-4" />
{savingTicket ? "Speichert..." : "Speichern"}
</Button>
</form>
</CardContent>
</Card>
</div>
<div className={cn("grid gap-4", project && "xl:grid-cols-2")}>
{project ? (