feat(uat): file preview/download fix, clients-by-country page, residential column picker
All checks were successful
Build & Push Docker Images / lint (push) Successful in 2m42s
Build & Push Docker Images / build-and-push (push) Successful in 7m20s

Batch #4 UAT items.

1. Documents — clicking any file dumped raw presigned-URL JSON. Was
   systemic: 6 surfaces linked a browser directly at the JSON-returning
   /files/[id]/{download,preview} routes. Those routes now 302-redirect
   when called with ?redirect=1 (default stays JSON for the dialog +
   interest-eoi-tab programmatic consumers); the six <Link> sites use it.
   The documents-hub file row now opens the inline FilePreviewDialog +
   has a per-row Download button, and the preview dialog header gained a
   persistent Download button for all file types.

2. Clients-by-country — the widget's "+N more" dead text is now a
   "Show all" link to a new /clients/by-country page rendering the full
   ranked country breakdown (each row drills into the filtered list).

3. Residential clients list — moved off its bespoke table onto the
   shared DataTable + ColumnPicker (same UX as clients/interests). Adds
   a "Date added" column, default-hides the empty "Residence" column,
   preserves the mobile card view, persists per-user column choices.

tsc clean, eslint clean, 1584/1584 vitest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 22:34:47 +02:00
parent eff57af571
commit 05950ae0b6
15 changed files with 506 additions and 170 deletions

View File

@@ -0,0 +1,5 @@
import { ClientsByCountryPage } from '@/components/clients/clients-by-country-page';
export default function ClientsByCountryRoute() {
return <ClientsByCountryPage />;
}

View File

@@ -8,6 +8,13 @@ export const GET = withAuth(
withPermission('files', 'view', async (req, ctx, params) => {
try {
const result = await getDownloadUrl(params.id!, ctx.portId);
// `?redirect=1` → 302 straight to the presigned (attachment) URL so a
// plain <a href>/<Link> downloads the file. Without it we return the
// JSON envelope for programmatic consumers (e.g. fetch + anchor click).
// Linking the browser at the JSON form used to dump raw `{data:{url}}`.
if (new URL(req.url).searchParams.has('redirect')) {
return NextResponse.redirect(result.url, 302);
}
return NextResponse.json({ data: result });
} catch (error) {
return errorResponse(error);

View File

@@ -8,6 +8,12 @@ export const GET = withAuth(
withPermission('files', 'view', async (req, ctx, params) => {
try {
const result = await getPreviewUrl(params.id!, ctx.portId);
// `?redirect=1` → 302 to the presigned (inline) URL so a plain
// <a href>/<Link> opens the file in the browser. Default returns the
// JSON envelope for programmatic consumers (e.g. FilePreviewDialog).
if (new URL(req.url).searchParams.has('redirect')) {
return NextResponse.redirect(result.url, 302);
}
return NextResponse.json({ data: result });
} catch (error) {
return errorResponse(error);