diff --git a/web/src/components/DataTable.jsx b/web/src/components/DataTable.jsx index 7d84b52..3803b42 100644 --- a/web/src/components/DataTable.jsx +++ b/web/src/components/DataTable.jsx @@ -265,34 +265,77 @@ export default function DataTable({ } // --- Resize (Pointer) --- + // + // Gerechnet wird mit der BEWEGUNG seit dem letzten Schritt, nicht mit dem + // Abstand zum Startpunkt. Nur so laesst sich die letzte Spalte verbreitern: + // Ihr Anfasser sitzt am rechten Rand, und dort ist fuer den Zeiger Schluss - + // nach rechts gibt es kein Fenster mehr. Steht er in der Randzone, waechst die + // Spalte deshalb von selbst weiter, Schritt fuer Schritt, solange die Taste + // haelt. Ein Taktgeber treibt das an, denn ein stehender Zeiger meldet keine + // Bewegung mehr. const resizing = useRef(null); + const rollTimer = useRef(null); + const RAND = 36; // Breite der Randzone + const SCHRITT = 14; // Pixel je Takt in der Randzone + function startResize(e, c) { e.preventDefault(); e.stopPropagation(); - const startW = widthOf(c); // Die flexible (grow-)Spalte auf ihre aktuelle Pixelbreite einfrieren. Sonst - // "klaut" das Breiterziehen einer Spalte der grow-Spalte den Platz – dann + // "klaut" das Breiterziehen einer Spalte der grow-Spalte den Platz - dann // schrumpft die Nachbarspalte und alles wandert nach links. Danach verhalten - // sich die Spalten wie in Excel (unabhängig, notfalls scrollt die Tabelle). + // sich die Spalten wie in Excel (unabhaengig, notfalls scrollt die Tabelle). setWidths((old) => { const next = { ...old }; for (const gc of growCols) if (next[gc.key] == null) next[gc.key] = Math.round(widthOf(gc)); return next; }); - resizing.current = { key: c.key, startX: e.clientX, startW }; + resizing.current = { key: c.key, breite: widthOf(c), zeigerX: e.clientX, letzterX: e.clientX }; + rollTimer.current = window.setInterval(anwenden, 30); window.addEventListener("pointermove", onResizeMove); window.addEventListener("pointerup", endResize); } + + function anwenden() { + const r = resizing.current; if (!r) return; + let delta = r.zeigerX - r.letzterX; + r.letzterX = r.zeigerX; + + const el = wrapRef.current; + let amRand = false; + if (el) { + const kasten = el.getBoundingClientRect(); + if (r.zeigerX > kasten.right - RAND) { delta += SCHRITT; amRand = true; } + else if (r.zeigerX < kasten.left + RAND) delta -= SCHRITT; + } + if (!delta) return; + + const neu = Math.max(60, r.breite + delta); + if (neu === r.breite) return; + r.breite = neu; + setWidths((old) => ({ ...old, [r.key]: Math.round(neu) })); + // Mitlaufen, damit der Anfasser unter dem Zeiger bleibt, waehrend die + // Tabelle nach rechts waechst. + if (amRand && el) requestAnimationFrame(() => { el.scrollLeft = el.scrollWidth; }); + } + function onResizeMove(e) { const r = resizing.current; if (!r) return; - const w = Math.max(60, Math.round(r.startW + (e.clientX - r.startX))); - setWidths((old) => ({ ...old, [r.key]: w })); + r.zeigerX = e.clientX; + anwenden(); } + function endResize() { resizing.current = null; + if (rollTimer.current) { window.clearInterval(rollTimer.current); rollTimer.current = null; } window.removeEventListener("pointermove", onResizeMove); window.removeEventListener("pointerup", endResize); } + // Beim Verlassen der Seite mitten im Ziehen bliebe sonst ein Taktgeber laufen. + useEffect(() => () => { + if (rollTimer.current) window.clearInterval(rollTimer.current); + }, []); + // --- Reorder (HTML5 Drag) --- const dragKey = useRef(null); const [dropKey, setDropKey] = useState(null); diff --git a/web/src/dashboard/cards.jsx b/web/src/dashboard/cards.jsx index 7dff2fc..4fea064 100644 --- a/web/src/dashboard/cards.jsx +++ b/web/src/dashboard/cards.jsx @@ -8,7 +8,7 @@ import BewegungIcon from "../components/BewegungIcon"; import { FristBadge, verleihRowClass } from "../components/Verleih"; import { useSettings } from "../settings"; import { - articlePrimary, articleSecondary, fmt, relativeExpiry, + articlePrimary, articleSecondary, bestandPrimary, bestandSecondary, fmt, relativeExpiry, } from "../units"; import Bars from "./charts/Bars"; import Donut from "./charts/Donut"; @@ -192,9 +192,12 @@ function KarteArtikel({ props: karteProps }) {
{daten.name}
-
{articlePrimary(daten)}
+ {/* Artikel, nicht Charge: Der Bestand heisst hier ``stock``. + ``articlePrimary`` sucht ``quantity`` und lieferte deshalb + "NaN Glaeser". */} +
{bestandPrimary(daten)}
-
{articleSecondary(daten)}
+
{bestandSecondary(daten)}
)} diff --git a/web/src/pages/Charges.jsx b/web/src/pages/Charges.jsx index 5d0725e..eeedfcd 100644 --- a/web/src/pages/Charges.jsx +++ b/web/src/pages/Charges.jsx @@ -254,9 +254,14 @@ export default function Charges() { { key: "created", header: "Eingelagert", width: 130, sortValue: (l) => new Date(l.created_at).getTime(), render: (l) => {new Date(l.created_at).toLocaleDateString("de-DE")} }, - { key: "verbrauch", header: "Verbrauch", label: "Verbrauch", width: 190, align: "num", + // 240 statt 190: Zwei beschriftete Knoepfe brauchen den Platz, und die + // Tabelle schneidet ab statt umzubrechen (.datatable ist table-layout:fixed + // mit overflow:hidden). Bei rechtsbuendigen Zellen faellt dabei der ANFANG + // weg - aus "Verbrauchen" wurde sichtbar "rbrauchen". Das Umbrechen unten + // faengt ab, wenn jemand die Spalte von Hand schmaler zieht. + { key: "verbrauch", header: "Verbrauch", label: "Verbrauch", width: 240, align: "num", render: (l) => ( - +