fix(jobs): align table and board query contracts
This commit is contained in:
@@ -52,6 +52,7 @@ import { JobApplication } from "../types";
|
||||
import { useViewResource } from "../hooks/useViewResource";
|
||||
import { getWorkflowAction } from "../jobWorkflowSignals";
|
||||
import type { ContentDensity } from "../theme";
|
||||
import { jobsQueryFromSearch, JobSortKey, ReadinessFilter } from "../jobsQuery";
|
||||
|
||||
interface PagedResult<T> {
|
||||
items: T[];
|
||||
@@ -93,10 +94,6 @@ interface Props {
|
||||
mode?: "jobs" | "trash";
|
||||
}
|
||||
|
||||
type JobSortKey = "dateApplied" | "company" | "jobTitle" | "status" | "daysSince" | "location";
|
||||
type ReadinessFilter = "all" | "needs-work" | "interview";
|
||||
|
||||
const JOB_SORT_KEYS: JobSortKey[] = ["dateApplied", "company", "jobTitle", "status", "daysSince", "location"];
|
||||
const JOB_STATUS_FILTERS = ["Applied", "Waiting", "Interview", "Offer", "Rejected", "Ghosted"];
|
||||
|
||||
function queryPage(search: string): number {
|
||||
@@ -104,26 +101,6 @@ function queryPage(search: string): number {
|
||||
return Number.isInteger(value) && value > 1 ? value - 1 : 0;
|
||||
}
|
||||
|
||||
function queryCompany(search: string): number | "All" {
|
||||
const value = Number(new URLSearchParams(search).get("companyId"));
|
||||
return Number.isInteger(value) && value > 0 ? value : "All";
|
||||
}
|
||||
|
||||
function queryStatus(search: string): string {
|
||||
const value = new URLSearchParams(search).get("status");
|
||||
return value || "All";
|
||||
}
|
||||
|
||||
function queryReadiness(search: string): ReadinessFilter {
|
||||
const value = new URLSearchParams(search).get("readiness");
|
||||
return value === "needs-work" || value === "interview" ? value : "all";
|
||||
}
|
||||
|
||||
function querySort(search: string): JobSortKey {
|
||||
const value = new URLSearchParams(search).get("sortBy") as JobSortKey | null;
|
||||
return value && JOB_SORT_KEYS.includes(value) ? value : "dateApplied";
|
||||
}
|
||||
|
||||
function parseTags(raw?: string | null): string[] {
|
||||
if (!raw) return [];
|
||||
try {
|
||||
@@ -173,6 +150,7 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
const { t, language } = useI18n();
|
||||
const { confirmAction } = useDialogActions();
|
||||
const location = useLocation();
|
||||
const routeQuery = useMemo(() => jobsQueryFromSearch(location.search), [location.search]);
|
||||
const navigate = useNavigate();
|
||||
const listRouteRef = useRef(`${location.pathname}${location.search}`);
|
||||
const restoredFocusJobIdRef = useRef<number | null>(null);
|
||||
@@ -183,24 +161,24 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
// Presentation only: not an account preference, saved-view field or API input.
|
||||
const [density, setDensity] = useState<ContentDensity>("comfortable");
|
||||
const [selectedIds, setSelectedIds] = useState<number[]>([]);
|
||||
const [search, setSearch] = useState(() => new URLSearchParams(location.search).get("q") ?? "");
|
||||
const [search, setSearch] = useState(routeQuery.q);
|
||||
const debouncedSearch = useDebouncedValue(search, 250);
|
||||
const [includeDeleted, setIncludeDeleted] = useState(() => mode === "trash" || new URLSearchParams(location.search).get("includeDeleted") === "1");
|
||||
const [includeDeleted, setIncludeDeleted] = useState(() => mode === "trash" || routeQuery.includeDeleted);
|
||||
const [columnsAnchor, setColumnsAnchor] = useState<null | HTMLElement>(null);
|
||||
const [statusFilter, setStatusFilter] = useState(() => queryStatus(location.search));
|
||||
const [locationFilter, setLocationFilter] = useState(() => new URLSearchParams(location.search).get("location") ?? "");
|
||||
const [statusFilter, setStatusFilter] = useState(routeQuery.status);
|
||||
const [locationFilter, setLocationFilter] = useState(routeQuery.location);
|
||||
const debouncedLocation = useDebouncedValue(locationFilter, 250);
|
||||
const [needsFollowUpOnly, setNeedsFollowUpOnly] = useState(() => new URLSearchParams(location.search).get("needsFollowUp") === "1");
|
||||
const [readinessFilter, setReadinessFilter] = useState<ReadinessFilter>(() => queryReadiness(location.search));
|
||||
const [needsFollowUpOnly, setNeedsFollowUpOnly] = useState(routeQuery.needsFollowUp);
|
||||
const [readinessFilter, setReadinessFilter] = useState<ReadinessFilter>(routeQuery.readiness ?? "all");
|
||||
const { companies, error: companiesError, reload: reloadCompanies } = useCompanies();
|
||||
const [companyFilterId, setCompanyFilterId] = useState<number | "All">(() => queryCompany(location.search));
|
||||
const [companyFilterId, setCompanyFilterId] = useState<number | "All">(routeQuery.companyId ?? "All");
|
||||
const [editJobId, setEditJobId] = useState<number | null>(null);
|
||||
const [reloadToken, setReloadToken] = useState(0);
|
||||
const [statusAnchor, setStatusAnchor] = useState<null | HTMLElement>(null);
|
||||
const [bulkStatusAnchor, setBulkStatusAnchor] = useState<null | HTMLElement>(null);
|
||||
const [statusJobId, setStatusJobId] = useState<number | null>(null);
|
||||
const [sortBy, setSortBy] = useState<JobSortKey>(() => querySort(location.search));
|
||||
const [sortDir, setSortDir] = useState<"asc" | "desc">(() => new URLSearchParams(location.search).get("sortDir") === "asc" ? "asc" : "desc");
|
||||
const [sortBy, setSortBy] = useState<JobSortKey>(routeQuery.sortBy);
|
||||
const [sortDir, setSortDir] = useState<"asc" | "desc">(routeQuery.sortDir);
|
||||
|
||||
const updateListRoute = useCallback((updates: Record<string, string | null>) => {
|
||||
setSelectedIds([]);
|
||||
@@ -219,18 +197,17 @@ export default function JobTable({ refreshToken, pageSize, onPageSizeChange, col
|
||||
}, [location.pathname, location.search]);
|
||||
|
||||
useEffect(() => {
|
||||
const next = new URLSearchParams(location.search);
|
||||
setSearch(next.get("q") ?? "");
|
||||
setStatusFilter(queryStatus(location.search));
|
||||
setCompanyFilterId(queryCompany(location.search));
|
||||
setLocationFilter(next.get("location") ?? "");
|
||||
setNeedsFollowUpOnly(next.get("needsFollowUp") === "1");
|
||||
setReadinessFilter(queryReadiness(location.search));
|
||||
setIncludeDeleted(mode === "trash" || next.get("includeDeleted") === "1");
|
||||
setSortBy(querySort(location.search));
|
||||
setSortDir(next.get("sortDir") === "asc" ? "asc" : "desc");
|
||||
setSearch(routeQuery.q);
|
||||
setStatusFilter(routeQuery.status);
|
||||
setCompanyFilterId(routeQuery.companyId ?? "All");
|
||||
setLocationFilter(routeQuery.location);
|
||||
setNeedsFollowUpOnly(routeQuery.needsFollowUp);
|
||||
setReadinessFilter(routeQuery.readiness ?? "all");
|
||||
setIncludeDeleted(mode === "trash" || routeQuery.includeDeleted);
|
||||
setSortBy(routeQuery.sortBy);
|
||||
setSortDir(routeQuery.sortDir);
|
||||
setPage(queryPage(location.search));
|
||||
}, [location.search, mode]);
|
||||
}, [location.search, routeQuery, mode]);
|
||||
|
||||
const changeSearch = (value: string) => {
|
||||
setSearch(value);
|
||||
|
||||
Reference in New Issue
Block a user