← back to Stayclaim
src/components/PermitTable.tsx
137 lines
import { SourceLinkInline } from './SourceLink';
import type { RealPermit, CodeEnforcementCase, NewsAddressMention } from '@/lib/db';
/** Permits attached to a listing — LADBS / BH / Pasadena / SM. Real records, real links. */
export function PermitTable({ permits }: { permits: RealPermit[] }) {
if (!permits?.length) return null;
return (
<section className="max-w-6xl mx-auto px-6 py-12 border-t border-ink/10">
<div className="flex items-baseline justify-between gap-4 mb-5 flex-wrap">
<div>
<p className="text-xs uppercase tracking-[0.15em] text-ink/50">Building permits</p>
<h2 className="font-display text-3xl text-ink tracking-[-0.01em]">
{permits.length} {permits.length === 1 ? 'permit' : 'permits'} on file
</h2>
</div>
<p className="text-[10px] uppercase tracking-[0.15em] text-ink/40 font-mono">
tier A · government primary record
</p>
</div>
<div className="bg-white border border-ink/10 overflow-x-auto">
<table className="w-full text-xs">
<thead className="bg-ink/[0.04] text-[10px] uppercase tracking-[0.15em] text-ink/55">
<tr>
<th className="text-left p-2.5">issued</th>
<th className="text-left p-2.5">type</th>
<th className="text-left p-2.5">use</th>
<th className="text-left p-2.5">work described</th>
<th className="text-right p-2.5">value</th>
<th className="text-left p-2.5">status</th>
<th className="text-right p-2.5">source</th>
</tr>
</thead>
<tbody>
{permits.map(p => (
<tr key={p.id} className="border-t border-ink/5 align-top">
<td className="p-2.5 font-mono text-ink/80">{p.issue_date ?? '—'}</td>
<td className="p-2.5">
<span className="font-medium text-ink">{p.permit_type ?? '—'}</span>
{p.permit_sub_type && <div className="text-ink/55 text-[11px]">{p.permit_sub_type}</div>}
</td>
<td className="p-2.5 text-ink/65">{p.use_desc ?? '—'}</td>
<td className="p-2.5 text-ink/65 max-w-md">{p.work_desc ?? '—'}</td>
<td className="p-2.5 text-right font-mono tabular-nums text-ink/80">
{(() => {
const v = p.valuation ? Number(String(p.valuation).replace(/[^0-9.-]/g, '')) : NaN;
return Number.isFinite(v) && v > 0 ? `$${v.toLocaleString()}` : '—';
})()}
</td>
<td className="p-2.5 text-ink/65">{p.status_desc ?? '—'}</td>
<td className="p-2.5 text-right">
<SourceLinkInline label={`#${p.permit_number}`} url={p.source_url} />
<div className="text-[10px] text-ink/40 mt-0.5">{p.source_label ?? p.source}</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
);
}
export function CodeEnforcementTable({ cases }: { cases: CodeEnforcementCase[] }) {
if (!cases?.length) return null;
return (
<section className="max-w-6xl mx-auto px-6 py-12 border-t border-ink/10">
<div className="mb-5">
<p className="text-xs uppercase tracking-[0.15em] text-ink/50">Code enforcement</p>
<h2 className="font-display text-3xl text-ink tracking-[-0.01em]">
{cases.length} {cases.length === 1 ? 'case' : 'cases'} on file
</h2>
</div>
<div className="bg-white border border-ink/10 overflow-x-auto">
<table className="w-full text-xs">
<thead className="bg-ink/[0.04] text-[10px] uppercase tracking-[0.15em] text-ink/55">
<tr>
<th className="text-left p-2.5">opened</th>
<th className="text-left p-2.5">type</th>
<th className="text-left p-2.5">status</th>
<th className="text-right p-2.5">source</th>
</tr>
</thead>
<tbody>
{cases.map(c => (
<tr key={c.id} className="border-t border-ink/5 align-top">
<td className="p-2.5 font-mono text-ink/80">{c.opened_at ?? '—'}</td>
<td className="p-2.5 text-ink/80">{c.case_type ?? '—'}</td>
<td className="p-2.5 text-ink/65">{c.status ?? '—'}</td>
<td className="p-2.5 text-right">
<SourceLinkInline label={`#${c.case_number}`} url={c.source_url} />
</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
);
}
export function NewsMentionsList({ mentions }: { mentions: NewsAddressMention[] }) {
if (!mentions?.length) return null;
return (
<section className="max-w-6xl mx-auto px-6 py-12 border-t border-ink/10">
<div className="mb-5">
<p className="text-xs uppercase tracking-[0.15em] text-ink/50">In the newspaper</p>
<h2 className="font-display text-3xl text-ink tracking-[-0.01em]">
{mentions.length} historical {mentions.length === 1 ? 'mention' : 'mentions'}
</h2>
<p className="text-xs text-ink/55 mt-1">
Click any source to view the actual scanned newspaper page on Internet Archive.
</p>
</div>
<ul className="divide-y divide-ink/10">
{mentions.map(m => (
<li key={m.id} className="py-4 flex flex-col gap-1">
<div className="flex items-baseline justify-between gap-3 flex-wrap">
<span className="font-display text-lg text-ink">{m.raw_address}</span>
<span className="font-mono text-[11px] text-ink/55">
{m.paper_name} · {m.issue_date ?? 'undated'}
</span>
</div>
{m.context && (
<p className="text-sm text-ink/70 leading-relaxed italic">
“{m.context.slice(0, 280)}{m.context.length > 280 ? '…' : ''}”
</p>
)}
<div>
<SourceLinkInline label={`view scanned page on Internet Archive`} url={m.ia_url} />
</div>
</li>
))}
</ul>
</section>
);
}