## Context

See `proposal.md` — Why. This is a web-only change: `web/components/admin/ImportView.tsx`
stops being a route screen and becomes the body of a modal opened from
`web/components/admin/GuestsView.tsx`. No API endpoint, schema, migration or generated client
is touched, so `make client` output is unchanged and CI's freshness check is unaffected.

Three existing facts shape the approach:

* `GuestsView` already owns an `event` filter, already renders `GuestForm` and `GuestDrawer`
  as modals conditioned on state, and already gates Add guest on an event being selected with
  a `title` explaining why. Import is the same shape as Add guest — another way rows arrive on
  one event's list — so it should be built the same way rather than inventing a second pattern.
* Permissions arrive as booleans computed in the server page from `session.permissions` and
  passed down (`canEdit`, `canDelete`, `canExport`, `canSend`). `import_guests` is an existing
  policy action with no client-side flag yet.
* `Modal` is the native-`<dialog>` component with hard-won behaviour around `close`,
  `cancel` and backdrop presses (see its comments). Import must use it as-is and add nothing.

## Goals / Non-Goals

**Goals:**

* One entry point for import, on the screen that already knows the destination event.
* `ImportView` keeps owning the preview/commit state machine — the move must not spread that
  logic into `GuestsView`.
* The guest table behind the modal shows the imported rows when the modal closes.

**Non-Goals:**

* No change to `POST /admin/events/{id}/guests/import/preview|commit` or their payloads.
* No batching, background import, or progress bar — the existing synchronous two-call flow is
  unchanged, including its behaviour on an 800-row file.
* No import entry point on the Events screen. That screen links to the guest list; adding a
  second import affordance there would recreate the duplication this change removes.

## Decisions

### D1 — The destination event is a prop, not a control

`ImportView` takes `eventId: string` (and `eventName?: string` for the heading) and drops its
own `<select>` and its `api.events()` fetch. The component becomes a pure function of the
event it is handed.

*Alternative considered:* keep the dropdown, seeded from the filter. Rejected — that is the
duplication the change exists to remove, and it reintroduces the failure it enables: an admin
reading "Mehedi" on the list while the modal quietly imports into Walima. The cost is that
importing into a different event now takes a filter change first, which is one extra click on
a rare action and makes the destination unmistakable.

*Consequence:* `GuestsView` renders the modal only when `event` is non-empty, so `ImportView`
never has to handle an empty destination. The Import button carries the same
`disabled` + `title` treatment as Add guest rather than a new explanatory paragraph.

### D2 — `ImportView` renders its own `Modal`, like `GuestForm`

`GuestsView` holds a single `importing: boolean` and renders `<ImportModal …>` when true; the
component wraps its own content in `Modal`. This matches `GuestForm` and `GuestDrawer`, and it
keeps `GuestsView`'s JSX from growing a second dialog shell.

Size is `wide`. The validation report has a three-column row table and a four-stat row; at
`default` (`max-w-lg`) the table would scroll horizontally on a laptop, which defeats a report
whose whole purpose is being looked at.

### D3 — The modal stays open on success and reports; the list reloads underneath

On a successful commit the component keeps the result card visible (counts plus skipped rows
with reasons) and calls `onImported()`, which triggers `GuestsView`'s existing `load()`.
Closing is the admin's act.

*Alternative considered:* close immediately and show a toast. Rejected — a partially valid file
produces a per-row skip report the admin needs to read and act on, and there is no toast in
this admin that could carry it. Auto-closing would throw away the only record of which twelve
rows did not import.

*Consequence:* the reload happens while the dialog is open, so the table is already correct
when it closes. The result card and the refreshed table are consistent because both follow the
same commit.

### D4 — `/admin/import` becomes a permanent redirect to `/admin/guests`

`web/app/admin/(protected)/import/page.tsx` keeps existing but calls `redirect("/admin/guests")`
from `next/navigation`, and the route stays inside the protected layout so an unauthenticated
hit is still handled by the layout's session check before the redirect.

*Alternatives considered:* delete the file (a 404 for anyone with the tab pinned, and the user
guide's own instructions become dead), or a `next.config` redirect (moves one page's behaviour
away from the page, for no gain). The redirect drops the query string, which the old screen
never read anyway.

*Note:* no event is carried across, because the old screen had no event in its address to carry.

### D5 — `canImport` follows the existing permission-prop pattern

The guests page computes `permissions.includes("import_guests")` and passes `canImport`. The
button and the modal are both gated on it. This is presentation only — `require(Action.IMPORT_GUESTS)`
on the API is what actually enforces, so a hand-crafted request from a viewer still gets a 403.

### D6 — Removing the nav item is part of the change, not a follow-up

`AdminNav`'s `ITEMS` loses its `/admin/import` entry. Leaving it would point at a redirect, so
the Import tab would highlight Guests — a tab that navigates to a different tab, which reads as
a bug. The `import_guests` permission then no longer gates any nav entry, which is fine: nav
gating and action gating are independent lists.

## Risks / Trade-offs

* **An admin who has bookmarked the import screen loses their muscle memory** → the redirect
  lands them on the guest list, where the Import button sits next to Add guest; the user guide
  section is rewritten in the same change so the printed steps match.
* **Importing into an event other than the one on screen now needs a filter change first** →
  accepted deliberately (D1); it is the same constraint Add guest has carried since design D11,
  and the modal names its destination so a wrong one is visible before the file is chosen.
* **A long commit holds a modal open with the page inert behind it** → unchanged from today in
  duration; the commit already blocks the old screen. The Import button shows a busy state and
  the modal cannot be dismissed into a half-written state because closing mid-commit does not
  cancel the request — the reload on reopen shows whatever was written.
* **`ImportView` used to fetch events itself; dropping that removes its only self-sufficiency**
  → intended. It has one caller, and a component that can only be used correctly with an event
  is better than one that silently allows none.

## Migration Plan

No data migration. Deploy is a single web build; the API is untouched, so there is no ordering
constraint between the two. Rollback is reverting the web commit — nothing persists that a
previous build could not read.

Documentation moves with the code: `docs/USER-GUIDE.md` §2b ("Import a spreadsheet") loses its
"Admin → Import" and "choose the event to import into" steps and gains "open Guests, choose the
event, Import CSV"; the table in §1 and the line in the appendix about the `events` column being
ignored are reworded to name the filter rather than the import screen.
