mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2025-08-25 13:49:40 +00:00
201 lines
5.7 KiB
TypeScript
201 lines
5.7 KiB
TypeScript
import {
|
|
Box,
|
|
Button,
|
|
Card,
|
|
CardContent,
|
|
Chip,
|
|
CircularProgress,
|
|
FormControl,
|
|
Grid,
|
|
InputLabel,
|
|
MenuItem,
|
|
Pagination,
|
|
Select,
|
|
SelectChangeEvent,
|
|
Typography
|
|
} from '@mui/material';
|
|
import React, { useEffect, useState } from 'react';
|
|
import { useNavigate } from 'react-router-dom';
|
|
import { juniorsApi, tasksApi } from '../../api/client';
|
|
import { Junior, PaginatedResponse } from '../../types/junior';
|
|
import { Task, TaskStatus } from '../../types/task';
|
|
|
|
const statusColors = {
|
|
PENDING: 'warning',
|
|
IN_PROGRESS: 'info',
|
|
COMPLETED: 'success'
|
|
} as const;
|
|
|
|
export const TasksList = () => {
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState('');
|
|
const [tasks, setTasks] = useState<Task[]>([]);
|
|
const [juniors, setJuniors] = useState<Junior[]>([]);
|
|
const [page, setPage] = useState(1);
|
|
const [totalPages, setTotalPages] = useState(1);
|
|
const [status, setStatus] = useState<TaskStatus>('PENDING');
|
|
const [selectedJuniorId, setSelectedJuniorId] = useState<string>('');
|
|
const navigate = useNavigate();
|
|
|
|
const fetchJuniors = async () => {
|
|
try {
|
|
const response = await juniorsApi.getJuniors(1, 50);
|
|
const data = response.data as PaginatedResponse<Junior>;
|
|
setJuniors(data.data);
|
|
} catch (err) {
|
|
console.error('Failed to load juniors:', err);
|
|
}
|
|
};
|
|
|
|
const fetchTasks = async (pageNum: number) => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await tasksApi.getTasks(status, pageNum, 10, selectedJuniorId || undefined);
|
|
const data = response.data as PaginatedResponse<Task>;
|
|
setTasks(data.data);
|
|
setTotalPages(data.meta.pageCount);
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Failed to load tasks');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchJuniors();
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
fetchTasks(page);
|
|
}, [page, status, selectedJuniorId]);
|
|
|
|
const handlePageChange = (event: React.ChangeEvent<unknown>, value: number) => {
|
|
setPage(value);
|
|
};
|
|
|
|
const handleStatusChange = (event: SelectChangeEvent) => {
|
|
setStatus(event.target.value as TaskStatus);
|
|
setPage(1);
|
|
};
|
|
|
|
const handleJuniorChange = (event: SelectChangeEvent) => {
|
|
setSelectedJuniorId(event.target.value);
|
|
setPage(1);
|
|
};
|
|
|
|
if (loading && page === 1) {
|
|
return (
|
|
<Box display="flex" justifyContent="center" alignItems="center" minHeight="200px">
|
|
<CircularProgress />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<Box p={3}>
|
|
<Typography color="error">{error}</Typography>
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box p={3}>
|
|
<Box display="flex" justifyContent="space-between" alignItems="center" mb={3}>
|
|
<Typography variant="h4">Tasks</Typography>
|
|
<Button
|
|
variant="contained"
|
|
color="primary"
|
|
onClick={() => navigate('/tasks/new')}
|
|
>
|
|
Create Task
|
|
</Button>
|
|
</Box>
|
|
|
|
<Box display="flex" gap={2} mb={3}>
|
|
<FormControl sx={{ minWidth: 200 }}>
|
|
<InputLabel>Status</InputLabel>
|
|
<Select
|
|
value={status}
|
|
label="Status"
|
|
onChange={handleStatusChange}
|
|
>
|
|
<MenuItem value="PENDING">Pending</MenuItem>
|
|
<MenuItem value="IN_PROGRESS">In Progress</MenuItem>
|
|
<MenuItem value="COMPLETED">Completed</MenuItem>
|
|
</Select>
|
|
</FormControl>
|
|
|
|
<FormControl sx={{ minWidth: 200 }}>
|
|
<InputLabel>Junior</InputLabel>
|
|
<Select
|
|
value={selectedJuniorId}
|
|
label="Junior"
|
|
onChange={handleJuniorChange}
|
|
>
|
|
<MenuItem value="">All Juniors</MenuItem>
|
|
{juniors.map(junior => (
|
|
<MenuItem key={junior.id} value={junior.id}>
|
|
{junior.fullName}
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
</Box>
|
|
|
|
<Grid container spacing={3}>
|
|
{tasks.map((task) => (
|
|
<Grid item xs={12} sm={6} md={4} key={task.id}>
|
|
<Card>
|
|
<CardContent>
|
|
<Box display="flex" justifyContent="space-between" alignItems="flex-start">
|
|
<Typography variant="h6" gutterBottom>
|
|
{task.title}
|
|
</Typography>
|
|
<Chip
|
|
label={task.status}
|
|
color={statusColors[task.status]}
|
|
size="small"
|
|
/>
|
|
</Box>
|
|
<Typography color="textSecondary" gutterBottom>
|
|
Due: {new Date(task.dueDate).toLocaleDateString()}
|
|
</Typography>
|
|
<Typography variant="body2" gutterBottom>
|
|
{task.description}
|
|
</Typography>
|
|
<Typography color="primary" gutterBottom>
|
|
Reward: ${task.rewardAmount}
|
|
</Typography>
|
|
<Typography variant="body2" color="textSecondary">
|
|
Assigned to: {task.junior.fullName}
|
|
</Typography>
|
|
<Box mt={2}>
|
|
<Button
|
|
variant="outlined"
|
|
fullWidth
|
|
onClick={() => navigate(`/tasks/${task.id}`)}
|
|
>
|
|
View Details
|
|
</Button>
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
))}
|
|
</Grid>
|
|
|
|
{totalPages > 1 && (
|
|
<Box display="flex" justifyContent="center" mt={4}>
|
|
<Pagination
|
|
count={totalPages}
|
|
page={page}
|
|
onChange={handlePageChange}
|
|
color="primary"
|
|
/>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
);
|
|
};
|