added canddiates listing
parent
259c41c049
commit
62491d5f75
|
|
@ -102,7 +102,7 @@ class Users(SQLModel, table=True):
|
||||||
select(cls)
|
select(cls)
|
||||||
.options(selectinload(cls.role))
|
.options(selectinload(cls.role))
|
||||||
.where(cls.is_deleted == False)
|
.where(cls.is_deleted == False)
|
||||||
.order_by(cls.created_at.desc())
|
.order_by(cls.created_at.desc(),cls.id.desc())
|
||||||
)
|
)
|
||||||
if search:
|
if search:
|
||||||
statement = statement.where(cls._search_filter(search))
|
statement = statement.where(cls._search_filter(search))
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
|
|
||||||
import Modal from '../ui/Modal'
|
import Modal from '../ui/Modal'
|
||||||
import { DataTableHead, DEFAULT_PAGE_SIZE, Pagination, pageAfterSizeChange, pageWindow, useDataTable } from '../ui/DataTable'
|
import { DataTableHead, DEFAULT_PAGE_SIZE, Pagination, pageWindow, useDataTable } from '../ui/DataTable'
|
||||||
import PageHeader from '../ui/PageHeader'
|
import PageHeader from '../ui/PageHeader'
|
||||||
import { Avatar, Badge, EmptyState, FieldError, Icon, SkeletonRows } from '../ui/primitives'
|
import { Avatar, Badge, EmptyState, FieldError, Icon, SkeletonRows } from '../ui/primitives'
|
||||||
import { useToast } from '../ui/Toast'
|
import { useToast } from '../ui/Toast'
|
||||||
|
|
@ -117,13 +117,12 @@ export default function Candidates() {
|
||||||
const [filters, setFilters] = useState(EMPTY_FILTERS)
|
const [filters, setFilters] = useState(EMPTY_FILTERS)
|
||||||
const [showFilters, setShowFilters] = useState(false)
|
const [showFilters, setShowFilters] = useState(false)
|
||||||
const [sortMode, setSortMode] = useState('recent')
|
const [sortMode, setSortMode] = useState('recent')
|
||||||
const [page, setPage] = useState(1)
|
const [skip, setSkip] = useState(0)
|
||||||
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE)
|
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE)
|
||||||
const [profileFor, setProfileFor] = useState(null)
|
const [profileFor, setProfileFor] = useState(null)
|
||||||
const [atsFor, setAtsFor] = useState(null)
|
const [atsFor, setAtsFor] = useState(null)
|
||||||
const [adding, setAdding] = useState(false)
|
const [adding, setAdding] = useState(false)
|
||||||
|
|
||||||
const skip = (page - 1) * pageSize
|
|
||||||
const countQuery = useQuery({
|
const countQuery = useQuery({
|
||||||
queryKey: qk.candidates.count({ roleId: CANDIDATE_ROLE_ID }),
|
queryKey: qk.candidates.count({ roleId: CANDIDATE_ROLE_ID }),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
|
@ -216,8 +215,7 @@ export default function Candidates() {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
if (sortMode === 'recent') list = [...list].sort((a, b) => (b.applied ?? 0) - (a.applied ?? 0))
|
if (sortMode === 'name') list = [...list].sort((a, b) => a.name.localeCompare(b.name))
|
||||||
else if (sortMode === 'name') list = [...list].sort((a, b) => a.name.localeCompare(b.name))
|
|
||||||
return list
|
return list
|
||||||
}, [candidates, filters, q, sortMode])
|
}, [candidates, filters, q, sortMode])
|
||||||
|
|
||||||
|
|
@ -238,11 +236,14 @@ export default function Candidates() {
|
||||||
const t = useDataTable({ columns, rows, pageSize: Math.max(rows.length, 1) })
|
const t = useDataTable({ columns, rows, pageSize: Math.max(rows.length, 1) })
|
||||||
const total = countQuery.data ?? 0
|
const total = countQuery.data ?? 0
|
||||||
const pages = Math.max(1, Math.ceil(total / pageSize))
|
const pages = Math.max(1, Math.ceil(total / pageSize))
|
||||||
const currentPage = Math.min(page, pages)
|
const from = total ? skip + 1 : 0
|
||||||
|
const to = total ? skip + rows.length : 0
|
||||||
|
const currentPage = Math.min(Math.floor(skip / pageSize) + 1, pages)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (page > pages) setPage(pages)
|
if (total <= 0 || skip < total) return
|
||||||
}, [page, pages])
|
setSkip(Math.max(0, Math.floor((total - 1) / pageSize) * pageSize))
|
||||||
|
}, [total, pageSize, skip])
|
||||||
|
|
||||||
const recentChips = recentlyViewed
|
const recentChips = recentlyViewed
|
||||||
.slice(0, 6)
|
.slice(0, 6)
|
||||||
|
|
@ -426,15 +427,15 @@ export default function Candidates() {
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<Pagination
|
<Pagination
|
||||||
from={total ? (currentPage - 1) * pageSize + 1 : 0}
|
from={from}
|
||||||
to={total ? (currentPage - 1) * pageSize + rows.length : 0}
|
to={to}
|
||||||
total={total}
|
total={total}
|
||||||
page={currentPage}
|
page={currentPage}
|
||||||
pages={pages}
|
pages={pages}
|
||||||
setPage={setPage}
|
setPage={(p) => setSkip((p - 1) * pageSize)}
|
||||||
pageButtons={pageWindow(currentPage, pages)}
|
pageButtons={pageWindow(currentPage, pages)}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
onPageSizeChange={(n) => { setPageSize(n); setPage((p) => pageAfterSizeChange(p, total, n)) }}
|
onPageSizeChange={(n) => setPageSize(n)}
|
||||||
pageSizeMax={500}
|
pageSizeMax={500}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||||
import OpenResumeButton from '../ui/OpenResumeButton'
|
import OpenResumeButton from '../ui/OpenResumeButton'
|
||||||
import PageHeader from '../ui/PageHeader'
|
import PageHeader from '../ui/PageHeader'
|
||||||
import { Tabs } from '../ui/Tabs'
|
import { Tabs } from '../ui/Tabs'
|
||||||
import { DEFAULT_PAGE_SIZE, Pagination, pageAfterSizeChange, pageWindow } from '../ui/DataTable'
|
import { DEFAULT_PAGE_SIZE, Pagination, pageWindow } from '../ui/DataTable'
|
||||||
import { Avatar, Badge, EmptyState, Icon } from '../ui/primitives'
|
import { Avatar, Badge, EmptyState, Icon } from '../ui/primitives'
|
||||||
import { JobCard, PickRoleModal } from '../ui/SuggestedRoles'
|
import { JobCard, PickRoleModal } from '../ui/SuggestedRoles'
|
||||||
import { useToast } from '../ui/Toast'
|
import { useToast } from '../ui/Toast'
|
||||||
|
|
@ -109,7 +109,7 @@ export default function Matching() {
|
||||||
const deepLink = searchParams.get('record')
|
const deepLink = searchParams.get('record')
|
||||||
|
|
||||||
const [tab, setTab] = useState('needs')
|
const [tab, setTab] = useState('needs')
|
||||||
const [page, setPage] = useState(1)
|
const [skip, setSkip] = useState(0)
|
||||||
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE)
|
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE)
|
||||||
const [selectedId, setSelectedId] = useState(deepLink || null)
|
const [selectedId, setSelectedId] = useState(deepLink || null)
|
||||||
const [q, setQ] = useState('')
|
const [q, setQ] = useState('')
|
||||||
|
|
@ -121,9 +121,9 @@ export default function Matching() {
|
||||||
const listParams = useMemo(() => ({
|
const listParams = useMemo(() => ({
|
||||||
...tabFilter,
|
...tabFilter,
|
||||||
top: pageSize,
|
top: pageSize,
|
||||||
skip: (page - 1) * pageSize,
|
skip,
|
||||||
...(q.trim() ? { search: q.trim() } : {}),
|
...(q.trim() ? { search: q.trim() } : {}),
|
||||||
}), [tabFilter, page, pageSize, q])
|
}), [tabFilter, skip, pageSize, q])
|
||||||
|
|
||||||
const listQuery = useQuery({
|
const listQuery = useQuery({
|
||||||
queryKey: qk.candidates.matching({ ...listParams, tab }),
|
queryKey: qk.candidates.matching({ ...listParams, tab }),
|
||||||
|
|
@ -147,11 +147,14 @@ export default function Matching() {
|
||||||
const filtered = rows
|
const filtered = rows
|
||||||
const total = listQuery.data?.total ?? 0
|
const total = listQuery.data?.total ?? 0
|
||||||
const pages = Math.max(1, Math.ceil(total / pageSize))
|
const pages = Math.max(1, Math.ceil(total / pageSize))
|
||||||
const currentPage = Math.min(page, pages)
|
const from = total ? skip + 1 : 0
|
||||||
|
const to = Math.min(skip + pageSize, total)
|
||||||
|
const currentPage = Math.min(Math.floor(skip / pageSize) + 1, pages)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (listQuery.isSuccess && page > pages) setPage(pages)
|
if (!listQuery.isSuccess || total <= 0 || skip < total) return
|
||||||
}, [listQuery.isSuccess, page, pages])
|
setSkip(Math.max(0, Math.floor((total - 1) / pageSize) * pageSize))
|
||||||
|
}, [listQuery.isSuccess, total, pageSize, skip])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!deepLink) return undefined
|
if (!deepLink) return undefined
|
||||||
|
|
@ -284,7 +287,7 @@ export default function Matching() {
|
||||||
<div style={{ padding: '0 8px', borderBottom: '1px solid var(--border)' }}>
|
<div style={{ padding: '0 8px', borderBottom: '1px solid var(--border)' }}>
|
||||||
<Tabs
|
<Tabs
|
||||||
value={tab}
|
value={tab}
|
||||||
onChange={(t) => { setTab(t); setPage(1); setSelectedId(null) }}
|
onChange={(t) => { setTab(t); setSkip(0); setSelectedId(null) }}
|
||||||
className="tabs tabs-wrap"
|
className="tabs tabs-wrap"
|
||||||
tabs={TABS.map((t) => ({
|
tabs={TABS.map((t) => ({
|
||||||
key: t.key,
|
key: t.key,
|
||||||
|
|
@ -301,7 +304,7 @@ export default function Matching() {
|
||||||
<Icon name="search" />
|
<Icon name="search" />
|
||||||
<input
|
<input
|
||||||
value={q}
|
value={q}
|
||||||
onChange={(e) => { setQ(e.target.value); setPage(1) }}
|
onChange={(e) => { setQ(e.target.value); setSkip(0) }}
|
||||||
placeholder="Search…"
|
placeholder="Search…"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -342,18 +345,17 @@ export default function Matching() {
|
||||||
</div>
|
</div>
|
||||||
{listQuery.isSuccess && total > 0 && (
|
{listQuery.isSuccess && total > 0 && (
|
||||||
<Pagination
|
<Pagination
|
||||||
from={total ? (currentPage - 1) * pageSize + 1 : 0}
|
from={from}
|
||||||
to={Math.min(currentPage * pageSize, total)}
|
to={to}
|
||||||
total={total}
|
total={total}
|
||||||
page={currentPage}
|
page={currentPage}
|
||||||
pages={pages}
|
pages={pages}
|
||||||
setPage={(p) => { setPage(p); setSelectedId(null) }}
|
setPage={(p) => { setSkip((p - 1) * pageSize); setSelectedId(null) }}
|
||||||
pageButtons={pageWindow(currentPage, pages)}
|
pageButtons={pageWindow(currentPage, pages)}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
pageSizeMax={PAGE_SIZE_MAX}
|
pageSizeMax={PAGE_SIZE_MAX}
|
||||||
onPageSizeChange={(n) => {
|
onPageSizeChange={(n) => {
|
||||||
setPageSize(n)
|
setPageSize(n)
|
||||||
setPage((p) => pageAfterSizeChange(p, total, n))
|
|
||||||
setSelectedId(null)
|
setSelectedId(null)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue