dropdwon inserted

pull/34/head
ahmed.mujtaba 2026-08-30 22:01:43 +05:00
parent fd53730efe
commit 9dadcb4698
1 changed files with 92 additions and 69 deletions

View File

@ -15,6 +15,7 @@ import AiFieldAssist from '../ui/AiFieldAssist'
import DataTable from '../ui/DataTable' import DataTable from '../ui/DataTable'
import Modal from '../ui/Modal' import Modal from '../ui/Modal'
import PageHeader from '../ui/PageHeader' import PageHeader from '../ui/PageHeader'
import { Tabs } from '../ui/Tabs'
import { Badge, EmptyState, FieldError, Icon, SkeletonRows } from '../ui/primitives' import { Badge, EmptyState, FieldError, Icon, SkeletonRows } from '../ui/primitives'
import { useToast } from '../ui/Toast' import { useToast } from '../ui/Toast'
import { useAuth } from '../auth/AuthContext' import { useAuth } from '../auth/AuthContext'
@ -981,17 +982,6 @@ function JobOwnership({ job, canEdit }) {
const managersQuery = useManagerDirectory() const managersQuery = useManagerDirectory()
const recruitersQuery = useRecruiterDirectory() const recruitersQuery = useRecruiterDirectory()
const historyQuery = useQuery({
queryKey: qk.assignments.job(job.id),
queryFn: async () => {
const res = await assignmentsApi.listJob(job.id, { currentOnly: false })
const rows = Array.isArray(res?.data) ? res.data : []
return rows.map((r) => assignmentsApi.toAssignmentView(r))
},
enabled: Boolean(job.id),
retry: false,
})
const patch = useMutation({ const patch = useMutation({
mutationFn: (body) => jobsApi.update(job.id, body), mutationFn: (body) => jobsApi.update(job.id, body),
onSuccess: () => { onSuccess: () => {
@ -1003,8 +993,6 @@ function JobOwnership({ job, canEdit }) {
onError: (err) => toast(friendlyAuthError(err, 'Could not update the assignment.'), 'error'), onError: (err) => toast(friendlyAuthError(err, 'Could not update the assignment.'), 'error'),
}) })
const history = historyQuery.data ?? []
return ( return (
<> <>
<div className="divider" /> <div className="divider" />
@ -1054,41 +1042,49 @@ function JobOwnership({ job, canEdit }) {
{canEdit && recruitersQuery.isError && ( {canEdit && recruitersQuery.isError && (
<p className="text-muted text-sm">The recruiter list needs the <code>tasks.view</code> permission.</p> <p className="text-muted text-sm">The recruiter list needs the <code>tasks.view</code> permission.</p>
)} )}
<div style={{ ...SECTION_LABEL, marginTop: 8 }}>Assignment history</div>
{historyQuery.isError ? (
<p className="text-muted text-sm">
{friendlyAuthError(historyQuery.error, 'History did not load.')}
</p>
) : historyQuery.isPending ? (
<p className="text-muted text-sm">Loading</p>
) : history.length === 0 ? (
<p className="text-muted text-sm">No assignment history yet.</p>
) : (
<div className="list-tight">
{history.map((row) => (
<div className="list-row" key={row.id}>
<div className="lr-main">
<div className="lr-title">{row.name || 'Unknown'}</div>
<div className="lr-sub">
{[
ROLE_LABEL[row.role] || row.role?.replace(/_/g, ' '),
row.validFrom ? fmtShort(row.validFrom) : null,
row.validTo ? `${fmtShort(row.validTo)}` : 'current',
row.assignedByName ? `by ${row.assignedByName}` : null,
].filter(Boolean).join(' · ')}
</div>
</div>
{!row.validTo && <Badge className="b-green">Current</Badge>}
</div>
))}
</div>
)}
</div> </div>
</> </>
) )
} }
function AssignmentHistory({ historyQuery }) {
const history = historyQuery.data ?? []
if (historyQuery.isError) {
return (
<p className="text-muted text-sm">
{friendlyAuthError(historyQuery.error, 'History did not load.')}
</p>
)
}
if (historyQuery.isPending) {
return <p className="text-muted text-sm">Loading</p>
}
if (history.length === 0) {
return <p className="text-muted">No assignment history yet.</p>
}
return (
<div className="list-tight">
{history.map((row) => (
<div className="list-row" key={row.id}>
<div className="lr-main">
<div className="lr-title">{row.name || 'Unknown'}</div>
<div className="lr-sub">
{[
ROLE_LABEL[row.role] || row.role?.replace(/_/g, ' '),
row.validFrom ? fmtShort(row.validFrom) : null,
row.validTo ? `${fmtShort(row.validTo)}` : 'current',
row.assignedByName ? `by ${row.assignedByName}` : null,
].filter(Boolean).join(' · ')}
</div>
</div>
{!row.validTo && <Badge className="b-green">Current</Badge>}
</div>
))}
</div>
)
}
/* Cover image, when the post has one fetched with the bearer token into an /* Cover image, when the post has one fetched with the bearer token into an
object URL, because a bare <img src> cannot carry auth headers. null (404) object URL, because a bare <img src> cannot carry auth headers. null (404)
simply renders nothing. */ simply renders nothing. */
@ -1113,6 +1109,18 @@ function JobCover({ jobId }) {
function JobDetail({ function JobDetail({
job: j, canEdit, canDelete, statusBusy, deleteBusy, onClose, onPublish, onEdit, onStatus, onDelete, job: j, canEdit, canDelete, statusBusy, deleteBusy, onClose, onPublish, onEdit, onStatus, onDelete,
}) { }) {
const [tab, setTab] = useState('details')
const historyQuery = useQuery({
queryKey: qk.assignments.job(j.id),
queryFn: async () => {
const res = await assignmentsApi.listJob(j.id, { currentOnly: false })
const rows = Array.isArray(res?.data) ? res.data : []
return rows.map((r) => assignmentsApi.toAssignmentView(r))
},
enabled: Boolean(j.id),
retry: false,
})
return ( return (
<Modal <Modal
title="Job Details" title="Job Details"
@ -1160,37 +1168,52 @@ function JobDetail({
</div> </div>
</div> </div>
<div className="info-grid mb-18"> <Tabs
<div className="info-item"><div className="il">Department</div><div className="iv">{j.department || '—'}</div></div> value={tab}
<div className="info-item"><div className="il">Location</div><div className="iv">{j.location || '—'}</div></div> onChange={setTab}
<div className="info-item"><div className="il">Employment Type</div><div className="iv">{j.type || '—'}</div></div> tabs={[
<div className="info-item"><div className="il">Platform</div><div className="iv">{platformLabel(j.platform) || '—'}</div></div> { key: 'details', label: 'Details' },
<div className="info-item"><div className="il">Vacancies</div><div className="iv">{j.vacancies ?? '—'}</div></div> { key: 'history', label: 'History', count: historyQuery.data?.length },
<div className="info-item"><div className="il">Experience</div><div className="iv">{j.experience || '—'}</div></div> ]}
<div className="info-item"><div className="il">Created</div><div className="iv">{j.created ? fmtShort(j.created) : '—'}</div></div> />
<div className="info-item"><div className="il">Created by</div><div className="iv">{j.createdByName || '—'}</div></div>
<div className="info-item"><div className="il">Hiring Manager</div><div className="iv">{j.hiringManager || '—'}</div></div>
<div className="info-item"><div className="il">Assigned Recruiter</div><div className="iv">{j.recruiter || '—'}</div></div>
<div className="info-item"><div className="il">Closed at</div><div className="iv">{j.closedAt ? fmtShort(j.closedAt) : '—'}</div></div>
</div>
<JobOwnership job={j} canEdit={canEdit} /> {tab === 'details' && (
{j.description && (
<> <>
<div className="divider" /> <div className="info-grid mb-18">
<div className="mb-16"> <div className="info-item"><div className="il">Department</div><div className="iv">{j.department || '—'}</div></div>
<div style={SECTION_LABEL}>Description</div> <div className="info-item"><div className="il">Location</div><div className="iv">{j.location || '—'}</div></div>
<p style={{ color: 'var(--text-2)' }}>{j.description}</p> <div className="info-item"><div className="il">Employment Type</div><div className="iv">{j.type || '—'}</div></div>
<div className="info-item"><div className="il">Platform</div><div className="iv">{platformLabel(j.platform) || '—'}</div></div>
<div className="info-item"><div className="il">Vacancies</div><div className="iv">{j.vacancies ?? '—'}</div></div>
<div className="info-item"><div className="il">Experience</div><div className="iv">{j.experience || '—'}</div></div>
<div className="info-item"><div className="il">Created</div><div className="iv">{j.created ? fmtShort(j.created) : '—'}</div></div>
<div className="info-item"><div className="il">Created by</div><div className="iv">{j.createdByName || '—'}</div></div>
<div className="info-item"><div className="il">Hiring Manager</div><div className="iv">{j.hiringManager || '—'}</div></div>
<div className="info-item"><div className="il">Assigned Recruiter</div><div className="iv">{j.recruiter || '—'}</div></div>
<div className="info-item"><div className="il">Closed at</div><div className="iv">{j.closedAt ? fmtShort(j.closedAt) : '—'}</div></div>
</div> </div>
<JobOwnership job={j} canEdit={canEdit} />
{j.description && (
<>
<div className="divider" />
<div className="mb-16">
<div style={SECTION_LABEL}>Description</div>
<p style={{ color: 'var(--text-2)' }}>{j.description}</p>
</div>
</>
)}
{!!(j.skills && j.skills.length) && (
<div>
<div style={SECTION_LABEL}>Required Skills</div>
<div className="k-tags">{j.skills.map((s) => <span className="tag" key={s}>{s}</span>)}</div>
</div>
)}
</> </>
)} )}
{!!(j.skills && j.skills.length) && (
<div> {tab === 'history' && <AssignmentHistory historyQuery={historyQuery} />}
<div style={SECTION_LABEL}>Required Skills</div>
<div className="k-tags">{j.skills.map((s) => <span className="tag" key={s}>{s}</span>)}</div>
</div>
)}
</Modal> </Modal>
) )
} }