The Struggle is Real. More UI Fixes
This commit is contained in:
@@ -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='© <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}
|
||||
|
||||
Reference in New Issue
Block a user