import React, { useEffect } from 'react'; import { Box, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography, FormControl, Select, MenuItem, InputLabel, Chip, IconButton, Dialog, AppBar, Toolbar, useMediaQuery, useTheme, Slide, Checkbox, TablePagination, TextField, InputAdornment, CircularProgress, Alert, Tooltip, Grid, SxProps, } from '@mui/material'; import { KeyboardArrowUp as ArrowUpIcon, KeyboardArrowDown as ArrowDownIcon, Business as BusinessIcon, Work as WorkIcon, Schedule as ScheduleIcon, ArrowBack as ArrowBackIcon, Search as SearchIcon, Visibility as VisibilityIcon, Edit as EditIcon, Delete as DeleteIcon, Close as CloseIcon, } from '@mui/icons-material'; import { TransitionProps } from '@mui/material/transitions'; import * as Types from 'types/types'; // Adjust the import path as necessary import { useAuth } from 'hooks/AuthContext'; import { StyledMarkdown } from 'components/StyledMarkdown'; import { Scrollable } from 'components/Scrollable'; import { useLocation } from 'react-router-dom'; // async searchJobs(query: string): Promise { // const results = await this.getJobs(); // const filtered = results.data.filter(job => // job.title.toLowerCase().includes(query.toLowerCase()) || // job.description.toLowerCase().includes(query.toLowerCase()) || // job.company?.toLowerCase().includes(query.toLowerCase()) // ); // return { // data: filtered, // totalPages: 1, // totalItems: filtered.length, // }; // } type SortField = 'updatedAt' | 'createdAt' | 'company' | 'title'; type SortOrder = 'asc' | 'desc'; interface JobsViewProps { filter?: Record; onJobSelect?: (selectedJobs: Types.Job[]) => void; onJobView?: (job: Types.Job) => void; onJobEdit?: (job: Types.Job) => void; onJobDelete?: (job: Types.Job) => Promise; selectable?: boolean; showActions?: boolean; showDetailsPanel?: boolean; variant?: 'table' | 'list' | 'responsive'; sx?: SxProps; } const Transition = React.forwardRef(function Transition( props: TransitionProps & { children: React.ReactElement; }, ref: React.Ref ) { return ; }); const JobInfoPanel: React.FC<{ job: Types.Job; onClose?: () => void; inDialog?: boolean }> = ({ job, onClose, inDialog = false, }) => ( {job.title} {onClose && !inDialog && ( )} {job.company} {job.details?.employmentType && ( )} {job.details?.location && ( 📍 {job.details.location.city}, {job.details.location.state || job.details.location.country} )} {job.requirements && job.requirements.technicalSkills && job.requirements.technicalSkills.required && job.requirements.technicalSkills.required.length > 0 && ( Required Skills {job.requirements.technicalSkills.required.map(skill => ( ))} )} Posted: {job.createdAt?.toLocaleDateString()} Updated: {job.updatedAt?.toLocaleDateString()} {/* {job.views && ( Views: {job.views} )} */} ); const JobsView: React.FC = ({ onJobSelect, onJobView, onJobEdit, onJobDelete, selectable = true, showActions = true, showDetailsPanel = true, filter = {}, sx = {}, }) => { const theme = useTheme(); const { apiClient, user } = useAuth(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const isSmall = useMediaQuery(theme.breakpoints.down('sm')); const location = useLocation(); const [jobs, setJobs] = React.useState([]); const [loading, setLoading] = React.useState(true); const [error, setError] = React.useState(null); const [page, setPage] = React.useState(0); const [limit, setLimit] = React.useState(25); const [total, setTotal] = React.useState(0); const [searchQuery, setSearchQuery] = React.useState(''); const [searchTimeout, setSearchTimeout] = React.useState(null); const [selectedJobs, setSelectedJobs] = React.useState>(new Set()); const [selectedJob, setSelectedJob] = React.useState(null); const [sortField, setSortField] = React.useState('updatedAt'); const [sortOrder, setSortOrder] = React.useState('desc'); const [mobileDialogOpen, setMobileDialogOpen] = React.useState(false); const [detailsPanelOpen, setDetailsPanelOpen] = React.useState(showDetailsPanel); if (location.pathname.indexOf('/candidate/jobs') === 0) { filter = { ...filter, owner_id: user?.id || '' }; } const fetchJobs = React.useCallback( async (pageNum = 0, searchTerm = '') => { try { setLoading(true); setError(null); const paginationRequest: Partial = { page: pageNum + 1, limit: limit, sortBy: sortField, sortOrder: sortOrder, filters: filter, }; let paginationResponse: Types.PaginatedResponse; if (searchTerm.trim()) { paginationResponse = await apiClient.searchJobs(searchTerm); } else { paginationResponse = await apiClient.getJobs(paginationRequest); } const sortedJobs = sortJobs(paginationResponse.data, sortField, sortOrder); let updated = false; if (jobs.length) { if (sortedJobs.length !== jobs.length) { updated = true; } else { for (let i = 0; i < sortedJobs.length; i++) { if (sortedJobs[i].id !== jobs[i].id) { updated = true; break; } } } } else { updated = true; } if (updated) { setJobs(sortedJobs); setTotal(paginationResponse.total); } } catch (err) { setError(err instanceof Error ? err.message : 'An error occurred while fetching jobs'); setJobs([]); setTotal(0); } finally { setLoading(false); } }, [limit, sortField, sortOrder, apiClient] ); useEffect(() => { if (jobs.length > 0 && !selectedJob && detailsPanelOpen) { console.log('Setting selected job from fetchJobs'); setSelectedJob(jobs[0]); } }, [jobs, selectedJob, detailsPanelOpen]); React.useEffect(() => { console.log('Fetching jobs with filter:', filter, 'searchQuery:', searchQuery); fetchJobs(0, searchQuery); }, [fetchJobs, searchQuery]); const sortJobs = (jobsList: Types.Job[], field: SortField, order: SortOrder): Types.Job[] => { return [...jobsList].sort((a, b) => { let aValue: number | string; let bValue: number | string; switch (field) { case 'updatedAt': aValue = a.updatedAt?.getTime() || 0; bValue = b.updatedAt?.getTime() || 0; break; case 'createdAt': aValue = a.createdAt?.getTime() || 0; bValue = b.createdAt?.getTime() || 0; break; case 'company': aValue = a.company?.toLowerCase() || ''; bValue = b.company?.toLowerCase() || ''; break; case 'title': aValue = a.title?.toLowerCase() || ''; bValue = b.title?.toLowerCase() || ''; break; default: return 0; } if (aValue < bValue) return order === 'asc' ? -1 : 1; if (aValue > bValue) return order === 'asc' ? 1 : -1; return 0; }); }; const handleSearchChange = (event: React.ChangeEvent): void => { console.log('Handling search change:', event.target.value); const value = event.target.value; setSearchQuery(value); if (searchTimeout) { clearTimeout(searchTimeout); } const timeout = setTimeout(() => { setPage(0); fetchJobs(0, value); }, 500); setSearchTimeout(timeout); }; const handlePageChange = (event: unknown, newPage: number): void => { console.log('Handling page change:', newPage); setPage(newPage); fetchJobs(newPage, searchQuery); }; const handleRowsPerPageChange = (event: React.ChangeEvent): void => { console.log('Handling rows per page change:', event.target.value); const newLimit = parseInt(event.target.value, 10); setLimit(newLimit); setPage(0); fetchJobs(0, searchQuery); }; const handleSelectAll = (event: React.ChangeEvent): void => { if (event.target.checked) { const newSelected = new Set(jobs.map(job => job.id || '')); setSelectedJobs(newSelected); onJobSelect?.(jobs); } else { setSelectedJobs(new Set()); onJobSelect?.([]); } }; const handleSelectJob = (jobId: string): void => { const newSelected = new Set(selectedJobs); if (newSelected.has(jobId)) { newSelected.delete(jobId); } else { newSelected.add(jobId); } setSelectedJobs(newSelected); const selectedJobsList = jobs.filter(job => newSelected.has(job.id || '')); onJobSelect?.(selectedJobsList); }; const handleSort = (field: SortField): void => { if (sortField === field) { setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); } else { setSortField(field); setSortOrder('desc'); } }; const handleJobRowClick = (job: Types.Job): void => { setSelectedJob(job); if (isMobile) { setMobileDialogOpen(true); } else if (detailsPanelOpen || !isMobile) { setDetailsPanelOpen(true); } onJobView?.(job); }; const getOwnerName = (owner?: Types.BaseUser): string => { if (!owner) return 'Unknown'; return `${owner.firstName || ''} ${owner.lastName || ''}`.trim() || owner.email || 'Unknown'; }; const truncateDescription = (description: string, maxLength = 100): string => { if (description.length <= maxLength) return description; return description.substring(0, maxLength) + '...'; }; const formatDate = (date: Date | undefined): string => { if (!date) return 'N/A'; return new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', ...(isMobile ? {} : { year: 'numeric' }), ...(isSmall ? {} : { hour: '2-digit', minute: '2-digit' }), }).format(date); }; const getSortIcon = (field: SortField): React.ReactElement => { if (sortField !== field) return <>; return sortOrder === 'asc' ? ( ) : ( ); }; const isSelected = (jobId: string): boolean => selectedJobs.has(jobId); const numSelected = selectedJobs.size; const rowCount = jobs.length; if (error) { return ( {error} ); } const tableContent = ( <> Jobs ({total}) ), }} /> Sort {selectable && ( 0 && numSelected < rowCount} checked={rowCount > 0 && numSelected === rowCount} onChange={handleSelectAll} /> )} handleSort('company')}> Company {getSortIcon('company')} handleSort('title')}> Title {getSortIcon('title')} Description {!isMobile && ( <> Owner handleSort('updatedAt')} > Updated {getSortIcon('updatedAt')} )} {/* Status */} {!isMobile && showActions && Actions} {loading ? ( ) : jobs.length === 0 ? ( No jobs found ) : ( jobs.map(job => { const isItemSelected = isSelected(job.id || ''); return ( handleJobRowClick(job)} sx={{ cursor: 'pointer' }} > {selectable && ( e.stopPropagation()}> handleSelectJob(job.id || '')} /> )} {job.company} {job.details?.location && ( {job.details.location.city}, {job.details.location.state} )} {job.title} {job.details?.employmentType && ( )} {truncateDescription(job.summary || job.description || '', 100)} {!isMobile && ( <> {getOwnerName(job.owner)} {formatDate(job.updatedAt)} )} {/* */} {!isMobile && showActions && ( e.stopPropagation()}> {onJobView && ( onJobView(job)}> )} {onJobEdit && ( onJobEdit(job)}> )} {onJobDelete && ( => { await onJobDelete(job); fetchJobs(0, searchQuery); }} > )} )} ); }) )}
); return ( {tableContent} {detailsPanelOpen && !isMobile && ( {selectedJob ? ( { console.log('Closing JobInfoPanel'); setDetailsPanelOpen(false); setSelectedJob(null); }} /> ) : ( Select a job to view details )} )} setMobileDialogOpen(false)} TransitionComponent={Transition} > setMobileDialogOpen(false)} > {selectedJob?.title} {selectedJob?.company} {selectedJob && } ); }; export { JobsView };