diff --git a/MtdrSpring/backend/src/main/frontend/src/App.js b/MtdrSpring/backend/src/main/frontend/src/App.js index 9470cb5..7824c37 100644 --- a/MtdrSpring/backend/src/main/frontend/src/App.js +++ b/MtdrSpring/backend/src/main/frontend/src/App.js @@ -1,1604 +1,106 @@ -// Modificación del archivo App.js principal -import React, { useState, useEffect, useRef } from 'react'; -import NewItem from './NewItem'; -import GitHubIntegration from './GitHubIntegration'; -import ReportsDashboard from './ReportsDashboard'; -import { API_LIST, GITHUB_CREATE_BRANCH, GITHUB_GET_BRANCHES, API_SPRINTS, API_USERS, API_TASKS_BY_SPRINT } from './API'; -import { Button, TableBody, Modal, Box, Typography, IconButton, Select, MenuItem, FormControl, InputLabel, TextField } from '@mui/material'; -import Moment from 'react-moment'; -import CloseIcon from '@mui/icons-material/Close'; -import ExitToAppIcon from '@mui/icons-material/ExitToApp'; -import AccessTimeIcon from '@mui/icons-material/AccessTime'; +import React, { useState, useEffect } from 'react'; +import Login from './components/Login'; +import TaskManager from './components/TaskManager'; +import GitHubIntegration from './components/GitHubIntegration'; +import ReportsDashboard from './components/ReportsDashboard'; +import { API_USERS } from './components/API'; import './index.css'; -// Componente de Login -function Login({ onLogin, loginError }) { - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); - const [isLoading, setIsLoading] = useState(false); - - const handleSubmit = async (e) => { - e.preventDefault(); - if (!username || !password) return; - - setIsLoading(true); - - try { - // Buscar usuario por nombre de usuario - const response = await fetch(`${API_USERS}/username/${username}`); - - if (!response.ok) { - throw new Error('Invalid credentials'); - } - - const userData = await response.json(); - - // Verificar contraseña (en un sistema real, esto se haría en el backend) - if (userData.password === password) { - // Guardar información en el localStorage - localStorage.setItem('currentUser', JSON.stringify({ - id: userData.id, - username: userData.username, - name: userData.name, - role: userData.role - })); - - onLogin(userData); - } else { - throw new Error('Invalid credentials'); - } - } catch (error) { - onLogin(null, error.message); - } finally { - setIsLoading(false); +function App() { + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [currentUser, setCurrentUser] = useState(null); + const [loginError, setLoginError] = useState(null); + const [activeTab, setActiveTab] = useState('tasks'); + + // Handle login + const handleLogin = (user, error = null) => { + if (user) { + setCurrentUser(user); + setIsAuthenticated(true); + setLoginError(null); + } else { + setLoginError(error); } }; - return ( -
-
-
- Oracle Logo -

TODO App Login

-
- -
-
- setUsername(e.target.value)} - required - /> -
- -
- setPassword(e.target.value)} - required - /> -
- - - - {loginError && ( -
- {loginError} -
- )} -
-
-
- ); -} - -// Modal para cambiar el estado de una tarea -function StatusChangeModal({ open, onClose, task, onStatusChange }) { - const [selectedStatus, setSelectedStatus] = useState(''); - const [actualHours, setActualHours] = useState(''); - const actualHoursRef = useRef(null); - - useEffect(() => { - if (task) { - setSelectedStatus(task.status || 'Pending'); - setActualHours(task.actualHours || ''); - } - }, [task]); - - const handleStatusChange = (status) => { - setSelectedStatus(status); + // Handle logout + const handleLogout = () => { + localStorage.removeItem('currentUser'); + setCurrentUser(null); + setIsAuthenticated(false); }; - const handleSubmit = () => { - // Validar que se ingresen horas reales cuando se completa una tarea - if (selectedStatus === 'Completed' && (!actualHours || actualHours <= 0)) { - if (actualHoursRef.current) { - actualHoursRef.current.focus(); + // Check for stored user on page load + useEffect(() => { + const storedUser = localStorage.getItem('currentUser'); + if (storedUser) { + try { + const parsedUser = JSON.parse(storedUser); + setCurrentUser(parsedUser); + setIsAuthenticated(true); + } catch { + localStorage.removeItem('currentUser'); } - return; } + }, []); - onStatusChange(selectedStatus, actualHours); - onClose(); - }; - - // Si no hay tarea seleccionada, no renderizar nada - if (!task) return null; + if (!isAuthenticated) { + return ; + } return ( - - -
- - Update Task Status - - - - -
- - - Task: {task.description} - +
+
+ +
-
-
handleStatusChange('Pending')} - > - {}} - /> - Pending -
-
handleStatusChange('In Progress')} - > - {}} - /> - In Progress -
-
handleStatusChange('In Review')} - > - {}} - /> - In Review -
-
handleStatusChange('Completed')} - > - {}} - /> - Completed -
-
+ Oracle Logo - {selectedStatus === 'Completed' && ( -
- - setActualHours(e.target.value)} - min="0.25" - step="0.25" - placeholder="Enter actual hours worked" - ref={actualHoursRef} - /> -
- )} +

TODO LIST

-
- - + {currentUser && ( +
+ Welcome, {currentUser.name || currentUser.username} + ({currentUser.role})
- - - ); -} - -// Componente principal -function App() { - // Estados de autenticación - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [currentUser, setCurrentUser] = useState(null); - const [loginError, setLoginError] = useState(null); - - // Estados de la aplicación - const [isLoading, setLoading] = useState(false); - const [isInserting, setInserting] = useState(false); - const [items, setItems] = useState([]); - const [error, setError] = useState(); - - // Estado para la pestaña activa - const [activeTab, setActiveTab] = useState('tasks'); - - // Estado para filtros - const [priorityFilter, setPriorityFilter] = useState('All'); - - // Estados para sprints y desarrolladores - const [sprints, setSprints] = useState([]); - const [selectedSprint, setSelectedSprint] = useState(null); - const [developers, setDevelopers] = useState([]); - const [tasksWithoutSprint, setTasksWithoutSprint] = useState([]); - const [loadingSprints, setLoadingSprints] = useState(false); - const [loadingDevelopers, setLoadingDevelopers] = useState(false); - - // Estado para el modal - const [modalOpen, setModalOpen] = useState(false); - const [selectedTask, setSelectedTask] = useState(null); - - // Estado para el modal de cambio de estado - const [statusModalOpen, setStatusModalOpen] = useState(false); - const [taskForStatusChange, setTaskForStatusChange] = useState(null); - - // Estados para el modal de creación de sprint - const [newSprintModalOpen, setNewSprintModalOpen] = useState(false); - const [newSprintName, setNewSprintName] = useState(''); - const [newSprintDescription, setNewSprintDescription] = useState(''); - const [newSprintStartDate, setNewSprintStartDate] = useState(''); - const [newSprintDuration, setNewSprintDuration] = useState(2); - const [isCreatingSprint, setIsCreatingSprint] = useState(false); - - // Comprobar si el usuario es desarrollador - const isDeveloper = currentUser?.role === 'Developer'; - - // Función para manejar el login - const handleLogin = (user, error = null) => { - if (user) { - setCurrentUser(user); - setIsAuthenticated(true); - setLoginError(null); - } else { - setLoginError(error); - } - }; - - // Función para manejar el logout - const handleLogout = () => { - localStorage.removeItem('currentUser'); - setCurrentUser(null); - setIsAuthenticated(false); - }; - - // Verificar si hay un usuario en localStorage al cargar la página - useEffect(() => { - const storedUser = localStorage.getItem('currentUser'); - if (storedUser) { - try { - const parsedUser = JSON.parse(storedUser); - setCurrentUser(parsedUser); - setIsAuthenticated(true); - } catch (error) { - localStorage.removeItem('currentUser'); - } - } - }, []); - - // Función para abrir el modal con la tarea seleccionada - const openTaskModal = (task) => { - setSelectedTask(task); - setModalOpen(true); - }; + )} - // Función para cerrar el modal - const closeTaskModal = () => { - setModalOpen(false); - }; - - // Función para abrir el modal de cambio de estado - const openStatusModal = (task) => { - setTaskForStatusChange(task); - setStatusModalOpen(true); - }; - - // Función para cerrar el modal de cambio de estado - const closeStatusModal = () => { - setStatusModalOpen(false); - setTaskForStatusChange(null); - }; - - // Funciones para el modal de creación de sprint - const openNewSprintModal = () => { - // Establecer la fecha de inicio por defecto como la fecha actual - const today = new Date(); - const formattedDate = today.toISOString().split('T')[0]; // Formato YYYY-MM-DD - - setNewSprintName(''); - setNewSprintDescription(''); - setNewSprintStartDate(formattedDate); - setNewSprintDuration(2); - setNewSprintModalOpen(true); - }; - - // Función para cerrar el modal de nuevo sprint - const closeNewSprintModal = () => { - setNewSprintModalOpen(false); - }; - - // Función para calcular la fecha de finalización basada en la fecha de inicio y la duración - const calculateEndDate = (startDate, durationWeeks) => { - if (!startDate) return ''; - - const start = new Date(startDate); - const end = new Date(start); - end.setDate(start.getDate() + (durationWeeks * 7)); - - return end.toISOString().split('T')[0]; // Formato YYYY-MM-DD - }; - - // Función para crear un nuevo sprint - const createNewSprint = () => { - if (!newSprintName.trim() || !newSprintStartDate) { - setError(new Error('Sprint name and start date are required')); - return; - } - - setIsCreatingSprint(true); - - const endDate = calculateEndDate(newSprintStartDate, newSprintDuration); - - const sprintData = { - name: newSprintName, - description: newSprintDescription, - startDate: newSprintStartDate, - endDate: endDate, - createdBy: currentUser?.id || null - }; - - fetch(API_SPRINTS, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(sprintData) - }) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Failed to create sprint'); - } - }) - .then( - (result) => { - // Agregar el nuevo sprint a la lista de sprints - setSprints([...sprints, result]); - setIsCreatingSprint(false); - closeNewSprintModal(); - }, - (error) => { - setIsCreatingSprint(false); - setError(error); - } - ); - }; - - // Función para cargar los sprints - const loadSprints = () => { - setLoadingSprints(true); - fetch(API_SPRINTS) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Error loading sprints'); - } - }) - .then( - (result) => { - setSprints(result); - setLoadingSprints(false); - }, - (error) => { - setLoadingSprints(false); - setError(error); - }); - }; - - // Función para cargar los desarrolladores - const loadDevelopers = () => { - setLoadingDevelopers(true); - fetch(API_USERS) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Error loading developers'); - } - }) - .then( - (result) => { - // Filtrar solo los usuarios con rol "Developer" - const devs = result.filter(user => user.role === 'Developer'); - setDevelopers(devs); - setLoadingDevelopers(false); - }, - (error) => { - setLoadingDevelopers(false); - setError(error); - }); - }; - - // Función para cargar tareas por sprint - const loadTasksBySprint = (sprintId) => { - setLoading(true); - fetch(`${API_TASKS_BY_SPRINT}/${sprintId}`) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Error loading tasks for sprint'); - } - }) - .then( - (result) => { - setLoading(false); - // Adaptando al nuevo formato de datos - setItems(result.map(item => ({ - id: item.id, - description: item.description, - done: item.done, - createdAt: item.creation_ts, - status: item.status || 'Pending', - priority: item.priority || 'Medium', - steps: item.steps || '', - assignedTo: item.assignedTo, - createdBy: item.createdBy, - isArchived: item.isArchived, - sprintId: item.sprintId, - estimatedHours: item.estimatedHours, - actualHours: item.actualHours - }))); - - // Cargar tareas sin sprint - loadTasksWithoutSprint(); - }, - (error) => { - setLoading(false); - setError(error); - }); - }; - - // Función para cargar tareas sin sprint asignado (usando filtrado del lado del cliente) - const loadTasksWithoutSprint = () => { - // Cargar todas las tareas - fetch(API_LIST) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Error loading tasks'); - } - }) - .then( - (result) => { - // Filtrar tareas sin sprint (donde sprintId es null o undefined) - const tasksWithoutSprint = result.filter(item => !item.sprintId); - - // Adaptando al nuevo formato de datos - setTasksWithoutSprint(tasksWithoutSprint.map(item => ({ - id: item.id, - description: item.description, - done: item.done, - createdAt: item.creation_ts, - status: item.status || 'Pending', - priority: item.priority || 'Medium', - steps: item.steps || '', - assignedTo: item.assignedTo, - createdBy: item.createdBy, - isArchived: item.isArchived, - sprintId: item.sprintId, - estimatedHours: item.estimatedHours, - actualHours: item.actualHours - }))); - }, - (error) => { - setError(error); - }); - }; - - // Función para manejar el cambio de sprint seleccionado - const handleSprintChange = (sprintId) => { - if (sprintId === '') { - // Si se selecciona "All Tasks", cargamos todas las tareas - setSelectedSprint(null); - setLoading(true); - fetch(API_LIST) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Error loading all tasks'); - } - }) - .then( - (result) => { - setLoading(false); - // Adaptando al nuevo formato de datos - setItems(result.map(item => ({ - id: item.id, - description: item.description, - done: item.done, - createdAt: item.creation_ts, - status: item.status || 'Pending', - priority: item.priority || 'Medium', - steps: item.steps || '', - assignedTo: item.assignedTo, - createdBy: item.createdBy, - isArchived: item.isArchived, - sprintId: item.sprintId, - estimatedHours: item.estimatedHours, - actualHours: item.actualHours - }))); - - // Cargar tareas sin sprint - loadTasksWithoutSprint(); - }, - (error) => { - setLoading(false); - setError(error); - }); - } else { - // Si se selecciona un sprint específico - const sprint = sprints.find(s => s.id === parseInt(sprintId)); - setSelectedSprint(sprint); - loadTasksBySprint(sprintId); - } - }; - - // Función para asignar una tarea a un sprint - const assignTaskToSprint = (taskId, sprintId) => { - // Primero obtenemos la tarea actual para preservar sus datos - fetch(`${API_LIST}/${taskId}`) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Failed to get task data'); - } - }) - .then(taskData => { - // Actualizamos la tarea con el nuevo sprintId - return fetch(`${API_LIST}/${taskId}`, { - method: 'PUT', // Usamos PUT en lugar de PATCH - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - ...taskData, - sprintId: sprintId - }) - }); - }) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Failed to assign task to sprint'); - } - }) - .then( - (result) => { - // Actualizar las listas de tareas - if (selectedSprint && selectedSprint.id === sprintId) { - loadTasksBySprint(sprintId); - } else { - // Si la tarea se asignó a otro sprint, solo actualizamos la lista de tareas sin sprint - loadTasksWithoutSprint(); - } - - // Si el modal está abierto con esta tarea, actualizamos la tarea seleccionada - if (selectedTask && selectedTask.id === taskId) { - setSelectedTask({ - ...selectedTask, - sprintId: sprintId - }); - } - }, - (error) => { - setError(error); - } - ); - }; - - function deleteItem(deleteId) { - fetch(`${API_LIST}/${deleteId}`, { - method: 'DELETE', - }) - .then(response => { - if (response.ok) { - return response; - } else { - throw new Error('Something went wrong ...'); - } - }) - .then( - (result) => { - const remainingItems = items.filter(item => item.id !== deleteId); - setItems(remainingItems); - // Si el modal está abierto con esta tarea, cerrarlo - if (selectedTask && selectedTask.id === deleteId) { - closeTaskModal(); - } - }, - (error) => { - setError(error); - } - ); - } - - // Función para cambiar el estado de una tarea conservando su prioridad - function handleStatusChange(task, newStatus, actualHours = null) { - // Crear el objeto con los datos a actualizar - const updateData = { - status: newStatus - }; - - // Si hay horas reales y el estado es "Completed", actualizar las horas reales - if (actualHours && newStatus === 'Completed') { - updateData.actualHours = parseFloat(actualHours); - } - - // Actualizar el estado de la tarea - fetch(`${API_LIST}/${task.id}/status`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(updateData) - }) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Failed to update task status'); - } - }) - .then( - (result) => { - // Si se actualizaron las horas reales, actualizar la tarea completa - if (actualHours && newStatus === 'Completed') { - return fetch(`${API_LIST}/${task.id}/actual-hours`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ hours: parseFloat(actualHours) }) - }); - } - return Promise.resolve(); - } - ) - .then( - (result) => { - // Recargar la tarea para ver los cambios - reloadOneItem(task.id); - }, - (error) => { - setError(error); - } - ); - } - - function reloadOneItem(id){ - fetch(`${API_LIST}/${id}`) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Something went wrong ...'); - } - }) - .then( - (result) => { - const updatedItems = items.map( - x => (x.id === id ? { - ...x, - 'description': result.description, - 'done': result.done, - 'status': result.status, - 'priority': result.priority, - 'steps': result.steps, - 'assignedTo': result.assignedTo, - 'createdBy': result.createdBy, - 'isArchived': result.isArchived, - 'creation_ts': result.creation_ts, - 'estimatedHours': result.estimatedHours, - 'actualHours': result.actualHours - } : x)); - setItems(updatedItems); - - // Actualizar la tarea seleccionada si está abierta en el modal - if (selectedTask && selectedTask.id === id) { - setSelectedTask(updatedItems.find(item => item.id === id)); - } - }, - (error) => { - setError(error); - }); - } - - // Deprecado: Este método conserva prioridad, pero ahora usamos handleStatusChange - function modifyItem(id, description, done) { - // Encuentra el item actual para preservar otros campos - const currentItem = items.find(item => item.id === id); - - // Actualiza solo los campos necesarios - var data = { - "description": description, - "done": done, - "status": done ? "Completed" : (currentItem.status === "Completed" ? "In Progress" : currentItem.status), - "priority": currentItem.priority // Preservamos la prioridad - }; - - return fetch(`${API_LIST}/${id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) - }) - .then(response => { - if (response.ok) { - return response; - } else { - throw new Error('Something went wrong ...'); - } - }); - } - - function updateTaskFromModal(id, updatedTask) { - fetch(`${API_LIST}/${id}`, { - method: 'PUT', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(updatedTask) - }) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Failed to update task'); - } - }) - .then( - (result) => { - reloadOneItem(id); - closeTaskModal(); - }, - (error) => { - setError(error); - } - ); - } - - useEffect(() => { - // Solo cargar datos si el usuario está autenticado - if (isAuthenticated) { - // Cargar sprints y desarrolladores al iniciar - loadSprints(); - loadDevelopers(); - - // Cargar todas las tareas inicialmente - setLoading(true); - fetch(API_LIST) - .then(response => { - if (response.ok) { - return response.json(); - } else { - throw new Error('Something went wrong ...'); - } - }) - .then( - (result) => { - setLoading(false); - // Adaptando al nuevo formato de datos - setItems(result.map(item => ({ - id: item.id, - description: item.description, - done: item.done, - createdAt: item.creation_ts, - status: item.status || 'Pending', - priority: item.priority || 'Medium', - steps: item.steps || '', - assignedTo: item.assignedTo, - createdBy: item.createdBy, - isArchived: item.isArchived, - sprintId: item.sprintId, - estimatedHours: item.estimatedHours, - actualHours: item.actualHours - }))); - - // Cargar tareas sin sprint - loadTasksWithoutSprint(); - }, - (error) => { - setLoading(false); - setError(error); - }); - } - }, [isAuthenticated]); - - function addItem(text, steps, priority, sprintId, assignedTo, estimatedHours) { - setInserting(true); - - // Obtener la fecha y hora actual - const currentTimestamp = new Date().toISOString(); - - // Si el usuario es developer, auto-asignar la tarea a sí mismo - const taskAssignedTo = isDeveloper ? currentUser.id : assignedTo; - - var data = { - description: text, - done: false, - status: "Pending", // Siempre comenzar en Pending - priority: priority, - steps: steps || '', - creation_ts: currentTimestamp, - sprintId: sprintId || null, - assignedTo: taskAssignedTo, - createdBy: currentUser?.id || null, // Asignar el creador como el usuario actual - estimatedHours: estimatedHours || null - }; - - fetch(API_LIST, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data), - }).then((response) => { - if (response.ok) { - return response; - } else { - throw new Error('Something went wrong ...'); - } - }).then( - (result) => { - // Obtenemos la ubicación del nuevo recurso - var id = result.headers.get('location'); - - // Creamos un nuevo item con los datos disponibles - var newItem = { - id: id, - description: text, - done: false, - status: "Pending", - priority: priority, - steps: steps || '', - createdAt: currentTimestamp, - assignedTo: taskAssignedTo, - createdBy: currentUser?.id || null, - isArchived: 0, - sprintId: sprintId || null, - estimatedHours: estimatedHours || null - }; - - // Si hay un sprint seleccionado y la tarea pertenece a ese sprint, la agregamos a la lista - if (selectedSprint && sprintId === selectedSprint.id) { - setItems([newItem, ...items]); - } - // Si no hay sprint seleccionado y la tarea no tiene sprint, la agregamos a la lista - else if (!selectedSprint && !sprintId) { - setItems([newItem, ...items]); - } - - // Si la tarea no tiene sprint asignado, la agregamos a la lista de tareas sin sprint - if (!sprintId) { - setTasksWithoutSprint([newItem, ...tasksWithoutSprint]); - } - - setInserting(false); - }, - (error) => { - setInserting(false); - setError(error); - } - ); - } - - // Filtrado por prioridad - const filteredItems = items.filter(item => { - // Filtro por prioridad - let priorityMatch = priorityFilter === 'All' || item.priority === priorityFilter; - - // Para desarrolladores, solo mostrar tareas asignadas a ellos - let assigneeMatch = true; - if (isDeveloper) { - assigneeMatch = item.assignedTo === currentUser.id; - } - - return priorityMatch && assigneeMatch; - }); - - // Agrupar por estado - const pendingItems = filteredItems.filter(item => item.status === 'Pending'); - const inProgressItems = filteredItems.filter(item => item.status === 'In Progress'); - const inReviewItems = filteredItems.filter(item => item.status === 'In Review'); - const completedItems = filteredItems.filter(item => item.status === 'Completed'); - - const priorityOptions = ['All', 'Low', 'Medium', 'High', 'Critical']; - - // Función para renderizar las filas de tareas - const renderTaskRows = (tasks) => { - if (tasks.length === 0) { - return ( - - - No tasks in this section. - - - ); - } - - return tasks.map(item => ( - openTaskModal(item)} className="task-row"> - -
{item.description}
- {item.estimatedHours && ( -
- - Est: {item.estimatedHours}h - {item.actualHours && ( - / Act: {item.actualHours}h - )} -
- )} - - - - {item.priority} - - - - {item.createdAt && {item.createdAt}} - - e.stopPropagation()}> - {/* Para usuarios no desarrolladores o para developers que son asignados a esta tarea */} - {(!isDeveloper || (isDeveloper && item.assignedTo === currentUser.id)) && ( - - )} - - - )); - }; - - // Filtrado de tareas sin sprint (para desarrolladores, mostrar solo sus tareas) - const filteredTasksWithoutSprint = tasksWithoutSprint.filter(item => { - if (isDeveloper) { - return item.assignedTo === currentUser.id; - } - return true; - }); - - // Estilos para el modal - const modalStyle = { - position: 'absolute', - top: '50%', - left: '50%', - transform: 'translate(-50%, -50%)', - width: '80%', - maxWidth: 600, - maxHeight: '80vh', - overflow: 'auto', - bgcolor: '#312D2A', // Color de fondo Oracle Dark - boxShadow: 24, - p: 4, - borderRadius: '8px', - color: 'white', - border: '1px solid rgba(255, 255, 255, 0.1)' - }; - - // Si el usuario no está autenticado, mostrar el componente de login - if (!isAuthenticated) { - return ; - } - - return ( -
- {/* Botón de logout en la esquina superior derecha */} -
- -
- - {/* Oracle Logo (tamaño aumentado) */} - Oracle Logo - -

TODO LIST

- - {/* Mostrar el nombre del usuario actual y su rol */} - {currentUser && ( -
- Welcome, {currentUser.name || currentUser.username} - ({currentUser.role}) -
- )} - - {/* Pestañas de navegación - Sólo mostrar las pestañas permitidas */} -
-
setActiveTab('tasks')} - > - Tasks -
- - {/* Sólo mostrar GitHub y KPI Reports para no desarrolladores */} - {!isDeveloper && ( - <> -
setActiveTab('github')} - > - GitHub Integration -
-
setActiveTab('reports')} - > - KPI Reports -
- - )} +
+
setActiveTab('tasks')} + > + Tasks
- - {/* Contenido condicional según la pestaña activa */} - {activeTab === 'tasks' ? ( + {!currentUser?.role === 'Developer' && ( <> - {/* Selector de Sprint */} -
-

Select Sprint

-
- - Sprint - - - {loadingSprints &&
} -
- - {/* Sólo mostrar botón para crear sprints a no desarrolladores */} - {!isDeveloper && ( - - )} +
setActiveTab('github')} + > + GitHub Integration
- - - - { error && -
-

Error: {error.message}

-
- } - - {/* Filtros (solo por prioridad) */} -
-
- - -
+
setActiveTab('reports')} + > + KPI Reports
- - { isLoading ? ( -
-
-
- ) : ( -
- {selectedSprint && ( -
-

Sprint: {selectedSprint.name}

-

{selectedSprint.description}

-
- Start: {new Date(selectedSprint.startDate).toLocaleDateString()} - End: {new Date(selectedSprint.endDate).toLocaleDateString()} -
-
- )} - - {/* Pending Tasks Section */} -
-

Pending {pendingItems.length}

-
- - - - {renderTaskRows(pendingItems)} - -
- - {/* In Progress Tasks Section */} -
-

In Progress {inProgressItems.length}

-
- - - - {renderTaskRows(inProgressItems)} - -
- - {/* In Review Tasks Section */} -
-

In Review {inReviewItems.length}

-
- - - - {renderTaskRows(inReviewItems)} - -
- - {/* Completed Tasks Section */} -
-

Completed {completedItems.length}

-
- - - - {renderTaskRows(completedItems)} - -
- - {/* Tasks Without Sprint Section */} -
-
-

Tasks Without Sprint {filteredTasksWithoutSprint.length}

-
- - - - {filteredTasksWithoutSprint.length === 0 ? ( - - - - ) : ( - filteredTasksWithoutSprint.map(item => ( - openTaskModal(item)} className="task-row"> - - - - - - )) - )} - -
- No tasks without sprint. -
-
{item.description}
- {item.estimatedHours && ( -
- - Est: {item.estimatedHours}h - {item.actualHours && ( - / Act: {item.actualHours}h - )} -
- )} -
- - {item.priority} - - - {item.createdAt && {item.createdAt}} - e.stopPropagation()}> - {selectedSprint && !isDeveloper && ( - - )} - {(!isDeveloper || (isDeveloper && item.assignedTo === currentUser.id)) && !selectedSprint && ( - - )} -
-
-
- )} - ) : activeTab === 'github' ? ( - - ) : ( - )} - - {/* Modal para detalles de tarea */} - - - {selectedTask && ( - <> -
- - Task Details - - - - -
- -
-
- Description - - {selectedTask.description} - -
- - {selectedTask.steps && ( -
- Steps - - {selectedTask.steps.split('\n').map((step, index) => ( -
- {step} -
- ))} -
-
- )} - -
-
- Status - - {selectedTask.status} - -
- -
- Priority - - {selectedTask.priority} - -
- - {/* Mostrar las horas estimadas */} - {selectedTask.estimatedHours && ( -
- Estimated Hours - - {selectedTask.estimatedHours}h - -
- )} - - {/* Mostrar las horas reales */} - {selectedTask.actualHours && ( -
- Actual Hours - - {selectedTask.actualHours}h - -
- )} - - {/* Mostrar el sprint asignado */} -
- Sprint - - {selectedTask.sprintId ? - (sprints.find(s => s.id === selectedTask.sprintId)?.name || `Sprint ID: ${selectedTask.sprintId}`) : - 'Not assigned'} - -
- -
- Created - - {selectedTask.createdAt && {selectedTask.createdAt}} - -
- - {selectedTask.assignedTo && ( -
- Assigned To - - {developers.find(d => d.id === selectedTask.assignedTo)?.name || `User ID: ${selectedTask.assignedTo}`} - -
- )} - - {selectedTask.createdBy && ( -
- Created By - - User ID: {selectedTask.createdBy} - -
- )} -
- -
- {/* Botón para cambiar estado disponible solo para no desarrolladores o para developers asignados a esta tarea */} - {(!isDeveloper || (isDeveloper && selectedTask.assignedTo === currentUser.id)) && ( - - )} - - {/* Botón para eliminar disponible solo para no desarrolladores */} - {!isDeveloper && selectedTask.status === "Completed" && ( - - )} -
-
- - )} -
-
- - {/* Modal para crear un nuevo sprint */} - - -
- - Create New Sprint - - - - -
- -
-
- Name - setNewSprintName(e.target.value)} - className="modal-input" - /> -
- -
- Description -