| <!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> |
| |
| ::-webkit-scrollbar { |
| width: 8px; |
| } |
| ::-webkit-scrollbar-track { |
| background: #f1f1f1; |
| } |
| ::-webkit-scrollbar-thumb { |
| background: #888; |
| border-radius: 4px; |
| } |
| ::-webkit-scrollbar-thumb:hover { |
| background: #555; |
| } |
| |
| |
| @keyframes fadeIn { |
| from { opacity: 0; transform: translateY(10px); } |
| to { opacity: 1; transform: translateY(0); } |
| } |
| |
| .task-item { |
| animation: fadeIn 0.3s ease-out forwards; |
| } |
| |
| |
| .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"> |
| |
| <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"> |
| |
| </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> |
| |
| |
| <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> |
| |
| |
| <div class="flex flex-col flex-1 overflow-hidden"> |
| |
| <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> |
| |
| |
| <div class="flex flex-1 overflow-hidden"> |
| |
| <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"> |
| |
| </div> |
| </div> |
| </div> |
| |
| |
| <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"> |
| |
| </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> |
| |
| |
| <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">​</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> |
| |
| |
| <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">​</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> |
| |
| 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 |
| }; |
| |
| |
| const elements = { |
| |
| sidebarToggle: document.getElementById('sidebar-toggle'), |
| |
| |
| 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'), |
| |
| |
| 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'), |
| |
| |
| taskListContainer: document.getElementById('task-list-container'), |
| noTasksMessage: document.getElementById('no-tasks-message'), |
| taskList: document.getElementById('task-list'), |
| addFirstTaskBtn: document.getElementById('add-first-task-btn'), |
| |
| |
| 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: document.getElementById('calendar'), |
| currentMonthDisplay: document.getElementById('current-month'), |
| prevMonth: document.getElementById('prev-month'), |
| nextMonth: document.getElementById('next-month'), |
| |
| |
| searchBtn: document.getElementById('search-btn'), |
| searchContainer: document.getElementById('search-container'), |
| searchInput: document.getElementById('search-input'), |
| |
| |
| addTaskBtn: document.getElementById('add-task-btn'), |
| |
| |
| 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'), |
| |
| |
| categoriesList: document.getElementById('categories-list'), |
| addCategoryBtn: document.getElementById('add-category-btn'), |
| |
| |
| 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'), |
| |
| |
| darkModeToggle: document.getElementById('dark-mode-toggle') |
| }; |
| |
| |
| function init() { |
| |
| if (state.tasks.length === 0) { |
| loadSampleTasks(); |
| } |
| |
| |
| setupEventListeners(); |
| |
| |
| renderCategories(); |
| renderTaskCategories(); |
| renderCalendar(); |
| renderTasks(); |
| |
| |
| const today = new Date(); |
| const formattedDate = today.toISOString().split('T')[0]; |
| elements.taskDate.value = formattedDate; |
| |
| |
| checkDarkModePreference(); |
| } |
| |
| |
| 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; |
| } |
| |
| |
| function setupEventListeners() { |
| |
| elements.sidebarToggle.addEventListener('click', toggleSidebar); |
| |
| |
| 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')); |
| |
| |
| 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); |
| |
| |
| elements.addTaskBtn.addEventListener('click', openAddTaskModal); |
| elements.addFirstTaskBtn.addEventListener('click', openAddTaskModal); |
| |
| |
| elements.saveTaskBtn.addEventListener('click', saveTask); |
| elements.cancelTaskBtn.addEventListener('click', closeTaskModal); |
| elements.newCategoryBtn.addEventListener('click', openAddCategoryModal); |
| |
| |
| elements.addCategoryBtn.addEventListener('click', openAddCategoryModal); |
| elements.saveCategoryBtn.addEventListener('click', saveCategory); |
| elements.cancelCategoryBtn.addEventListener('click', closeCategoryModal); |
| |
| |
| elements.searchBtn.addEventListener('click', toggleSearch); |
| elements.searchInput.addEventListener('input', handleSearch); |
| |
| |
| elements.prevMonth.addEventListener('click', goToPreviousMonth); |
| elements.nextMonth.addEventListener('click', goToNextMonth); |
| |
| |
| elements.darkModeToggle.addEventListener('change', toggleDarkMode); |
| } |
| |
| |
| function toggleSidebar() { |
| const sidebar = document.querySelector('.md\\:hidden'); |
| if (sidebar) { |
| sidebar.classList.toggle('hidden'); |
| } |
| } |
| |
| |
| function switchView(view) { |
| state.currentView = view; |
| |
| |
| updateActiveViewButton(); |
| |
| |
| updateViewTitle(); |
| |
| |
| renderTasks(); |
| |
| |
| elements.searchContainer.classList.add('hidden'); |
| } |
| |
| |
| function updateActiveViewButton() { |
| |
| 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'); |
| }); |
| |
| |
| 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'); |
| } |
| |
| |
| 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'); |
| } |
| } |
| |
| |
| 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; |
| } |
| |
| |
| 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; |
| } |
| |
| |
| 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; |
| }); |
| |
| |
| 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'); |
| |
| |
| filteredTasks.forEach(task => { |
| const taskElement = createTaskElement(task); |
| elements.taskList.appendChild(taskElement); |
| }); |
| } |
| } |
| |
| |
| 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; |
| |
| |
| const taskDate = new Date(task.date); |
| const formattedDate = taskDate.toLocaleDateString('it-IT', { |
| weekday: 'short', |
| day: 'numeric', |
| month: 'short' |
| }); |
| |
| |
| let timeHtml = ''; |
| if (task.time) { |
| timeHtml = `<span class="text-gray-500 ml-2">${task.time}</span>`; |
| } |
| |
| |
| 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>`; |
| } |
| } |
| |
| |
| 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> |
| `; |
| |
| |
| const completeBtn = taskElement.querySelector('.complete-task-btn'); |
| completeBtn.addEventListener('click', (e) => { |
| e.stopPropagation(); |
| toggleTaskCompletion(task.id); |
| }); |
| |
| taskElement.addEventListener('click', () => showTaskDetails(task.id)); |
| |
| return taskElement; |
| } |
| |
| |
| 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 (state.currentView === 'completed' && state.tasks[taskIndex].completed) { |
| |
| } else { |
| |
| const taskElement = document.querySelector(`.task-item[data-task-id="${taskId}"]`); |
| if (taskElement) { |
| taskElement.remove(); |
| |
| |
| if (elements.taskList.children.length === 0) { |
| elements.noTasksMessage.classList.remove('hidden'); |
| elements.taskListContainer.classList.add('flex', 'items-center', 'justify-center'); |
| } |
| } |
| } |
| |
| |
| if (state.selectedTask && state.selectedTask.id === taskId) { |
| showTaskDetails(taskId); |
| } |
| } |
| } |
| |
| |
| function showTaskDetails(taskId) { |
| const task = state.tasks.find(task => task.id === taskId); |
| if (task) { |
| state.selectedTask = task; |
| |
| |
| const taskDate = new Date(task.date); |
| const formattedDate = taskDate.toLocaleDateString('it-IT', { |
| weekday: 'long', |
| day: 'numeric', |
| month: 'long', |
| year: 'numeric' |
| }); |
| |
| |
| let timeHtml = ''; |
| if (task.time) { |
| timeHtml = ` alle ${task.time}`; |
| } |
| |
| |
| 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>`; |
| } |
| } |
| |
| |
| const importantIcon = task.important ? '<i class="fas fa-star text-yellow-500 mr-2"></i>' : ''; |
| |
| |
| 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'; |
| |
| |
| elements.taskDetails.classList.remove('hidden'); |
| elements.noTaskSelected.classList.add('hidden'); |
| } |
| } |
| |
| |
| function openAddTaskModal() { |
| elements.modalTitle.textContent = 'Nuova attività'; |
| elements.taskId.value = ''; |
| elements.taskTitle.value = ''; |
| elements.taskDescription.value = ''; |
| |
| |
| 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'); |
| } |
| |
| |
| 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'); |
| } |
| } |
| |
| |
| function closeTaskModal() { |
| elements.taskModal.classList.add('hidden'); |
| } |
| |
| |
| 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) { |
| |
| const taskIndex = state.tasks.findIndex(task => task.id === taskData.id); |
| if (taskIndex !== -1) { |
| state.tasks[taskIndex] = { |
| ...state.tasks[taskIndex], |
| ...taskData |
| }; |
| } |
| } else { |
| |
| state.tasks.push(taskData); |
| } |
| |
| closeTaskModal(); |
| renderTasks(); |
| |
| |
| if (state.selectedTask && state.selectedTask.id === taskData.id) { |
| showTaskDetails(taskData.id); |
| } |
| } |
| |
| |
| 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); |
| |
| |
| const taskElement = document.querySelector(`.task-item[data-task-id="${taskId}"]`); |
| if (taskElement) { |
| taskElement.remove(); |
| } |
| |
| |
| elements.taskDetails.classList.add('hidden'); |
| elements.noTaskSelected.classList.remove('hidden'); |
| state.selectedTask = null; |
| |
| |
| if (elements.taskList.children.length === 0) { |
| elements.noTasksMessage.classList.remove('hidden'); |
| elements.taskListContainer.classList.add('flex', 'items-center', 'justify-center'); |
| } |
| |
| renderTasks(); |
| } |
| } |
| } |
| |
| |
| function renderCategories() { |
| |
| elements.categoriesList.innerHTML = ''; |
| |
| |
| 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); |
| }); |
| } |
| |
| |
| function renderTaskCategories() { |
| |
| elements.taskCategory.innerHTML = '<option value="">Nessuna categoria</option>'; |
| |
| |
| state.categories.forEach(category => { |
| const option = document.createElement('option'); |
| option.value = category.id; |
| option.textContent = category.name; |
| elements.taskCategory.appendChild(option); |
| }); |
| } |
| |
| |
| function filterByCategory(categoryId) { |
| state.currentView = 'category'; |
| state.selectedCategory = categoryId; |
| |
| |
| const category = state.categories.find(cat => cat.id === categoryId); |
| elements.currentView.textContent = `Categoria: ${category.name}`; |
| |
| |
| renderTasksByCategory(categoryId); |
| |
| |
| updateActiveViewButton(); |
| } |
| |
| |
| function renderTasksByCategory(categoryId) { |
| const filteredTasks = state.tasks.filter(task => |
| task.category === categoryId && !task.completed |
| ); |
| |
| |
| 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'); |
| |
| |
| filteredTasks.forEach(task => { |
| const taskElement = createTaskElement(task); |
| elements.taskList.appendChild(taskElement); |
| }); |
| } |
| } |
| |
| |
| function openAddCategoryModal() { |
| elements.categoryName.value = ''; |
| elements.categoryColor.value = 'bg-blue-500 text-blue-100'; |
| elements.categoryModal.classList.remove('hidden'); |
| |
| |
| closeTaskModal(); |
| } |
| |
| |
| function closeCategoryModal() { |
| elements.categoryModal.classList.add('hidden'); |
| } |
| |
| |
| 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(); |
| } |
| |
| |
| function toggleSearch() { |
| elements.searchContainer.classList.toggle('hidden'); |
| if (!elements.searchContainer.classList.contains('hidden')) { |
| elements.searchInput.focus(); |
| } |
| } |
| |
| |
| 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)) |
| ); |
| |
| |
| 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'); |
| |
| |
| filteredTasks.forEach(task => { |
| const taskElement = createTaskElement(task); |
| elements.taskList.appendChild(taskElement); |
| }); |
| } |
| } |
| |
| |
| function renderCalendar() { |
| |
| elements.calendar.innerHTML = ''; |
| |
| |
| const monthNames = ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre']; |
| elements.currentMonthDisplay.textContent = `${monthNames[state.currentMonth]} ${state.currentYear}`; |
| |
| |
| const firstDay = new Date(state.currentYear, state.currentMonth, 1).getDay(); |
| const daysInMonth = new Date(state.currentYear, state.currentMonth + 1, 0).getDate(); |
| |
| |
| const adjustedFirstDay = firstDay === 0 ? 6 : firstDay - 1; |
| |
| |
| 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); |
| }); |
| |
| |
| for (let i = 0; i < adjustedFirstDay; i++) { |
| const emptyElement = document.createElement('div'); |
| emptyElement.className = 'h-8'; |
| elements.calendar.appendChild(emptyElement); |
| } |
| |
| |
| 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'; |
| |
| |
| if (day === currentDay && state.currentMonth === currentMonth && state.currentYear === currentYear) { |
| dayElement.classList.add('current-day'); |
| } |
| |
| dayElement.textContent = day; |
| |
| |
| dayElement.addEventListener('click', () => { |
| filterByDate(day); |
| }); |
| |
| elements.calendar.appendChild(dayElement); |
| } |
| |
| |
| 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]; |
| |
| if (dayElement) { |
| const indicator = document.createElement('div'); |
| indicator.className = 'w-1 h-1 mx-auto rounded-full bg-indigo-500'; |
| dayElement.appendChild(indicator); |
| } |
| } |
| } |
| }); |
| } |
| |
| |
| 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 |
| ); |
| |
| |
| 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'); |
| |
| |
| filteredTasks.forEach(task => { |
| const taskElement = createTaskElement(task); |
| elements.taskList.appendChild(taskElement); |
| }); |
| } |
| |
| |
| updateActiveViewButton(); |
| } |
| |
| |
| function goToPreviousMonth() { |
| if (state.currentMonth === 0) { |
| state.currentMonth = 11; |
| state.currentYear--; |
| } else { |
| state.currentMonth--; |
| } |
| renderCalendar(); |
| } |
| |
| |
| function goToNextMonth() { |
| if (state.currentMonth === 11) { |
| state.currentMonth = 0; |
| state.currentYear++; |
| } else { |
| state.currentMonth++; |
| } |
| renderCalendar(); |
| } |
| |
| |
| function checkDarkModePreference() { |
| const darkModePref = localStorage.getItem('darkMode') === 'true'; |
| if (darkModePref) { |
| enableDarkMode(); |
| } else { |
| disableDarkMode(); |
| } |
| } |
| |
| |
| function toggleDarkMode() { |
| if (state.darkMode) { |
| disableDarkMode(); |
| } else { |
| enableDarkMode(); |
| } |
| } |
| |
| |
| function enableDarkMode() { |
| document.documentElement.classList.add('dark'); |
| localStorage.setItem('darkMode', 'true'); |
| state.darkMode = true; |
| elements.darkModeToggle.checked = true; |
| } |
| |
| |
| function disableDarkMode() { |
| document.documentElement.classList.remove('dark'); |
| localStorage.setItem('darkMode', 'false'); |
| state.darkMode = false; |
| elements.darkModeToggle.checked = false; |
| } |
| |
| |
| document.addEventListener('DOMContentLoaded', init); |
| |
| |
| 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> |