Update
This commit is contained in:
1 parent
3ebcea0b3d
commit
312dd3b3ad
3 files changed
+65
-55
No files matched your search
+61
-52
@@ -30,7 +30,7 @@ import {
|
|||||||
UserCircle,
|
UserCircle,
|
||||||
X,
|
X,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { FormEvent, MouseEvent, useCallback, useEffect, useMemo, useState } from "react";
|
import { FormEvent, MouseEvent, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { useShallow } from "zustand/react/shallow";
|
import { useShallow } from "zustand/react/shallow";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
@@ -781,6 +781,7 @@ export function App() {
|
|||||||
const [ticketReturnPaths, setTicketReturnPaths] = useState<Record<string, string>>({});
|
const [ticketReturnPaths, setTicketReturnPaths] = useState<Record<string, string>>({});
|
||||||
const [draggedOpenTicketKey, setDraggedOpenTicketKey] = useState<string | null>(null);
|
const [draggedOpenTicketKey, setDraggedOpenTicketKey] = useState<string | null>(null);
|
||||||
const [sidebarTodos, setSidebarTodos] = useState<TicketTodo[]>([]);
|
const [sidebarTodos, setSidebarTodos] = useState<TicketTodo[]>([]);
|
||||||
|
const openTicketsListRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const navigate = useCallback(
|
const navigate = useCallback(
|
||||||
(to: string) => {
|
(to: string) => {
|
||||||
@@ -875,6 +876,11 @@ export function App() {
|
|||||||
);
|
);
|
||||||
}, [currentUser, openTickets]);
|
}, [currentUser, openTickets]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const activeTicket = openTicketsListRef.current?.querySelector<HTMLElement>("[data-open-ticket-active='true']");
|
||||||
|
activeTicket?.scrollIntoView({ block: "nearest" });
|
||||||
|
}, [path, openTickets]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!currentUser || currentUser.role === "admin" || !timersLoaded || timerOwnerId !== currentUser.id) {
|
if (!currentUser || currentUser.role === "admin" || !timersLoaded || timerOwnerId !== currentUser.id) {
|
||||||
return;
|
return;
|
||||||
@@ -1301,62 +1307,65 @@ export function App() {
|
|||||||
)}
|
)}
|
||||||
</SidebarMenu>
|
</SidebarMenu>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
|
|
||||||
{!isAdmin ? <SidebarTodayTodos todos={sidebarTodos} onNavigate={navigate} /> : null}
|
{!isAdmin ? <SidebarTodayTodos todos={sidebarTodos} onNavigate={navigate} /> : null}
|
||||||
|
|
||||||
{!isAdmin && openTickets.length > 0 ? (
|
{!isAdmin && openTickets.length > 0 ? (
|
||||||
<SidebarGroup className="pt-0 group-data-[collapsible=icon]:hidden">
|
<SidebarGroup className="min-h-0 flex-1 pt-0 group-data-[collapsible=icon]:hidden">
|
||||||
<SidebarGroupLabel className="h-6 px-2 text-xs">Geöffnete Tickets</SidebarGroupLabel>
|
<SidebarGroupLabel className="h-6 px-2 text-xs">Geöffnete Tickets</SidebarGroupLabel>
|
||||||
<SidebarGroupContent>
|
<SidebarGroupContent className="min-h-0 flex-1 overflow-hidden">
|
||||||
<SidebarMenu>
|
<div ref={openTicketsListRef} className="max-h-[clamp(8rem,calc(100svh-31rem),24rem)] overflow-y-auto overscroll-contain pr-1 [scrollbar-gutter:stable] [scrollbar-width:thin]">
|
||||||
{openTickets.map((ticket) => {
|
<SidebarMenu>
|
||||||
const isActive = pathWithoutSearch(path) === pathWithoutSearch(ticket.path);
|
{openTickets.map((ticket) => {
|
||||||
|
const isActive = pathWithoutSearch(path) === pathWithoutSearch(ticket.path);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenuItem key={ticket.key}>
|
<SidebarMenuItem key={ticket.key}>
|
||||||
<div
|
<div
|
||||||
draggable
|
data-open-ticket-active={isActive ? "true" : undefined}
|
||||||
onDragStart={(event) => startOpenTicketDrag(event, ticket)}
|
draggable
|
||||||
onDragOver={(event) => overOpenTicket(event, ticket)}
|
onDragStart={(event) => startOpenTicketDrag(event, ticket)}
|
||||||
onDrop={(event) => dropOpenTicket(event, ticket)}
|
onDragOver={(event) => overOpenTicket(event, ticket)}
|
||||||
onDragEnd={() => setDraggedOpenTicketKey(null)}
|
onDrop={(event) => dropOpenTicket(event, ticket)}
|
||||||
className={cn(
|
onDragEnd={() => setDraggedOpenTicketKey(null)}
|
||||||
"mx-2 flex cursor-grab items-center gap-1 rounded-md px-2 py-1.5 active:cursor-grabbing",
|
className={cn(
|
||||||
isActive ? "bg-primary text-primary-foreground" : "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
"mx-2 flex cursor-grab items-center gap-1 rounded-md px-2 py-1.5 active:cursor-grabbing",
|
||||||
draggedOpenTicketKey === ticket.key && "opacity-60",
|
isActive ? "bg-primary text-primary-foreground" : "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground",
|
||||||
)}
|
draggedOpenTicketKey === ticket.key && "opacity-60",
|
||||||
>
|
)}
|
||||||
<GripVertical className="size-3.5 shrink-0 text-muted-foreground" />
|
|
||||||
<a
|
|
||||||
href={ticket.path}
|
|
||||||
className="flex min-w-0 flex-1 items-center gap-2 text-sm"
|
|
||||||
title={
|
|
||||||
ticket.periodType === "all"
|
|
||||||
? `${ticket.ticketNumber} (alle Sessions)`
|
|
||||||
: `${ticket.ticketNumber} (${ticket.periodType === "month" ? "Monat" : "Tag"} ${ticket.period})`
|
|
||||||
}
|
|
||||||
onClick={navHandler(ticket.path)}
|
|
||||||
>
|
>
|
||||||
<span className={cn("size-2 shrink-0 rounded-full", ticket.dirty ? "bg-amber-400" : isActive ? "bg-primary-foreground" : "bg-muted-foreground/70")} />
|
<GripVertical className="size-3.5 shrink-0 text-muted-foreground" />
|
||||||
<FileText className="size-4 shrink-0" />
|
<a
|
||||||
<span className="truncate">{ticket.ticketNumber}</span>
|
href={ticket.path}
|
||||||
</a>
|
className="flex min-w-0 flex-1 items-center gap-2 text-sm"
|
||||||
<Button
|
title={
|
||||||
type="button"
|
ticket.periodType === "all"
|
||||||
size="icon"
|
? `${ticket.ticketNumber} (alle Sessions)`
|
||||||
variant="ghost"
|
: `${ticket.ticketNumber} (${ticket.periodType === "month" ? "Monat" : "Tag"} ${ticket.period})`
|
||||||
className={cn("size-6 shrink-0", isActive ? "hover:bg-primary-foreground/15 hover:text-primary-foreground" : "")}
|
}
|
||||||
onClick={() => closeOpenTicket(ticket)}
|
onClick={navHandler(ticket.path)}
|
||||||
aria-label={`${ticket.ticketNumber} schließen`}
|
>
|
||||||
>
|
<span className={cn("size-2 shrink-0 rounded-full", ticket.dirty ? "bg-amber-400" : isActive ? "bg-primary-foreground" : "bg-muted-foreground/70")} />
|
||||||
<X className="size-3.5" />
|
<FileText className="size-4 shrink-0" />
|
||||||
</Button>
|
<span className="truncate">{ticket.ticketNumber}</span>
|
||||||
</div>
|
</a>
|
||||||
</SidebarMenuItem>
|
<Button
|
||||||
);
|
type="button"
|
||||||
})}
|
size="icon"
|
||||||
</SidebarMenu>
|
variant="ghost"
|
||||||
|
className={cn("size-6 shrink-0", isActive ? "hover:bg-primary-foreground/15 hover:text-primary-foreground" : "")}
|
||||||
|
onClick={() => closeOpenTicket(ticket)}
|
||||||
|
aria-label={`${ticket.ticketNumber} schließen`}
|
||||||
|
>
|
||||||
|
<X className="size-3.5" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</SidebarMenuItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</SidebarMenu>
|
||||||
|
</div>
|
||||||
</SidebarGroupContent>
|
</SidebarGroupContent>
|
||||||
</SidebarGroup>
|
</SidebarGroup>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
--font-heading: var(--font-sans);
|
--font-heading: var(--font-sans);
|
||||||
--font-sans: var(--font-sans);
|
--font-sans: var(--font-sans);
|
||||||
--breakpoint-xxl: 108.125rem;
|
--breakpoint-xxl: 108.125rem;
|
||||||
|
--breakpoint-details: 85rem;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1242,7 +1242,7 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
|
|||||||
</Alert>
|
</Alert>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
<div className={cn("grid gap-4", isGlobalTicket && "xxl:grid-cols-2")}>
|
<div className={cn("grid gap-4", isGlobalTicket && "details:grid-cols-2")}>
|
||||||
{isGlobalTicket ? (
|
{isGlobalTicket ? (
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="p-4">
|
<CardHeader className="p-4">
|
||||||
@@ -1300,7 +1300,7 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="px-4 pb-4">
|
<CardContent className="px-4 pb-4">
|
||||||
<form className="grid gap-3 md:grid-cols-2" onSubmit={saveTicketData}>
|
<form className="grid grid-cols-2 gap-3 [@media_(min-width:85rem)_and_(max-width:108.124rem)]:grid-cols-1 xxl:grid-cols-2" onSubmit={saveTicketData}>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<label className="text-sm font-medium" htmlFor="edit-ticket-number">Ticketnummer</label>
|
<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 />
|
<Input id="edit-ticket-number" value={editTicketNumber} disabled={!canEditTicketData} onChange={(event) => setEditTicketNumber(event.currentTarget.value)} required />
|
||||||
@@ -1338,7 +1338,7 @@ export function TicketDetailPage({ periodType, period, ticketId, onNavigate, onO
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" className="md:col-span-2 md:justify-self-end" disabled={!canEditTicketData || savingTicket}>
|
<Button type="submit" className="col-span-2 justify-self-end [@media_(min-width:85rem)_and_(max-width:108.124rem)]:col-span-1 xxl:col-span-2" disabled={!canEditTicketData || savingTicket}>
|
||||||
<Save className="size-4" />
|
<Save className="size-4" />
|
||||||
{savingTicket ? "Speichert..." : "Speichern"}
|
{savingTicket ? "Speichert..." : "Speichern"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
Reference in new issue
Block a user