From 08e06fc21da7aab311697c0e3942be30c20c2a2c Mon Sep 17 00:00:00 2001 From: Greg Date: Wed, 14 May 2025 19:31:21 +0200 Subject: [PATCH] Calendar --- app.js | 5 +- calendar.js | 341 ++++++++++++++++++++++++++++++++++++++++++++++++++++ index.html | 10 ++ modal.js | 2 +- style.css | 65 ++++++++-- 5 files changed, 413 insertions(+), 10 deletions(-) create mode 100644 calendar.js diff --git a/app.js b/app.js index 4d480b2..1b807e1 100644 --- a/app.js +++ b/app.js @@ -121,7 +121,7 @@ const sortableColumns = [ { key: 'creationDate', label: 'Creation Date' }, { key: 'nextDate', label: 'Next Date' }, { key: 'dueDate', label: 'Due Date' }, - { key: 'urgent', label: 'Urgent' }, + { key: 'urgent', label: 'Urgency' }, { key: 'importance', label: 'Importance' }, { key: 'prio', label: 'Prio' } ]; @@ -182,7 +182,7 @@ function renderTodos() { { key: 'nextDate', label: 'Next Date', sortable: true }, { key: 'dueDate', label: 'Due Date', sortable: true }, { key: 'status', label: 'Status' }, - { key: 'urgent', label: 'Urgent', sortable: true }, + { key: 'urgent', label: 'Urgency', sortable: true }, { key: 'importance', label: 'Importance', sortable: true }, { key: 'prio', label: 'Prio', sortable: true }, { key: 'timeEstimation', label: 'Time Est. (min)' }, @@ -341,6 +341,7 @@ function handleCellEdit(e) { } }); } else if (key === 'status') { + // Use native select for status to ensure proper dropdown behavior input = document.createElement('select'); ['','Busy','Done','W4A'].forEach(opt => { const option = document.createElement('option'); diff --git a/calendar.js b/calendar.js new file mode 100644 index 0000000..44b0764 --- /dev/null +++ b/calendar.js @@ -0,0 +1,341 @@ +// Calendar functionality for Todo app +document.addEventListener('DOMContentLoaded', () => { + // Set up event listeners + const calendarBtn = document.getElementById('tab-calendar-btn'); + const calendarModal = document.getElementById('calendar-modal'); + const calendarCloseBtn = document.getElementById('calendar-close-btn'); + + if (calendarBtn) { + calendarBtn.addEventListener('click', showCalendarModal); + } + + if (calendarCloseBtn) { + calendarCloseBtn.addEventListener('click', () => { + calendarModal.style.display = 'none'; + }); + } + + // Close modal when clicking outside of it + window.addEventListener('click', (e) => { + if (e.target === calendarModal) { + calendarModal.style.display = 'none'; + } + }); +}); + +// Function to show the calendar modal +function showCalendarModal() { + const calendarModal = document.getElementById('calendar-modal'); + const calendarContainer = document.getElementById('calendar-container'); + + if (!calendarModal || !calendarContainer) { + console.error('Calendar modal elements not found'); + return; + } + + // Get todos from various sources + let todos = []; + + // First try to get todos from the global window.todos array + if (window.todos && Array.isArray(window.todos)) { + todos = window.todos.slice(); // Make a copy of the array + console.log('Using todos from window.todos:', todos.length); + } + + // If window.todos is empty, try the localStorage + if (!todos.length) { + try { + const STORAGE_KEY = 'todos-v1'; // Match the key used in app.js + const storedTodos = localStorage.getItem(STORAGE_KEY); + if (storedTodos) { + todos = JSON.parse(storedTodos); + console.log('Using todos from localStorage:', todos.length); + } + } catch (e) { + console.error('Error loading todos from localStorage:', e); + } + } + + // If we still don't have todos, try one more approach + if (!todos.length) { + // Force a load from localStorage via the app's loadTodos function if available + if (typeof loadTodos === 'function') { + try { + loadTodos(); + if (window.todos && window.todos.length) { + todos = window.todos.slice(); + console.log('Loaded todos using loadTodos():', todos.length); + } + } catch (e) { + console.error('Error using loadTodos():', e); + } + } + } + + // Log the todos we found + console.log('Found todos for calendar:', todos); + + // Only add sample data for testing if explicitly requested + // We're not adding sample data by default now to prioritize real tasks + + // Generate the calendar HTML + const calendarHTML = generateCalendarHTML(todos); + + // Update the calendar container + calendarContainer.innerHTML = calendarHTML; + + // Show the modal + calendarModal.style.display = 'flex'; +} + +// Function to generate the HTML for the calendar modal content +function generateCalendarHTML(todos) { + // Get the current date + const currentDate = new Date(); + const currentMonth = currentDate.getMonth(); + const currentYear = currentDate.getFullYear(); + + // Ensure todos is an array + todos = todos || []; + + // Debug log the todos + console.log('Generating calendar with todos:', todos.length); + + // Generate the calendar HTML for the modal + return ` +
+

${getMonthName(currentMonth)} ${currentYear}

+
+
+
Mon
+
Tue
+
Wed
+
Thu
+
Fri
+
Sat
+
Sun
+ ${generateCalendarDays(currentMonth, currentYear, todos)} +
+ + + `; +} + +// Helper function to get month name +function getMonthName(month) { + const monthNames = [ + 'January', 'February', 'March', 'April', 'May', 'June', + 'July', 'August', 'September', 'October', 'November', 'December' + ]; + return monthNames[month]; +} + +// Function to generate calendar days +function generateCalendarDays(month, year, todos) { + const today = new Date(); + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + const daysInMonth = lastDay.getDate(); + + // Adjust day of week to start from Monday (0 = Monday, 6 = Sunday) + let startingDayOfWeek = firstDay.getDay() - 1; + if (startingDayOfWeek === -1) startingDayOfWeek = 6; // Sunday becomes 6 + + // Get previous month's last days to fill in the first week + const prevMonth = month === 0 ? 11 : month - 1; + const prevMonthYear = month === 0 ? year - 1 : year; + const prevMonthLastDay = new Date(prevMonthYear, prevMonth + 1, 0).getDate(); + + let calendarHTML = ''; + + // Previous month's days + for (let i = 0; i < startingDayOfWeek; i++) { + const day = prevMonthLastDay - startingDayOfWeek + i + 1; + calendarHTML += ` +
+
${day}
+
+ `; + } + + // Current month's days + for (let day = 1; day <= daysInMonth; day++) { + const date = new Date(year, month, day); + const isToday = date.getDate() === today.getDate() && + date.getMonth() === today.getMonth() && + date.getFullYear() === today.getFullYear(); + + // Format the date as YYYY-MM-DD for comparison with todos + const formattedDate = formatDateYYYYMMDD(date); + + // Filter todos for this day + const todosForDay = todos.filter(todo => { + // Debug log to check date formats + if (day === 1) { + console.log('Todo dates for debugging:', { + id: todo.id, + nextDate: todo.nextDate, + dueDate: todo.dueDate, + formattedDate: formattedDate + }); + } + + // Normalize date formats for comparison + const todoNextDate = todo.nextDate ? todo.nextDate.substring(0, 10) : null; + const todoDueDate = todo.dueDate ? todo.dueDate.substring(0, 10) : null; + + return ( + (todoNextDate && todoNextDate === formattedDate) || + (todoDueDate && todoDueDate === formattedDate) + ); + }); + + // Debug log for this day's todos + if (todosForDay.length > 0) { + console.log(`Todos for ${formattedDate}:`, todosForDay); + } + + calendarHTML += ` +
+
${day}
+ ${generateTodoItemsHTML(todosForDay, formattedDate)} +
+ `; + } + + // Next month's days to complete the grid (6 rows x 7 columns = 42 cells) + const totalCells = 42; + const remainingCells = totalCells - (startingDayOfWeek + daysInMonth); + + for (let day = 1; day <= remainingCells; day++) { + calendarHTML += ` +
+
${day}
+
+ `; + } + + return calendarHTML; +} + +// Helper function to format date as YYYY-MM-DD +function formatDateYYYYMMDD(date) { + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + return `${year}-${month}-${day}`; +} + +// Function to generate HTML for todo items in a day +function generateTodoItemsHTML(todos, date) { + if (!todos.length) return ''; + + let html = ''; + + todos.forEach(todo => { + const isDueToday = todo.dueDate === date; + const isUrgent = todo.urgent >= 5; + const className = isDueToday ? 'todo-item due-today' : (isUrgent ? 'todo-item urgent' : 'todo-item'); + + // Make the todo item clickable to open the edit modal + html += ` +
+ #${todo.id}: ${truncateText(todo.task, 25)} +
+ `; + }); + + return html; +} + +// Helper function to truncate text +function truncateText(text, maxLength) { + if (!text) return ''; + return text.length > maxLength ? text.substring(0, maxLength) + '...' : text; +} diff --git a/index.html b/index.html index b270083..669dcb3 100644 --- a/index.html +++ b/index.html @@ -18,6 +18,7 @@
+
@@ -49,7 +50,16 @@
+ + + diff --git a/modal.js b/modal.js index 38d668d..5d00ce6 100644 --- a/modal.js +++ b/modal.js @@ -129,7 +129,7 @@ function showEditTodoModal(rowIdx, key) {
- +
diff --git a/style.css b/style.css index ebc5374..2abb8e6 100644 --- a/style.css +++ b/style.css @@ -158,12 +158,14 @@ h3 { color: var(--muted); font-size: 1.05em; font-weight: 600; - padding: 12px 32px; + padding: 10px 16px; border-radius: 999px; cursor: pointer; transition: all 0.2s ease-in-out; position: relative; overflow: hidden; + flex: none; + min-width: 13ch; } .tab-btn:hover { @@ -477,15 +479,63 @@ label { } .modal-content { background: #fff; - padding: 36px 28px 28px 28px; + padding: 36px 40px 32px 40px; border-radius: 18px; box-shadow: 0 8px 40px rgba(0,0,0,0.18); - min-width: 340px; - max-width: 96vw; - max-height: 90vh; + min-width: 800px; + width: 80vw; + max-width: 1400px; + max-height: 98vh; overflow-y: auto; animation: modalSlideIn 0.2s cubic-bezier(0.2, 0.8, 0.2, 1); border: 2px solid #e0e0e0; + display: flex; + flex-direction: column; +} + +.calendar-modal-content { + width: 90vw; + max-width: 1200px; + padding: 24px; + max-height: 90vh; +} + +/* Responsive grid for modal form fields */ +#modal-todo-form { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px 32px; + align-items: start; +} +#modal-todo-form .form-section { + grid-column: span 1; + margin-bottom: 0; +} +#modal-todo-form .form-section.grid-2 { + grid-column: span 2; +} +#modal-todo-form .form-section.grid-3 { + grid-column: span 3; +} +#modal-todo-form .form-section:last-child { + grid-column: span 3; +} + +@media (max-width: 900px) { + .modal-content { + min-width: 340px; + max-width: 98vw; + padding: 28px 10px 18px 10px; + } + #modal-todo-form { + grid-template-columns: 1fr; + gap: 16px 0; + } + #modal-todo-form .form-section, + #modal-todo-form .form-section.grid-2, + #modal-todo-form .form-section.grid-3 { + grid-column: span 1; + } } .modal-edit-todo h2 { margin-top: 0; @@ -643,8 +693,8 @@ label { border-bottom: 2px solid #e5e7eb; } .tab-btn { - flex: 1; - padding: 12px 0; + flex: none; + padding: 10px 16px; background: none; border: none; border-bottom: 3px solid transparent; @@ -652,6 +702,7 @@ label { font-weight: 500; cursor: pointer; transition: border-bottom 0.2s, background 0.2s; + min-width: 13ch; } .tab-btn.active { border-bottom: 3px solid #2563eb;