// ============================================================ // EXTENSIONS — first tab ported from vanilla JS to React. // Read-only list view with a simple add form. The old vanilla // version has richer UI (trash, restore, purge, search) — this // minimum-viable port proves the migration pipeline works: // shared types + api wrapper + React Query + Tailwind shadcn. // The full CRUD UI lands in a follow-up when polish time arrives. // ============================================================ import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { api } from '@/lib/api'; import type { ExtensionsListOk, Extension } from '@/shared/types'; import { ExtensionCreateSchema, type ExtensionCreate } from '@/shared/schemas'; function ExtensionRow({ ext }: { ext: Extension }) { return (
{ext.name}
{ext.location}
{ext.number}
{ext.type}
); } function AddForm({ onDone }: { onDone: () => void }) { const qc = useQueryClient(); const [form, setForm] = useState({ location: '', name: '', number: '', type: 'extension', notes: '', }); const [error, setError] = useState(null); const createMutation = useMutation({ mutationFn: (body: ExtensionCreate) => api.post<{ id: number }>('/api/extensions', body), onSuccess: () => { qc.invalidateQueries({ queryKey: ['extensions'] }); onDone(); }, onError: (e: Error) => setError(e.message), }); function submit(e: React.FormEvent) { e.preventDefault(); setError(null); const parsed = ExtensionCreateSchema.safeParse(form); if (!parsed.success) { setError(parsed.error.issues.map((i: { message: string }) => i.message).join(', ')); return; } createMutation.mutate(parsed.data); } const input = 'w-full rounded-md border border-input bg-background px-3 py-2 text-sm'; return (
setForm({ ...form, location: e.target.value })} /> setForm({ ...form, name: e.target.value })} /> setForm({ ...form, number: e.target.value })} />
{error &&
{error}
}
); } export default function Extensions() { const [adding, setAdding] = useState(false); const { data, isLoading, error } = useQuery({ queryKey: ['extensions'], queryFn: () => api.get('/api/extensions'), }); return (

Pagers & Extensions

Per-user directory. This React port is the migration proof-of-life.

{!adding && ( )}
{adding && setAdding(false)} />} {isLoading &&
Loading…
} {error &&
{(error as Error).message}
} {data && data.items.length === 0 && (
No extensions yet. Click Add to create the first one.
)} {data && data.items.length > 0 && (
{data.items.map((ext: Extension) => )}
)}
); }