mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
@@ -27,8 +27,18 @@ export default function AdvertisementList() {
|
||||
const [typeFilter, setTypeFilter] = useState("all");
|
||||
const [placementFilter, setPlacementFilter] = useState("all");
|
||||
const [statusFilter, setStatusFilter] = useState("all");
|
||||
const [searchInput, setSearchInput] = useState("");
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
// TODO(ads-3): Filters are not actually filtering — the "status" filter in
|
||||
// particular compares against the stored `status` column, but status is only
|
||||
// recomputed on read (see deriveStatus() in
|
||||
// controllers/admin/advertisements.controller.js) and never persisted back
|
||||
// to the DB. An ad that lapsed to "expired" still has status="active" in
|
||||
// the row, so filtering by status here misses/matches the wrong rows.
|
||||
// Needs either persisting the derived status on write/read, or filtering
|
||||
// server-side using the same derivation logic. Also verify type/placement
|
||||
// filters actually round-trip once ads-1/ads-2 land.
|
||||
useEffect(() => {
|
||||
const filters = [];
|
||||
if (typeFilter !== "all") filters.push({ field: "type", value: typeFilter });
|
||||
@@ -103,6 +113,7 @@ export default function AdvertisementList() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* TODO(ads-2): Remove this "All placements" dropdown entirely. */}
|
||||
<Select value={placementFilter} onValueChange={setPlacementFilter}>
|
||||
<SelectTrigger className="w-[220px] bg-background">
|
||||
<SelectValue placeholder="All placements" />
|
||||
@@ -127,17 +138,31 @@ export default function AdvertisementList() {
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<div className="relative flex-1 min-w-[160px]">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search advertisements..."
|
||||
className="pl-8 bg-background"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
{/* TODO(ads-5): Verify this already satisfies the spec — search only
|
||||
fires on button click / Enter (`search` state, not `searchInput`,
|
||||
drives the fetch effect above), typing alone does not refetch.
|
||||
Looks done already; double-check then mark complete. */}
|
||||
<div className="flex items-center gap-2 flex-1 min-w-[160px]">
|
||||
<div className="relative flex-1">
|
||||
<Search className="absolute left-2.5 top-1/2 -translate-y-1/2 size-4 text-muted-foreground" />
|
||||
<Input
|
||||
placeholder="Search advertisements..."
|
||||
className="pl-8 bg-background"
|
||||
value={searchInput}
|
||||
onChange={(e) => setSearchInput(e.target.value)}
|
||||
onKeyDown={(e) => { if (e.key === "Enter") setSearch(searchInput); }}
|
||||
/>
|
||||
</div>
|
||||
<Button variant="outline" size="icon" className="shrink-0 bg-background" onClick={() => setSearch(searchInput)} aria-label="Search">
|
||||
<Search className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TODO(ads-8): Add pagination controls for this grid — currently
|
||||
always fetches page 1 / limit 24 with no way to reach further
|
||||
pages (see `pagination` from useAdvertisements, already returned
|
||||
by the API but unused here). */}
|
||||
{/* ── Grid ───────────────────────────────────────────────────── */}
|
||||
{loading ? (
|
||||
<div className="flex items-center justify-center py-20">
|
||||
|
||||
Reference in New Issue
Block a user