The Struggle is Real. More UI Fixes

This commit is contained in:
2026-02-18 20:56:11 +02:00
parent 0a99fb25f3
commit 8d53701d8b
2 changed files with 81 additions and 132 deletions

View File

@@ -65,9 +65,15 @@ function Subsection({ title, children, isFirst = false }) {
);
}
/** A single row of fields that wraps if it doesn't fit */
function FieldRow({ children }) {
return <dl className="flex flex-wrap gap-x-8 gap-y-3 mb-3">{children}</dl>;
/** A grid row of fields — Nth items align across rows within a subsection */
function FieldRow({ children, columns }) {
const childArray = Array.isArray(children) ? children.filter(Boolean) : [children];
const count = columns || childArray.length;
return (
<dl style={{ display: "grid", gridTemplateColumns: `repeat(${count}, 1fr)`, gap: "0.75rem 2rem", marginBottom: "0.75rem" }}>
{children}
</dl>
);
}
// --- Log level styles ---
@@ -452,9 +458,9 @@ export default function DeviceDetail() {
const deviceInfoSection = (
<section className="rounded-lg border p-5" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}>
<div className="device-info-row">
{/* Status */}
<div className="device-info-item">
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gridTemplateRows: "auto auto", gap: "1rem", alignItems: "start" }}>
{/* Row 1, Col 1: Status */}
<div style={{ display: "flex", alignItems: "center", gap: "0.75rem" }}>
<div
className="w-10 h-10 rounded-full flex items-center justify-center shrink-0"
style={{ backgroundColor: isOnline ? "var(--success-bg)" : "var(--bg-card-hover)" }}
@@ -477,29 +483,28 @@ export default function DeviceDetail() {
</div>
</div>
{/* Serial + Hardware Variant */}
<div className="device-info-item">
<div>
<div className="text-xs font-medium uppercase tracking-wide" style={{ color: "var(--text-muted)" }}>Serial Number</div>
<div className="text-sm font-mono mt-0.5" style={{ color: "var(--text-primary)" }}>{device.device_id}</div>
<div className="text-xs mt-0.5" style={{ color: "var(--text-muted)" }}>HW: VesperCore</div>
</div>
{/* Row 1, Col 2: Hardware Variant */}
<div>
<div className="text-xs font-medium uppercase tracking-wide" style={{ color: "var(--text-muted)" }}>Hardware Variant</div>
<div className="text-sm mt-0.5" style={{ color: "var(--text-primary)" }}>VesperCore</div>
</div>
{/* Admin Note */}
<div className="device-info-item">
<div>
<div className="text-xs font-medium uppercase tracking-wide" style={{ color: "var(--text-muted)" }}>Admin Note</div>
<div className="text-sm mt-0.5" style={{ color: "var(--text-muted)" }}>-</div>
</div>
{/* Row 1-2, Col 3: Admin Notes (spans 2 rows) */}
<div style={{ gridRow: "1 / 3" }}>
<div className="text-xs font-medium uppercase tracking-wide" style={{ color: "var(--text-muted)" }}>Admin Notes</div>
<div className="text-sm mt-0.5" style={{ color: "var(--text-muted)" }}>-</div>
</div>
{/* Document ID */}
<div className="device-info-item">
<div>
<div className="text-xs font-medium uppercase tracking-wide" style={{ color: "var(--text-muted)" }}>Document ID</div>
<div className="text-xs font-mono mt-0.5" style={{ color: "var(--text-muted)" }}>{device.id}</div>
</div>
{/* Row 2, Col 1: Serial Number */}
<div>
<div className="text-xs font-medium uppercase tracking-wide" style={{ color: "var(--text-muted)" }}>Serial Number</div>
<div className="text-sm font-mono mt-0.5" style={{ color: "var(--text-primary)" }}>{device.device_id}</div>
</div>
{/* Row 2, Col 2: Document ID */}
<div>
<div className="text-xs font-medium uppercase tracking-wide" style={{ color: "var(--text-muted)" }}>Document ID</div>
<div className="text-xs font-mono mt-0.5" style={{ color: "var(--text-muted)" }}>{device.id}</div>
</div>
</div>
</section>
@@ -534,10 +539,12 @@ export default function DeviceDetail() {
);
const locationSection = (
<SectionCard title="Location">
<section className="rounded-lg border" style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}>
{coords ? (
<div className="location-split">
<div className="location-fields">
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 0, height: "100%" }}>
{/* Left column: title + fields */}
<div className="p-6">
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>Location</h2>
<dl className="space-y-4">
<Field label="Location">{device.device_location}</Field>
<Field label="Coordinates">
@@ -558,8 +565,9 @@ export default function DeviceDetail() {
{locationName && <Field label="Nearest Place">{locationName}</Field>}
</dl>
</div>
<div className="location-map">
<div className="rounded-md overflow-hidden border w-full" style={{ borderColor: "var(--border-primary)", height: 250 }}>
{/* Right column: map with equal padding */}
<div style={{ padding: "1rem", display: "flex" }}>
<div className="rounded-md overflow-hidden border" style={{ borderColor: "var(--border-primary)", flex: 1 }}>
<MapContainer center={[coords.lat, coords.lng]} zoom={13} style={{ height: "100%", width: "100%" }} scrollWheelZoom={false}>
<TileLayer
attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'
@@ -571,12 +579,15 @@ export default function DeviceDetail() {
</div>
</div>
) : (
<dl className="space-y-4">
<Field label="Location">{device.device_location}</Field>
<Field label="Coordinates">{device.device_location_coordinates || "-"}</Field>
</dl>
<div className="p-6">
<h2 className="text-lg font-semibold mb-4" style={{ color: "var(--text-heading)" }}>Location</h2>
<dl className="space-y-4">
<Field label="Location">{device.device_location}</Field>
<Field label="Coordinates">{device.device_location_coordinates || "-"}</Field>
</dl>
</div>
)}
</SectionCard>
</section>
);
const deviceSettingsSection = (
@@ -592,27 +603,29 @@ export default function DeviceDetail() {
</FieldRow>
</Subsection>
<Subsection title="Alert Settings">
<FieldRow>
<FieldRow columns={3}>
<Field label="Alerts Status"><BoolBadge value={clock.ringAlertsMasterOn} yesLabel="ON" noLabel="OFF" /></Field>
<Field label="Alerts Type"><span className="capitalize">{clock.ringAlerts || "-"}</span></Field>
<Field label="Ring Intervals">{clock.ringIntervals}</Field>
</FieldRow>
<FieldRow>
<FieldRow columns={3}>
<Field label="Hour Bell">{clock.hourAlertsBell}</Field>
<Field label="Half-Hour Bell">{clock.halfhourAlertsBell}</Field>
<Field label="Quarter Bell">{clock.quarterAlertsBell}</Field>
</FieldRow>
<FieldRow>
<FieldRow columns={3}>
<Field label="Daytime Silence"><BoolBadge value={clock.isDaySilenceOn} yesLabel="ON" noLabel="OFF" /></Field>
<Field label="Day-Time Period">
{formatTimestamp(clock.daySilenceFrom)} - {formatTimestamp(clock.daySilenceTo)}
</Field>
<div />
</FieldRow>
<FieldRow>
<FieldRow columns={3}>
<Field label="Nighttime Silence"><BoolBadge value={clock.isNightSilenceOn} yesLabel="ON" noLabel="OFF" /></Field>
<Field label="Nighttime Period">
{formatTimestamp(clock.nightSilenceFrom)} - {formatTimestamp(clock.nightSilenceTo)}
</Field>
<div />
</FieldRow>
</Subsection>
<Subsection title="Backlight Settings">
@@ -635,27 +648,27 @@ export default function DeviceDetail() {
{/* Right Column */}
<div>
<Subsection title="Network" isFirst>
<FieldRow>
<FieldRow columns={2}>
<Field label="Hostname">{net.hostname}</Field>
<Field label="Has Static IP"><BoolBadge value={net.useStaticIP} /></Field>
</FieldRow>
</Subsection>
<Subsection title="Clock Settings">
<FieldRow>
<FieldRow columns={2}>
<Field label="Has Clock"><BoolBadge value={attr.hasClock} /></Field>
<Field label="Ring Intervals">{clock.ringIntervals}</Field>
</FieldRow>
<FieldRow>
<FieldRow columns={2}>
<Field label="Odd Output">{clock.clockOutputs?.[0] ?? "-"}</Field>
<Field label="Even Output">{clock.clockOutputs?.[1] ?? "-"}</Field>
</FieldRow>
<FieldRow>
<FieldRow columns={2}>
<Field label="Run Pulse">{clock.clockTimings?.[0] != null ? msToSeconds(clock.clockTimings[0]) : "-"}</Field>
<Field label="Pause Pulse">{clock.clockTimings?.[1] != null ? msToSeconds(clock.clockTimings[1]) : "-"}</Field>
</FieldRow>
</Subsection>
<Subsection title="Bell Settings">
<FieldRow>
<FieldRow columns={2}>
<Field label="Bells Active"><BoolBadge value={attr.hasBells} /></Field>
<Field label="Total">{attr.totalBells ?? "-"}</Field>
</FieldRow>
@@ -844,26 +857,23 @@ export default function DeviceDetail() {
const renderDoubleColumn = () => (
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
{/* Row 1: Device Info + Subscription — equal height */}
<div className="device-equal-row">
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "1.5rem", alignItems: "stretch" }}>
{deviceInfoSection}
{subscriptionSection}
</div>
{/* Row 2: Device Settings — full width */}
{deviceSettingsSection}
{/* Row 3: Location+Misc (left) vs Warranty (right) */}
<div className="device-columns">
<div className="device-flex-fill" style={{ flex: 1, gap: "1.5rem" }}>
<div className="flex-grow">{locationSection}</div>
{miscSection}
</div>
<div style={{ flex: 1, minWidth: 0 }}>
{warrantySection}
</div>
{/* Row 3: Location + Warranty */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "1.5rem", alignItems: "start" }}>
{locationSection}
{warrantySection}
</div>
{/* Row 4: Notes vs App Users */}
<div className="device-columns">
<div style={{ flex: 1, minWidth: 0 }}>{notesSection}</div>
<div style={{ flex: 1, minWidth: 0 }}>{appUsersSection}</div>
{/* Row 4: Misc full width */}
{miscSection}
{/* Row 5: Notes + App Users — equal width */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "1.5rem", alignItems: "start" }}>
{notesSection}
{appUsersSection}
</div>
{/* Latest Logs */}
{logsSection}
@@ -872,24 +882,22 @@ export default function DeviceDetail() {
const renderTripleColumn = () => (
<div style={{ display: "flex", flexDirection: "column", gap: "1.5rem" }}>
{/* Row 1: DevInfo+Subscription (cols 1-2 equal height) + Location (col 3) */}
<div className="device-columns">
<div className="device-equal-row" style={{ flex: 2 }}>
{deviceInfoSection}
{subscriptionSection}
</div>
<div style={{ flex: 1, minWidth: 0 }}>{locationSection}</div>
{/* Row 1: DevInfo + Subscription + Location — all equal height */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "1.5rem", alignItems: "stretch" }}>
{deviceInfoSection}
{subscriptionSection}
{locationSection}
</div>
{/* Row 2: Device Settings (cols 1-2) + Warranty (col 3) */}
<div className="device-columns">
<div style={{ flex: 2, minWidth: 0 }}>{deviceSettingsSection}</div>
<div style={{ flex: 1, minWidth: 0 }}>{warrantySection}</div>
<div style={{ display: "grid", gridTemplateColumns: "2fr 1fr", gap: "1.5rem", alignItems: "stretch" }}>
{deviceSettingsSection}
{warrantySection}
</div>
{/* Row 3: Misc (col1) + Notes (col2) + App Users (col3) */}
<div className="device-columns">
<div style={{ flex: 1, minWidth: 0 }}>{miscSection}</div>
<div style={{ flex: 1, minWidth: 0 }}>{notesSection}</div>
<div style={{ flex: 1, minWidth: 0 }}>{appUsersSection}</div>
{/* Row 3: Misc + Notes + App Users — equal width */}
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "1.5rem", alignItems: "start" }}>
{miscSection}
{notesSection}
{appUsersSection}
</div>
{/* Latest Logs */}
{logsSection}

View File

@@ -146,69 +146,10 @@ input[type="range"]::-moz-range-thumb {
}
/* Device detail column layout */
.device-columns {
display: flex;
gap: 1.5rem;
align-items: flex-start;
}
.device-column {
display: flex;
flex-direction: column;
gap: 1.5rem;
flex: 1;
min-width: 0;
}
.device-full-row {
width: 100%;
margin-bottom: 1.5rem;
}
.device-equal-row {
display: flex;
gap: 1.5rem;
align-items: stretch;
}
.device-equal-row > * {
flex: 1;
min-width: 0;
}
.device-flex-fill {
display: flex;
flex-direction: column;
}
.device-flex-fill > .flex-grow {
flex: 1;
}
/* Device info horizontal subsections */
.device-info-row {
display: flex;
flex-wrap: wrap;
gap: 0;
}
.device-info-row > .device-info-item {
padding: 0 1.5rem;
border-left: 1px solid var(--border-secondary);
display: flex;
align-items: center;
gap: 0.75rem;
}
.device-info-row > .device-info-item:first-child {
padding-left: 0;
border-left: none;
}
/* Location 2-column internal layout */
.location-split {
display: flex;
gap: 1.5rem;
align-items: stretch;
}
.location-split > .location-fields {
flex: 0 0 auto;
min-width: 200px;
}
.location-split > .location-map {
flex: 1;
display: flex;
align-items: center;
}
/* File input */