← back to Stayclaim
src/app/locations-by-year/page.tsx
125 lines
import Link from 'next/link';
import type { Metadata } from 'next';
import { listAllFilmingShoots, yearOf } from '@/lib/db';
import { BreadcrumbArchive } from '@/components/BreadcrumbArchive';
export const metadata: Metadata = {
title: 'Locations by year',
description: 'Every public filming permit in the archive, sorted by year. The visual record of what was shot in LA when.',
alternates: { canonical: '/locations-by-year' },
};
const KIND_LABEL: Record<string, string> = {
film: 'Feature',
tv: 'TV',
commercial: 'Commercial',
music_video: 'Music video',
documentary: 'Documentary',
short: 'Short',
student: 'Student',
};
export default async function LocationsByYearPage() {
const shoots = await listAllFilmingShoots();
// Group by year.
const byYear: Record<string, typeof shoots> = {};
for (const s of shoots) {
const y = s.shoot_date_from ? yearOf(s.shoot_date_from) : 'Undated';
(byYear[y] ??= []).push(s);
}
const years = Object.keys(byYear).sort((a, b) =>
a === 'Undated' ? 1 : b === 'Undated' ? -1 : Number(b) - Number(a)
);
return (
<>
<div className="max-w-5xl mx-auto px-6 pt-6">
<BreadcrumbArchive items={[{ label: 'Archive', href: '/' }, { label: 'Locations by year' }]} />
</div>
<header className="max-w-5xl mx-auto px-6 py-12 border-b border-ink/10">
<p className="text-xs uppercase tracking-[0.15em] text-ink/50 mb-2">Locations by year</p>
<h1 className="font-display text-5xl md:text-6xl text-ink tracking-[-0.02em] leading-[1.02]">
Every shoot, sorted by year.
</h1>
<p className="mt-4 font-display italic text-xl text-ink/70 max-w-prose">
Public film permits at addresses in the archive — pulled from FilmLA / data.lacity.org.
</p>
<p className="mt-3 font-mono text-[11px] text-ink/45">
{shoots.length} {shoots.length === 1 ? 'shoot' : 'shoots'} across {years.length} {years.length === 1 ? 'year' : 'years'}
</p>
{years.length > 0 && (
<nav className="mt-6 flex flex-wrap gap-2">
{years.map(y => (
<a
key={y}
href={`#y-${y}`}
className="font-mono text-[11px] uppercase tracking-[0.15em] border border-ink/15 px-2 py-1 hover:border-coral hover:text-coral transition"
>
{y}
</a>
))}
</nav>
)}
</header>
<section className="max-w-5xl mx-auto px-6 py-10">
{shoots.length === 0 ? (
<p className="text-sm italic text-ink/55">
No shoots ingested yet. Run <code className="font-mono text-xs">scripts/ingest-la-film-permits.ts</code>.
</p>
) : (
years.map(year => (
<div key={year} id={`y-${year}`} className="mb-12 scroll-mt-24">
<div className="flex items-baseline gap-4 mb-4 pb-2 border-b border-ink/10">
<h2 className="font-display text-4xl text-ink tracking-[-0.01em]">{year}</h2>
<span className="font-mono text-[11px] text-ink/45">
{byYear[year]!.length} {byYear[year]!.length === 1 ? 'shoot' : 'shoots'}
</span>
</div>
<ol className="divide-y divide-ink/5">
{byYear[year]!.map(s => (
<li key={s.id} className="grid md:grid-cols-[120px_1fr_auto] gap-3 md:gap-6 py-4 items-baseline">
<span className="font-mono text-[10px] uppercase tracking-[0.15em] text-ink/55">
{KIND_LABEL[s.production_kind] ?? s.production_kind}
{s.role ? ` · ${s.role.replace('_', ' ')}` : ''}
</span>
<div>
<div className="flex items-baseline gap-3 flex-wrap">
<Link
href={`/film/${encodeURIComponent(s.production_slug)}`}
className="font-display text-xl text-ink hover:text-coral transition"
>
{s.production_title}
{s.production_year ? <span className="ml-2 text-ink/50">({s.production_year})</span> : null}
</Link>
<span className="text-ink/30">·</span>
<Link
href={`/address/${encodeURIComponent(s.listing_slug)}`}
className="text-base text-ink/70 hover:text-coral transition"
>
{s.listing_title}
</Link>
{s.listing_city && (
<span className="text-[11px] text-ink/50">{s.listing_city}</span>
)}
</div>
{s.scene_note && (
<p className="mt-1 text-sm text-ink/65 leading-relaxed">{s.scene_note}</p>
)}
</div>
<span className="font-mono text-[10px] text-ink/45 md:text-right">
{s.permit_number}
</span>
</li>
))}
</ol>
</div>
))
)}
</section>
</>
);
}