fix(jobs): align table and board query contracts
This commit is contained in:
@@ -23,6 +23,7 @@ import ViewStateNotice from "./ViewStateNotice";
|
||||
import { JobApplication } from "../types";
|
||||
import { useI18n } from "../i18n/I18nProvider";
|
||||
import { useViewResource } from "../hooks/useViewResource";
|
||||
import { jobsQueryFromSearch } from "../jobsQuery";
|
||||
import {
|
||||
GROUP_ENTRY_STATUS,
|
||||
PIPELINE_GROUPS,
|
||||
@@ -77,8 +78,10 @@ export default function KanbanBoard() {
|
||||
const palette = theme.vars?.palette ?? theme.palette;
|
||||
const { t, language } = useI18n();
|
||||
const location = useLocation();
|
||||
const params = useMemo(() => jobsQueryFromSearch(location.search), [location.search]);
|
||||
const boardRef = useRef<HTMLDivElement>(null);
|
||||
const navigate = useNavigate();
|
||||
const focusAfterMoveRef = useRef<number | null>(null);
|
||||
const [focusAfterMoveId, setFocusAfterMoveId] = useState<number | null>(null);
|
||||
const restoredFocusRef = useRef<number | null>(null);
|
||||
const [dragJobId, setDragJobId] = useState<number | null>(null);
|
||||
const [dragOverColumn, setDragOverColumn] = useState<ColumnKey | null>(null);
|
||||
@@ -89,29 +92,31 @@ export default function KanbanBoard() {
|
||||
|
||||
const jobsResource = useViewResource(
|
||||
async () => {
|
||||
const response = await api.get<JobApplication[]>("/jobapplications/board");
|
||||
const response = await api.get<JobApplication[]>("/jobapplications/board", { params });
|
||||
return response.data;
|
||||
},
|
||||
{
|
||||
initialData: [],
|
||||
errorMessage: t("kanbanLoadFailed"),
|
||||
deps: [t],
|
||||
deps: [t, params],
|
||||
},
|
||||
);
|
||||
|
||||
const jobs = jobsResource.data;
|
||||
|
||||
useEffect(() => {
|
||||
if (jobsResource.loading || jobsResource.error) return;
|
||||
if (jobsResource.loading || jobsResource.refreshing || jobsResource.error) return;
|
||||
const returnId = window.history.state?.jobBoardFocusId ?? (location.state as { focusJobId?: unknown } | null)?.focusJobId;
|
||||
const targetId = focusAfterMoveRef.current ?? (typeof returnId === "number" && restoredFocusRef.current !== returnId ? returnId : null);
|
||||
const targetId = focusAfterMoveId ?? (typeof returnId === "number" && restoredFocusRef.current !== returnId ? returnId : null);
|
||||
if (targetId == null) return;
|
||||
const card = document.querySelector<HTMLElement>(`[data-kanban-card-id="${targetId}"] [role="button"]`);
|
||||
if (!card) return;
|
||||
card.focus();
|
||||
focusAfterMoveRef.current = null;
|
||||
// A successful move may leave the filtered population. Keep keyboard context in the board.
|
||||
const target = card ?? (focusAfterMoveId != null ? boardRef.current?.querySelector<HTMLElement>('[role="group"]') : null);
|
||||
if (!target) return;
|
||||
target.focus();
|
||||
setFocusAfterMoveId(null);
|
||||
restoredFocusRef.current = targetId;
|
||||
}, [jobs, jobsResource.loading, jobsResource.error, location.state]);
|
||||
}, [jobs, jobsResource.loading, jobsResource.refreshing, jobsResource.error, location.state, focusAfterMoveId]);
|
||||
|
||||
const openJob = (id: number) => {
|
||||
window.history.replaceState({ ...window.history.state, jobBoardFocusId: id }, "");
|
||||
@@ -151,8 +156,8 @@ export default function KanbanBoard() {
|
||||
setMoveError("");
|
||||
try {
|
||||
await api.patch(`/jobapplications/${movingJobId}/status`, { status });
|
||||
focusAfterMoveRef.current = movingJobId;
|
||||
jobsResource.setData((prev) => prev.map((j) => (j.id === movingJobId ? { ...j, status } : j)));
|
||||
await jobsResource.reload();
|
||||
setFocusAfterMoveId(movingJobId);
|
||||
setAnnouncement(t("kanbanMoveSucceeded", { status: statusLabel(t, status) }));
|
||||
} catch (error: any) {
|
||||
setMoveError(getApiErrorMessage(error, t("kanbanMoveFailed")));
|
||||
@@ -165,8 +170,8 @@ export default function KanbanBoard() {
|
||||
setMoveError("");
|
||||
try {
|
||||
await api.patch(`/jobapplications/${id}/status`, { status });
|
||||
focusAfterMoveRef.current = id;
|
||||
jobsResource.setData((prev) => prev.map((j) => (j.id === id ? { ...j, status } : j)));
|
||||
await jobsResource.reload();
|
||||
setFocusAfterMoveId(id);
|
||||
setAnnouncement(t("kanbanMoveSucceeded", { status: statusLabel(t, status) }));
|
||||
} catch (error: any) {
|
||||
setMoveError(getApiErrorMessage(error, t("kanbanMoveFailed")));
|
||||
@@ -192,27 +197,27 @@ export default function KanbanBoard() {
|
||||
}, [groups, t]);
|
||||
|
||||
return (
|
||||
<Box sx={{ mt: 2, width: "100%", minWidth: 0, maxWidth: "100%", overflowX: "clip", contain: "inline-size" }}>
|
||||
<Box ref={boardRef} sx={{ mt: 2, width: "100%", minWidth: 0, maxWidth: "100%", overflowX: "clip", contain: "inline-size" }}>
|
||||
<Box sx={{ display: "flex", alignItems: { xs: "stretch", sm: "center" }, justifyContent: "space-between", gap: 1.5, flexDirection: { xs: "column", sm: "row" }, mb: 2 }}>
|
||||
<Box>
|
||||
<Typography variant="body2" sx={{ color: "text.secondary" }}>{t("kanbanHint")}</Typography>
|
||||
<Typography variant="caption" sx={{ color: "text.secondary" }}>{t("kanbanSeparateViews")}</Typography>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", gap: 0.75, flexShrink: 0 }}>
|
||||
<Button component={RouterLink} to="/jobs" variant="outlined" size="small">{t("jobApplications")}</Button>
|
||||
<Button component={RouterLink} to={{ pathname: "/jobs", search: location.search }} variant="outlined" size="small">{t("jobApplications")}</Button>
|
||||
<Button variant="contained" size="small" aria-current="page" disabled>{t("kanbanBoard")}</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<ViewStateNotice
|
||||
loading={jobsResource.loading}
|
||||
loading={jobsResource.loading || jobsResource.refreshing}
|
||||
error={jobsResource.error}
|
||||
title={t("kanbanLoadFailed")}
|
||||
description={t("kanbanLoadFailedBody")}
|
||||
onRetry={jobsResource.reload}
|
||||
/>
|
||||
|
||||
{!jobsResource.loading && !jobsResource.error ? (
|
||||
{!jobsResource.loading && !jobsResource.refreshing && !jobsResource.error ? (
|
||||
<Box
|
||||
sx={{
|
||||
display: { xs: "flex", lg: "grid" },
|
||||
|
||||
Reference in New Issue
Block a user