Tweaked JobViewer for mobile
This commit is contained in:
parent
85eac72750
commit
0bc9f74c7f
@ -171,7 +171,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.label as string,
|
label: item.label as string,
|
||||||
icon: item.icon || null,
|
icon: item.icon || null,
|
||||||
action: () => item.path && navigate(item.path),
|
action: () => item.path && navigate(item.path.replace(/:.*$/, '')),
|
||||||
group: 'profile'
|
group: 'profile'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -195,7 +195,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.label as string,
|
label: item.label as string,
|
||||||
icon: item.icon || null,
|
icon: item.icon || null,
|
||||||
action: () => item.path && navigate(item.path),
|
action: () => item.path && navigate(item.path.replace(/:.*$/, '')),
|
||||||
group: 'account'
|
group: 'account'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -219,7 +219,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.label as string,
|
label: item.label as string,
|
||||||
icon: item.icon || null,
|
icon: item.icon || null,
|
||||||
action: () => item.path && navigate(item.path),
|
action: () => item.path && navigate(item.path.replace(/:.*$/, '')),
|
||||||
group: 'admin'
|
group: 'admin'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -254,7 +254,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.label as string,
|
label: item.label as string,
|
||||||
icon: item.icon || null,
|
icon: item.icon || null,
|
||||||
action: () => item.path && navigate(item.path),
|
action: () => item.path && navigate(item.path.replace(/:.*$/, '')),
|
||||||
group: 'system'
|
group: 'system'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -267,7 +267,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
id: item.id,
|
id: item.id,
|
||||||
label: item.label as string,
|
label: item.label as string,
|
||||||
icon: item.icon || null,
|
icon: item.icon || null,
|
||||||
action: () => item.path && navigate(item.path),
|
action: () => item.path && navigate(item.path.replace(/:.*$/, '')),
|
||||||
group: 'other'
|
group: 'other'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -328,7 +328,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
|
|
||||||
// Navigation handlers
|
// Navigation handlers
|
||||||
const handleNavigate = (path: string) => {
|
const handleNavigate = (path: string) => {
|
||||||
navigate(path);
|
navigate(path.replace(/:.*$/, ''));
|
||||||
setMobileOpen(false);
|
setMobileOpen(false);
|
||||||
// Close all dropdowns
|
// Close all dropdowns
|
||||||
setDropdownAnchors({});
|
setDropdownAnchors({});
|
||||||
@ -372,6 +372,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
onClick={() => child.path && handleNavigate(child.path)}
|
onClick={() => child.path && handleNavigate(child.path)}
|
||||||
selected={isCurrentPath(child)}
|
selected={isCurrentPath(child)}
|
||||||
disabled={!child.path}
|
disabled={!child.path}
|
||||||
|
sx={{ display: 'flex', alignItems: 'center', "& *": { m: 0, p: 0 }, m: 0 }}
|
||||||
>
|
>
|
||||||
{child.icon && <ListItemIcon>{child.icon}</ListItemIcon>}
|
{child.icon && <ListItemIcon>{child.icon}</ListItemIcon>}
|
||||||
<ListItemText>{child.label}</ListItemText>
|
<ListItemText>{child.label}</ListItemText>
|
||||||
@ -487,7 +488,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
item.group === 'divider' ? (
|
item.group === 'divider' ? (
|
||||||
<Divider key={`divider-${index}`} />
|
<Divider key={`divider-${index}`} />
|
||||||
) : (
|
) : (
|
||||||
<ListItem key={item.id} disablePadding>
|
<ListItem key={item.id}>
|
||||||
<ListItemButton onClick={() => handleUserMenuAction(item)}>
|
<ListItemButton onClick={() => handleUserMenuAction(item)}>
|
||||||
{item.icon && <ListItemIcon sx={{ minWidth: 36 }}>{item.icon}</ListItemIcon>}
|
{item.icon && <ListItemIcon sx={{ minWidth: 36 }}>{item.icon}</ListItemIcon>}
|
||||||
<ListItemText primary={item.label} />
|
<ListItemText primary={item.label} />
|
||||||
|
@ -14,17 +14,24 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
Chip,
|
Chip,
|
||||||
Divider,
|
|
||||||
IconButton,
|
IconButton,
|
||||||
Tooltip
|
Dialog,
|
||||||
|
AppBar,
|
||||||
|
Toolbar,
|
||||||
|
useMediaQuery,
|
||||||
|
useTheme,
|
||||||
|
Slide
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import {
|
import {
|
||||||
KeyboardArrowUp as ArrowUpIcon,
|
KeyboardArrowUp as ArrowUpIcon,
|
||||||
KeyboardArrowDown as ArrowDownIcon,
|
KeyboardArrowDown as ArrowDownIcon,
|
||||||
Business as BusinessIcon,
|
Business as BusinessIcon,
|
||||||
Work as WorkIcon,
|
Work as WorkIcon,
|
||||||
Schedule as ScheduleIcon
|
Schedule as ScheduleIcon,
|
||||||
|
Close as CloseIcon,
|
||||||
|
ArrowBack as ArrowBackIcon
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import { TransitionProps } from '@mui/material/transitions';
|
||||||
import { JobInfo } from 'components/ui/JobInfo';
|
import { JobInfo } from 'components/ui/JobInfo';
|
||||||
import { Job } from "types/types";
|
import { Job } from "types/types";
|
||||||
import { useAuth } from 'hooks/AuthContext';
|
import { useAuth } from 'hooks/AuthContext';
|
||||||
@ -38,8 +45,21 @@ interface JobViewerProps {
|
|||||||
onSelect?: (job: Job) => void;
|
onSelect?: (job: Job) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Transition = React.forwardRef(function Transition(
|
||||||
|
props: TransitionProps & {
|
||||||
|
children: React.ReactElement;
|
||||||
|
},
|
||||||
|
ref: React.Ref<unknown>,
|
||||||
|
) {
|
||||||
|
return <Slide direction="up" ref={ref} {...props} />;
|
||||||
|
});
|
||||||
|
|
||||||
const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
|
const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
|
const isSmall = useMediaQuery(theme.breakpoints.down('sm'));
|
||||||
|
|
||||||
const { apiClient } = useAuth();
|
const { apiClient } = useAuth();
|
||||||
const { selectedJob, setSelectedJob } = useSelectedJob();
|
const { selectedJob, setSelectedJob } = useSelectedJob();
|
||||||
const { setSnack } = useAppState();
|
const { setSnack } = useAppState();
|
||||||
@ -47,6 +67,7 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [sortField, setSortField] = useState<SortField>('updatedAt');
|
const [sortField, setSortField] = useState<SortField>('updatedAt');
|
||||||
const [sortOrder, setSortOrder] = useState<SortOrder>('desc');
|
const [sortOrder, setSortOrder] = useState<SortOrder>('desc');
|
||||||
|
const [mobileDialogOpen, setMobileDialogOpen] = useState(false);
|
||||||
const { jobId } = useParams<{ jobId?: string }>();
|
const { jobId } = useParams<{ jobId?: string }>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -56,6 +77,7 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
|
|||||||
const results = await apiClient.getJobs();
|
const results = await apiClient.getJobs();
|
||||||
const jobsData: Job[] = results.data || [];
|
const jobsData: Job[] = results.data || [];
|
||||||
setJobs(jobsData);
|
setJobs(jobsData);
|
||||||
|
|
||||||
if (jobId) {
|
if (jobId) {
|
||||||
const job = jobsData.find(j => j.id === jobId);
|
const job = jobsData.find(j => j.id === jobId);
|
||||||
if (job) {
|
if (job) {
|
||||||
@ -125,7 +147,15 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
|
|||||||
const handleJobSelect = (job: Job) => {
|
const handleJobSelect = (job: Job) => {
|
||||||
setSelectedJob(job);
|
setSelectedJob(job);
|
||||||
onSelect?.(job);
|
onSelect?.(job);
|
||||||
navigate(`/candidate/jobs/${job.id}`);
|
if (isMobile) {
|
||||||
|
setMobileDialogOpen(true);
|
||||||
|
} else {
|
||||||
|
navigate(`/candidate/jobs/${job.id}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMobileDialogClose = () => {
|
||||||
|
setMobileDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const sortedJobs = sortJobs(jobs, sortField, sortOrder);
|
const sortedJobs = sortJobs(jobs, sortField, sortOrder);
|
||||||
@ -135,9 +165,8 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
|
|||||||
return new Intl.DateTimeFormat('en-US', {
|
return new Intl.DateTimeFormat('en-US', {
|
||||||
month: 'short',
|
month: 'short',
|
||||||
day: 'numeric',
|
day: 'numeric',
|
||||||
year: 'numeric',
|
...(isMobile ? {} : { year: 'numeric' }),
|
||||||
hour: '2-digit',
|
...(isSmall ? {} : { hour: '2-digit', minute: '2-digit' })
|
||||||
minute: '2-digit'
|
|
||||||
}).format(date);
|
}).format(date);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -146,170 +175,363 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
|
|||||||
return sortOrder === 'asc' ? <ArrowUpIcon fontSize="small" /> : <ArrowDownIcon fontSize="small" />;
|
return sortOrder === 'asc' ? <ArrowUpIcon fontSize="small" /> : <ArrowDownIcon fontSize="small" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const JobList = () => (
|
||||||
<Box sx={{ display: 'flex', height: '100%', gap: 2, p: 2, position: 'relative' }}>
|
<Paper
|
||||||
{/* Left Panel - Job List */}
|
elevation={isMobile ? 0 : 1}
|
||||||
<Paper sx={{ width: '50%', display: 'flex', flexDirection: 'column' }}>
|
sx={{
|
||||||
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
|
display: 'flex',
|
||||||
<Typography variant="h6" gutterBottom>
|
flexDirection: 'column',
|
||||||
Jobs ({jobs.length})
|
...(isMobile ? {
|
||||||
</Typography>
|
width: '100%',
|
||||||
|
boxShadow: 'none',
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
} : { width: '50%' })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box sx={{
|
||||||
|
p: isMobile ? 0.5 : 1,
|
||||||
|
borderBottom: 1,
|
||||||
|
borderColor: 'divider',
|
||||||
|
backgroundColor: isMobile ? 'background.paper' : 'inherit'
|
||||||
|
}}>
|
||||||
|
<Typography
|
||||||
|
variant={isSmall ? "subtitle2" : isMobile ? "subtitle1" : "h6"}
|
||||||
|
gutterBottom
|
||||||
|
sx={{ mb: isMobile ? 0.5 : 1, fontWeight: 600 }}
|
||||||
|
>
|
||||||
|
Jobs ({jobs.length})
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<FormControl size="small" sx={{ minWidth: 200 }}>
|
<FormControl size="small" sx={{ minWidth: isSmall ? 120 : isMobile ? 150 : 200 }}>
|
||||||
<InputLabel>Sort by</InputLabel>
|
<InputLabel>Sort by</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={`${sortField}-${sortOrder}`}
|
value={`${sortField}-${sortOrder}`}
|
||||||
label="Sort by"
|
label="Sort by"
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const [field, order] = e.target.value.split('-') as [SortField, SortOrder];
|
const [field, order] = e.target.value.split('-') as [SortField, SortOrder];
|
||||||
setSortField(field);
|
setSortField(field);
|
||||||
setSortOrder(order);
|
setSortOrder(order);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value="updatedAt-desc">Updated (Newest)</MenuItem>
|
<MenuItem value="updatedAt-desc">Updated (Newest)</MenuItem>
|
||||||
<MenuItem value="updatedAt-asc">Updated (Oldest)</MenuItem>
|
<MenuItem value="updatedAt-asc">Updated (Oldest)</MenuItem>
|
||||||
<MenuItem value="createdAt-desc">Created (Newest)</MenuItem>
|
<MenuItem value="createdAt-desc">Created (Newest)</MenuItem>
|
||||||
<MenuItem value="createdAt-asc">Created (Oldest)</MenuItem>
|
<MenuItem value="createdAt-asc">Created (Oldest)</MenuItem>
|
||||||
<MenuItem value="company-asc">Company (A-Z)</MenuItem>
|
<MenuItem value="company-asc">Company (A-Z)</MenuItem>
|
||||||
<MenuItem value="company-desc">Company (Z-A)</MenuItem>
|
<MenuItem value="company-desc">Company (Z-A)</MenuItem>
|
||||||
<MenuItem value="title-asc">Title (A-Z)</MenuItem>
|
<MenuItem value="title-asc">Title (A-Z)</MenuItem>
|
||||||
<MenuItem value="title-desc">Title (Z-A)</MenuItem>
|
<MenuItem value="title-desc">Title (Z-A)</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<TableContainer sx={{ flex: 1, overflow: 'auto' }}>
|
<TableContainer sx={{
|
||||||
<Table stickyHeader size="small">
|
flex: 1,
|
||||||
<TableHead>
|
overflow: 'auto',
|
||||||
<TableRow>
|
'& .MuiTable-root': {
|
||||||
|
tableLayout: isMobile ? 'fixed' : 'auto'
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<Table stickyHeader size="small">
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
userSelect: 'none',
|
||||||
|
py: isMobile ? 0.25 : 0.5,
|
||||||
|
px: isMobile ? 0.5 : 1,
|
||||||
|
width: isMobile ? '25%' : 'auto',
|
||||||
|
backgroundColor: 'background.paper'
|
||||||
|
}}
|
||||||
|
onClick={() => handleSort('company')}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
|
<BusinessIcon fontSize={isMobile ? "small" : "medium"} />
|
||||||
|
<Typography variant="caption" fontWeight="bold" noWrap>
|
||||||
|
{isSmall ? 'Co.' : isMobile ? 'Company' : 'Company'}
|
||||||
|
</Typography>
|
||||||
|
{getSortIcon('company')}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
userSelect: 'none',
|
||||||
|
py: isMobile ? 0.25 : 0.5,
|
||||||
|
px: isMobile ? 0.5 : 1,
|
||||||
|
width: isMobile ? '45%' : 'auto',
|
||||||
|
backgroundColor: 'background.paper'
|
||||||
|
}}
|
||||||
|
onClick={() => handleSort('title')}
|
||||||
|
>
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
|
<WorkIcon fontSize={isMobile ? "small" : "medium"} />
|
||||||
|
<Typography variant="caption" fontWeight="bold" noWrap>Title</Typography>
|
||||||
|
{getSortIcon('title')}
|
||||||
|
</Box>
|
||||||
|
</TableCell>
|
||||||
|
{!isMobile && (
|
||||||
<TableCell
|
<TableCell
|
||||||
sx={{ cursor: 'pointer', userSelect: 'none' }}
|
sx={{
|
||||||
onClick={() => handleSort('company')}
|
cursor: 'pointer',
|
||||||
>
|
userSelect: 'none',
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
py: 0.5,
|
||||||
<BusinessIcon fontSize="small" />
|
px: 1,
|
||||||
Company
|
backgroundColor: 'background.paper'
|
||||||
{getSortIcon('company')}
|
}}
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
sx={{ cursor: 'pointer', userSelect: 'none' }}
|
|
||||||
onClick={() => handleSort('title')}
|
|
||||||
>
|
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
|
||||||
<WorkIcon fontSize="small" />
|
|
||||||
Title
|
|
||||||
{getSortIcon('title')}
|
|
||||||
</Box>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell
|
|
||||||
sx={{ cursor: 'pointer', userSelect: 'none' }}
|
|
||||||
onClick={() => handleSort('updatedAt')}
|
onClick={() => handleSort('updatedAt')}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
|
||||||
<ScheduleIcon fontSize="small" />
|
<ScheduleIcon fontSize="medium" />
|
||||||
Updated
|
<Typography variant="caption" fontWeight="bold">Updated</Typography>
|
||||||
{getSortIcon('updatedAt')}
|
{getSortIcon('updatedAt')}
|
||||||
</Box>
|
</Box>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>Status</TableCell>
|
)}
|
||||||
</TableRow>
|
<TableCell sx={{
|
||||||
</TableHead>
|
py: isMobile ? 0.25 : 0.5,
|
||||||
<TableBody>
|
px: isMobile ? 0.5 : 1,
|
||||||
{sortedJobs.map((job) => (
|
width: isMobile ? '30%' : 'auto',
|
||||||
<TableRow
|
backgroundColor: 'background.paper'
|
||||||
key={job.id}
|
}}>
|
||||||
hover
|
<Typography variant="caption" fontWeight="bold" noWrap>
|
||||||
selected={selectedJob?.id === job.id}
|
{isMobile ? 'Status' : 'Status'}
|
||||||
onClick={() => handleJobSelect(job)}
|
</Typography>
|
||||||
sx={{
|
</TableCell>
|
||||||
cursor: 'pointer',
|
</TableRow>
|
||||||
'&.Mui-selected': {
|
</TableHead>
|
||||||
backgroundColor: 'action.selected',
|
<TableBody>
|
||||||
}
|
{sortedJobs.map((job) => (
|
||||||
}}
|
<TableRow
|
||||||
>
|
key={job.id}
|
||||||
<TableCell>
|
hover
|
||||||
<Typography variant="body2" fontWeight="medium">
|
selected={selectedJob?.id === job.id}
|
||||||
{job.company || 'N/A'}
|
onClick={() => handleJobSelect(job)}
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
height: isMobile ? 48 : 'auto',
|
||||||
|
'&.Mui-selected': {
|
||||||
|
backgroundColor: 'action.selected',
|
||||||
|
},
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'action.hover',
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TableCell sx={{
|
||||||
|
py: isMobile ? 0.25 : 0.5,
|
||||||
|
px: isMobile ? 0.5 : 1,
|
||||||
|
overflow: 'hidden'
|
||||||
|
}}>
|
||||||
|
<Typography
|
||||||
|
variant={isMobile ? "caption" : "body2"}
|
||||||
|
fontWeight="medium"
|
||||||
|
noWrap
|
||||||
|
sx={{ fontSize: isMobile ? '0.75rem' : '0.875rem' }}
|
||||||
|
>
|
||||||
|
{job.company || 'N/A'}
|
||||||
|
</Typography>
|
||||||
|
{!isMobile && job.details?.location && (
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
noWrap
|
||||||
|
sx={{ display: 'block', fontSize: '0.7rem' }}
|
||||||
|
>
|
||||||
|
{job.details.location.city}, {job.details.location.state || job.details.location.country}
|
||||||
</Typography>
|
</Typography>
|
||||||
{job.details?.location && (
|
)}
|
||||||
<Typography variant="caption" color="text.secondary">
|
</TableCell>
|
||||||
{job.details.location.city}, {job.details.location.state || job.details.location.country}
|
<TableCell sx={{
|
||||||
</Typography>
|
py: isMobile ? 0.25 : 0.5,
|
||||||
)}
|
px: isMobile ? 0.5 : 1,
|
||||||
</TableCell>
|
overflow: 'hidden'
|
||||||
<TableCell>
|
}}>
|
||||||
<Typography variant="body2" fontWeight="medium">
|
<Typography
|
||||||
{job.title || 'N/A'}
|
variant={isMobile ? "caption" : "body2"}
|
||||||
</Typography>
|
fontWeight="medium"
|
||||||
{job.details?.employmentType && (
|
noWrap
|
||||||
<Chip
|
sx={{ fontSize: isMobile ? '0.75rem' : '0.875rem' }}
|
||||||
label={job.details.employmentType}
|
>
|
||||||
size="small"
|
{job.title || 'N/A'}
|
||||||
variant="outlined"
|
</Typography>
|
||||||
sx={{ mt: 0.5, fontSize: '0.7rem', height: 20 }}
|
{!isMobile && job.details?.employmentType && (
|
||||||
/>
|
<Chip
|
||||||
)}
|
label={job.details.employmentType}
|
||||||
</TableCell>
|
size="small"
|
||||||
<TableCell>
|
variant="outlined"
|
||||||
<Typography variant="body2">
|
sx={{
|
||||||
|
mt: 0.25,
|
||||||
|
fontSize: '0.6rem',
|
||||||
|
height: 16,
|
||||||
|
'& .MuiChip-label': { px: 0.5 }
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
{!isMobile && (
|
||||||
|
<TableCell sx={{ py: 0.5, px: 1 }}>
|
||||||
|
<Typography variant="body2" sx={{ fontSize: '0.8rem' }}>
|
||||||
{formatDate(job.updatedAt)}
|
{formatDate(job.updatedAt)}
|
||||||
</Typography>
|
</Typography>
|
||||||
{job.createdAt && (
|
{job.createdAt && (
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ display: 'block', fontSize: '0.7rem' }}
|
||||||
|
>
|
||||||
Created: {formatDate(job.createdAt)}
|
Created: {formatDate(job.createdAt)}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
)}
|
||||||
<Chip
|
<TableCell sx={{
|
||||||
label={job.details?.isActive ? "Active" : "Inactive"}
|
py: isMobile ? 0.25 : 0.5,
|
||||||
color={job.details?.isActive ? "success" : "default"}
|
px: isMobile ? 0.5 : 1,
|
||||||
size="small"
|
overflow: 'hidden'
|
||||||
variant="outlined"
|
}}>
|
||||||
/>
|
<Chip
|
||||||
</TableCell>
|
label={job.details?.isActive ? "Active" : "Inactive"}
|
||||||
</TableRow>
|
color={job.details?.isActive ? "success" : "default"}
|
||||||
))}
|
size="small"
|
||||||
</TableBody>
|
variant="outlined"
|
||||||
</Table>
|
sx={{
|
||||||
</TableContainer>
|
fontSize: isMobile ? '0.65rem' : '0.7rem',
|
||||||
</Paper>
|
height: isMobile ? 20 : 22,
|
||||||
|
'& .MuiChip-label': {
|
||||||
|
px: isMobile ? 0.5 : 0.75,
|
||||||
|
py: 0
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Paper>
|
||||||
|
);
|
||||||
|
|
||||||
{/* Right Panel - Job Details */}
|
const JobDetails = ({ inDialog = false }: { inDialog?: boolean }) => (
|
||||||
<Paper sx={{ width: '50%', display: 'flex', flexDirection: 'column' }}>
|
<Box sx={{
|
||||||
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
|
flex: 1,
|
||||||
<Typography variant="h6">
|
overflow: 'auto',
|
||||||
|
p: inDialog ? 1.5 : 0.75,
|
||||||
|
height: inDialog ? '100%' : 'auto'
|
||||||
|
}}>
|
||||||
|
{selectedJob ? (
|
||||||
|
<JobInfo
|
||||||
|
job={selectedJob}
|
||||||
|
variant="all"
|
||||||
|
sx={{
|
||||||
|
border: 'none',
|
||||||
|
boxShadow: 'none',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'& .MuiTypography-h6': {
|
||||||
|
fontSize: inDialog ? '1.25rem' : '1.1rem'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
height: '100%',
|
||||||
|
color: 'text.secondary',
|
||||||
|
textAlign: 'center',
|
||||||
|
p: 2
|
||||||
|
}}>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Select a job to view details
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
height: '100%',
|
||||||
|
p: 0.5,
|
||||||
|
backgroundColor: 'background.default'
|
||||||
|
}}>
|
||||||
|
<JobList />
|
||||||
|
|
||||||
|
<Dialog
|
||||||
|
fullScreen
|
||||||
|
open={mobileDialogOpen}
|
||||||
|
onClose={handleMobileDialogClose}
|
||||||
|
TransitionComponent={Transition}
|
||||||
|
TransitionProps={{ timeout: 300 }}
|
||||||
|
>
|
||||||
|
<AppBar sx={{ position: 'relative', elevation: 1 }}>
|
||||||
|
<Toolbar variant="dense" sx={{ minHeight: 48 }}>
|
||||||
|
<IconButton
|
||||||
|
edge="start"
|
||||||
|
color="inherit"
|
||||||
|
onClick={handleMobileDialogClose}
|
||||||
|
aria-label="back"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<ArrowBackIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Box sx={{ ml: 1, flex: 1, minWidth: 0 }}>
|
||||||
|
<Typography
|
||||||
|
variant="h6"
|
||||||
|
component="div"
|
||||||
|
noWrap
|
||||||
|
sx={{ fontSize: '1rem' }}
|
||||||
|
>
|
||||||
|
{selectedJob?.title}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="caption"
|
||||||
|
component="div"
|
||||||
|
sx={{ color: 'rgba(255, 255, 255, 0.7)' }}
|
||||||
|
noWrap
|
||||||
|
>
|
||||||
|
{selectedJob?.company}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
<JobDetails inDialog />
|
||||||
|
</Dialog>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
height: '100%',
|
||||||
|
gap: 0.75,
|
||||||
|
p: 0.75,
|
||||||
|
backgroundColor: 'background.default'
|
||||||
|
}}>
|
||||||
|
<JobList />
|
||||||
|
|
||||||
|
<Paper sx={{
|
||||||
|
width: '50%',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
elevation: 1
|
||||||
|
}}>
|
||||||
|
<Box sx={{
|
||||||
|
p: 0.75,
|
||||||
|
borderBottom: 1,
|
||||||
|
borderColor: 'divider',
|
||||||
|
backgroundColor: 'background.paper'
|
||||||
|
}}>
|
||||||
|
<Typography variant="h6" sx={{ fontSize: '1.1rem', fontWeight: 600 }}>
|
||||||
Job Details
|
Job Details
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
<JobDetails />
|
||||||
<Box sx={{ flex: 1, overflow: 'auto', p: 2 }}>
|
|
||||||
{selectedJob ? (
|
|
||||||
<JobInfo
|
|
||||||
job={selectedJob}
|
|
||||||
variant="all"
|
|
||||||
sx={{
|
|
||||||
border: 'none',
|
|
||||||
boxShadow: 'none',
|
|
||||||
backgroundColor: 'transparent'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Box sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
height: '100%',
|
|
||||||
color: 'text.secondary'
|
|
||||||
}}>
|
|
||||||
<Typography variant="body1">
|
|
||||||
Select a job to view details
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
@ -44,6 +44,7 @@ import { DocumentManager } from "components/DocumentManager";
|
|||||||
import { useAuth } from "hooks/AuthContext";
|
import { useAuth } from "hooks/AuthContext";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { JobViewer } from "components/ui/JobViewer";
|
import { JobViewer } from "components/ui/JobViewer";
|
||||||
|
import { CandidatePicker } from "components/ui/CandidatePicker";
|
||||||
|
|
||||||
// Beta page components for placeholder routes
|
// Beta page components for placeholder routes
|
||||||
const BackstoryPage = () => (
|
const BackstoryPage = () => (
|
||||||
@ -129,6 +130,36 @@ export const navigationConfig: NavigationConfig = {
|
|||||||
component: <CandidateChatPage />,
|
component: <CandidateChatPage />,
|
||||||
userTypes: ["guest", "candidate", "employer"],
|
userTypes: ["guest", "candidate", "employer"],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "explore",
|
||||||
|
label: "Explore",
|
||||||
|
icon: <SearchIcon />,
|
||||||
|
userTypes: ["candidate", "guest", "employer"],
|
||||||
|
children: [
|
||||||
|
// {
|
||||||
|
// id: "explore-candidates",
|
||||||
|
// label: "Candidates",
|
||||||
|
// path: "/candidate/candidates",
|
||||||
|
// icon: <SearchIcon />,
|
||||||
|
// component: (
|
||||||
|
// <CandidatePicker />
|
||||||
|
// ),
|
||||||
|
// userTypes: ["candidate", "guest", "employer"],
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
id: "explore-jobs",
|
||||||
|
label: "Jobs",
|
||||||
|
path: "/candidate/jobs/:jobId?",
|
||||||
|
icon: <SearchIcon />,
|
||||||
|
component: (
|
||||||
|
<JobViewer />
|
||||||
|
),
|
||||||
|
userTypes: ["candidate", "guest", "employer"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
showInNavigation: true,
|
||||||
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "generate-candidate",
|
id: "generate-candidate",
|
||||||
label: "Generate Candidate",
|
label: "Generate Candidate",
|
||||||
@ -163,17 +194,6 @@ export const navigationConfig: NavigationConfig = {
|
|||||||
showInNavigation: false,
|
showInNavigation: false,
|
||||||
showInUserMenu: true,
|
showInUserMenu: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: "candidate-jobs",
|
|
||||||
label: "Jobs",
|
|
||||||
icon: <WorkIcon />,
|
|
||||||
path: "/candidate/jobs/:jobId?",
|
|
||||||
component: <JobViewer />,
|
|
||||||
userTypes: ["candidate", "guest", "employer"],
|
|
||||||
userMenuGroup: "profile",
|
|
||||||
showInNavigation: false,
|
|
||||||
showInUserMenu: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: "candidate-docs",
|
id: "candidate-docs",
|
||||||
label: "Content",
|
label: "Content",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user