Vertrag-UI: Sticky-Header + Sticky-Footer
ContractDetail: der obere Balken (Vertragsnummer, Kunde-Link, Aktions-Buttons wie Folgevertrag/Bearbeiten/Löschen) bleibt beim Scrollen sichtbar. -mx-8 px-8 überbrückt das Layout-Padding, damit der Balken bündig sitzt. ContractForm: Heading + Kunde-Zeile sticky oben, Speichern/ Abbrechen sticky unten. Damit muss man beim langen Formular nicht mehr nach oben oder unten rollen. ScrollToTopButton: von bottom-6 auf bottom-24 verschoben, sonst sitzt der runde Nach-oben-Button beim Runterscrollen genau über dem Speichern-Button im Sticky-Footer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -29,10 +29,14 @@ export default function ScrollToTopButton() {
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
// bottom-24 statt bottom-6: der Nach-oben-Button muss auf Seiten mit
|
||||
// Sticky-Footer (z.B. ContractForm Speichern/Abbrechen) über dem Footer
|
||||
// sitzen, sonst hängt er beim Runterscrollen genau über dem Speichern-
|
||||
// Button. 96px reichen für alle heute vorhandenen Sticky-Footer.
|
||||
return (
|
||||
<button
|
||||
onClick={scrollToTop}
|
||||
className="fixed bottom-6 right-6 p-3 bg-gray-200 hover:bg-gray-300 text-gray-600 rounded-full shadow-md transition-all duration-300 opacity-70 hover:opacity-100 z-50"
|
||||
className="fixed bottom-24 right-6 p-3 bg-gray-200 hover:bg-gray-300 text-gray-600 rounded-full shadow-md transition-all duration-300 opacity-70 hover:opacity-100 z-50"
|
||||
aria-label="Nach oben scrollen"
|
||||
title="Nach oben"
|
||||
>
|
||||
|
||||
@@ -1795,7 +1795,10 @@ export default function ContractDetail() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
{/* Sticky-Header: Vertragsnummer + Kunde + Aktionsbuttons bleiben
|
||||
beim Scrollen oben stehen. -mx-8 px-8 zieht den Balken auf die
|
||||
Layout-Padding-Kante, damit's optisch bündig ist. */}
|
||||
<div className="sticky top-0 z-20 -mx-8 px-8 py-4 mb-6 bg-gray-100 border-b border-gray-200 flex items-center justify-between">
|
||||
<div>
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<Button
|
||||
|
||||
@@ -777,44 +777,49 @@ export default function ContractForm() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate(back.to, { state: back.state })}>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
</Button>
|
||||
<h1 className="text-2xl font-bold">
|
||||
{isEdit ? 'Vertrag bearbeiten' : 'Neuer Vertrag'}
|
||||
</h1>
|
||||
{/* Sticky-Header: Zurück-Button + Titel + Kunde-Zeile bleiben beim
|
||||
Scrollen oben stehen. Layout-Padding wird per -mx-8 px-8
|
||||
überbrückt, damit der Balken bündig ist. */}
|
||||
<div className="sticky top-0 z-20 -mx-8 px-8 pt-4 pb-3 mb-6 bg-gray-100 border-b border-gray-200">
|
||||
<div className="flex items-center gap-4 mb-2">
|
||||
<Button variant="ghost" size="sm" onClick={() => navigate(back.to, { state: back.state })}>
|
||||
<ArrowLeft className="w-4 h-4" />
|
||||
</Button>
|
||||
<h1 className="text-2xl font-bold">
|
||||
{isEdit ? 'Vertrag bearbeiten' : 'Neuer Vertrag'}
|
||||
</h1>
|
||||
</div>
|
||||
{customer && (
|
||||
<p className="text-gray-500 ml-12 flex items-center gap-1">
|
||||
Kunde:{' '}
|
||||
<Link
|
||||
to={`/customers/${customer.id}`}
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
{customer.companyName || `${customer.firstName} ${customer.lastName}`}
|
||||
</Link>
|
||||
<a
|
||||
href={`/customers/${customer.id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Kundenakte in neuem Tab öffnen"
|
||||
aria-label="Kundenakte in neuem Tab öffnen"
|
||||
className="text-gray-400 hover:text-blue-600 p-1 rounded"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowCustomerInfo(true)}
|
||||
title="Wichtige Kundendaten anzeigen (Schnellansicht mit Copy-Buttons)"
|
||||
aria-label="Kundendaten anzeigen"
|
||||
className="text-gray-400 hover:text-blue-600 p-1 rounded"
|
||||
>
|
||||
<Info className="w-4 h-4" />
|
||||
</button>
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{customer && (
|
||||
<p className="text-gray-500 ml-12 mb-6 flex items-center gap-1">
|
||||
Kunde:{' '}
|
||||
<Link
|
||||
to={`/customers/${customer.id}`}
|
||||
className="text-blue-600 hover:underline"
|
||||
>
|
||||
{customer.companyName || `${customer.firstName} ${customer.lastName}`}
|
||||
</Link>
|
||||
<a
|
||||
href={`/customers/${customer.id}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
title="Kundenakte in neuem Tab öffnen"
|
||||
aria-label="Kundenakte in neuem Tab öffnen"
|
||||
className="text-gray-400 hover:text-blue-600 p-1 rounded"
|
||||
>
|
||||
<ExternalLink className="w-4 h-4" />
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowCustomerInfo(true)}
|
||||
title="Wichtige Kundendaten anzeigen (Schnellansicht mit Copy-Buttons)"
|
||||
aria-label="Kundendaten anzeigen"
|
||||
className="text-gray-400 hover:text-blue-600 p-1 rounded"
|
||||
>
|
||||
<Info className="w-4 h-4" />
|
||||
</button>
|
||||
</p>
|
||||
)}
|
||||
|
||||
{error && (
|
||||
<div className="mb-4 p-4 bg-red-50 border border-red-200 text-red-700 rounded-lg">
|
||||
@@ -1784,7 +1789,11 @@ export default function ContractForm() {
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<div className="flex justify-end gap-4">
|
||||
{/* Sticky-Footer: Speichern/Abbrechen bleiben unten sichtbar,
|
||||
damit man beim langen Formular nicht ganz nach unten scrollen
|
||||
muss. -mx-8 px-8 -mb-8 zieht den Balken bündig an die
|
||||
Layout-Padding-Kante. */}
|
||||
<div className="sticky bottom-0 z-20 -mx-8 px-8 pt-3 pb-3 -mb-8 mt-6 bg-gray-100 border-t border-gray-200 flex justify-end gap-4">
|
||||
<Button type="button" variant="secondary" onClick={() => navigate(back.to, { state: back.state })}>
|
||||
Abbrechen
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user