DEV Community

Roberto Luna
Roberto Luna

Posted on

Adding Extension and IP Columns to Guest and Office Phones in a Next.js Dashboard

Adding Extension and IP Columns to Guest and Office Phones in a Next.js Dashboard

TL;DR: I extended the Prisma schema and the dashboard UI to expose phone extension and IP address for both Guest and Office phones. The change required schema migration, TypeScript model updates, and a few tweaks in the data‑grid components.


The Problem

Our internal inventory dashboard lists phones by model, serial number, building, etc. Two fields that the facilities team constantly asks for—dialing extension and network IP—were missing from the Guest Phone and Office Phone tables. The UI displayed a “null” placeholder, and the backend had no way to store the data, leading to manual CSV merges that broke the single source of truth.

The symptom was simple: when querying GuestPhone or OfficePhone via Prisma, the ext and ip properties were always undefined. The UI component DataGrid threw a TypeScript error because the Row interface didn’t contain those keys, and the Prisma client generated type definitions that omitted them entirely.


What I Tried First

My first instinct was to add the two columns directly in the UI layer, treating them as computed fields that would be filled from an external CSV import. I created a temporary ext and ip property inside the React component’s state, hoping to map them later. The approach failed for two reasons:

  1. Type safety broke – the generated Prisma types didn’t include the fields, so TypeScript complained whenever I tried to assign them.
  2. Persistence was missing – without schema changes, the data never persisted to the database, so the UI always reverted to null after a refresh.

I rolled back those changes and decided to address the root cause: the database schema.


The Implementation

1. Extend the Prisma schema

I opened prisma/schema.prisma and added two nullable string columns to both GuestPhone and OfficePhone. The diff looks like this:

@@ -544,6 +544,11 @@ model GuestPhone {
   model String?
   serialNumber String @unique
   building String?
+  // Extensión de marcación ("5" + no. de habitación) e IP — cruzadas desde
+  // el listad
+  ext String?   // e.g., "5123"
+  ip  String?   // e.g., "10.0.2.45"
 }

 model OfficePhone {
   id          Int      @id @default(autoincrement())
   dept        String?
   ext         String?   // added
   serialNumber String   @unique
+  ip          String?   // added
   collaboratorNumber String?
 }
Enter fullscreen mode Exit fullscreen mode

Why nullable? Existing rows don’t have these values yet, so making them optional avoids a migration that would otherwise require default data for every record.

After saving, I ran:

npx prisma migrate dev --name add-ext-ip-to-phones
Enter fullscreen mode Exit fullscreen mode

Prisma generated the migration SQL, added the columns, and updated the client types (@prisma/client).

2. Update TypeScript interfaces

The dashboard pages import a local Row interface to type the data grid rows. I added the new fields in both pages.

src/app/(dashboard)/guest-phones/page.tsx

@@ -9,6 +9,8 @@ interface Row {
   model: string | null;
   building: string | null;
   serialNumber: string;
+  ext: string | null;
+  ip: string | null;
 }
Enter fullscreen mode Exit fullscreen mode

src/app/(dashboard)/office-phones/page.tsx

@@ -10,6 +10,7 @@ interface Row {
   dept: string | null;
   ext: string | null;
   serialNumber: string;
+  ip: string? null;
   collaboratorNumber: string | null;
 }
Enter fullscreen mode Exit fullscreen mode

Note the small typo I caught during the diff: ip: string? null; – I corrected it to ip: string | null; before committing.

3. Adjust the data‑grid column definitions

Both pages use a columns array for the DataGrid. I inserted two new columns after the serialNumber column.

// guest-phones/page.tsx (excerpt)
const columns = [
  { field: 'model', headerName: 'Model', flex: 1 },
  { field: 'building', headerName: 'Building', flex: 1 },
  { field: 'serialNumber', headerName: 'Serial #', flex: 1 },
  { field: 'ext', headerName: 'Ext', flex: 0.5, renderCell: (params) => params.value ?? '-' },
  { field: 'ip', headerName: 'IP', flex: 1, renderCell: (params) => params.value ?? '-' },
];
Enter fullscreen mode Exit fullscreen mode

The same pattern was applied to office-phones/page.tsx. The renderCell fallback ensures the UI stays clean when values are still null.

4. Populate existing rows (optional script)

Since the columns are nullable, the UI works out‑of‑the‑box, but we wanted to seed the data from an existing CSV that the facilities team maintains. I wrote a one‑off script under scripts/seedPhoneMetadata.ts:

import { PrismaClient } from '@prisma/client';
import csv from 'csv-parser';
import fs from 'fs';

const prisma = new PrismaClient();

async function main() {
  const rows: any[] = [];

  fs.createReadStream('data/phone-metadata.csv')
    .pipe(csv())
    .on('data', (data) => rows.push(data))
    .on('end', async () => {
      for (const r of rows) {
        const { serialNumber, ext, ip } = r;
        await prisma.guestPhone.updateMany({
          where: { serialNumber },
          data: { ext: ext || null, ip: ip || null },
        });
        await prisma.officePhone.updateMany({
          where: { serialNumber },
          data: { ext: ext || null, ip: ip || null },
        });
      }
      console.log('Metadata seeded');
    });
}

main()
  .catch(e => console.error(e))
  .finally(() => prisma.$disconnect());
Enter fullscreen mode Exit fullscreen mode

Running ts-node scripts/seedPhoneMetadata.ts filled the new columns for the handful of phones we already had. Future inserts now require ext and ip in the payload, but they remain optional.

5. Verify end‑to‑end

I refreshed the dashboard, added a new Guest Phone via the “Add” modal (which now includes Ext and IP inputs), and saw the values appear instantly in the grid. The Prisma client generated type definitions now expose ext?: string | null and ip?: string | null, so my TypeScript code compiles without any any casts.


Key Takeaway

When you need to expose new data points in a full‑stack app, extend the source of truth first (the database schema). Adding columns directly in the UI creates a type mismatch and forces you to carry temporary work‑arounds. A single migration, followed by interface updates, keeps the type system happy and guarantees persistence.


What's Next

I plan to add inline editing for the ext and ip fields in the data grid, leveraging @tanstack/react-table’s cell editors. That will let the facilities team update phone metadata without leaving the dashboard, closing the feedback loop entirely.


Roberto Luna Osorio – Full Stack Developer & Project Lead

Playa del Carmen, México

vibecoding #buildinpublic #prisma #nextjs #typescript #react #database-migration


Part of my Build in Public series — sharing the real process of building SaaS projects from Playa del Carmen, México.

Repo: zaerohell/pcview · 2026-10-12

#playadev #buildinpublic

Top comments (1)

Collapse
 
dalbaeb123321 profile image
Dalbaeb •

good