Spaces:
Running
Running
| import { useEffect, useMemo, useState } from 'react' | |
| import { fetchJobs, filterJobs, fetchStatistics } from '../api/jobs.js' | |
| import { formatDateTime } from '../utils/date.js' | |
| export const INITIAL_FILTERS = { | |
| titles: [], | |
| companies: [], | |
| locations: [], | |
| salaryMin: '', | |
| salaryMax: '', | |
| top: false, | |
| dateFrom: null, | |
| dateTo: null, | |
| } | |
| export function useJobs() { | |
| const [jobs, setJobs] = useState([]) | |
| const [isLoading, setIsLoading] = useState(false) | |
| const [filters, setFilters] = useState(INITIAL_FILTERS) | |
| const [lastUpdatedAt, setLastUpdatedAt] = useState(formatDateTime(new Date())) | |
| const [nextPlannedAt, setNextPlannedAt] = useState(formatDateTime(new Date(Date.now() + 6 * 36e5))) | |
| const [pageIndex, setPageIndex] = useState(0) | |
| const [pageSize, setPageSize] = useState(25) | |
| const [totalJobs, setTotalJobs] = useState(0) | |
| const [serverFilter, setServerFilter] = useState(null) | |
| async function load() { | |
| setIsLoading(true) | |
| try { | |
| let api | |
| if (serverFilter) { | |
| api = await filterJobs({ filter: serverFilter, pageIndex, pageSize }) | |
| } else { | |
| api = await fetchJobs({ pageIndex, pageSize }) | |
| } | |
| const payload = api?.data | |
| const items = payload?.data || [] | |
| const paging = payload?.paging || {} | |
| setJobs(items) | |
| setTotalJobs(Number(paging.totalCount) || 0) | |
| try { | |
| const stats = await fetchStatistics() | |
| const last = stats?.data?.lastUpdate | |
| const next = stats?.data?.nextUpdate | |
| setLastUpdatedAt(last ? formatDateTime(last) : formatDateTime(new Date())) | |
| setNextPlannedAt(next ? formatDateTime(next) : formatDateTime(new Date(Date.now() + 6 * 36e5))) | |
| } catch (_) { | |
| setLastUpdatedAt(formatDateTime(new Date())) | |
| setNextPlannedAt(formatDateTime(new Date(Date.now() + 6 * 36e5))) | |
| } | |
| } catch (e) { | |
| setJobs([]) | |
| setTotalJobs(0) | |
| } finally { | |
| setIsLoading(false) | |
| } | |
| } | |
| useEffect(() => { | |
| load() | |
| }, [pageIndex, pageSize, serverFilter]) | |
| const pageCount = useMemo(() => Math.max(1, Math.ceil((totalJobs || 0) / pageSize)), [totalJobs, pageSize]) | |
| function setPage(newIndex) { | |
| const clamped = Math.max(0, Math.min(newIndex, pageCount - 1)) | |
| setPageIndex(clamped) | |
| } | |
| function setSize(newSize) { | |
| setPageSize(newSize) | |
| setPageIndex(0) | |
| } | |
| function refresh() { | |
| load() | |
| } | |
| function buildServerFilter() { | |
| const filter = {} | |
| if (filters.titles && filters.titles.length) filter.titles = filters.titles | |
| if (filters.companies && filters.companies.length) filter.companies = filters.companies | |
| if (filters.locations && filters.locations.length) filter.locations = filters.locations | |
| if (filters.salaryMin !== '' && !Number.isNaN(Number(filters.salaryMin))) filter.minSalary = Number(filters.salaryMin) | |
| if (filters.salaryMax !== '' && !Number.isNaN(Number(filters.salaryMax))) filter.maxSalary = Number(filters.salaryMax) | |
| if (filters.top) filter.isTop5 = true | |
| if (filters.dateFrom) filter.minDate = new Date(filters.dateFrom).toISOString().slice(0, 10) | |
| if (filters.dateTo) filter.maxDate = new Date(filters.dateTo).toISOString().slice(0, 10) | |
| return filter | |
| } | |
| function submitFilters() { | |
| const f = buildServerFilter() | |
| setServerFilter(Object.keys(f).length ? f : null) | |
| setPageIndex(0) | |
| } | |
| const domain = useMemo(() => { | |
| const unique = (arr) => Array.from(new Set(arr)).sort() | |
| return { | |
| titles: unique(jobs.map(j => j.title)), | |
| companies: unique(jobs.map(j => j.company)), | |
| locations: unique(jobs.map(j => j.location)), | |
| } | |
| }, [jobs]) | |
| function resetFilters() { | |
| setFilters(INITIAL_FILTERS) | |
| setServerFilter(null) | |
| setPageIndex(0) | |
| } | |
| return { jobs, totalJobs, pageIndex, pageCount, pageSize, setPage, setSize, isLoading, lastUpdatedAt, nextPlannedAt, refresh, setFilters, filters, domain, resetFilters, submitFilters } | |
| } | |