Merge branch 'phase/039-laufzeit-zugang'

This commit is contained in:
2026-10-03 17:08:54 +02:00
16 changed files with 81 additions and 15 deletions
+9
View File
@@ -17,6 +17,15 @@ und dieses Projekt folgt [Semantic Versioning](https://semver.org/lang/de/).
### Sicherheit
## [0.22.2] – 2026-10-03
### Behoben
- **Auf dem Telefon führte kein Weg zu den Rechnungsformularen**: Im Reiter *Rechnungen* der Host-Detailansicht war die Laufzeit nicht verlinkt, und in der Rechnungsliste ist diese Spalte erst ab 992 px sichtbar (#039)
- Der Reiter *Rechnungen* rechnete den Einkauf noch ohne Rabatt (#039)
### Geändert
- Der Reiter *Rechnungen* nutzt auf dem Telefon das Kartenlayout (#039)
## [0.22.1] – 2026-10-03
### Behoben
+7 -2
View File
@@ -2,7 +2,7 @@
## Zeitübersicht
**Gesamt:** 20 h 33 min
**Gesamt:** 21 h 03 min
| Issue | Zeit |
|---|---|
@@ -44,6 +44,7 @@
| #036 Standort am Host | 0 h 30 min |
| #037 Rabatt je Position | 0 h 55 min |
| #038 Überbreite auf dem Handy | 0 h 40 min |
| #039 Zugang zur Laufzeit | 0 h 30 min |
| Meilenstein | Zeit |
|---|---|
@@ -66,7 +67,7 @@
## 2026-09-17 · Sitzung 1 · 19:36–laufend
**Issue:** #001–#007 (Meilensteine v0.1.0 bis v0.4.0)
**Version:** 0.22.1
**Version:** 0.22.2
### Erledigt
- Auftrag gelesen, Rückfragen gestellt
@@ -199,6 +200,10 @@
- Dabei einen eigenen Fehler aus #037 gefunden: Die Detailseite rechnete weiter mit dem Listenpreis. Beim Ändern einer Rechengröße reicht es nicht, die Abfragen anzupassen – auch die Seiten rechnen selbst
- Das Aufnahme-Skript kann jetzt gezielt in Telefonbreite aufnehmen; damit ließ sich die Behebung belegen statt nur behaupten
- #039: Auf dem Telefon führte kein Weg zu den Rechnungsformularen – die Laufzeit war nicht verlinkt und die Spalte erst ab 992 px sichtbar
- Dritte Stelle, die den Einkauf ohne Rabatt rechnete. Beim Ändern einer Rechengröße gehört jede Stelle gesucht, die sie selbst bildet – nicht nur die Abfragen
- Bootstraps `d-*`-Klassen setzen `display` mit `!important`; für Spalten in der Kartenansicht braucht es eigene Klassen
### Nächster Schritt
- #016 Benutzerhandbuch mit Screenshots, danach #017 Favicons und #019 CI-Pipeline
- Für 1.0.0 fehlt noch: PMG (kein Host verfügbar) und der Fall „aktive Subscription auf PBS“ (#023)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 114 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 170 KiB

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 KiB

After

Width:  |  Height:  |  Size: 177 KiB

+32
View File
@@ -0,0 +1,32 @@
# #039 Auf dem Telefon führte kein Weg zur Laufzeit
**Status:** erledigt
**Meilenstein:** v0.22.2
**Labels:** bug, ui
**Geschätzt:** 1 h **Tatsächlich:** 0 h 30 min
## Auslöser
„Kann ich am Handy nicht rein.“ Gemeint war der Reiter *Rechnungen* in der Host-Detailansicht: Die
Zeile zeigt Laufzeit, Rechnung und Beträge – aber nichts davon war anklickbar.
## Ursache
Zwei Stellen:
1. In der Host-Detailansicht war die Laufzeit nie verlinkt, auch am Rechner nicht. Man kam nur über
die Rechnungsliste dorthin.
2. In der Rechnungsliste ist die Laufzeit-Spalte erst ab 992 px eingeblendet. Auf dem Telefon fehlte
damit der einzige Verweis auf die Formulare – die Karte verwies nur auf den Host.
## Behebung
- Die Laufzeit in der Host-Detailansicht ist ein Link und zugleich die Überschrift der Karte
- In der Rechnungsliste bleibt die Laufzeit auf dem Telefon sichtbar; ausgeblendet wird sie nur
zwischen 768 und 992 px, wo die Tabelle am engsten ist
- Der Reiter *Rechnungen* nutzt ebenfalls das Kartenlayout
## Nebenbefund
Auch dieser Reiter rechnete den Einkauf noch ohne Rabatt – 1.100 € statt 990 €. Dritte Stelle nach
#037 und #038; alle gefunden, weil Florian die Zahl auf dem Bildschirm mit seiner Rechnung verglichen hat.
## Gelernt
Bootstraps `d-*`-Hilfsklassen setzen `display` mit `!important` und überschreiben damit das
Kartenlayout. Für Spalten, die in der Kartenansicht sichtbar bleiben sollen, braucht es eine eigene
Klasse (`pl-spalte-laufzeit`).
+2 -1
View File
@@ -42,4 +42,5 @@ Details und Architektur: [../architektur.md](../architektur.md). Zeiten werden a
| [#036](036-standort-am-host.md) | Standort am Host setzen | erledigt | v0.21.0 | 1 h | 0 h 30 min |
| [#037](037-rabatt-und-ablauf.md) | Rabatt je Position und der tatsächliche Ablauf | erledigt | v0.22.0 | 3 h | 0 h 55 min |
| [#038](038-ueberbreite-handy.md) | Seite wird auf dem Handy herausgezoomt | erledigt | v0.22.1 | 2 h | 0 h 40 min |
| | **Summe** | | | **162 h** | **20 h 33 min** |
| [#039](039-zugang-zur-laufzeit.md) | Auf dem Telefon führte kein Weg zur Laufzeit | erledigt | v0.22.2 | 1 h | 0 h 30 min |
| | **Summe** | | | **163 h** | **21 h 03 min** |
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "proxledger",
"version": "0.22.1",
"version": "0.22.2",
"private": true,
"scripts": {
"dev": "next dev",
+15 -8
View File
@@ -346,7 +346,7 @@ export default async function HostDetailSeite({ params }: { params: Promise<{ id
icon: "bi-receipt",
inhalt: (
<div className="table-responsive">
<table className="table table-striped align-middle mb-0">
<table className="table table-striped align-middle mb-0 pl-karten-tabelle">
<thead>
<tr>
<th scope="col">Laufzeit</th>
@@ -359,21 +359,28 @@ export default async function HostDetailSeite({ params }: { params: Promise<{ id
</thead>
<tbody>
{host.laufzeiten.map((laufzeit) => {
// Nach Abzug des Rabatts – das ist, was tatsächlich gezahlt wird (#037).
const einkauf = laufzeit.proxmoxPosition
? Number(laufzeit.proxmoxPosition.nettoBetrag)
? Number(laufzeit.proxmoxPosition.nettoBetrag) -
Number(laufzeit.proxmoxPosition.rabatt ?? 0)
: null;
const verkauf = laufzeit.kundenPosition
? Number(laufzeit.kundenPosition.nettoBetrag)
: null;
return (
<tr key={laufzeit.id}>
<td>
<td className="pl-karten-kopf">
<Link
href={`/rechnungen/${laufzeit.id}`}
className="fw-semibold text-decoration-none"
>
{formatDatum(laufzeit.start)} – {formatDatum(laufzeit.ende)}
</Link>
{laufzeit.status === "VORSCHLAG" ? (
<span className="badge pl-bg-info ms-2">Vorschlag</span>
) : null}
</td>
<td>
<td data-label="Proxmox-Rechnung">
<CopyableCode wert={laufzeit.proxmoxPosition?.rechnung.nummer ?? null} />
<div>
<EinkaufBadge
@@ -385,8 +392,8 @@ export default async function HostDetailSeite({ params }: { params: Promise<{ id
/>
</div>
</td>
<td>{formatEuro(einkauf)}</td>
<td>
<td data-label="Einkauf">{formatEuro(einkauf)}</td>
<td data-label="Kundenrechnung">
<CopyableCode wert={laufzeit.kundenPosition?.rechnung.nummer ?? null} />
<div>
<VerkaufBadge
@@ -395,8 +402,8 @@ export default async function HostDetailSeite({ params }: { params: Promise<{ id
/>
</div>
</td>
<td>{formatEuro(verkauf)}</td>
<td>
<td data-label="Verkauf">{formatEuro(verkauf)}</td>
<td data-label="Marge">
{einkauf !== null && verkauf !== null
? formatEuro(verkauf - einkauf)
: "–"}
+2 -2
View File
@@ -204,7 +204,7 @@ export default async function RechnungenSeite({ searchParams }: { searchParams:
<th scope="col" className="d-none d-md-table-cell">
Host
</th>
<th scope="col" className="d-none d-lg-table-cell">
<th scope="col" className="pl-spalte-laufzeit">
Laufzeit
</th>
<th scope="col">Proxmox-Rechnung</th>
@@ -254,7 +254,7 @@ export default async function RechnungenSeite({ searchParams }: { searchParams:
{zeile.hostname}
</Link>
</td>
<td className="d-none d-lg-table-cell" data-label="Laufzeit">
<td className="pl-spalte-laufzeit" data-label="Laufzeit">
<Link href={`/rechnungen/${zeile.id}`} className="text-decoration-none">
{formatDatum(zeile.start)} – {formatDatum(zeile.ende)}
</Link>
+12
View File
@@ -401,6 +401,18 @@ body {
max-width: calc(100vw - 1rem);
}
/*
* Die Laufzeit-Spalte ist auf dem Telefon der einzige Weg zu den Rechnungsformularen, deshalb
* bleibt sie dort sichtbar. Zwischen 768 und 992 px ist die Tabelle am engsten – dort entfällt sie.
* Bewusst eine eigene Klasse statt der Bootstrap-Hilfsklassen: deren `display: … !important`
* überschreibt das Kartenlayout (#039).
*/
@media (min-width: 768px) and (max-width: 991.98px) {
.pl-spalte-laufzeit {
display: none;
}
}
/* ---------- Mobil ---------- */
@media (max-width: 575.98px) {