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) => (
-
+