ui-polish 4/6: screen sweep B (17 remaining page screens)
AiAssistant, AiStudio, Analytics, Assessments, Calendar, CvImport, Help, JobBoard, Notifications, Offers, Pipeline, Rbac, RecruiterHub, Reports, Talent, TalentPool, Tasks — same bounded checklist: - Every remaining hand-rolled .page-head replaced with <PageHeader/>; zero copies of the block remain in the codebase. - JobBoard fixes its non-functional inline ellipsis (overflow without white-space never truncated) with .truncate + title. - Tasks imports the shared PRIORITY_CLASS and labels its unlabeled card-head add button; remaining .act-btn tooltips mirrored into aria-labels (Assessments, JobBoard, Notifications, Offers, Talent). - Assessments swaps a hand-rolled empty state for the imported EmptyState; Help drops an inline h2 font size the base rule now covers. - Mechanical inline-style swaps to utilities on exact matches. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>pull/29/head
parent
4757895029
commit
af6ef715c5
|
|
@ -1,5 +1,6 @@
|
|||
import { useState } from 'react'
|
||||
import Chat from '../app/ai/Chat'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Icon } from '../ui/primitives'
|
||||
|
||||
export default function AiAssistant() {
|
||||
|
|
@ -8,20 +9,18 @@ export default function AiAssistant() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">AI Assistant</h1>
|
||||
<p className="page-sub">Your recruiting copilot — powered by AI (interface preview)</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="AI Assistant"
|
||||
sub="Your recruiting copilot — powered by AI (interface preview)"
|
||||
actions={<>
|
||||
<span className="integration-status pending">
|
||||
<span className="pulse" />Model endpoint · Not connected
|
||||
</span>
|
||||
<button className="btn btn-secondary" onClick={() => setResetKey((k) => k + 1)}>
|
||||
<Icon name="plus" /> New Chat
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>}
|
||||
/>
|
||||
<div className="card">
|
||||
<div className="card-body">
|
||||
<Chat resetKey={resetKey} />
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState } from 'react'
|
||||
import Modal from '../ui/Modal'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Badge, Icon } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
import { aiModules } from '../data/seed'
|
||||
|
|
@ -11,20 +12,16 @@ export default function AiStudio() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">AI Studio</h1>
|
||||
<p className="page-sub">
|
||||
Next-generation AI modules — designed and API-ready for backend integration
|
||||
</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="AI Studio"
|
||||
sub="Next-generation AI modules — designed and API-ready for backend integration"
|
||||
actions={
|
||||
<span className="integration-status pending"><span className="pulse" />{betaCount} in Beta</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="card brand-hero mb-18">
|
||||
<div className="card-body" style={{ display: 'flex', alignItems: 'center', gap: 20, flexWrap: 'wrap' }}>
|
||||
<div className="card-body flex items-center flex-wrap" style={{ gap: 20 }}>
|
||||
<div className="ai-logo" style={{ margin: 0, width: 56, height: 56 }}><Icon name="sparkles" /></div>
|
||||
<div style={{ flex: 1, minWidth: 220 }}>
|
||||
<h2 style={{ fontSize: 19, marginBottom: 4 }}>Everything is API-ready</h2>
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import { useMutation, useQueries, useQuery } from '@tanstack/react-query'
|
|||
import Chart, { ChartLegend } from '../ui/Chart'
|
||||
import Charts from '../lib/charts'
|
||||
import DataTable from '../ui/DataTable'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { EmptyState, Icon } from '../ui/primitives'
|
||||
import { qk } from '../lib/queryKeys'
|
||||
import { friendlyAuthError } from '../lib/errors'
|
||||
|
|
@ -396,12 +397,10 @@ export default function Analytics() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Analytics</h1>
|
||||
<p className="page-sub">Deep-dive metrics across your recruitment funnel</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Analytics"
|
||||
sub="Deep-dive metrics across your recruitment funnel"
|
||||
actions={<>
|
||||
<div className="pill-tabs">
|
||||
{RANGES.map((r) => (
|
||||
<span
|
||||
|
|
@ -426,8 +425,8 @@ export default function Analytics() {
|
|||
<option key={r.id} value={r.id}>{r.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</>}
|
||||
/>
|
||||
|
||||
{kpisQuery.isError && (
|
||||
<div className="card mb-18">
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
|||
|
||||
import DataTable from '../ui/DataTable'
|
||||
import Modal from '../ui/Modal'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Avatar, Badge, EmptyState, FieldError, Icon, KpiCard, ProgressBar, ScoreChip } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
import { useAuth } from '../auth/AuthContext'
|
||||
|
|
@ -168,10 +169,11 @@ export default function Assessments() {
|
|||
key: '_a', label: 'Actions', align: 'right',
|
||||
render: (a) => (
|
||||
<div className="row-actions">
|
||||
<button className="act-btn" data-tip="View" onClick={() => setViewing(a)}><Icon name="eye" /></button>
|
||||
<button className="act-btn" data-tip="View" aria-label="View assessment" onClick={() => setViewing(a)}><Icon name="eye" /></button>
|
||||
<button
|
||||
className="act-btn"
|
||||
data-tip="Remind"
|
||||
aria-label="Send reminder"
|
||||
disabled={!canEdit || remind.isPending}
|
||||
title={!canEdit ? 'Requires assessments.edit' : undefined}
|
||||
onClick={() => remind.mutate(a.id)}
|
||||
|
|
@ -185,12 +187,10 @@ export default function Assessments() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Assessments</h1>
|
||||
<p className="page-sub">Coding tests, take-homes, and evaluations</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Assessments"
|
||||
sub="Coding tests, take-homes, and evaluations"
|
||||
actions={
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
disabled={!canCreate}
|
||||
|
|
@ -199,8 +199,8 @@ export default function Assessments() {
|
|||
>
|
||||
<Icon name="plus" /> Assign Assessment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="grid g-kpi mb-18">
|
||||
<KpiCard label="Total Assigned" value={totalCount} icon="file" tone="i-indigo" />
|
||||
|
|
@ -320,11 +320,9 @@ export default function Assessments() {
|
|||
)}
|
||||
</>
|
||||
) : (
|
||||
<div className="empty-state">
|
||||
<Icon name="clock" />
|
||||
<h3>Assessment not completed</h3>
|
||||
<p>Results will appear once the candidate submits.</p>
|
||||
</div>
|
||||
<EmptyState icon="clock" title="Assessment not completed">
|
||||
Results will appear once the candidate submits.
|
||||
</EmptyState>
|
||||
)}
|
||||
</Modal>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import { useMemo, useState } from 'react'
|
|||
import { useNavigate } from 'react-router-dom'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Avatar, EmptyState, Icon } from '../ui/primitives'
|
||||
import { qk } from '../lib/queryKeys'
|
||||
import { friendlyAuthError } from '../lib/errors'
|
||||
|
|
@ -122,15 +123,10 @@ export default function Calendar() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Calendar</h1>
|
||||
<p className="page-sub">
|
||||
Interview schedule at a glance
|
||||
{monthQuery.isSuccess ? ` · ${events.length} this month` : ''}
|
||||
</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Calendar"
|
||||
sub={`Interview schedule at a glance${monthQuery.isSuccess ? ` · ${events.length} this month` : ''}`}
|
||||
actions={<>
|
||||
<div className="flex items-center gap-8">
|
||||
<button className="btn btn-icon btn-secondary" onClick={() => step(-1)} aria-label="Previous month">
|
||||
<Icon name="chevron-left" />
|
||||
|
|
@ -152,8 +148,8 @@ export default function Calendar() {
|
|||
>
|
||||
<Icon name="plus" /> Schedule
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>}
|
||||
/>
|
||||
|
||||
{monthQuery.isError ? (
|
||||
<div className="card">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
import { useRef, useState } from 'react'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Badge, EmptyState, Icon, ScoreChip } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
import JobCandidates from './JobCandidates'
|
||||
|
|
@ -124,25 +125,20 @@ export default function CvImport() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">CV Import</h1>
|
||||
<p className="page-sub">Upload resume PDFs — parsed, scored against a job, and saved automatically</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<span className="integration-status pending"><span className="pulse" />AI Resume Scoring · Live</span>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
title="CV Import"
|
||||
sub="Upload resume PDFs — parsed, scored against a job, and saved automatically"
|
||||
actions={<span className="integration-status pending"><span className="pulse" />AI Resume Scoring · Live</span>}
|
||||
/>
|
||||
|
||||
<div className="grid g-2-1">
|
||||
<div>
|
||||
<div className="card mb-18">
|
||||
<div className="card-body">
|
||||
<div className="flex items-center gap-8" style={{ marginBottom: 16 }}>
|
||||
<div className="flex items-center gap-8 mb-16">
|
||||
<span className="fw-600 text-sm" style={{ flexShrink: 0 }}>Score against</span>
|
||||
<select
|
||||
className="select"
|
||||
style={{ flex: 1 }}
|
||||
className="select flex-1"
|
||||
value={jobId}
|
||||
onChange={(e) => setJobId(e.target.value)}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { useState } from 'react'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Icon } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
|
||||
|
|
@ -23,16 +24,11 @@ export default function Help() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Help Center</h1>
|
||||
<p className="page-sub">Find answers and get support</p>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader title="Help Center" sub="Find answers and get support" />
|
||||
|
||||
<div className="card brand-hero mb-18">
|
||||
<div className="card-body" style={{ padding: 32, textAlign: 'center' }}>
|
||||
<h2 style={{ fontSize: 22, marginBottom: 8 }}>How can we help you?</h2>
|
||||
<h2 className="mb-8">How can we help you?</h2>
|
||||
<p style={{ opacity: 0.85, marginBottom: 18 }}>
|
||||
Search our knowledge base or browse the topics below
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import { Link, useNavigate } from 'react-router-dom'
|
|||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import DataTable from '../ui/DataTable'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Badge, EmptyState, Icon, KpiCard } from '../ui/primitives'
|
||||
import { qk } from '../lib/queryKeys'
|
||||
import { friendlyAuthError } from '../lib/errors'
|
||||
|
|
@ -235,11 +236,12 @@ export default function JobBoard() {
|
|||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
data-tip="Open live post"
|
||||
aria-label="Open live post"
|
||||
>
|
||||
<Icon name="external" />
|
||||
</a>
|
||||
) : (
|
||||
<button className="act-btn" data-tip="Not published yet" disabled>
|
||||
<button className="act-btn" data-tip="Not published yet" aria-label="Not published yet" disabled>
|
||||
<Icon name="external" />
|
||||
</button>
|
||||
)}
|
||||
|
|
@ -250,12 +252,10 @@ export default function JobBoard() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Job Board</h1>
|
||||
<p className="page-sub">Where each requisition was published, and whether it landed</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Job Board"
|
||||
sub="Where each requisition was published, and whether it landed"
|
||||
actions={<>
|
||||
<Link className="btn btn-secondary" to="/analytics"><Icon name="trending-up" /> Analytics</Link>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
|
|
@ -263,8 +263,8 @@ export default function JobBoard() {
|
|||
>
|
||||
<Icon name="send" /> Publish a Job
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>}
|
||||
/>
|
||||
|
||||
<div className="grid g-kpi mb-18">
|
||||
<KpiCard label="Total Posts" value={postsQuery.isPending ? '—' : stats.total} icon="layers" tone="i-indigo" />
|
||||
|
|
@ -309,12 +309,12 @@ export default function JobBoard() {
|
|||
<span className={`kpi-icn ${d.connected ? 'i-green' : 'i-indigo'}`} style={{ width: 34, height: 34, borderRadius: 9 }}>
|
||||
<Icon name={d.connected ? 'check-circle' : 'layers'} />
|
||||
</span>
|
||||
<div style={{ minWidth: 0 }}>
|
||||
<div className="lr-title" style={{ overflow: 'hidden', textOverflow: 'ellipsis' }}>{d.name}</div>
|
||||
<div className="min-w-0">
|
||||
<div className="lr-title truncate" title={d.name}>{d.name}</div>
|
||||
<div className="lr-sub">{d.service || (d.connected ? 'channel' : 'not connected')}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center" style={{ justifyContent: 'space-between' }}>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="cell-sub">{d.posts} post{d.posts === 1 ? '' : 's'}</span>
|
||||
<Badge className={d.connected ? 'b-green' : 'b-gray'}>
|
||||
{d.connected ? 'Connected' : 'Available'}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useNavigate } from 'react-router-dom'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { EmptyState, Icon } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
import { qk } from '../lib/queryKeys'
|
||||
|
|
@ -50,12 +51,10 @@ export default function Notifications() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Notifications</h1>
|
||||
<p className="page-sub">Stay on top of hiring activity</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Notifications"
|
||||
sub="Stay on top of hiring activity"
|
||||
actions={
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
disabled={markAll.isPending}
|
||||
|
|
@ -63,8 +62,8 @@ export default function Notifications() {
|
|||
>
|
||||
<Icon name="check" /> Mark all read
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="card">
|
||||
{query.isPending && (
|
||||
|
|
@ -107,6 +106,7 @@ export default function Notifications() {
|
|||
<button
|
||||
className="act-btn"
|
||||
data-tip="Dismiss"
|
||||
aria-label="Dismiss notification"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
remove.mutate(n.id)
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
|||
|
||||
import DataTable from '../ui/DataTable'
|
||||
import Modal from '../ui/Modal'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Avatar, Badge, EmptyState, FieldError, Icon, KpiCard } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
import { qk } from '../lib/queryKeys'
|
||||
|
|
@ -224,12 +225,13 @@ export default function Offers() {
|
|||
key: '_a', label: 'Actions', align: 'right',
|
||||
render: (o) => (
|
||||
<div className="row-actions">
|
||||
<button className="act-btn" data-tip="View" onClick={() => setViewing(o)}>
|
||||
<button className="act-btn" data-tip="View" aria-label="View offer" onClick={() => setViewing(o)}>
|
||||
<Icon name="eye" />
|
||||
</button>
|
||||
<button
|
||||
className="act-btn"
|
||||
data-tip={o.status === 'draft' ? 'Send offer' : 'Resend'}
|
||||
aria-label={o.status === 'draft' ? 'Send offer' : 'Resend offer'}
|
||||
disabled={busy || ['accepted', 'declined'].includes(o.status)}
|
||||
onClick={() => issue.mutate(o.id)}
|
||||
>
|
||||
|
|
@ -242,17 +244,15 @@ export default function Offers() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Offers</h1>
|
||||
<p className="page-sub">Track offer letters and acceptance</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Offers"
|
||||
sub="Track offer letters and acceptance"
|
||||
actions={
|
||||
<button className="btn btn-primary" onClick={() => setCreating(true)}>
|
||||
<Icon name="plus" /> Create Offer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="grid g-kpi mb-18">
|
||||
<KpiCard label="Offers Sent" value={allQuery.isPending ? '—' : stats.sent} icon="send" tone="i-indigo" />
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { useMemo, useState } from 'react'
|
|||
import { useNavigate } from 'react-router-dom'
|
||||
import { keepPreviousData, useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Avatar, EmptyState, Icon, ScoreChip } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
import { useAuth } from '../auth/AuthContext'
|
||||
|
|
@ -178,17 +179,15 @@ export default function Pipeline() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Pipeline</h1>
|
||||
<p className="page-sub">
|
||||
{canEdit
|
||||
? 'Drag candidates between stages to update their status'
|
||||
: 'Read-only — moving a candidate needs the pipeline.edit permission'}
|
||||
{total > candidates.length && ` · showing ${candidates.length} of ${total} applications`}
|
||||
</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Pipeline"
|
||||
sub={<>
|
||||
{canEdit
|
||||
? 'Drag candidates between stages to update their status'
|
||||
: 'Read-only — moving a candidate needs the pipeline.edit permission'}
|
||||
{total > candidates.length && ` · showing ${candidates.length} of ${total} applications`}
|
||||
</>}
|
||||
actions={<>
|
||||
<select className="select" value={jobId} onChange={(e) => setJobId(e.target.value)}>
|
||||
<option value="">All Jobs</option>
|
||||
{jobs.map((j) => (
|
||||
|
|
@ -201,8 +200,8 @@ export default function Pipeline() {
|
|||
>
|
||||
<Icon name="plus" /> Add Candidate
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>}
|
||||
/>
|
||||
|
||||
{board.isError ? (
|
||||
<EmptyState title="Could not load the pipeline">
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { useEffect, useMemo, useState } from 'react'
|
|||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
import Modal from '../ui/Modal'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Badge, EmptyState, FieldError, Icon } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
import { useFormState } from '../components/AuthLayout'
|
||||
|
|
@ -129,19 +130,15 @@ export default function Rbac() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Access Control</h1>
|
||||
<p className="page-sub">
|
||||
Roles, permission bundles and the {totalTags || '104'}-tag vocabulary, live from the server
|
||||
</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Access Control"
|
||||
sub={<>Roles, permission bundles and the {totalTags || '104'}-tag vocabulary, live from the server</>}
|
||||
actions={
|
||||
<button className="btn btn-primary" onClick={() => setCreating(true)}>
|
||||
<Icon name="plus" /> New Role
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{rolesQuery.isPending && (
|
||||
<div className="card"><div className="card-body">
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import { useQuery } from '@tanstack/react-query'
|
|||
|
||||
import Chart from '../ui/Chart'
|
||||
import Charts from '../lib/charts'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Avatar, EmptyState, Icon, KpiCard } from '../ui/primitives'
|
||||
import { qk } from '../lib/queryKeys'
|
||||
import { friendlyAuthError } from '../lib/errors'
|
||||
|
|
@ -188,20 +189,18 @@ export default function RecruiterHub() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Recruiter Hub</h1>
|
||||
<p className="page-sub">Per-recruiter performance, scoped server-side</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Recruiter Hub"
|
||||
sub="Per-recruiter performance, scoped server-side"
|
||||
actions={
|
||||
<select className="select" value={selected.id} onChange={(e) => setRecruiterId(e.target.value)}>
|
||||
{recruiters.map((x) => <option key={x.id} value={x.id}>{x.name}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="card brand-hero mb-18">
|
||||
<div className="card-body" style={{ display: 'flex', alignItems: 'center', gap: 18, flexWrap: 'wrap' }}>
|
||||
<div className="card-body flex items-center flex-wrap" style={{ gap: 18 }}>
|
||||
<Avatar name={name} initials={initialsOf(name)} color="rgba(255,255,255,.18)" className="avatar-lg" />
|
||||
<div style={{ flex: 1, minWidth: 200 }}>
|
||||
<div style={{ fontSize: 20, fontWeight: 700 }}>{name}</div>
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import Chart, { ChartLegend } from '../ui/Chart'
|
|||
import Charts from '../lib/charts'
|
||||
import DataTable from '../ui/DataTable'
|
||||
import Modal from '../ui/Modal'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { EmptyState, Icon, KpiCard, ProgressBar } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
import { qk } from '../lib/queryKeys'
|
||||
|
|
@ -461,17 +462,15 @@ export default function Reports() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Reports</h1>
|
||||
<p className="page-sub">Recruitment metrics across the selected window</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Reports"
|
||||
sub="Recruitment metrics across the selected window"
|
||||
actions={
|
||||
<select className="select" value={rangeKey} onChange={(e) => setRangeKey(e.target.value)}>
|
||||
{RANGES.map((r) => <option key={r.key} value={r.key}>{r.label}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
{kpisQuery.isError && (
|
||||
<div className="card mb-18"><div className="card-body">
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { useEffect, useMemo, useRef, useState } from 'react'
|
|||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
import Modal from '../ui/Modal'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Badge, EmptyState, Icon } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
import { qk } from '../lib/queryKeys'
|
||||
|
|
@ -186,6 +187,7 @@ function ProfileCard({ p, onView, onDismiss, dismissing }) {
|
|||
<a
|
||||
className="act-btn"
|
||||
data-tip="Open LinkedIn profile"
|
||||
aria-label="Open LinkedIn profile"
|
||||
href={p.linkedinUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
|
|
@ -196,6 +198,7 @@ function ProfileCard({ p, onView, onDismiss, dismissing }) {
|
|||
<button
|
||||
className="act-btn"
|
||||
data-tip="View profile"
|
||||
aria-label="View profile"
|
||||
onClick={(e) => { e.stopPropagation(); onView(p) }}
|
||||
>
|
||||
<Icon name="eye" />
|
||||
|
|
@ -203,6 +206,7 @@ function ProfileCard({ p, onView, onDismiss, dismissing }) {
|
|||
<button
|
||||
className="act-btn"
|
||||
data-tip="Dismiss"
|
||||
aria-label="Dismiss profile"
|
||||
disabled={dismissing}
|
||||
onClick={(e) => { e.stopPropagation(); onDismiss(p) }}
|
||||
>
|
||||
|
|
@ -440,19 +444,15 @@ export default function Talent() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Find Talent</h1>
|
||||
<p className="page-sub">Source matching LinkedIn profiles for a job via Apify</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<span className="integration-status pending"><span className="pulse" />LinkedIn Sourcing · Live</span>
|
||||
</div>
|
||||
</div>
|
||||
<PageHeader
|
||||
title="Find Talent"
|
||||
sub="Source matching LinkedIn profiles for a job via Apify"
|
||||
actions={<span className="integration-status pending"><span className="pulse" />LinkedIn Sourcing · Live</span>}
|
||||
/>
|
||||
|
||||
<div className="card mb-18">
|
||||
<div className="card-body">
|
||||
<div className="flex items-center gap-8" style={{ flexWrap: 'wrap' }}>
|
||||
<div className="flex items-center gap-8 flex-wrap">
|
||||
<select
|
||||
className="select"
|
||||
style={{ flex: 1, minWidth: 180 }}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import { useMemo, useState } from 'react'
|
|||
import { useNavigate } from 'react-router-dom'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Avatar, Badge, EmptyState, Icon, ScoreChip } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
import CandidateProfile from './CandidateProfile'
|
||||
|
|
@ -200,17 +201,15 @@ export default function TalentPool() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Talent Pool</h1>
|
||||
<p className="page-sub">{pool.length} silver-medalists & passive candidates to re-engage</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Talent Pool"
|
||||
sub={<>{pool.length} silver-medalists & passive candidates to re-engage</>}
|
||||
actions={
|
||||
<button className="btn btn-primary" onClick={() => toast('Talent campaign created', 'success')}>
|
||||
<Icon name="send" /> Start Campaign
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="card mb-18">
|
||||
<div className="card-body" style={{ padding: 16 }}>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ import { useNavigate } from 'react-router-dom'
|
|||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
|
||||
import Modal from '../ui/Modal'
|
||||
import { Badge, EmptyState, FieldError, Icon } from '../ui/primitives'
|
||||
import PageHeader from '../ui/PageHeader'
|
||||
import { Badge, EmptyState, FieldError, Icon, PRIORITY_CLASS } from '../ui/primitives'
|
||||
import { useToast } from '../ui/Toast'
|
||||
import { useAuth } from '../auth/AuthContext'
|
||||
import { useFormState } from '../components/AuthLayout'
|
||||
|
|
@ -28,7 +29,6 @@ import * as savedSearchesApi from '../api/savedSearches'
|
|||
import { fmtDate, fmtShort } from '../data/seed'
|
||||
|
||||
const FILTERS = ['All', 'Open', 'Completed', 'Overdue', 'High', 'Medium', 'Low']
|
||||
const PRIORITY_CLASS = { High: 'b-red', Medium: 'b-amber', Low: 'b-gray' }
|
||||
// Mirrors backend/tasks/views.py CREATOR_ROLES — the server is the enforcer,
|
||||
// this only keeps the button honest.
|
||||
const CREATOR_ROLES = ['system_administrator', 'hr_administrator', 'recruiter']
|
||||
|
|
@ -156,12 +156,10 @@ export default function Tasks() {
|
|||
|
||||
return (
|
||||
<div className="page">
|
||||
<div className="page-head">
|
||||
<div>
|
||||
<h1 className="page-title">Tasks</h1>
|
||||
<p className="page-sub">{openCount} open · {overdueCount} overdue</p>
|
||||
</div>
|
||||
<div className="page-head-actions">
|
||||
<PageHeader
|
||||
title="Tasks"
|
||||
sub={`${openCount} open · ${overdueCount} overdue`}
|
||||
actions={
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
disabled={!canCreate}
|
||||
|
|
@ -170,8 +168,8 @@ export default function Tasks() {
|
|||
>
|
||||
<Icon name="plus" /> New Task
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="grid g-2-1">
|
||||
<div className="card">
|
||||
|
|
@ -241,7 +239,7 @@ export default function Tasks() {
|
|||
<div className="card" style={{ alignSelf: 'start' }}>
|
||||
<div className="card-head">
|
||||
<div><h3>Saved Searches</h3><span className="ch-sub">Quick candidate filters</span></div>
|
||||
<button className="act-btn" onClick={() => setAddingSearch(true)}><Icon name="plus" /></button>
|
||||
<button className="act-btn" aria-label="Add saved search" data-tip="Add saved search" onClick={() => setAddingSearch(true)}><Icon name="plus" /></button>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
{savedQuery.isPending && <EmptyState icon="bookmark" title="Loading…">Fetching saved searches.</EmptyState>}
|
||||
|
|
@ -277,6 +275,7 @@ export default function Tasks() {
|
|||
<button
|
||||
className="act-btn"
|
||||
data-tip="Delete"
|
||||
aria-label="Delete saved search"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
deleteSearch.mutate(s.id)
|
||||
|
|
|
|||
Loading…
Reference in New Issue