let currentSectionId = null;
let currentAreaId = null;
let prioritiesMap = {};
let statusesMap = {};
let taskTypesMap = {};
let isTodoFilterActive = false;
let isForTodayFilterActive = false;
document.addEventListener('DOMContentLoaded', () => {
loadSidebar();
loadDropdowns();
});
let openModals = [];
function showModal(id) {
document.getElementById(id).style.display = 'flex';
if (!openModals.includes(id)) openModals.push(id);
}
function closeModal(id) {
document.getElementById(id).style.display = 'none';
openModals = openModals.filter(m => m !== id);
}
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && openModals.length > 0) {
const topModalId = openModals.pop();
document.getElementById(topModalId).style.display = 'none';
}
});
let searchTimeout = null;
function handleSearch(e) {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
loadTasks();
}, 300);
}
async function fetchAPI(endpoint, method = 'GET', body = null) {
const options = { method, headers: { 'Content-Type': 'application/json' } };
if (body) options.body = JSON.stringify(body);
const res = await fetch(`/api/${endpoint}`, options);
if (!res.ok) throw new Error('API Error');
return res.json();
}
let expandedAreas = new Set();
let expandedProjects = new Set();
function toggleArea(areaId) {
const el = document.getElementById(`area-sections-${areaId}`);
const icon = document.getElementById(`area-icon-${areaId}`);
if (el) {
if (el.style.display === 'none') {
el.style.display = 'block';
icon.innerText = '▼';
expandedAreas.add(areaId);
} else {
el.style.display = 'none';
icon.innerText = '▶';
expandedAreas.delete(areaId);
}
}
}
function toggleProject(projId) {
const el = document.getElementById(`project-content-${projId}`);
const icon = document.getElementById(`project-icon-${projId}`);
if (el) {
if (el.style.display === 'none') {
el.style.display = 'block';
icon.innerText = '▼';
expandedProjects.add(projId);
} else {
el.style.display = 'none';
icon.innerText = '▶';
expandedProjects.delete(projId);
}
}
}
async function loadSidebar() {
const projects = await fetchAPI('projects');
const container = document.getElementById('sidebar-content');
container.innerHTML = '';
for (const proj of projects) {
const pDiv = document.createElement('div');
pDiv.className = 'tree-item tree-project';
const isProjExpanded = expandedProjects.has(proj.id);
const projIconChar = isProjExpanded ? '▼' : '▶';
pDiv.innerHTML = `
${projIconChar}
📂 ${proj.name}
`;
container.appendChild(pDiv);
const projectContentContainer = document.createElement('div');
projectContentContainer.id = `project-content-${proj.id}`;
projectContentContainer.style.display = isProjExpanded ? 'block' : 'none';
const dDiv = document.createElement('div');
dDiv.className = 'tree-item tree-area';
dDiv.innerHTML = `📚 Documentación`;
projectContentContainer.appendChild(dDiv);
const areas = await fetchAPI(`areas?project_id=${proj.id}`);
for (const area of areas) {
const aDiv = document.createElement('div');
aDiv.className = 'tree-item tree-area';
const isExpanded = expandedAreas.has(area.id);
const iconChar = isExpanded ? '▼' : '▶';
aDiv.innerHTML = `
${iconChar}
📁 ${area.name}
`;
projectContentContainer.appendChild(aDiv);
const sectionsContainer = document.createElement('div');
sectionsContainer.id = `area-sections-${area.id}`;
sectionsContainer.style.display = isExpanded ? 'block' : 'none';
const sections = await fetchAPI(`sections?area_id=${area.id}`);
for (const sec of sections) {
const sDiv = document.createElement('div');
sDiv.className = 'tree-item tree-section';
const taskCount = sec.tasks ? sec.tasks.length : 0;
sDiv.innerHTML = `
📄 ${sec.name}
${taskCount}
`;
sectionsContainer.appendChild(sDiv);
}
projectContentContainer.appendChild(sectionsContainer);
}
container.appendChild(projectContentContainer);
}
}
async function loadDropdowns() {
const priorities = await fetchAPI('priorities');
const statuses = await fetchAPI('statuses');
const taskTypes = await fetchAPI('task_types');
priorities.forEach(p => prioritiesMap[p.id] = p);
statuses.forEach(s => statusesMap[s.id] = s);
taskTypes.forEach(t => taskTypesMap[t.id] = t);
const pSelect = document.getElementById('taskPriority');
const sSelect = document.getElementById('taskStatus');
const pEditSelect = document.getElementById('editTaskPriority');
const sEditSelect = document.getElementById('editTaskStatus');
const pFilter = document.getElementById('filterPriority');
const sFilter = document.getElementById('filterStatus');
const tFilter = document.getElementById('filterType');
const pOptions = priorities.map(p => ``).join('');
const sOptions = statuses.map(s => ``).join('');
const tOptions = taskTypes.map(t => ``).join('');
pSelect.innerHTML = pOptions;
sSelect.innerHTML = sOptions;
pEditSelect.innerHTML = pOptions;
sEditSelect.innerHTML = sOptions;
pFilter.innerHTML = '' + pOptions;
sFilter.innerHTML = '' + sOptions;
tFilter.innerHTML = '' + tOptions;
}
async function selectSection(id, name, areaId, projId) {
currentSectionId = id;
currentAreaId = areaId;
currentDocProjectId = projId; // Update global project context
document.getElementById('docs-board').style.display = 'none';
if (document.getElementById('admin-board')) document.getElementById('admin-board').style.display = 'none';
document.getElementById('board').style.display = 'block';
document.getElementById('current-view-title').innerText = `Sección: ${name}`;
document.getElementById('btn-add-task').style.display = 'flex';
document.getElementById('taskSectionId').value = id;
await loadFavorites();
const tTypes = await fetchAPI(`task_types?area_id=${areaId}`);
let optionsHtml = '';
tTypes.forEach(t => {
taskTypesMap[t.id] = t;
optionsHtml += ``;
});
document.getElementById('taskType').innerHTML = optionsHtml;
document.getElementById('editTaskType').innerHTML = optionsHtml;
loadTasks();
}
let allProjectsData = [];
async function loadAllTasksDropdowns() {
allProjectsData = await fetchAPI('projects');
const filterP = document.getElementById('filterProject');
let html = '';
allProjectsData.forEach(p => {
html += ``;
});
filterP.innerHTML = html;
}
async function onFilterProjectChange() {
const pId = document.getElementById('filterProject').value;
const filterA = document.getElementById('filterArea');
const filterS = document.getElementById('filterSection');
filterA.innerHTML = '';
filterS.innerHTML = '';
if (pId) {
const areas = await fetchAPI(`areas?project_id=${pId}`);
areas.forEach(a => {
filterA.innerHTML += ``;
});
}
loadTasks();
}
async function onFilterAreaChange() {
const aId = document.getElementById('filterArea').value;
const filterS = document.getElementById('filterSection');
filterS.innerHTML = '';
if (aId) {
const sections = await fetchAPI(`sections?area_id=${aId}`);
sections.forEach(s => {
filterS.innerHTML += ``;
});
}
loadTasks();
}
function showAllTasks() {
currentSectionId = null;
currentAreaId = null;
if (typeof currentDocProjectId !== 'undefined') currentDocProjectId = null;
document.getElementById('docs-board').style.display = 'none';
if (document.getElementById('admin-board')) document.getElementById('admin-board').style.display = 'none';
document.getElementById('board').style.display = 'block';
document.getElementById('current-view-title').innerText = `Todas las Tareas`;
document.getElementById('btn-add-task').style.display = 'none';
document.getElementById('filterProject').style.display = 'inline-block';
document.getElementById('filterArea').style.display = 'inline-block';
document.getElementById('filterSection').style.display = 'inline-block';
document.getElementById('filterProject').value = "";
document.getElementById('filterArea').innerHTML = '';
document.getElementById('filterSection').innerHTML = '';
loadAllTasksDropdowns();
loadTasks();
}
async function loadTasks() {
const query = document.getElementById('taskSearchInput') ? document.getElementById('taskSearchInput').value.trim() : '';
const showHidden = document.getElementById('showHiddenTasks').checked;
const fVisibility = document.getElementById('filterVisibility') ? document.getElementById('filterVisibility').value : 'visible';
const fType = document.getElementById('filterType').value;
const fStatus = document.getElementById('filterStatus').value;
const fPriority = document.getElementById('filterPriority').value;
const isGlobalView = (document.getElementById('current-view-title').innerText.includes('Todas las Tareas') || document.getElementById('current-view-title').innerText.includes('Global'));
if (!currentSectionId && !isGlobalView && !query && !fType && !fStatus && !fPriority) {
document.getElementById('board').innerHTML = 'Selecciona una sección en el panel izquierdo para ver o añadir tareas.
';
return;
}
if (currentSectionId) {
document.getElementById('filterProject').style.display = 'none';
document.getElementById('filterArea').style.display = 'none';
document.getElementById('filterSection').style.display = 'none';
}
let url = 'tasks?';
if (currentSectionId) {
url += `section_id=${currentSectionId}&`;
} else if (isGlobalView) {
const pId = document.getElementById('filterProject').value;
const aId = document.getElementById('filterArea').value;
const sId = document.getElementById('filterSection').value;
if (pId) url += `project_id=${pId}&`;
if (aId) url += `area_id=${aId}&`;
if (sId) url += `section_id=${sId}&`;
}
if (query) url += `search=${encodeURIComponent(query)}&`;
if (showHidden) url += `show_hidden=true&`;
if (fVisibility) url += `visibility=${fVisibility}&`;
if (fType) url += `task_type_id=${fType}&`;
if (fStatus) url += `status_id=${fStatus}&`;
if (fPriority) url += `priority_id=${fPriority}&`;
if (isTodoFilterActive) url += `todo=true&`;
if (isForTodayFilterActive) url += `for_today=true&`;
if (query && !isGlobalView) {
document.getElementById('current-view-title').innerText = currentSectionId ? `Búsqueda en Sección: "${query}"` : `Búsqueda Global: "${query}"`;
} else if (currentSectionId) {
const activeNode = document.querySelector(`.tree-section span[onclick*="(${currentSectionId},"]`);
if (activeNode) {
const name = activeNode.innerText.replace('📄 ', '');
document.getElementById('current-view-title').innerText = `Sección: ${name}`;
}
}
const tasks = await fetchAPI(url);
const sortOrder = document.getElementById('sortOrder') ? document.getElementById('sortOrder').value : 'created_desc';
tasks.sort((a, b) => {
if (sortOrder.startsWith('recent_comment')) {
const maxA = a.comments && a.comments.length > 0 ? Math.max(...a.comments.map(c => new Date(c.created_at).getTime())) : 0;
const maxB = b.comments && b.comments.length > 0 ? Math.max(...b.comments.map(c => new Date(c.created_at).getTime())) : 0;
return sortOrder === 'recent_comment_desc' ? maxB - maxA : maxA - maxB;
} else {
const dateA = new Date(a.created_at).getTime();
const dateB = new Date(b.created_at).getTime();
return sortOrder === 'created_desc' ? dateB - dateA : dateA - dateB;
}
});
const board = document.getElementById('board');
board.innerHTML = '';
if (tasks.length === 0) {
board.innerHTML = 'No hay tareas.
';
return;
}
const showLocation = (!currentSectionId && isGlobalView);
let tableHtml = `
`;
board.innerHTML = tableHtml;
}
// Creations
async function createProject(e) {
e.preventDefault();
const res = await fetchAPI('projects', 'POST', { name: document.getElementById('projName').value });
document.getElementById('projName').value = '';
if (res && res.id) expandedProjects.add(res.id);
closeModal('projectModal');
loadSidebar();
}
function openAreaModal(projId) {
document.getElementById('areaProjId').value = projId;
showModal('areaModal');
}
async function createArea(e) {
e.preventDefault();
const projId = parseInt(document.getElementById('areaProjId').value);
await fetchAPI('areas', 'POST', {
name: document.getElementById('areaName').value,
project_id: projId
});
document.getElementById('areaName').value = '';
expandedProjects.add(projId);
closeModal('areaModal');
loadSidebar();
}
function openTaskTypeModal(areaId) {
document.getElementById('taskTypeAreaId').value = areaId;
showModal('taskTypeModal');
}
async function createTaskType(e) {
e.preventDefault();
await fetchAPI('task_types', 'POST', {
name: document.getElementById('taskTypeName').value,
color: document.getElementById('taskTypeColor').value,
area_id: parseInt(document.getElementById('taskTypeAreaId').value)
});
document.getElementById('taskTypeName').value = '';
closeModal('taskTypeModal');
loadDropdowns();
if (currentAreaId) {
const tTypes = await fetchAPI(`task_types?area_id=${currentAreaId}`);
let optionsHtml = '';
tTypes.forEach(t => optionsHtml += ``);
document.getElementById('taskType').innerHTML = optionsHtml;
document.getElementById('editTaskType').innerHTML = optionsHtml;
}
}
function openSectionModal(areaId) {
document.getElementById('sectionAreaId').value = areaId;
showModal('sectionModal');
}
async function createSection(e) {
e.preventDefault();
const areaId = parseInt(document.getElementById('sectionAreaId').value);
await fetchAPI('sections', 'POST', {
name: document.getElementById('sectionName').value,
area_id: areaId
});
document.getElementById('sectionName').value = '';
expandedAreas.add(areaId);
closeModal('sectionModal');
loadSidebar();
}
async function createTask(e) {
e.preventDefault();
const startDate = document.getElementById('taskStartDate').value;
const endDate = document.getElementById('taskEndDate').value;
const task = {
name: document.getElementById('taskName').value,
description: document.getElementById('taskDesc').value,
responsible: document.getElementById('taskResp').value,
section_id: parseInt(document.getElementById('taskSectionId').value),
task_type_id: parseInt(document.getElementById('taskType').value) || null,
priority_id: parseInt(document.getElementById('taskPriority').value) || null,
status_id: parseInt(document.getElementById('taskStatus').value) || null,
start_date: startDate ? new Date(startDate).toISOString() : null,
end_date: endDate ? new Date(endDate).toISOString() : null,
visible: document.getElementById('taskVisible').checked
};
await fetchAPI('tasks', 'POST', task);
document.getElementById('taskName').value = '';
document.getElementById('taskDesc').value = '';
document.getElementById('taskResp').value = '';
document.getElementById('taskStartDate').value = '';
document.getElementById('taskEndDate').value = '';
document.getElementById('taskVisible').checked = true;
closeModal('taskModal');
loadTasks();
loadSidebar();
}
// Task Edit & Comments
function toggleTaskVisibilityIcon() {
const cb = document.getElementById('editTaskVisible');
cb.checked = !cb.checked;
updateVisibilityIcon(cb.checked);
}
function updateVisibilityIcon(isVisible) {
const btn = document.getElementById('visibilityToggleBtn');
if (isVisible) {
btn.innerHTML = ``;
btn.title = "Tarea Visible";
} else {
btn.innerHTML = ``;
btn.title = "Tarea Oculta";
}
}
async function openTaskDetails(taskId) {
const task = await fetchAPI(`tasks/${taskId}`);
document.getElementById('editTaskId').value = task.id;
document.getElementById('editTaskSectionId').value = task.section_id;
document.getElementById('editTaskCreatedAt').innerText = new Date(task.created_at).toLocaleString();
document.getElementById('editTaskName').value = task.name;
document.getElementById('editTaskDesc').value = task.description || '';
document.getElementById('editTaskResp').value = task.responsible || '';
if (task.section && task.section.area) {
const tTypes = await fetchAPI(`task_types?area_id=${task.section.area.id}`);
let optionsHtml = '';
tTypes.forEach(t => optionsHtml += ``);
document.getElementById('editTaskType').innerHTML = optionsHtml;
}
document.getElementById('editTaskType').value = task.task_type_id || '';
document.getElementById('editTaskPriority').value = task.priority_id || '';
document.getElementById('editTaskStatus').value = task.status_id || '';
document.getElementById('editTaskStartDate').value = task.start_date ? task.start_date.split('T')[0] : '';
document.getElementById('editTaskEndDate').value = task.end_date ? task.end_date.split('T')[0] : '';
document.getElementById('editTaskVisible').checked = task.visible;
updateVisibilityIcon(task.visible);
const totalHours = (task.time_logs || []).reduce((acc, log) => acc + log.hours, 0);
document.getElementById('editTaskTotalHours').innerText = totalHours;
window.currentTaskTimeLogs = task.time_logs || [];
renderComments(task.comments);
renderFiles(task.files);
showModal('taskDetailModal');
}
async function updateTask(e) {
e.preventDefault();
const taskId = document.getElementById('editTaskId').value;
const startDate = document.getElementById('editTaskStartDate').value;
const endDate = document.getElementById('editTaskEndDate').value;
const task = {
name: document.getElementById('editTaskName').value,
description: document.getElementById('editTaskDesc').value,
responsible: document.getElementById('editTaskResp').value,
section_id: parseInt(document.getElementById('editTaskSectionId').value),
task_type_id: parseInt(document.getElementById('editTaskType').value) || null,
priority_id: parseInt(document.getElementById('editTaskPriority').value) || null,
status_id: parseInt(document.getElementById('editTaskStatus').value) || null,
start_date: startDate ? new Date(startDate).toISOString() : null,
end_date: endDate ? new Date(endDate).toISOString() : null,
visible: document.getElementById('editTaskVisible').checked
};
await fetchAPI(`tasks/${taskId}`, 'PUT', task);
closeModal('taskDetailModal');
loadTasks();
loadSidebar();
}
function renderComments(comments) {
const list = document.getElementById('commentsList');
list.innerHTML = '';
if (!comments || comments.length === 0) {
list.innerHTML = 'No hay comentarios aún.
';
return;
}
// Sort comments descending (newest first)
const sortedComments = [...comments].sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
sortedComments.forEach(c => {
const div = document.createElement('div');
div.style.background = 'rgba(255,255,255,0.05)';
div.style.padding = '10px';
div.style.borderRadius = '8px';
div.style.marginBottom = '10px';
div.style.display = 'flex';
div.style.justifyContent = 'space-between';
const date = new Date(c.created_at).toLocaleString();
div.innerHTML = `
`;
div.querySelector('.comment-text').innerHTML = marked.parse(c.content, { breaks: true });
div.querySelector('.btn-edit-comment').onclick = () => {
document.getElementById('editCommentId').value = c.id;
document.getElementById('editCommentTaskId').value = c.task_id;
document.getElementById('editCommentContent').value = c.content;
showModal('editCommentModal');
};
div.querySelector('.btn-del-comment').onclick = () => deleteComment(c.id, c.task_id);
list.appendChild(div);
});
}
async function submitEditComment(e) {
e.preventDefault();
const commentId = document.getElementById('editCommentId').value;
const taskId = document.getElementById('editCommentTaskId').value;
const content = document.getElementById('editCommentContent').value;
await fetchAPI(`comments/${commentId}`, 'PUT', { content });
closeModal('editCommentModal');
openTaskDetails(taskId);
loadTasks();
}
async function deleteComment(commentId, taskId) {
if (!confirm('¿Seguro que deseas eliminar este comentario?')) return;
await fetchAPI(`comments/${commentId}`, 'DELETE');
openTaskDetails(taskId);
loadTasks();
}
async function addComment(e) {
e.preventDefault();
const taskId = document.getElementById('editTaskId').value;
const content = document.getElementById('newCommentContent').value;
await fetchAPI(`tasks/${taskId}/comments`, 'POST', { content });
document.getElementById('newCommentContent').value = '';
openTaskDetails(taskId);
loadTasks();
}
function renderFiles(files) {
const list = document.getElementById('filesList');
list.innerHTML = '';
if (!files || files.length === 0) {
list.innerHTML = 'No hay archivos adjuntos.
';
return;
}
files.forEach(f => {
const div = document.createElement('div');
div.style.background = 'rgba(255,255,255,0.05)';
div.style.padding = '10px';
div.style.borderRadius = '8px';
div.style.display = 'flex';
div.style.justifyContent = 'space-between';
div.style.alignItems = 'center';
div.innerHTML = `
📎 ${f.filename}
${new Date(f.created_at).toLocaleString()}
`;
list.appendChild(div);
});
}
function openFileLocal(filePath) {
window.open(filePath, '_blank');
}
let currentUploadMode = null;
function updateFileCount(mode) {
currentUploadMode = mode;
const fileInput = document.getElementById('newTaskFile');
const folderInput = document.getElementById('newTaskFolder');
const textDiv = document.getElementById('fileSelectionText');
if (mode === 'file' && fileInput.files.length > 0) {
folderInput.value = '';
const count = fileInput.files.length;
textDiv.innerText = `${count} archivo(s) seleccionado(s)`;
} else if (mode === 'folder' && folderInput.files.length > 0) {
fileInput.value = '';
const count = folderInput.files.length;
textDiv.innerText = `Carpeta con ${count} archivo(s) seleccionada`;
} else {
currentUploadMode = null;
textDiv.innerText = 'Ningún archivo seleccionado';
}
}
async function uploadFile(e) {
e.preventDefault();
const taskId = document.getElementById('editTaskId').value;
let filesToUpload = [];
if (currentUploadMode === 'file') {
filesToUpload = document.getElementById('newTaskFile').files;
} else if (currentUploadMode === 'folder') {
filesToUpload = document.getElementById('newTaskFolder').files;
}
if (filesToUpload.length === 0) {
alert('Por favor selecciona archivos o una carpeta primero.');
return;
}
const formData = new FormData();
for (let i = 0; i < filesToUpload.length; i++) {
formData.append('files', filesToUpload[i]);
}
const res = await fetch(`/api/tasks/${taskId}/files`, {
method: 'POST',
body: formData
});
if (res.ok) {
document.getElementById('newTaskFile').value = '';
document.getElementById('newTaskFolder').value = '';
updateFileCount(null);
openTaskDetails(taskId);
} else {
alert('Error al subir los archivos');
}
}
async function deleteFile(fileId, taskId) {
if (!confirm('¿Seguro que deseas eliminar este archivo?')) return;
const res = await fetch(`/api/tasks/files/${fileId}`, {
method: 'DELETE'
});
if (res.ok) {
openTaskDetails(taskId);
} else {
alert('Error al eliminar el archivo');
}
}
async function deleteSection(sectionId) {
if (!confirm('¿Seguro que deseas eliminar esta sección y TODAS sus tareas?')) return;
const res = await fetch(`/api/sections/${sectionId}`, {
method: 'DELETE'
});
if (res.ok) {
if (currentSectionId === sectionId) {
currentSectionId = null;
document.getElementById('current-view-title').innerText = 'Selecciona una Sección';
document.getElementById('btn-add-task').style.display = 'none';
document.getElementById('board').innerHTML = 'Selecciona una sección en el panel izquierdo para ver o añadir tareas.
';
}
loadSidebar();
} else {
alert('Error al eliminar la sección');
}
}
async function deleteTask(taskId) {
if (!confirm('¿Seguro que deseas eliminar esta tarea?')) return;
const res = await fetch(`/api/tasks/${taskId}`, {
method: 'DELETE'
});
if (res.ok) {
closeModal('taskDetailModal');
loadTasks();
loadSidebar();
} else {
alert('Error al eliminar la tarea');
}
}
// --- Admin Panel ---
async function openAdminPanel() {
document.getElementById('board').style.display = 'none';
if (document.getElementById('docs-board')) document.getElementById('docs-board').style.display = 'none';
document.getElementById('btn-add-task').style.display = 'none';
document.getElementById('admin-board').style.display = 'block';
document.getElementById('current-view-title').innerText = 'Administración';
await loadAdminProjects();
await loadAdminTaskTypeDropdowns();
}
async function loadAdminProjects() {
const projects = await fetchAPI('projects?include_hidden=true');
const container = document.getElementById('admin-projects-list');
let html = '';
projects.forEach(p => {
html += `
`;
});
if (projects.length === 0) {
html = 'No hay proyectos.
';
}
container.innerHTML = html;
}
async function toggleProjectVisibility(projectId, isVisible) {
await fetchAPI(`projects/${projectId}/visibility?visible=${isVisible}`, 'PUT');
loadSidebar(); // Refresh sidebar automatically
loadAdminProjects(); // Refresh UI to update colors
}
async function loadAdminTaskTypeDropdowns() {
const projects = await fetchAPI('projects');
const projectSelect = document.getElementById('adminTaskTypeProject');
let html = '';
projects.forEach(p => {
html += ``;
});
projectSelect.innerHTML = html;
document.getElementById('adminTaskTypeArea').innerHTML = '';
document.getElementById('admin-task-types-list').innerHTML = 'Selecciona un área para ver sus tipos de tareas.
';
}
async function onAdminTaskTypeProjectChange() {
const pId = document.getElementById('adminTaskTypeProject').value;
const areaSelect = document.getElementById('adminTaskTypeArea');
const taskTypeList = document.getElementById('admin-task-types-list');
areaSelect.innerHTML = '';
taskTypeList.innerHTML = 'Selecciona un área para ver sus tipos de tareas.
';
if (pId) {
const areas = await fetchAPI(`areas?project_id=${pId}`);
let html = '';
areas.forEach(a => {
html += ``;
});
areaSelect.innerHTML = html;
}
}
async function loadAdminTaskTypesFiltered() {
const aId = document.getElementById('adminTaskTypeArea').value;
const container = document.getElementById('admin-task-types-list');
if (!aId) {
container.innerHTML = 'Selecciona un área para ver sus tipos de tareas.
';
return;
}
const taskTypes = await fetchAPI(`task_types?area_id=${aId}`);
let html = '';
taskTypes.forEach(t => {
html += `
`;
});
if (taskTypes.length === 0) {
html = 'No hay tipos de tareas en esta área.
';
}
container.innerHTML = html;
}
function openEditTaskTypeAdmin(id, name, color) {
document.getElementById('editTaskTypeIdAdmin').value = id;
document.getElementById('editTaskTypeNameAdmin').value = name;
document.getElementById('editTaskTypeColorAdmin').value = color;
showModal('editTaskTypeModal');
}
async function submitEditTaskType(e) {
e.preventDefault();
const id = document.getElementById('editTaskTypeIdAdmin').value;
const name = document.getElementById('editTaskTypeNameAdmin').value;
const color = document.getElementById('editTaskTypeColorAdmin').value;
await fetchAPI(`task_types/${id}`, 'PUT', { name, color });
closeModal('editTaskTypeModal');
loadAdminTaskTypesFiltered();
await loadDropdowns();
if (currentAreaId) {
const tTypes = await fetchAPI(`task_types?area_id=${currentAreaId}`);
let optionsHtml = '';
tTypes.forEach(t => optionsHtml += ``);
document.getElementById('taskType').innerHTML = optionsHtml;
document.getElementById('editTaskType').innerHTML = optionsHtml;
}
loadTasks();
}
async function deleteTaskTypeAdmin(id) {
if (!confirm('¿Seguro que deseas eliminar este tipo de tarea? Las tareas que lo tengan asignado quedarán sin tipo.')) return;
const res = await fetch(`/api/task_types/${id}`, { method: 'DELETE' });
if (res.ok) {
loadAdminTaskTypesFiltered();
await loadDropdowns();
if (currentAreaId) {
const tTypes = await fetchAPI(`task_types?area_id=${currentAreaId}`);
let optionsHtml = '';
tTypes.forEach(t => optionsHtml += ``);
document.getElementById('taskType').innerHTML = optionsHtml;
document.getElementById('editTaskType').innerHTML = optionsHtml;
}
loadTasks();
} else {
alert('Error al eliminar el tipo de tarea');
}
}
// Task File Drag and Drop
document.addEventListener('DOMContentLoaded', () => {
const fileForm = document.getElementById('fileForm');
const dragOverlay = document.getElementById('task-drag-overlay');
if (!fileForm || !dragOverlay) return;
let dragCounter = 0;
fileForm.addEventListener('dragenter', (e) => {
e.preventDefault();
dragCounter++;
dragOverlay.style.display = 'flex';
});
fileForm.addEventListener('dragleave', (e) => {
e.preventDefault();
dragCounter--;
if (dragCounter === 0) {
dragOverlay.style.display = 'none';
}
});
fileForm.addEventListener('dragover', (e) => {
e.preventDefault();
});
fileForm.addEventListener('drop', async (e) => {
e.preventDefault();
dragCounter = 0;
dragOverlay.style.display = 'none';
const taskId = document.getElementById('editTaskId').value;
if (!taskId) return;
const files = e.dataTransfer.files;
if (!files || files.length === 0) return;
const formData = new FormData();
for (let i = 0; i < files.length; i++) {
formData.append('files', files[i]);
}
try {
const res = await fetch(`/api/tasks/${taskId}/files`, { method: 'POST', body: formData });
if (res.ok) {
document.getElementById('newTaskFile').value = '';
document.getElementById('newTaskFolder').value = '';
updateFileCount(null);
openTaskDetails(taskId);
} else {
alert('Error al subir archivos');
}
} catch (error) {
alert('Error al subir archivos');
}
});
});
// --- Time Logs ---
function openTimeLogModal() {
const today = new Date().toISOString().split('T')[0];
document.getElementById('timeLogDate').value = today;
document.getElementById('timeLogHours').value = '';
renderTimeLogs(window.currentTaskTimeLogs);
showModal('timeLogModal');
}
function renderTimeLogs(logs) {
const list = document.getElementById('timeLogsList');
list.innerHTML = '';
if (!logs || logs.length === 0) {
list.innerHTML = 'No hay registros de horas.
';
return;
}
const sortedLogs = [...logs].sort((a, b) => new Date(b.date) - new Date(a.date));
sortedLogs.forEach(log => {
const div = document.createElement('div');
div.style.background = 'rgba(255,255,255,0.05)';
div.style.padding = '8px';
div.style.borderRadius = '6px';
div.style.display = 'flex';
div.style.justifyContent = 'space-between';
div.style.alignItems = 'center';
const dateStr = new Date(log.date).toLocaleDateString();
div.innerHTML = `
${dateStr}
${log.hours}h
`;
list.appendChild(div);
});
}
async function addTimeLog(e) {
e.preventDefault();
const taskId = document.getElementById('editTaskId').value;
const date = document.getElementById('timeLogDate').value;
const hours = parseFloat(document.getElementById('timeLogHours').value);
const isoDate = new Date(date).toISOString();
const res = await fetchAPI(`tasks/${taskId}/time_logs`, 'POST', { date: isoDate, hours: hours });
if (res) {
await openTaskDetails(taskId);
loadTasks();
renderTimeLogs(window.currentTaskTimeLogs);
document.getElementById('timeLogHours').value = '';
}
}
async function deleteTimeLog(logId) {
if (!confirm('¿Seguro que deseas eliminar este registro de horas?')) return;
const taskId = document.getElementById('editTaskId').value;
const res = await fetch(`/api/time_logs/${logId}`, { method: 'DELETE' });
if (res.ok) {
await openTaskDetails(taskId);
loadTasks();
renderTimeLogs(window.currentTaskTimeLogs);
} else {
alert('Error al eliminar el registro de horas.');
}
}
// --- Reportes de Administración ---
async function generateTimeReport() {
const startDate = document.getElementById('reportStartDate').value;
const endDate = document.getElementById('reportEndDate').value;
if (!startDate || !endDate) {
alert('Por favor selecciona la fecha de inicio y fin.');
return;
}
const resultsDiv = document.getElementById('reportResults');
resultsDiv.style.display = 'block';
resultsDiv.innerHTML = 'Cargando...
';
const logs = await fetchAPI(`reports/time_logs?start_date=${startDate}&end_date=${endDate}`);
if (!logs || logs.length === 0) {
resultsDiv.innerHTML = 'No hay registros de horas en este periodo.
';
return;
}
let totalHoursPeriod = 0;
const tasksMap = {};
logs.forEach(log => {
totalHoursPeriod += log.hours;
if (!tasksMap[log.task.id]) {
tasksMap[log.task.id] = {
name: log.task.name,
totalHours: 0,
logs: []
};
}
tasksMap[log.task.id].totalHours += log.hours;
tasksMap[log.task.id].logs.push(log);
});
let html = `
Total del periodo: ${totalHoursPeriod} horas
`;
Object.values(tasksMap).forEach(task => {
html += `
📄 ${task.name}
${task.totalHours} horas
`;
task.logs.forEach(log => {
const dateStr = new Date(log.date).toLocaleDateString();
html += `
📅 ${dateStr}
${log.hours} h
`;
});
html += `
`;
});
html += `
`;
resultsDiv.innerHTML = html;
}
// --- Todo Filter ---
async function toggleForToday(taskId, checkbox) {
const todayStr = new Date().toISOString().split('T')[0];
const newDate = checkbox.checked ? todayStr : null;
await fetchAPI(`tasks/${taskId}/for_today`, 'PATCH', { for_today_date: newDate });
}
function toggleTodoFilter() {
isTodoFilterActive = !isTodoFilterActive;
const btn = document.getElementById('btnTodoFilter');
if (isTodoFilterActive) {
btn.style.background = 'rgba(16, 185, 129, 0.2)';
btn.style.color = '#10b981';
btn.style.borderColor = '#10b981';
// Ensure we are in a view where the project can be chosen
const isGlobal = (document.getElementById('current-view-title').innerText.includes('Todas las Tareas') || document.getElementById('current-view-title').innerText.includes('Global'));
if (!isGlobal || currentSectionId) {
showAllTasks();
return; // showAllTasks already calls loadTasks
}
} else {
btn.style.background = 'rgba(255,255,255,0.05)';
btn.style.color = 'white';
btn.style.borderColor = 'var(--border-color)';
}
loadTasks();
}
function toggleForTodayFilter() {
isForTodayFilterActive = !isForTodayFilterActive;
const btn = document.getElementById('btnForTodayFilter');
if (isForTodayFilterActive) {
btn.style.background = 'rgba(236, 72, 153, 0.2)';
btn.style.color = '#ec4899';
btn.style.borderColor = '#ec4899';
const isGlobal = (document.getElementById('current-view-title').innerText.includes('Todas las Tareas') || document.getElementById('current-view-title').innerText.includes('Global'));
if (!isGlobal || currentSectionId) {
showAllTasks();
return;
}
} else {
btn.style.background = 'rgba(255,255,255,0.05)';
btn.style.color = 'white';
btn.style.borderColor = 'var(--border-color)';
}
loadTasks();
}