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

View File

@@ -146,69 +146,10 @@ input[type="range"]::-moz-range-thumb {
} }
/* Device detail column layout */ /* Device detail column layout */
.device-columns {
display: flex;
gap: 1.5rem;
align-items: flex-start;
}
.device-column { .device-column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.5rem; 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 */ /* File input */