Some more UI changes. Also added device issue indicator
This commit is contained in:
@@ -367,6 +367,8 @@ export default function DeviceDetail() {
|
|||||||
const [savingNotes, setSavingNotes] = useState(false);
|
const [savingNotes, setSavingNotes] = useState(false);
|
||||||
const notesRef = useRef(null);
|
const notesRef = useRef(null);
|
||||||
const [usersLoading, setUsersLoading] = useState(false);
|
const [usersLoading, setUsersLoading] = useState(false);
|
||||||
|
const [unresolvedIssues, setUnresolvedIssues] = useState(0);
|
||||||
|
const notesPanelRef = useRef(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData();
|
loadData();
|
||||||
@@ -396,6 +398,14 @@ export default function DeviceDetail() {
|
|||||||
setDeviceUsers([]);
|
setDeviceUsers([]);
|
||||||
}).finally(() => setUsersLoading(false));
|
}).finally(() => setUsersLoading(false));
|
||||||
|
|
||||||
|
// Load unresolved issues count
|
||||||
|
api.get(`/equipment/notes?device_id=${id}`).then((data) => {
|
||||||
|
const issues = (data.notes || []).filter(
|
||||||
|
(n) => (n.category === "issue" || n.category === "action_item") && n.status !== "completed"
|
||||||
|
);
|
||||||
|
setUnresolvedIssues(issues.length);
|
||||||
|
}).catch(() => setUnresolvedIssues(0));
|
||||||
|
|
||||||
// Reverse geocode
|
// Reverse geocode
|
||||||
const coords = parseCoordinates(d.device_location_coordinates);
|
const coords = parseCoordinates(d.device_location_coordinates);
|
||||||
if (coords) {
|
if (coords) {
|
||||||
@@ -944,7 +954,7 @@ export default function DeviceDetail() {
|
|||||||
</SectionCard>
|
</SectionCard>
|
||||||
);
|
);
|
||||||
|
|
||||||
const notesSection = <NotesPanel deviceId={id} />;
|
const notesSection = <div ref={notesPanelRef}><NotesPanel deviceId={id} /></div>;
|
||||||
const logsSection = <DeviceLogsPanel deviceSerial={device.device_id} />;
|
const logsSection = <DeviceLogsPanel deviceSerial={device.device_id} />;
|
||||||
|
|
||||||
// ===== Layout rendering =====
|
// ===== Layout rendering =====
|
||||||
@@ -1021,10 +1031,11 @@ export default function DeviceDetail() {
|
|||||||
<button onClick={() => navigate("/devices")} className="text-sm hover:underline mb-2 inline-block" style={{ color: "var(--accent)" }}>
|
<button onClick={() => navigate("/devices")} className="text-sm hover:underline mb-2 inline-block" style={{ color: "var(--accent)" }}>
|
||||||
← Back to Devices
|
← Back to Devices
|
||||||
</button>
|
</button>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-4">
|
||||||
<h1 className="text-2xl font-bold" style={{ color: "var(--text-heading)" }}>
|
<h1 className="text-2xl font-bold" style={{ color: "var(--text-heading)" }}>
|
||||||
{device.device_name || "Unnamed Device"}
|
{device.device_name || "Unnamed Device"}
|
||||||
</h1>
|
</h1>
|
||||||
|
<div style={{ width: 1, height: 24, backgroundColor: "var(--border-primary)" }} />
|
||||||
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
|
<div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
|
||||||
<span
|
<span
|
||||||
className="w-3 h-3 rounded-full inline-block"
|
className="w-3 h-3 rounded-full inline-block"
|
||||||
@@ -1039,6 +1050,19 @@ export default function DeviceDetail() {
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
{unresolvedIssues > 0 && (
|
||||||
|
<>
|
||||||
|
<div style={{ width: 1, height: 24, backgroundColor: "var(--border-primary)" }} />
|
||||||
|
<button
|
||||||
|
onClick={() => notesPanelRef.current?.scrollIntoView({ behavior: "smooth", block: "start" })}
|
||||||
|
className="flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md cursor-pointer hover:opacity-80 transition-opacity"
|
||||||
|
style={{ backgroundColor: "var(--danger-bg)", color: "var(--danger-text)", border: "none" }}
|
||||||
|
>
|
||||||
|
<span style={{ fontSize: "0.85rem", lineHeight: 1 }}>⚠</span>
|
||||||
|
{unresolvedIssues} Issue{unresolvedIssues !== 1 ? "s" : ""} Unresolved
|
||||||
|
</button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{canEdit && (
|
{canEdit && (
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export default function NotesPanel({ deviceId, userId }) {
|
|||||||
{note.title}
|
{note.title}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs truncate" style={{ color: "var(--text-muted)" }}>{note.content}</p>
|
<p className="text-xs" style={{ color: "var(--text-muted)", display: "-webkit-box", WebkitLineClamp: 3, WebkitBoxOrient: "vertical", overflow: "hidden" }}>{note.content}</p>
|
||||||
<p className="text-xs mt-1" style={{ color: "var(--text-muted)" }}>
|
<p className="text-xs mt-1" style={{ color: "var(--text-muted)" }}>
|
||||||
{note.created_by && `${note.created_by} · `}{note.created_at}
|
{note.created_by && `${note.created_by} · `}{note.created_at}
|
||||||
</p>
|
</p>
|
||||||
@@ -256,7 +256,7 @@ export default function NotesPanel({ deviceId, userId }) {
|
|||||||
{msg.subject || "No subject"}
|
{msg.subject || "No subject"}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs truncate" style={{ color: "var(--text-muted)" }}>{msg.message}</p>
|
<p className="text-xs" style={{ color: "var(--text-muted)", display: "-webkit-box", WebkitLineClamp: 3, WebkitBoxOrient: "vertical", overflow: "hidden" }}>{msg.message}</p>
|
||||||
<p className="text-xs mt-1" style={{ color: "var(--text-muted)" }}>
|
<p className="text-xs mt-1" style={{ color: "var(--text-muted)" }}>
|
||||||
{msg.sender_name && `${msg.sender_name} · `}{msg.phone && `${msg.phone} · `}{msg.date_sent}
|
{msg.sender_name && `${msg.sender_name} · `}{msg.phone && `${msg.phone} · `}{msg.date_sent}
|
||||||
</p>
|
</p>
|
||||||
@@ -269,7 +269,7 @@ export default function NotesPanel({ deviceId, userId }) {
|
|||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
className="rounded-lg border p-6"
|
className="rounded-lg border p-6"
|
||||||
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)" }}
|
style={{ backgroundColor: "var(--bg-card)", borderColor: "var(--border-primary)", minWidth: 0, overflow: "hidden" }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<h2 className="text-lg font-semibold" style={{ color: "var(--text-heading)" }}>
|
<h2 className="text-lg font-semibold" style={{ color: "var(--text-heading)" }}>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default function Header() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<h2 className="text-lg font-semibold" style={{ color: "var(--text-heading)" }}>
|
<h2 className="text-lg font-semibold" style={{ color: "var(--text-heading)" }}>
|
||||||
Admin Panel
|
BellSystems - Control Panel
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
|
|||||||
@@ -225,8 +225,8 @@ export default function MelodyDetail() {
|
|||||||
<Field label="Tone">
|
<Field label="Tone">
|
||||||
<span className="capitalize">{info.melodyTone}</span>
|
<span className="capitalize">{info.melodyTone}</span>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Total Active Notes (bells)">{info.totalNotes}</Field>
|
|
||||||
<Field label="Steps">{info.steps}</Field>
|
<Field label="Steps">{info.steps}</Field>
|
||||||
|
<Field label="Total Active Notes (bells)">{info.totalNotes}</Field>
|
||||||
<Field label="Min Speed">{info.minSpeed}</Field>
|
<Field label="Min Speed">{info.minSpeed}</Field>
|
||||||
<Field label="Max Speed">{info.maxSpeed}</Field>
|
<Field label="Max Speed">{info.maxSpeed}</Field>
|
||||||
<Field label="Color">
|
<Field label="Color">
|
||||||
|
|||||||
@@ -309,14 +309,14 @@ export default function UserDetail() {
|
|||||||
</div>
|
</div>
|
||||||
{/* Fields */}
|
{/* Fields */}
|
||||||
<dl className="grid grid-cols-2 md:grid-cols-3 gap-4 flex-1">
|
<dl className="grid grid-cols-2 md:grid-cols-3 gap-4 flex-1">
|
||||||
<Field label="Document ID">
|
<Field label="Title">{user.userTitle}</Field>
|
||||||
|
<Field label="Phone">{user.phone_number}</Field>
|
||||||
|
<Field label="UID">
|
||||||
<span className="font-mono text-xs" style={{ color: "var(--text-muted)" }}>
|
<span className="font-mono text-xs" style={{ color: "var(--text-muted)" }}>
|
||||||
{user.id}
|
{user.uid || user.id}
|
||||||
</span>
|
</span>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="UID">
|
<Field label="Email">{user.email}</Field>
|
||||||
<span className="font-mono text-xs">{user.uid}</span>
|
|
||||||
</Field>
|
|
||||||
<Field label="Status">
|
<Field label="Status">
|
||||||
<span
|
<span
|
||||||
className="px-2 py-0.5 text-xs rounded-full"
|
className="px-2 py-0.5 text-xs rounded-full"
|
||||||
@@ -331,9 +331,6 @@ export default function UserDetail() {
|
|||||||
{user.status || "unknown"}
|
{user.status || "unknown"}
|
||||||
</span>
|
</span>
|
||||||
</Field>
|
</Field>
|
||||||
<Field label="Email">{user.email}</Field>
|
|
||||||
<Field label="Phone">{user.phone_number}</Field>
|
|
||||||
<Field label="Title">{user.userTitle}</Field>
|
|
||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user