feat(14-05): manual company-search combobox fallback (D-05/D-09)

- Every review card gets a Command/Popover combobox fed by
  /api/data/companies-list, fetched once via ensureCompaniesLoaded()
  and shared across cards
- Selecting a company enables a "Link to selected company" button that
  calls the same resolve() handler as the candidate buttons
- Zero-candidate reviews (D-09) show only the manual picker; reviews
  with candidates show both candidate buttons and the manual picker
This commit is contained in:
lorentz 2026-07-11 14:43:56 -04:00
parent 40efa1d3b6
commit 13272f9a6f

View file

@ -9,9 +9,19 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Skeleton } from '@/components/ui/skeleton'; import { Skeleton } from '@/components/ui/skeleton';
import { AlertTriangle, CheckCircle2, Loader2 } from 'lucide-react'; import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from '@/components/ui/command';
import { AlertTriangle, Check, CheckCircle2, ChevronsUpDown, Loader2 } from 'lucide-react';
import DataTable, { type Column } from '@/components/admin/DataTable'; import DataTable, { type Column } from '@/components/admin/DataTable';
import DetailModal from '@/components/admin/DetailModal'; import DetailModal from '@/components/admin/DetailModal';
import { cn } from '@/lib/utils';
interface Pax8CompanyListItem { interface Pax8CompanyListItem {
id: string; id: string;
@ -88,6 +98,7 @@ export default function Pax8Page() {
const [selectedManualCompany, setSelectedManualCompany] = useState< const [selectedManualCompany, setSelectedManualCompany] = useState<
Record<string, ManualCompanyOption> Record<string, ManualCompanyOption>
>({}); >({});
const [openPopoverId, setOpenPopoverId] = useState<string | null>(null);
const fetchCompanies = async ( const fetchCompanies = async (
currentPage: number, currentPage: number,
@ -398,7 +409,80 @@ export default function Pax8Page() {
</Alert> </Alert>
)} )}
{/* Manual company-search combobox implemented in Task 3 */} <div className="flex items-center gap-2 pt-1">
<Popover
open={openPopoverId === review.id}
onOpenChange={(open) => {
setOpenPopoverId(open ? review.id : null);
if (open) void ensureCompaniesLoaded();
}}
>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className="flex-1 justify-between font-normal"
>
{selectedManualCompany[review.id]?.company_name ?? 'Search company…'}
<ChevronsUpDown className="size-4 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-[320px] p-0" align="start">
<Command>
<CommandInput placeholder="Search company…" />
<CommandList>
<CommandEmpty>
{companiesLoading ? 'Loading companies…' : 'No company found.'}
</CommandEmpty>
<CommandGroup>
{(allCompanies ?? []).map((company) => (
<CommandItem
key={company.id}
value={company.company_name}
onSelect={() => {
setSelectedManualCompany((prev) => ({
...prev,
[review.id]: company,
}));
setOpenPopoverId(null);
}}
>
<Check
className={cn(
'size-4',
selectedManualCompany[review.id]?.id === company.id
? 'opacity-100'
: 'opacity-0'
)}
/>
{company.company_name}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
<Button
disabled={
!selectedManualCompany[review.id] ||
resolving === `${review.id}:${selectedManualCompany[review.id]?.id}`
}
onClick={() => {
const selected = selectedManualCompany[review.id];
if (selected) void resolve(review.id, selected.id, selected.company_name);
}}
>
{resolving === `${review.id}:${selectedManualCompany[review.id]?.id}` ? (
<>
<Loader2 className="size-3 animate-spin mr-1" />
Linking
</>
) : (
'Link to selected company'
)}
</Button>
</div>
</CardContent> </CardContent>
</Card> </Card>
))} ))}