set the contractor upload and classification workflow

This commit is contained in:
Khalim Conn-Kowlessar 2026-04-17 18:18:07 +00:00
parent 3a4d102ea4
commit a5b3cfe85b

View file

@ -1,7 +1,6 @@
"use client";
import { useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import {
Dialog,
DialogContent,
@ -256,138 +255,37 @@ function PasGuidancePanel() {
);
}
// ── Searchable DocType combobox ────────────────────────────────────────────
// ── DocType select ────────────────────────────────────────────────────────
function DocTypeSelect({ value, onChange, showHint = false }: { value: string; onChange: (v: string) => void; showHint?: boolean }) {
const selected = FILE_TYPE_OPTIONS.find((o) => o.value === value);
const [open, setOpen] = useState(false);
const [query, setQuery] = useState("");
const [dropdownStyle, setDropdownStyle] = useState<React.CSSProperties>({});
const triggerRef = useRef<HTMLButtonElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
function openDropdown() {
if (!triggerRef.current) return;
const rect = triggerRef.current.getBoundingClientRect();
const spaceBelow = window.innerHeight - rect.bottom;
const dropdownHeight = 280;
const showAbove = spaceBelow < dropdownHeight && rect.top > dropdownHeight;
setDropdownStyle({
position: "fixed",
left: rect.left,
width: rect.width,
zIndex: 9999,
...(showAbove
? { bottom: window.innerHeight - rect.top + 4 }
: { top: rect.bottom + 4 }),
});
setOpen(true);
}
function close() {
setOpen(false);
setQuery("");
}
function select(val: string) {
onChange(val);
close();
}
// Focus search input when opening
useEffect(() => {
if (open) setTimeout(() => inputRef.current?.focus(), 0);
}, [open]);
const filtered = query.trim()
? FILE_TYPE_OPTIONS.filter((o) =>
o.label.toLowerCase().includes(query.toLowerCase()) ||
o.group.toLowerCase().includes(query.toLowerCase())
)
: null;
return (
<div className="space-y-1">
{/* Trigger */}
<button
ref={triggerRef}
type="button"
onClick={() => open ? close() : openDropdown()}
className={`flex h-8 w-full items-center justify-between rounded-md border px-3 py-1 text-xs transition-colors
${open ? "border-brandblue ring-1 ring-brandblue/30" : "border-input hover:border-gray-300"}
bg-background`}
>
<span className={selected ? "text-gray-800" : "text-gray-400"}>
{selected ? selected.label : "Select type…"}
</span>
<ChevronDown className={`h-3.5 w-3.5 text-gray-400 transition-transform ${open ? "rotate-180" : ""}`} />
</button>
{open && typeof document !== "undefined" && createPortal(
<>
{/* Transparent overlay — position:fixed escapes overflow:hidden, catches all outside clicks */}
<div className="fixed inset-0" style={{ zIndex: 9998 }} onClick={close} />
{/* Dropdown — above the overlay */}
<div style={dropdownStyle} className="rounded-md border border-gray-200 bg-white shadow-xl">
<div className="border-b border-gray-100 p-2">
<input
ref={inputRef}
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search…"
className="w-full rounded border border-gray-200 px-2 py-1 text-xs outline-none focus:border-brandblue"
/>
</div>
<div className="max-h-56 overflow-y-auto py-1">
{filtered ? (
filtered.length === 0 ? (
<p className="px-3 py-2 text-xs text-gray-400">No results</p>
) : (
filtered.map((o) => (
<button
key={o.value}
type="button"
onClick={() => select(o.value)}
className={`flex w-full flex-col px-3 py-1.5 text-left text-xs hover:bg-brandlightblue/30 transition-colors
${value === o.value ? "bg-brandlightblue/20 font-medium text-brandblue" : "text-gray-700"}`}
>
<span>{o.label}</span>
<span className="text-[10px] text-gray-400">{o.group}</span>
</button>
))
)
) : (
FILE_TYPE_GROUPS.map((group) => {
const items = FILE_TYPE_OPTIONS.filter((o) => o.group === group);
if (!items.length) return null;
return (
<div key={group}>
<p className="px-3 pt-2 pb-0.5 text-[10px] font-semibold uppercase tracking-wide text-gray-400">
{group}
</p>
{items.map((o) => (
<button
key={o.value}
type="button"
onClick={() => select(o.value)}
className={`flex w-full px-3 py-1.5 text-left text-xs hover:bg-brandlightblue/30 transition-colors
${value === o.value ? "bg-brandlightblue/20 font-medium text-brandblue" : "text-gray-700"}`}
>
{o.label}
</button>
))}
</div>
);
})
)}
</div>
</div>
</>,
document.body
)}
<Select value={value || "__unset__"} onValueChange={(v) => onChange(v === "__unset__" ? "" : v)}>
<SelectTrigger className="h-8 text-xs w-full">
<SelectValue placeholder="Select type…" />
</SelectTrigger>
<SelectContent>
<SelectItem value="__unset__" className="text-xs text-gray-400">Select type</SelectItem>
{FILE_TYPE_GROUPS.map((group) => {
const items = FILE_TYPE_OPTIONS.filter((o) => o.group === group);
if (!items.length) return null;
return (
<SelectGroup key={group}>
<SelectLabel className="text-[10px] font-semibold uppercase tracking-wide text-gray-400 px-2 py-1">
{group}
</SelectLabel>
{items.map((o) => (
<SelectItem key={o.value} value={o.value} className="text-xs">
{o.label}
</SelectItem>
))}
</SelectGroup>
);
})}
</SelectContent>
</Select>
{showHint && selected?.hint && (
<p className="text-[10px] text-blue-600 leading-snug px-0.5">{selected.hint}</p>
)}