update position page design

This commit is contained in:
2026-08-13 21:41:14 +05:30
parent 6340962907
commit 1c653264b5
124 changed files with 5746 additions and 596 deletions

View File

@@ -5,7 +5,10 @@ import { cn } from '@/lib/utils';
import { Surface } from '@/components/ds/Surface';
import { IconButton } from '@/components/ds/IconButton';
import { Alert } from '@/components/ds/Alert';
import { useCreateJobPosting, usePreferences, useRoleCategories } from '@/lib/krowHooks';
import {
useAssignments, useAssignWorkers, useCreateJobPosting, useMarkInterviewReady,
usePreferences, useRoleCategories,
} from '@/lib/krowHooks';
import { ROLE_CATEGORIES } from '@/lib/roleCategories';
import { AddSkillDialog } from '@/components/skills/AddSkillDialog';
import { runAction } from '@/lib/skills/actions';
@@ -114,12 +117,67 @@ export default function KrowAssistant({
return createJob.mutateAsync(result.data);
}, [createJob]);
/**
* The workforce picture the panel reasons over.
*
* The same collections the Positions page reads, so a recommendation and the
* card behind it are looking at one moment. Assembled here rather than in the
* routing layer because this is where the app's data already is; routing stays
* pure and testable.
*/
const { data: assignments = [] } = useAssignments();
const workforce = React.useMemo(() => ({
positions: facts.postings || [],
currentPositionId: null,
context: {
profiles: facts.profiles || [],
applications: facts.applications || [],
assignments,
courses: facts.forge?.library || [],
staff: facts.staff || [],
},
}), [facts.postings, facts.profiles, facts.applications, facts.forge, facts.staff, assignments]);
/**
* The workforce write, through the mutation the app already has.
*
* `useAssignWorkers` creates the assignment, moves the application and logs
* the activity — one path, reused rather than reimplemented. It only ever runs
* on a turn the admin explicitly confirmed.
*/
const assignWorkers = useAssignWorkers();
const onAssignWorkers = React.useCallback(async (plan) => {
const created = await assignWorkers.mutateAsync({
position: plan.position,
workers: plan.workers,
applications: facts.applications || [],
});
return created?.length ? created : null;
}, [assignWorkers, facts.applications]);
/**
* Move an assigned candidate to interview stage.
*
* The status transition the Position and Candidates pages already perform —
* not a new interview record. `AIInterviewModal` writes the AIInterview when
* the interview is actually conducted, and creating an empty one here would be
* a second interview store claiming something that has not happened.
*/
const markInterviewReady = useMarkInterviewReady();
const onScheduleInterview = React.useCallback(
(plan) => markInterviewReady.mutateAsync({ application: plan.application, position: plan.position }),
[markInterviewReady]
);
const { messages, pending, error, busy, send, stop, reset } = useConversation({
contextId: context.id,
facts,
onNavigate: goToPage,
onAction: performAction,
onCreatePosition: createPosition,
onAssignWorkers,
onScheduleInterview,
workforce,
disabledSkills,
customSkills,
roles,
@@ -127,6 +185,13 @@ export default function KrowAssistant({
courses: facts.forge?.library || [],
});
/* A block inside an answer asking the next question, in place. Same entry
point as the composer and the chips, so an in-panel action is an ordinary
turn rather than a second way for the panel to change. */
const askOwliver = React.useCallback((question) => {
if (!busy) send({ question });
}, [busy, send]);
const [input, setInput] = React.useState('');
const scrollRef = React.useRef(null);
const isEmpty = messages.length === 0 && !pending;
@@ -146,8 +211,8 @@ export default function KrowAssistant({
const skillPrompts = skills
.filter((s) => s.prompt)
.map((s) => ({ label: s.prompt, prompt: s.prompt }));
return [...skillPrompts, ...buildPrompts(context.id, facts)];
}, [followUp, skills, context.id, facts]);
return [...skillPrompts, ...buildPrompts(context.id, facts, workforce)];
}, [followUp, skills, context.id, facts, workforce]);
/* Follow the newest content. Direct `scrollTop` rather than smooth scrolling:
at streaming frequency a smooth scroll never catches up and the thread
@@ -281,6 +346,7 @@ export default function KrowAssistant({
text={message.text}
blocks={message.blocks}
stopped={message.stopped}
onPrompt={askOwliver}
/>
</React.Fragment>
))}