visual fixes
This commit is contained in:
@@ -31,6 +31,7 @@ export const DEFAULT_UNIT_DEFINITIONS = [
|
||||
{ symbol: "V", label: "Volt", isSystem: true },
|
||||
{ symbol: "A", label: "Ampere", isSystem: true },
|
||||
{ symbol: "Pa", label: "Pascal", isSystem: true },
|
||||
{ symbol: "bool", label: "Digital / Bool", isSystem: true },
|
||||
];
|
||||
|
||||
function safeParse(jsonValue) {
|
||||
@@ -267,6 +268,7 @@ export async function getPreferences(redis, userId) {
|
||||
theme: "system",
|
||||
csvDelimiter: ";",
|
||||
sidebarPinned: true,
|
||||
trendLineWidth: 1.8,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -278,6 +280,7 @@ export async function savePreferences(redis, userId, preferences) {
|
||||
theme: preferences.theme || current.theme || "system",
|
||||
csvDelimiter: preferences.csvDelimiter || current.csvDelimiter || ";",
|
||||
sidebarPinned: typeof preferences.sidebarPinned === "boolean" ? preferences.sidebarPinned : current.sidebarPinned,
|
||||
trendLineWidth: Number.isFinite(Number(preferences.trendLineWidth)) ? Number(preferences.trendLineWidth) : Number(current.trendLineWidth || 1.8),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
await redis.set(`preferences:${userId}`, JSON.stringify(payload));
|
||||
|
||||
+77153
File diff suppressed because one or more lines are too long
+23
-3
@@ -59,7 +59,7 @@ const emptyTrendRange = {
|
||||
presetDays: "1",
|
||||
from: "",
|
||||
to: "",
|
||||
maxRows: 4000,
|
||||
maxRows: 20000,
|
||||
};
|
||||
|
||||
const defaultSelectionDraft = {
|
||||
@@ -700,6 +700,7 @@ export default function App() {
|
||||
const [themeMode, setThemeMode] = useState("system");
|
||||
const [csvDelimiter, setCsvDelimiter] = useState(";");
|
||||
const [sidebarPinned, setSidebarPinned] = useState(true);
|
||||
const [trendLineWidth, setTrendLineWidth] = useState("1.8");
|
||||
const [sidebarHover, setSidebarHover] = useState(false);
|
||||
const [trendFocus, setTrendFocus] = useState(false);
|
||||
const [adminIsp, setAdminIsp] = useState("");
|
||||
@@ -738,6 +739,8 @@ export default function App() {
|
||||
const sidebarCollapsed = !sidebarPinned && !sidebarHover;
|
||||
const currentView = views.find((view) => view.id === activeView) || views[0];
|
||||
const selectionListClassName = selections.length <= 2 ? "selection-list compact" : "selection-list";
|
||||
const trendMaxRows = Number(trendRange.maxRows) || 0;
|
||||
const showTrendLimitWarning = trendMaxRows > 20000;
|
||||
const roleOptions = roles.length ? roles : [
|
||||
{ name: "viewer", label: "Viewer" },
|
||||
{ name: "technician", label: "Techniker" },
|
||||
@@ -888,6 +891,7 @@ export default function App() {
|
||||
setThemeMode(pref.theme || "system");
|
||||
setCsvDelimiter(pref.csvDelimiter || ";");
|
||||
setSidebarPinned(typeof pref.sidebarPinned === "boolean" ? pref.sidebarPinned : true);
|
||||
setTrendLineWidth(String(pref.trendLineWidth || 1.8));
|
||||
setIsps(ispResult.isps || []);
|
||||
setSelections(selectionResult.selections);
|
||||
setDashboard(dashboardResult);
|
||||
@@ -967,8 +971,9 @@ export default function App() {
|
||||
theme: themeMode,
|
||||
csvDelimiter,
|
||||
sidebarPinned,
|
||||
trendLineWidth: Number(trendLineWidth),
|
||||
}).catch(() => undefined);
|
||||
}, [csvDelimiter, preferencesReady, sidebarPinned, themeMode, token]);
|
||||
}, [csvDelimiter, preferencesReady, sidebarPinned, themeMode, token, trendLineWidth]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedIsp || metadataCache[selectedIsp]) {
|
||||
@@ -2180,6 +2185,7 @@ export default function App() {
|
||||
onCsvDelimiterChange={setCsvDelimiter}
|
||||
allowExports
|
||||
allowFocusMode
|
||||
lineWidth={Number(trendLineWidth)}
|
||||
/>
|
||||
) : (
|
||||
<div className="panel empty-state slim-empty">
|
||||
@@ -2258,7 +2264,18 @@ export default function App() {
|
||||
</label>
|
||||
<label>
|
||||
<span>Max. Zeilen</span>
|
||||
<input type="number" min="100" max="10000" value={trendRange.maxRows} onChange={(event) => setTrendRange((current) => ({ ...current, maxRows: event.target.value }))} />
|
||||
<input type="number" min="100" value={trendRange.maxRows} onChange={(event) => setTrendRange((current) => ({ ...current, maxRows: event.target.value }))} />
|
||||
</label>
|
||||
<label>
|
||||
<span>Liniendicke</span>
|
||||
<select value={trendLineWidth} onChange={(event) => setTrendLineWidth(event.target.value)}>
|
||||
<option value="1">1.0 px</option>
|
||||
<option value="1.4">1.4 px</option>
|
||||
<option value="1.8">1.8 px</option>
|
||||
<option value="2.2">2.2 px</option>
|
||||
<option value="2.8">2.8 px</option>
|
||||
<option value="3.4">3.4 px</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Start</span>
|
||||
@@ -2270,6 +2287,8 @@ export default function App() {
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{showTrendLimitWarning ? <p className="warning-note">{"Mehr als 20000 Datensätze sind möglich, können aber zu längeren Ladezeiten führen."}</p> : null}
|
||||
|
||||
<div className="button-row">
|
||||
<button className="primary-button compact-button" type="button" onClick={runTrendQuery}>{busy ? "Lädt ..." : "Trend anzeigen"}</button>
|
||||
</div>
|
||||
@@ -2328,6 +2347,7 @@ export default function App() {
|
||||
onCsvDelimiterChange={setCsvDelimiter}
|
||||
allowExports
|
||||
allowFocusMode
|
||||
lineWidth={Number(trendLineWidth)}
|
||||
/>
|
||||
) : (
|
||||
<div className="panel empty-state slim-empty">
|
||||
|
||||
@@ -72,9 +72,12 @@ export default function TrendChart({
|
||||
allowExports = true,
|
||||
allowFocusMode = true,
|
||||
compact = false,
|
||||
lineWidth = 1.8,
|
||||
}) {
|
||||
const wrapperRef = useRef(null);
|
||||
const chartHostRef = useRef(null);
|
||||
const dragBoxRef = useRef(null);
|
||||
const domainRef = useRef({ x: [0, 1], y: [0, 1] });
|
||||
const [selectedKeys, setSelectedKeys] = useState([]);
|
||||
const [dragBox, setDragBox] = useState(null);
|
||||
const [zoomDomain, setZoomDomain] = useState(null);
|
||||
@@ -116,17 +119,23 @@ export default function TrendChart({
|
||||
const xDomain = zoomDomain?.x || baseXDomain;
|
||||
const yDomain = zoomDomain?.y || baseYDomain;
|
||||
const hasFocusSelection = selectedKeys.length > 0 && selectedKeys.length < series.length;
|
||||
const normalizedLineWidth = Number.isFinite(Number(lineWidth)) ? Number(lineWidth) : 1.8;
|
||||
|
||||
useEffect(() => {
|
||||
domainRef.current = { x: xDomain, y: yDomain };
|
||||
}, [xDomain, yDomain]);
|
||||
|
||||
const mapDragToDomain = (clientX, clientY) => {
|
||||
const rect = wrapperRef.current?.getBoundingClientRect();
|
||||
const rect = chartHostRef.current?.getBoundingClientRect() || wrapperRef.current?.getBoundingClientRect();
|
||||
if (!rect) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const activeDomain = domainRef.current;
|
||||
const xRatio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
||||
const yRatio = Math.max(0, Math.min(1, (clientY - rect.top) / rect.height));
|
||||
const xValue = xDomain[0] + (xDomain[1] - xDomain[0]) * xRatio;
|
||||
const yValue = yDomain[1] - (yDomain[1] - yDomain[0]) * yRatio;
|
||||
const xValue = activeDomain.x[0] + (activeDomain.x[1] - activeDomain.x[0]) * xRatio;
|
||||
const yValue = activeDomain.y[1] - (activeDomain.y[1] - activeDomain.y[0]) * yRatio;
|
||||
|
||||
return {
|
||||
xValue,
|
||||
@@ -145,7 +154,9 @@ export default function TrendChart({
|
||||
if (!mapped) {
|
||||
return;
|
||||
}
|
||||
setDragBox({ start: mapped, end: mapped });
|
||||
const nextDragBox = { start: mapped, end: mapped };
|
||||
dragBoxRef.current = nextDragBox;
|
||||
setDragBox(nextDragBox);
|
||||
};
|
||||
|
||||
const updateDragBox = (clientX, clientY) => {
|
||||
@@ -153,7 +164,14 @@ export default function TrendChart({
|
||||
if (!mapped) {
|
||||
return;
|
||||
}
|
||||
setDragBox((current) => current ? { ...current, end: mapped } : current);
|
||||
setDragBox((current) => {
|
||||
if (!current) {
|
||||
return current;
|
||||
}
|
||||
const nextDragBox = { ...current, end: mapped };
|
||||
dragBoxRef.current = nextDragBox;
|
||||
return nextDragBox;
|
||||
});
|
||||
};
|
||||
|
||||
const finalizeDragBox = (box) => {
|
||||
@@ -163,15 +181,18 @@ export default function TrendChart({
|
||||
|
||||
const deltaX = Math.abs(box.end.xPixel - box.start.xPixel);
|
||||
const deltaY = Math.abs(box.end.yPixel - box.start.yPixel);
|
||||
if (deltaX < 12 || deltaY < 12) {
|
||||
if (deltaX < 12 && deltaY < 12) {
|
||||
dragBoxRef.current = null;
|
||||
setDragBox(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const currentDomain = domainRef.current;
|
||||
setZoomDomain({
|
||||
x: [Math.min(box.start.xValue, box.end.xValue), Math.max(box.start.xValue, box.end.xValue)],
|
||||
y: [Math.min(box.start.yValue, box.end.yValue), Math.max(box.start.yValue, box.end.yValue)],
|
||||
x: deltaX < 12 ? currentDomain.x : [Math.min(box.start.xValue, box.end.xValue), Math.max(box.start.xValue, box.end.xValue)],
|
||||
y: deltaY < 12 ? currentDomain.y : [Math.min(box.start.yValue, box.end.yValue), Math.max(box.start.yValue, box.end.yValue)],
|
||||
});
|
||||
dragBoxRef.current = null;
|
||||
setDragBox(null);
|
||||
};
|
||||
|
||||
@@ -185,7 +206,7 @@ export default function TrendChart({
|
||||
};
|
||||
|
||||
const handleWindowMouseUp = () => {
|
||||
finalizeDragBox(dragBox);
|
||||
finalizeDragBox(dragBoxRef.current);
|
||||
};
|
||||
|
||||
window.addEventListener("mousemove", handleWindowMouseMove);
|
||||
@@ -444,10 +465,11 @@ export default function TrendChart({
|
||||
<Line
|
||||
key={entry.key}
|
||||
yAxisId={entry.kind === "digital" ? "digital" : "analog"}
|
||||
type={entry.kind === "digital" ? "stepAfter" : "monotone"}
|
||||
type={entry.kind === "digital" ? "stepAfter" : "linear"}
|
||||
dataKey={entry.key}
|
||||
dot={false}
|
||||
strokeWidth={selectedKeys.includes(entry.key) ? 2.35 : 1.8}
|
||||
connectNulls
|
||||
strokeWidth={selectedKeys.includes(entry.key) ? normalizedLineWidth + 0.55 : normalizedLineWidth}
|
||||
stroke={entry.color}
|
||||
strokeOpacity={isolated ? 0.1 : 1}
|
||||
isAnimationActive={false}
|
||||
|
||||
+94
-10
@@ -525,27 +525,42 @@ span {
|
||||
.user-row,
|
||||
.stat-card {
|
||||
justify-content: space-between;
|
||||
padding: 0.5rem 0.58rem;
|
||||
padding: 0.42rem 0.5rem;
|
||||
}
|
||||
|
||||
.point-copy,
|
||||
.user-copy {
|
||||
display: grid;
|
||||
gap: 0.06rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.point-item {
|
||||
align-items: flex-start;
|
||||
gap: 0.42rem;
|
||||
}
|
||||
|
||||
.point-item .point-copy strong,
|
||||
.point-item .point-copy span {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.selection-chips {
|
||||
min-height: 0;
|
||||
gap: 0.34rem;
|
||||
gap: 0.28rem;
|
||||
}
|
||||
|
||||
.chip {
|
||||
display: grid;
|
||||
gap: 0.12rem;
|
||||
border-left: 3px solid var(--brand-2);
|
||||
min-width: 94px;
|
||||
padding: 0.34rem 0.42rem;
|
||||
border-radius: 12px;
|
||||
gap: 0.1rem;
|
||||
border-left: 2px solid var(--brand-2);
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
padding: 0.26rem 0.36rem;
|
||||
border-radius: 10px;
|
||||
flex: 1 1 150px;
|
||||
}
|
||||
|
||||
.chip strong,
|
||||
@@ -556,8 +571,21 @@ span {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.chip strong,
|
||||
.chip span {
|
||||
font-size: 11px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.chip strong {
|
||||
font-size: 10px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.chip span {
|
||||
font-size: 10px;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.chart-panel {
|
||||
@@ -572,6 +600,8 @@ span {
|
||||
width: 100%;
|
||||
min-height: 320px;
|
||||
user-select: none;
|
||||
cursor: crosshair;
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.chart-wrapper.dragging {
|
||||
@@ -641,7 +671,8 @@ span {
|
||||
|
||||
.latest-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(215px, 1fr));
|
||||
gap: 0.42rem;
|
||||
}
|
||||
|
||||
.trend-layout {
|
||||
@@ -674,6 +705,48 @@ span {
|
||||
border-left: 4px solid var(--brand-2);
|
||||
}
|
||||
|
||||
.latest-card {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.latest-card > div:first-child {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.latest-card small,
|
||||
.latest-card strong,
|
||||
.latest-card .value-stamp {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.latest-card small {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.latest-card strong {
|
||||
font-size: 11px;
|
||||
line-height: 1.18;
|
||||
}
|
||||
|
||||
.latest-card .stat-value {
|
||||
justify-content: flex-end;
|
||||
min-width: max-content;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.latest-card .stat-value span {
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
@@ -791,6 +864,12 @@ span {
|
||||
border: 1px solid rgba(37, 99, 235, 0.22);
|
||||
}
|
||||
|
||||
.warning-note {
|
||||
margin: 0;
|
||||
color: #c2410c;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.checkbox-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -889,9 +968,14 @@ span {
|
||||
.protocol-grid,
|
||||
.inline-fields,
|
||||
.inline-fields.multi-line,
|
||||
.trend-layout {
|
||||
.trend-layout,
|
||||
.latest-card {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.latest-card .stat-value {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user