taskflow / index.html
federi's picture
Add 3 files
3b10dbb verified
<!DOCTYPE html>
<html lang="it">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TaskFlow - Gestione Attività</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Custom scrollbar */
::-webkit-scrollbar {
width: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Animation for task completion */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.task-item {
animation: fadeIn 0.3s ease-out forwards;
}
/* Calendar day highlight */
.current-day {
background-color: #3b82f6;
color: white;
border-radius: 50%;
}
</style>
</head>
<body class="bg-gray-100 font-sans">
<div class="flex h-screen overflow-hidden">
<!-- Sidebar -->
<div class="hidden md:flex md:flex-shrink-0">
<div class="flex flex-col w-64 bg-indigo-700 text-white">
<div class="flex items-center justify-center h-16 px-4">
<div class="flex items-center">
<i class="fas fa-tasks text-2xl mr-2"></i>
<span class="text-xl font-semibold">TaskFlow</span>
</div>
</div>
<div class="flex flex-col flex-grow px-4 py-4 overflow-y-auto">
<div class="space-y-1">
<button id="all-tasks-btn" class="flex items-center w-full px-4 py-2 text-sm font-medium text-white bg-indigo-800 rounded-md group">
<i class="fas fa-list mr-3"></i>
Tutte le attività
</button>
<button id="today-tasks-btn" class="flex items-center w-full px-4 py-2 text-sm font-medium text-indigo-100 hover:text-white hover:bg-indigo-600 rounded-md group">
<i class="fas fa-calendar-day mr-3"></i>
Oggi
</button>
<button id="upcoming-tasks-btn" class="flex items-center w-full px-4 py-2 text-sm font-medium text-indigo-100 hover:text-white hover:bg-indigo-600 rounded-md group">
<i class="fas fa-calendar-week mr-3"></i>
Prossimi
</button>
<button id="important-tasks-btn" class="flex items-center w-full px-4 py-2 text-sm font-medium text-indigo-100 hover:text-white hover:bg-indigo-600 rounded-md group">
<i class="fas fa-star mr-3"></i>
Importanti
</button>
<button id="completed-tasks-btn" class="flex items-center w-full px-4 py-2 text-sm font-medium text-indigo-100 hover:text-white hover:bg-indigo-600 rounded-md group">
<i class="fas fa-check-circle mr-3"></i>
Completate
</button>
</div>
<div class="mt-8">
<h3 class="px-4 text-xs font-semibold text-indigo-200 uppercase tracking-wider">Categorie</h3>
<div class="mt-2 space-y-1" id="categories-list">
<!-- Categories will be added dynamically -->
</div>
<div class="mt-2">
<button id="add-category-btn" class="flex items-center w-full px-4 py-2 text-sm font-medium text-indigo-100 hover:text-white hover:bg-indigo-600 rounded-md group">
<i class="fas fa-plus mr-3"></i>
Aggiungi categoria
</button>
</div>
</div>
<div class="mt-auto mb-4">
<div class="flex items-center px-4 py-2 text-sm text-indigo-200">
<i class="fas fa-moon mr-3"></i>
Modalità scura
<label class="ml-auto relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="dark-mode-toggle" class="sr-only peer">
<div class="w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-4 peer-focus:ring-indigo-300 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-indigo-600"></div>
</label>
</div>
</div>
</div>
</div>
</div>
<!-- Mobile sidebar -->
<div class="md:hidden fixed bottom-0 left-0 right-0 bg-white shadow-lg z-10">
<div class="flex justify-around">
<button id="mobile-all-tasks" class="p-4 text-indigo-600">
<i class="fas fa-list text-xl"></i>
</button>
<button id="mobile-today-tasks" class="p-4 text-gray-500">
<i class="fas fa-calendar-day text-xl"></i>
</button>
<button id="mobile-add-task" class="p-4 text-gray-500">
<i class="fas fa-plus-circle text-2xl text-indigo-600"></i>
</button>
<button id="mobile-upcoming-tasks" class="p-4 text-gray-500">
<i class="fas fa-calendar-week text-xl"></i>
</button>
<button id="mobile-important-tasks" class="p-4 text-gray-500">
<i class="fas fa-star text-xl"></i>
</button>
</div>
</div>
<!-- Main content -->
<div class="flex flex-col flex-1 overflow-hidden">
<!-- Top navigation -->
<div class="flex items-center justify-between h-16 px-4 bg-white border-b border-gray-200">
<div class="flex items-center">
<button id="sidebar-toggle" class="md:hidden text-gray-500 focus:outline-none">
<i class="fas fa-bars text-xl"></i>
</button>
<h1 id="current-view" class="ml-4 text-lg font-semibold text-gray-900">Tutte le attività</h1>
</div>
<div class="flex items-center space-x-4">
<div class="relative">
<button id="search-btn" class="p-1 text-gray-500 hover:text-gray-700">
<i class="fas fa-search"></i>
</button>
<div id="search-container" class="hidden absolute right-0 mt-2 w-64 bg-white rounded-md shadow-lg z-10">
<input type="text" id="search-input" class="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Cerca attività...">
</div>
</div>
<button id="add-task-btn" class="flex items-center px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500">
<i class="fas fa-plus mr-2"></i>
<span class="hidden md:inline">Nuova attività</span>
</button>
</div>
</div>
<!-- Content area -->
<div class="flex flex-1 overflow-hidden">
<!-- Task list -->
<div class="flex-1 overflow-y-auto p-4">
<div id="task-list-container">
<div id="no-tasks-message" class="flex flex-col items-center justify-center h-64 text-gray-500">
<i class="fas fa-tasks text-5xl mb-4"></i>
<p class="text-xl">Nessuna attività trovata</p>
<button id="add-first-task-btn" class="mt-4 px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
Aggiungi la tua prima attività
</button>
</div>
<div id="task-list" class="space-y-3">
<!-- Tasks will be added dynamically -->
</div>
</div>
</div>
<!-- Calendar and task details (hidden on mobile) -->
<div class="hidden lg:flex lg:flex-col w-80 border-l border-gray-200 bg-white">
<div class="p-4 border-b border-gray-200">
<h2 class="text-lg font-semibold">Calendario</h2>
<div class="mt-4">
<div class="flex justify-between items-center mb-4">
<button id="prev-month" class="p-1 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-left"></i>
</button>
<h3 id="current-month" class="font-medium">Settembre 2023</h3>
<button id="next-month" class="p-1 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-right"></i>
</button>
</div>
<div id="calendar" class="grid grid-cols-7 gap-1">
<!-- Calendar days will be added dynamically -->
</div>
</div>
</div>
<div class="flex-1 overflow-y-auto p-4">
<h2 class="text-lg font-semibold mb-4">Dettagli attività</h2>
<div id="task-details" class="hidden">
<h3 id="task-detail-title" class="text-xl font-semibold mb-2"></h3>
<div class="flex items-center text-sm text-gray-500 mb-4">
<i class="far fa-calendar-alt mr-2"></i>
<span id="task-detail-date"></span>
</div>
<div class="mb-4">
<span id="task-detail-category" class="inline-block px-2 py-1 text-xs font-semibold rounded-full"></span>
</div>
<p id="task-detail-description" class="text-gray-700 mb-4"></p>
<div class="flex space-x-2">
<button id="edit-task-btn" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">
Modifica
</button>
<button id="delete-task-btn" class="px-4 py-2 bg-red-600 text-white rounded-md hover:bg-red-700">
Elimina
</button>
</div>
</div>
<div id="no-task-selected" class="flex flex-col items-center justify-center h-64 text-gray-500">
<i class="fas fa-info-circle text-5xl mb-4"></i>
<p class="text-center">Seleziona un'attività per visualizzare i dettagli</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Add/Edit Task Modal -->
<div id="task-modal" class="fixed inset-0 z-50 hidden overflow-y-auto">
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
<h3 id="modal-title" class="text-lg leading-6 font-medium text-gray-900 mb-4">
Nuova attività
</h3>
<form id="task-form" class="space-y-4">
<input type="hidden" id="task-id">
<div>
<label for="task-title" class="block text-sm font-medium text-gray-700">Titolo</label>
<input type="text" id="task-title" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" required>
</div>
<div>
<label for="task-description" class="block text-sm font-medium text-gray-700">Descrizione</label>
<textarea id="task-description" rows="3" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500"></textarea>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label for="task-date" class="block text-sm font-medium text-gray-700">Data</label>
<input type="date" id="task-date" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
</div>
<div>
<label for="task-time" class="block text-sm font-medium text-gray-700">Ora</label>
<input type="time" id="task-time" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
</div>
</div>
<div>
<label for="task-category" class="block text-sm font-medium text-gray-700">Categoria</label>
<div class="flex mt-1">
<select id="task-category" class="flex-1 border border-gray-300 rounded-l-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
<option value="">Nessuna categoria</option>
</select>
<button type="button" id="new-category-btn" class="inline-flex items-center px-3 py-2 border border-l-0 border-gray-300 bg-gray-50 text-gray-500 hover:bg-gray-100 rounded-r-md">
<i class="fas fa-plus"></i>
</button>
</div>
</div>
<div class="flex items-center">
<input type="checkbox" id="task-important" class="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded">
<label for="task-important" class="ml-2 block text-sm text-gray-700">Contrassegna come importante</label>
</div>
</form>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button type="button" id="save-task-btn" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm">
Salva
</button>
<button type="button" id="cancel-task-btn" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Annulla
</button>
</div>
</div>
</div>
</div>
<!-- Add Category Modal -->
<div id="category-modal" class="fixed inset-0 z-50 hidden overflow-y-auto">
<div class="flex items-center justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div class="fixed inset-0 transition-opacity" aria-hidden="true">
<div class="absolute inset-0 bg-gray-500 opacity-75"></div>
</div>
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-md sm:w-full">
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div class="sm:flex sm:items-start">
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
<h3 class="text-lg leading-6 font-medium text-gray-900 mb-4">
Nuova categoria
</h3>
<form id="category-form" class="space-y-4">
<div>
<label for="category-name" class="block text-sm font-medium text-gray-700">Nome categoria</label>
<input type="text" id="category-name" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500" required>
</div>
<div>
<label for="category-color" class="block text-sm font-medium text-gray-700">Colore</label>
<select id="category-color" class="mt-1 block w-full border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500">
<option value="bg-red-500 text-red-100">Rosso</option>
<option value="bg-blue-500 text-blue-100">Blu</option>
<option value="bg-green-500 text-green-100">Verde</option>
<option value="bg-yellow-500 text-yellow-100">Giallo</option>
<option value="bg-purple-500 text-purple-100">Viola</option>
<option value="bg-pink-500 text-pink-100">Rosa</option>
<option value="bg-indigo-500 text-indigo-100">Indaco</option>
<option value="bg-gray-500 text-gray-100">Grigio</option>
</select>
</div>
</form>
</div>
</div>
</div>
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
<button type="button" id="save-category-btn" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:ml-3 sm:w-auto sm:text-sm">
Salva
</button>
<button type="button" id="cancel-category-btn" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm">
Annulla
</button>
</div>
</div>
</div>
</div>
<script>
// State management
const state = {
tasks: [],
categories: [
{ id: 1, name: "Lavoro", color: "bg-blue-500 text-blue-100" },
{ id: 2, name: "Personale", color: "bg-green-500 text-green-100" },
{ id: 3, name: "Studio", color: "bg-purple-500 text-purple-100" }
],
currentView: 'all',
selectedTask: null,
currentMonth: new Date().getMonth(),
currentYear: new Date().getFullYear(),
darkMode: false
};
// DOM Elements
const elements = {
// Sidebar
sidebarToggle: document.getElementById('sidebar-toggle'),
// Views
currentView: document.getElementById('current-view'),
allTasksBtn: document.getElementById('all-tasks-btn'),
todayTasksBtn: document.getElementById('today-tasks-btn'),
upcomingTasksBtn: document.getElementById('upcoming-tasks-btn'),
importantTasksBtn: document.getElementById('important-tasks-btn'),
completedTasksBtn: document.getElementById('completed-tasks-btn'),
// Mobile views
mobileAllTasks: document.getElementById('mobile-all-tasks'),
mobileTodayTasks: document.getElementById('mobile-today-tasks'),
mobileUpcomingTasks: document.getElementById('mobile-upcoming-tasks'),
mobileImportantTasks: document.getElementById('mobile-important-tasks'),
mobileAddTask: document.getElementById('mobile-add-task'),
// Task list
taskListContainer: document.getElementById('task-list-container'),
noTasksMessage: document.getElementById('no-tasks-message'),
taskList: document.getElementById('task-list'),
addFirstTaskBtn: document.getElementById('add-first-task-btn'),
// Task details
taskDetails: document.getElementById('task-details'),
noTaskSelected: document.getElementById('no-task-selected'),
taskDetailTitle: document.getElementById('task-detail-title'),
taskDetailDate: document.getElementById('task-detail-date'),
taskDetailCategory: document.getElementById('task-detail-category'),
taskDetailDescription: document.getElementById('task-detail-description'),
editTaskBtn: document.getElementById('edit-task-btn'),
deleteTaskBtn: document.getElementById('delete-task-btn'),
// Calendar
calendar: document.getElementById('calendar'),
currentMonthDisplay: document.getElementById('current-month'),
prevMonth: document.getElementById('prev-month'),
nextMonth: document.getElementById('next-month'),
// Search
searchBtn: document.getElementById('search-btn'),
searchContainer: document.getElementById('search-container'),
searchInput: document.getElementById('search-input'),
// Add task
addTaskBtn: document.getElementById('add-task-btn'),
// Task modal
taskModal: document.getElementById('task-modal'),
modalTitle: document.getElementById('modal-title'),
taskForm: document.getElementById('task-form'),
taskId: document.getElementById('task-id'),
taskTitle: document.getElementById('task-title'),
taskDescription: document.getElementById('task-description'),
taskDate: document.getElementById('task-date'),
taskTime: document.getElementById('task-time'),
taskCategory: document.getElementById('task-category'),
taskImportant: document.getElementById('task-important'),
newCategoryBtn: document.getElementById('new-category-btn'),
saveTaskBtn: document.getElementById('save-task-btn'),
cancelTaskBtn: document.getElementById('cancel-task-btn'),
// Categories
categoriesList: document.getElementById('categories-list'),
addCategoryBtn: document.getElementById('add-category-btn'),
// Category modal
categoryModal: document.getElementById('category-modal'),
categoryForm: document.getElementById('category-form'),
categoryName: document.getElementById('category-name'),
categoryColor: document.getElementById('category-color'),
saveCategoryBtn: document.getElementById('save-category-btn'),
cancelCategoryBtn: document.getElementById('cancel-category-btn'),
// Dark mode
darkModeToggle: document.getElementById('dark-mode-toggle')
};
// Initialize the app
function init() {
// Load sample tasks if none exist
if (state.tasks.length === 0) {
loadSampleTasks();
}
// Set up event listeners
setupEventListeners();
// Render initial views
renderCategories();
renderTaskCategories();
renderCalendar();
renderTasks();
// Set current date in date picker
const today = new Date();
const formattedDate = today.toISOString().split('T')[0];
elements.taskDate.value = formattedDate;
// Check for dark mode preference
checkDarkModePreference();
}
// Load sample tasks
function loadSampleTasks() {
const sampleTasks = [
{
id: 1,
title: "Completare il progetto TaskFlow",
description: "Implementare tutte le funzionalità principali del task manager",
date: "2023-09-15",
time: "15:00",
category: 1,
important: true,
completed: false
},
{
id: 2,
title: "Fare la spesa settimanale",
description: "Acquistare frutta, verdura e altri generi alimentari",
date: "2023-09-10",
time: "10:00",
category: 2,
important: false,
completed: false
},
{
id: 3,
title: "Studiare per l'esame di JavaScript",
description: "Rivedere i concetti avanzati di JavaScript e fare esercizi pratici",
date: "2023-09-20",
time: "14:00",
category: 3,
important: true,
completed: false
},
{
id: 4,
title: "Incontrare il team di sviluppo",
description: "Discutere i progressi del progetto e pianificare le prossime attività",
date: "2023-09-12",
time: "09:30",
category: 1,
important: true,
completed: false
},
{
id: 5,
title: "Andare in palestra",
description: "Allenamento completo di 1 ora",
date: "2023-09-08",
time: "18:00",
category: 2,
important: false,
completed: true
}
];
state.tasks = sampleTasks;
}
// Set up event listeners
function setupEventListeners() {
// Sidebar toggle
elements.sidebarToggle.addEventListener('click', toggleSidebar);
// View buttons
elements.allTasksBtn.addEventListener('click', () => switchView('all'));
elements.todayTasksBtn.addEventListener('click', () => switchView('today'));
elements.upcomingTasksBtn.addEventListener('click', () => switchView('upcoming'));
elements.importantTasksBtn.addEventListener('click', () => switchView('important'));
elements.completedTasksBtn.addEventListener('click', () => switchView('completed'));
// Mobile view buttons
elements.mobileAllTasks.addEventListener('click', () => switchView('all'));
elements.mobileTodayTasks.addEventListener('click', () => switchView('today'));
elements.mobileUpcomingTasks.addEventListener('click', () => switchView('upcoming'));
elements.mobileImportantTasks.addEventListener('click', () => switchView('important'));
elements.mobileAddTask.addEventListener('click', openAddTaskModal);
// Add task buttons
elements.addTaskBtn.addEventListener('click', openAddTaskModal);
elements.addFirstTaskBtn.addEventListener('click', openAddTaskModal);
// Task modal
elements.saveTaskBtn.addEventListener('click', saveTask);
elements.cancelTaskBtn.addEventListener('click', closeTaskModal);
elements.newCategoryBtn.addEventListener('click', openAddCategoryModal);
// Category modal
elements.addCategoryBtn.addEventListener('click', openAddCategoryModal);
elements.saveCategoryBtn.addEventListener('click', saveCategory);
elements.cancelCategoryBtn.addEventListener('click', closeCategoryModal);
// Search
elements.searchBtn.addEventListener('click', toggleSearch);
elements.searchInput.addEventListener('input', handleSearch);
// Calendar navigation
elements.prevMonth.addEventListener('click', goToPreviousMonth);
elements.nextMonth.addEventListener('click', goToNextMonth);
// Dark mode toggle
elements.darkModeToggle.addEventListener('change', toggleDarkMode);
}
// Toggle sidebar on mobile
function toggleSidebar() {
const sidebar = document.querySelector('.md\\:hidden');
if (sidebar) {
sidebar.classList.toggle('hidden');
}
}
// Switch between views
function switchView(view) {
state.currentView = view;
// Update active button in sidebar
updateActiveViewButton();
// Update current view title
updateViewTitle();
// Render tasks for the selected view
renderTasks();
// Close search if open
elements.searchContainer.classList.add('hidden');
}
// Update active view button in sidebar
function updateActiveViewButton() {
// Reset all buttons
const viewButtons = [
elements.allTasksBtn,
elements.todayTasksBtn,
elements.upcomingTasksBtn,
elements.importantTasksBtn,
elements.completedTasksBtn
];
viewButtons.forEach(button => {
button.classList.remove('bg-indigo-800', 'text-white');
button.classList.add('text-indigo-100', 'hover:text-white', 'hover:bg-indigo-600');
});
// Set active button
let activeButton;
switch (state.currentView) {
case 'all':
activeButton = elements.allTasksBtn;
break;
case 'today':
activeButton = elements.todayTasksBtn;
break;
case 'upcoming':
activeButton = elements.upcomingTasksBtn;
break;
case 'important':
activeButton = elements.importantTasksBtn;
break;
case 'completed':
activeButton = elements.completedTasksBtn;
break;
}
if (activeButton) {
activeButton.classList.remove('text-indigo-100', 'hover:text-white', 'hover:bg-indigo-600');
activeButton.classList.add('bg-indigo-800', 'text-white');
}
// Update mobile buttons
const mobileButtons = [
elements.mobileAllTasks,
elements.mobileTodayTasks,
elements.mobileUpcomingTasks,
elements.mobileImportantTasks
];
mobileButtons.forEach(button => {
button.classList.remove('text-indigo-600');
button.classList.add('text-gray-500');
});
let mobileActiveButton;
switch (state.currentView) {
case 'all':
mobileActiveButton = elements.mobileAllTasks;
break;
case 'today':
mobileActiveButton = elements.mobileTodayTasks;
break;
case 'upcoming':
mobileActiveButton = elements.mobileUpcomingTasks;
break;
case 'important':
mobileActiveButton = elements.mobileImportantTasks;
break;
}
if (mobileActiveButton) {
mobileActiveButton.classList.remove('text-gray-500');
mobileActiveButton.classList.add('text-indigo-600');
}
}
// Update view title
function updateViewTitle() {
let title;
switch (state.currentView) {
case 'all':
title = 'Tutte le attività';
break;
case 'today':
title = 'Attività di oggi';
break;
case 'upcoming':
title = 'Prossime attività';
break;
case 'important':
title = 'Attività importanti';
break;
case 'completed':
title = 'Attività completate';
break;
default:
title = 'Tutte le attività';
}
elements.currentView.textContent = title;
}
// Render tasks based on current view
function renderTasks() {
let filteredTasks = [];
switch (state.currentView) {
case 'all':
filteredTasks = state.tasks.filter(task => !task.completed);
break;
case 'today':
const today = new Date().toISOString().split('T')[0];
filteredTasks = state.tasks.filter(task => task.date === today && !task.completed);
break;
case 'upcoming':
const todayDate = new Date();
todayDate.setHours(0, 0, 0, 0);
filteredTasks = state.tasks.filter(task => {
const taskDate = new Date(task.date);
return taskDate >= todayDate && !task.completed;
});
break;
case 'important':
filteredTasks = state.tasks.filter(task => task.important && !task.completed);
break;
case 'completed':
filteredTasks = state.tasks.filter(task => task.completed);
break;
}
// Sort tasks by date and time
filteredTasks.sort((a, b) => {
const dateA = new Date(`${a.date}T${a.time || '00:00'}`);
const dateB = new Date(`${b.date}T${b.time || '00:00'}`);
return dateA - dateB;
});
// Clear task list
elements.taskList.innerHTML = '';
if (filteredTasks.length === 0) {
elements.noTasksMessage.classList.remove('hidden');
elements.taskListContainer.classList.add('flex', 'items-center', 'justify-center');
} else {
elements.noTasksMessage.classList.add('hidden');
elements.taskListContainer.classList.remove('flex', 'items-center', 'justify-center');
// Add tasks to the list
filteredTasks.forEach(task => {
const taskElement = createTaskElement(task);
elements.taskList.appendChild(taskElement);
});
}
}
// Create task element
function createTaskElement(task) {
const taskElement = document.createElement('div');
taskElement.className = 'task-item bg-white p-4 rounded-lg shadow hover:shadow-md transition-shadow cursor-pointer';
taskElement.dataset.taskId = task.id;
// Format date
const taskDate = new Date(task.date);
const formattedDate = taskDate.toLocaleDateString('it-IT', {
weekday: 'short',
day: 'numeric',
month: 'short'
});
// Format time if exists
let timeHtml = '';
if (task.time) {
timeHtml = `<span class="text-gray-500 ml-2">${task.time}</span>`;
}
// Get category
let categoryHtml = '';
if (task.category) {
const category = state.categories.find(cat => cat.id === task.category);
if (category) {
categoryHtml = `<span class="inline-block px-2 py-1 text-xs font-semibold rounded-full ${category.color}">${category.name}</span>`;
}
}
// Important icon
const importantIcon = task.important ? '<i class="fas fa-star text-yellow-500 mr-2"></i>' : '';
taskElement.innerHTML = `
<div class="flex items-start justify-between">
<div class="flex items-center">
<button class="complete-task-btn mr-3 p-1 rounded-full border border-gray-300 hover:bg-gray-100">
<i class="fas fa-check text-gray-400"></i>
</button>
<div>
<h3 class="font-medium text-gray-900">${importantIcon}${task.title}</h3>
<div class="flex items-center mt-1 text-sm text-gray-500">
<i class="far fa-calendar-alt mr-1"></i>
<span>${formattedDate}</span>
${timeHtml}
</div>
</div>
</div>
<div>
${categoryHtml}
</div>
</div>
`;
// Add event listeners
const completeBtn = taskElement.querySelector('.complete-task-btn');
completeBtn.addEventListener('click', (e) => {
e.stopPropagation();
toggleTaskCompletion(task.id);
});
taskElement.addEventListener('click', () => showTaskDetails(task.id));
return taskElement;
}
// Toggle task completion
function toggleTaskCompletion(taskId) {
const taskIndex = state.tasks.findIndex(task => task.id === taskId);
if (taskIndex !== -1) {
state.tasks[taskIndex].completed = !state.tasks[taskIndex].completed;
renderTasks();
// If viewing completed tasks, keep the task in the list
if (state.currentView === 'completed' && state.tasks[taskIndex].completed) {
// Do nothing, task will stay in the list
} else {
// Otherwise, remove the task from the current view
const taskElement = document.querySelector(`.task-item[data-task-id="${taskId}"]`);
if (taskElement) {
taskElement.remove();
// Show no tasks message if list is empty
if (elements.taskList.children.length === 0) {
elements.noTasksMessage.classList.remove('hidden');
elements.taskListContainer.classList.add('flex', 'items-center', 'justify-center');
}
}
}
// Update task details if the task is selected
if (state.selectedTask && state.selectedTask.id === taskId) {
showTaskDetails(taskId);
}
}
}
// Show task details
function showTaskDetails(taskId) {
const task = state.tasks.find(task => task.id === taskId);
if (task) {
state.selectedTask = task;
// Format date
const taskDate = new Date(task.date);
const formattedDate = taskDate.toLocaleDateString('it-IT', {
weekday: 'long',
day: 'numeric',
month: 'long',
year: 'numeric'
});
// Format time if exists
let timeHtml = '';
if (task.time) {
timeHtml = ` alle ${task.time}`;
}
// Get category
let categoryHtml = '';
if (task.category) {
const category = state.categories.find(cat => cat.id === task.category);
if (category) {
categoryHtml = `<span class="${category.color}">${category.name}</span>`;
}
}
// Important icon
const importantIcon = task.important ? '<i class="fas fa-star text-yellow-500 mr-2"></i>' : '';
// Update task details
elements.taskDetailTitle.innerHTML = `${importantIcon}${task.title}`;
elements.taskDetailDate.textContent = `${formattedDate}${timeHtml}`;
if (categoryHtml) {
elements.taskDetailCategory.innerHTML = categoryHtml;
elements.taskDetailCategory.classList.remove('hidden');
} else {
elements.taskDetailCategory.classList.add('hidden');
}
elements.taskDetailDescription.textContent = task.description || 'Nessuna descrizione fornita';
// Show details and hide placeholder
elements.taskDetails.classList.remove('hidden');
elements.noTaskSelected.classList.add('hidden');
}
}
// Open add task modal
function openAddTaskModal() {
elements.modalTitle.textContent = 'Nuova attività';
elements.taskId.value = '';
elements.taskTitle.value = '';
elements.taskDescription.value = '';
// Set today's date as default
const today = new Date();
const formattedDate = today.toISOString().split('T')[0];
elements.taskDate.value = formattedDate;
elements.taskTime.value = '';
elements.taskCategory.value = '';
elements.taskImportant.checked = false;
elements.taskModal.classList.remove('hidden');
}
// Open edit task modal
function openEditTaskModal(taskId) {
const task = state.tasks.find(task => task.id === taskId);
if (task) {
elements.modalTitle.textContent = 'Modifica attività';
elements.taskId.value = task.id;
elements.taskTitle.value = task.title;
elements.taskDescription.value = task.description || '';
elements.taskDate.value = task.date;
elements.taskTime.value = task.time || '';
elements.taskCategory.value = task.category || '';
elements.taskImportant.checked = task.important || false;
elements.taskModal.classList.remove('hidden');
}
}
// Close task modal
function closeTaskModal() {
elements.taskModal.classList.add('hidden');
}
// Save task
function saveTask() {
const title = elements.taskTitle.value.trim();
if (!title) {
alert('Il titolo è obbligatorio');
return;
}
const taskData = {
id: elements.taskId.value ? parseInt(elements.taskId.value) : Date.now(),
title: title,
description: elements.taskDescription.value.trim(),
date: elements.taskDate.value,
time: elements.taskTime.value || null,
category: elements.taskCategory.value ? parseInt(elements.taskCategory.value) : null,
important: elements.taskImportant.checked,
completed: false
};
if (elements.taskId.value) {
// Update existing task
const taskIndex = state.tasks.findIndex(task => task.id === taskData.id);
if (taskIndex !== -1) {
state.tasks[taskIndex] = {
...state.tasks[taskIndex],
...taskData
};
}
} else {
// Add new task
state.tasks.push(taskData);
}
closeTaskModal();
renderTasks();
// If we're editing, update the task details
if (state.selectedTask && state.selectedTask.id === taskData.id) {
showTaskDetails(taskData.id);
}
}
// Delete task
function deleteTask() {
if (state.selectedTask) {
if (confirm('Sei sicuro di voler eliminare questa attività?')) {
const taskId = state.selectedTask.id;
state.tasks = state.tasks.filter(task => task.id !== taskId);
// Remove task from list
const taskElement = document.querySelector(`.task-item[data-task-id="${taskId}"]`);
if (taskElement) {
taskElement.remove();
}
// Hide task details
elements.taskDetails.classList.add('hidden');
elements.noTaskSelected.classList.remove('hidden');
state.selectedTask = null;
// Show no tasks message if list is empty
if (elements.taskList.children.length === 0) {
elements.noTasksMessage.classList.remove('hidden');
elements.taskListContainer.classList.add('flex', 'items-center', 'justify-center');
}
renderTasks();
}
}
}
// Render categories in sidebar and task form
function renderCategories() {
// Clear categories list
elements.categoriesList.innerHTML = '';
// Add categories to sidebar
state.categories.forEach(category => {
const categoryElement = document.createElement('button');
categoryElement.className = 'flex items-center w-full px-4 py-2 text-sm font-medium text-indigo-100 hover:text-white hover:bg-indigo-600 rounded-md group';
categoryElement.dataset.categoryId = category.id;
categoryElement.innerHTML = `
<span class="inline-block w-3 h-3 rounded-full ${category.color.replace('text-', 'bg-').split(' ')[0]} mr-3"></span>
${category.name}
`;
categoryElement.addEventListener('click', () => filterByCategory(category.id));
elements.categoriesList.appendChild(categoryElement);
});
}
// Render task categories in select dropdown
function renderTaskCategories() {
// Clear select
elements.taskCategory.innerHTML = '<option value="">Nessuna categoria</option>';
// Add categories to select
state.categories.forEach(category => {
const option = document.createElement('option');
option.value = category.id;
option.textContent = category.name;
elements.taskCategory.appendChild(option);
});
}
// Filter tasks by category
function filterByCategory(categoryId) {
state.currentView = 'category';
state.selectedCategory = categoryId;
// Update view title
const category = state.categories.find(cat => cat.id === categoryId);
elements.currentView.textContent = `Categoria: ${category.name}`;
// Render tasks
renderTasksByCategory(categoryId);
// Update active buttons
updateActiveViewButton();
}
// Render tasks by category
function renderTasksByCategory(categoryId) {
const filteredTasks = state.tasks.filter(task =>
task.category === categoryId && !task.completed
);
// Clear task list
elements.taskList.innerHTML = '';
if (filteredTasks.length === 0) {
elements.noTasksMessage.classList.remove('hidden');
elements.taskListContainer.classList.add('flex', 'items-center', 'justify-center');
} else {
elements.noTasksMessage.classList.add('hidden');
elements.taskListContainer.classList.remove('flex', 'items-center', 'justify-center');
// Add tasks to the list
filteredTasks.forEach(task => {
const taskElement = createTaskElement(task);
elements.taskList.appendChild(taskElement);
});
}
}
// Open add category modal
function openAddCategoryModal() {
elements.categoryName.value = '';
elements.categoryColor.value = 'bg-blue-500 text-blue-100';
elements.categoryModal.classList.remove('hidden');
// Close task modal if open
closeTaskModal();
}
// Close category modal
function closeCategoryModal() {
elements.categoryModal.classList.add('hidden');
}
// Save category
function saveCategory() {
const name = elements.categoryName.value.trim();
if (!name) {
alert('Il nome della categoria è obbligatorio');
return;
}
const categoryData = {
id: Date.now(),
name: name,
color: elements.categoryColor.value
};
state.categories.push(categoryData);
closeCategoryModal();
renderCategories();
renderTaskCategories();
}
// Toggle search
function toggleSearch() {
elements.searchContainer.classList.toggle('hidden');
if (!elements.searchContainer.classList.contains('hidden')) {
elements.searchInput.focus();
}
}
// Handle search
function handleSearch() {
const query = elements.searchInput.value.toLowerCase();
if (query === '') {
renderTasks();
return;
}
const filteredTasks = state.tasks.filter(task =>
task.title.toLowerCase().includes(query) ||
(task.description && task.description.toLowerCase().includes(query))
);
// Clear task list
elements.taskList.innerHTML = '';
if (filteredTasks.length === 0) {
elements.noTasksMessage.classList.remove('hidden');
elements.taskListContainer.classList.add('flex', 'items-center', 'justify-center');
} else {
elements.noTasksMessage.classList.add('hidden');
elements.taskListContainer.classList.remove('flex', 'items-center', 'justify-center');
// Add tasks to the list
filteredTasks.forEach(task => {
const taskElement = createTaskElement(task);
elements.taskList.appendChild(taskElement);
});
}
}
// Render calendar
function renderCalendar() {
// Clear calendar
elements.calendar.innerHTML = '';
// Set month and year in header
const monthNames = ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'];
elements.currentMonthDisplay.textContent = `${monthNames[state.currentMonth]} ${state.currentYear}`;
// Get first day of month and total days in month
const firstDay = new Date(state.currentYear, state.currentMonth, 1).getDay();
const daysInMonth = new Date(state.currentYear, state.currentMonth + 1, 0).getDate();
// Adjust first day (Sunday = 0 in JavaScript, but we want Monday as first day)
const adjustedFirstDay = firstDay === 0 ? 6 : firstDay - 1;
// Add day names
const dayNames = ['Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab', 'Dom'];
dayNames.forEach(day => {
const dayElement = document.createElement('div');
dayElement.className = 'text-center text-sm font-medium text-gray-500 py-1';
dayElement.textContent = day;
elements.calendar.appendChild(dayElement);
});
// Add empty cells for days before the first day of the month
for (let i = 0; i < adjustedFirstDay; i++) {
const emptyElement = document.createElement('div');
emptyElement.className = 'h-8';
elements.calendar.appendChild(emptyElement);
}
// Add day cells
const today = new Date();
const currentDay = today.getDate();
const currentMonth = today.getMonth();
const currentYear = today.getFullYear();
for (let day = 1; day <= daysInMonth; day++) {
const dayElement = document.createElement('div');
dayElement.className = 'text-center py-1 cursor-pointer hover:bg-gray-100';
// Highlight current day
if (day === currentDay && state.currentMonth === currentMonth && state.currentYear === currentYear) {
dayElement.classList.add('current-day');
}
dayElement.textContent = day;
// Add event listener
dayElement.addEventListener('click', () => {
filterByDate(day);
});
elements.calendar.appendChild(dayElement);
}
// Add tasks indicators
state.tasks.forEach(task => {
if (!task.completed) {
const taskDate = new Date(task.date);
if (taskDate.getMonth() === state.currentMonth && taskDate.getFullYear() === state.currentYear) {
const day = taskDate.getDate();
const dayElement = elements.calendar.children[adjustedFirstDay + day + 6]; // +6 for day names
if (dayElement) {
const indicator = document.createElement('div');
indicator.className = 'w-1 h-1 mx-auto rounded-full bg-indigo-500';
dayElement.appendChild(indicator);
}
}
}
});
}
// Filter by date
function filterByDate(day) {
const date = new Date(state.currentYear, state.currentMonth, day);
const formattedDate = date.toISOString().split('T')[0];
state.currentView = 'date';
elements.currentView.textContent = `Attività per ${day}/${state.currentMonth + 1}/${state.currentYear}`;
const filteredTasks = state.tasks.filter(task =>
task.date === formattedDate && !task.completed
);
// Clear task list
elements.taskList.innerHTML = '';
if (filteredTasks.length === 0) {
elements.noTasksMessage.classList.remove('hidden');
elements.taskListContainer.classList.add('flex', 'items-center', 'justify-center');
} else {
elements.noTasksMessage.classList.add('hidden');
elements.taskListContainer.classList.remove('flex', 'items-center', 'justify-center');
// Add tasks to the list
filteredTasks.forEach(task => {
const taskElement = createTaskElement(task);
elements.taskList.appendChild(taskElement);
});
}
// Update active buttons
updateActiveViewButton();
}
// Go to previous month
function goToPreviousMonth() {
if (state.currentMonth === 0) {
state.currentMonth = 11;
state.currentYear--;
} else {
state.currentMonth--;
}
renderCalendar();
}
// Go to next month
function goToNextMonth() {
if (state.currentMonth === 11) {
state.currentMonth = 0;
state.currentYear++;
} else {
state.currentMonth++;
}
renderCalendar();
}
// Check dark mode preference
function checkDarkModePreference() {
const darkModePref = localStorage.getItem('darkMode') === 'true';
if (darkModePref) {
enableDarkMode();
} else {
disableDarkMode();
}
}
// Toggle dark mode
function toggleDarkMode() {
if (state.darkMode) {
disableDarkMode();
} else {
enableDarkMode();
}
}
// Enable dark mode
function enableDarkMode() {
document.documentElement.classList.add('dark');
localStorage.setItem('darkMode', 'true');
state.darkMode = true;
elements.darkModeToggle.checked = true;
}
// Disable dark mode
function disableDarkMode() {
document.documentElement.classList.remove('dark');
localStorage.setItem('darkMode', 'false');
state.darkMode = false;
elements.darkModeToggle.checked = false;
}
// Initialize the app when DOM is loaded
document.addEventListener('DOMContentLoaded', init);
// Add event listeners for task details buttons
document.addEventListener('click', function(e) {
if (e.target.id === 'edit-task-btn') {
openEditTaskModal(state.selectedTask.id);
} else if (e.target.id === 'delete-task-btn') {
deleteTask();
}
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=federi/taskflow" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>