Adding Real‑Time IP Communication Status to the TV Inventory Dashboard
TL;DR: I extended the Prisma schema and React UI to surface a boolean “IP Comm” flag for each TV device, letting the inventory table show live connectivity status. The change required a new column, type updates, a badge component, and a small store tweak—all done without breaking existing queries.
The Problem
Our inventory page lists hundreds of TV devices, but we had no way to tell if a TV was currently reachable over the network. The backend periodically ingests a CSV report (sis_report.csv) that contains a terminalAddress (MAC) and a boolean ip_comm indicating whether the TV responded to a ping test. The UI displayed a “—” for that column because the Prisma model TvDevice didn’t have a matching field, resulting in a runtime error:
TypeError: Cannot read property 'ipComm' of undefined
at InventoryTable (src/features/inventory/InventoryTable.tsx:42:27)
The missing field also meant we couldn’t filter or sort by connectivity status, limiting our ability to quickly isolate offline units.
What I Tried First
My first attempt was to compute the IP status on the client side by joining the CSV data with the fetched devices in useDevicesStore. I added a helper calcIpComm(devices, csvRows) that returned a map of MAC → boolean. Then I patched each TvDeviceRow with ipComm before rendering.
Problems:
- Performance hit – The CSV can be >10 k rows; parsing it on every page load caused a noticeable UI lag (>500 ms).
- Stale data – The CSV is refreshed by a separate cron job; the client‑side merge never updated until a full page reload.
-
Type mismatch – The
TvDeviceRowinterface inuse-devices-store.tsdidn’t have anipCommfield, so TypeScript threw errors across the codebase.
I reverted that approach and decided to push the IP flag into the database so the UI could treat it like any other column.
The Implementation
1. Extend the Prisma schema
File: prisma/schema.prisma
@@ -49,6 +49,13 @@ model TvDevice {
tvPowerOns7Days Int?
stateModified DateTime?
+ // IP communication status – derived from the SIS report (MAC ↔ ip_comm)
+ // Null means we haven’t processed a report for this device yet.
+ ipComm Boolean? @default(null)
+ ipCommCheckedAt DateTime? @default(null)
}
Why Boolean? The SIS report only tells us “reachable” (true) or “not reachable” (false). Using null signals “unknown” – useful for newly added TVs that haven’t been scanned yet.
After the schema change I ran:
npx prisma migrate dev --name add-ip-comm-status
Prisma generated the migration SQL:
ALTER TABLE "TvDevice" ADD COLUMN "ipComm" BOOLEAN;
ALTER TABLE "TvDevice" ADD COLUMN "ipCommCheckedAt" TIMESTAMP;
2. Update the TypeScript store
File: src/hooks/use-devices-store.ts
@@ -21,6 +21,8 @@ export interface TvDeviceRow {
chromecastState: string | null;
stays60Days: number | null;
tvPowerOns7Days: number | null;
+ ipComm: boolean | null;
+ ipCommCheckedAt: string | null;
}
The store pulls rows via prisma.tvDevice.findMany(). Adding the fields required no extra code because Prisma automatically includes them in the generated type.
3. Create a badge component for IP status
File: src/components/inventory/status-badge.tsx
export function IpCommBadge({ ipComm }: { ipComm: boolean | null }) {
if (ipComm === null) {
return <Badge variant="neutral">—</Badge>;
}
return ipComm ? (
<Badge variant="success">Online</Badge>
) : (
<Badge variant="danger">Offline</Badge>
);
}
I kept the UI consistent with existing badges (OccupiedBadge, OutOfServiceBadge). The component returns a neutral dash for unknown status, mirroring the rest of the table.
4. Wire the badge into the inventory table
File: src/features/inventory/InventoryTable.tsx
@@ -18,6 +18,7 @@ import {
SerialBadge,
OutOfServiceBadge,
OccupiedBadge,
+ IpCommBadge,
} from "@/components/inventory/status-badge";
Then inside the row rendering loop:
<td className="px-4 py-2">
<IpCommBadge ipComm={row.ipComm} />
</td>
I also added a column header:
<th className="px-4 py-2 text-left">IP Comm</th>
5. Populate the new columns from the SIS report
The backend job that processes sis_report.csv already runs nightly. I extended its parser (src/jobs/process-sis-report.ts) to upsert the ipComm and ipCommCheckedAt fields:
await prisma.tvDevice.upsert({
where: { terminalAddress: mac },
update: {
ipComm: ipCommFlag,
ipCommCheckedAt: new Date(),
},
create: {
terminalAddress: mac,
// other required fields...
ipComm: ipCommFlag,
ipCommCheckedAt: new Date(),
},
});
Because the fields are optional, existing rows are updated without a migration lock, and newly discovered devices receive the flag immediately.
6. Adjust the API response shape
Our GraphQL resolver (src/graphql/resolvers/tvDevice.ts) now includes ipComm and ipCommCheckedAt in the selection set:
return prisma.tvDevice.findMany({
select: {
id: true,
name: true,
ipComm: true,
ipCommCheckedAt: true,
// …other fields
},
});
No client‑side changes were needed beyond the badge import.
Key Takeaway
Never treat a missing column as a UI‑only problem; push the data into the source of truth. Adding a boolean flag to the Prisma model gave us a single source of truth, eliminated client‑side merging, and let us reuse existing type‑generation pipelines. The pattern—extend the schema, run a migration, update the store interface, and add a small UI component—scales cleanly for any new telemetry field.
What's Next
I plan to add a real‑time WebSocket subscription that pushes ipComm updates as soon as the SIS job finishes, so the badge flips from “—” to “Online/Offline” without a page refresh. This will involve:
- Adding a
pubsub.publish('IP_COMM_UPDATED', device)call in the job. - Exposing a GraphQL subscription resolver.
- Updating
InventoryTableto listen viauseSubscription.
That will close the loop between data ingestion and UI responsiveness.
Tags: #vibecoding #buildinpublic #typescript #react #prisma #nextjs #graphql #webdev
Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.
Repo: zaerohell/tvview · 2026-10-12
#playadev #buildinpublic
Top comments (0)