Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | 2x 31x 31x | import { Link } from "@tanstack/react-router";
import { memo } from "react";
import type { Project } from "../../lib/api";
import { formatRelativeDate } from "../../lib/format-date";
import { StatusBadge } from "../ui/status-badge";
import { TableCell, TableRow } from "../ui/table";
import { projectStatusCode } from "./project-status";
/**
* One row of the projects list view — `projects-list.html`, list view.
*
* The mockup prints the other half of the ▦/☰ toggle as ONE ruled table, no
* chips: `Status · Project · Locality · Photos · Updated`, every figure
* tabular and right-aligned, rows tinting on hover. Both of those come from
* the `Table` family (`TableRow`'s hover tint, `TableCell`'s `numeric`), which
* is why this is composed rather than hand-rolled — Phase 6 adds a grep test
* forbidding a raw `<table>` outside `components/ui/`.
*
* NO ACTION COLUMN, AND THAT IS THE POINT
* This row used to carry publish / archive / unarchive / delete, which is why
* unarchive was unreachable: the default status filter excluded `archived`, so
* the only row that could offer it never rendered. The full action set now
* lives in the detail page's `[ ⋯ ]` menu at every status, where an archived
* project can always come back. The mockup's table has no action column for
* the same reason.
*/
export interface ProjectListItemProps {
project: Project;
/**
* The resolved locality name. Omitted when the project records no locality,
* or when it sits outside the studio's own city and so is not in the
* loaded taxonomy — an em dash is honest; a guess is not.
*/
localityName?: string | null;
}
export const ProjectListItem = memo(function ProjectListItem({
project,
localityName,
}: ProjectListItemProps) {
const code = projectStatusCode(project.status);
return (
<TableRow>
<TableCell>
<StatusBadge status={code.label} variant={code.tone} />
</TableCell>
<TableCell>
<Link
to="/projects/$projectId"
params={{ projectId: project.id }}
search={{ tab: "" }}
className="font-semibold text-foreground-default underline-offset-4 hover:underline"
title={project.title}
>
{project.title}
</Link>
</TableCell>
<TableCell className="text-foreground-muted">
{localityName || <span aria-hidden="true">—</span>}
{!localityName && <span className="sr-only">No locality recorded</span>}
</TableCell>
<TableCell numeric>
{typeof project.photoCount === "number" ? (
project.photoCount
) : (
<>
<span aria-hidden="true">—</span>
<span className="sr-only">Photo count unavailable</span>
</>
)}
</TableCell>
<TableCell numeric className="text-foreground-muted">
{formatRelativeDate(project.dateUpdated)}
</TableCell>
</TableRow>
);
});
|