Calendar
This commit is contained in:
parent
23a38d42c2
commit
08e06fc21d
5
app.js
5
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');
|
||||
|
||||
341
calendar.js
Normal file
341
calendar.js
Normal file
@ -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 `
|
||||
<div class="calendar-header">
|
||||
<h2 class="calendar-title">${getMonthName(currentMonth)} ${currentYear}</h2>
|
||||
</div>
|
||||
<div class="calendar-grid">
|
||||
<div class="calendar-day-header">Mon</div>
|
||||
<div class="calendar-day-header">Tue</div>
|
||||
<div class="calendar-day-header">Wed</div>
|
||||
<div class="calendar-day-header">Thu</div>
|
||||
<div class="calendar-day-header">Fri</div>
|
||||
<div class="calendar-day-header">Sat</div>
|
||||
<div class="calendar-day-header">Sun</div>
|
||||
${generateCalendarDays(currentMonth, currentYear, todos)}
|
||||
</div>
|
||||
<style>
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.calendar-title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
.calendar-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
.calendar-day-header {
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
padding: 10px;
|
||||
background-color: #f3f4f6;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.calendar-day {
|
||||
min-height: 100px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 5px;
|
||||
padding: 8px;
|
||||
position: relative;
|
||||
background-color: white;
|
||||
}
|
||||
.calendar-day-number {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 8px;
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
}
|
||||
.calendar-day.today {
|
||||
background-color: #f0f9ff;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
.calendar-day.other-month {
|
||||
background-color: #f9fafb;
|
||||
color: #9ca3af;
|
||||
}
|
||||
.todo-item {
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
background-color: #e0f2fe;
|
||||
border-left: 3px solid #3b82f6;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
||||
line-height: 1.3;
|
||||
}
|
||||
.todo-item.urgent {
|
||||
background-color: #fee2e2;
|
||||
border-left-color: #ef4444;
|
||||
}
|
||||
.todo-item.due-today {
|
||||
background-color: #fef3c7;
|
||||
border-left-color: #f59e0b;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
// Function to edit a todo from the calendar
|
||||
function editTodoFromCalendar(todoId) {
|
||||
// Close the calendar modal
|
||||
document.getElementById('calendar-modal').style.display = 'none';
|
||||
|
||||
// Find the todo in the todos array by ID
|
||||
if (typeof window.showEditTodoModal === 'function') {
|
||||
window.showEditTodoModal(null, todoId);
|
||||
} else {
|
||||
console.error('Edit modal function not available');
|
||||
alert('Could not open edit modal. Please try again.');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
`;
|
||||
}
|
||||
|
||||
// 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 += `
|
||||
<div class="calendar-day other-month">
|
||||
<div class="calendar-day-number">${day}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 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 += `
|
||||
<div class="calendar-day ${isToday ? 'today' : ''}">
|
||||
<div class="calendar-day-number">${day}</div>
|
||||
${generateTodoItemsHTML(todosForDay, formattedDate)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 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 += `
|
||||
<div class="calendar-day other-month">
|
||||
<div class="calendar-day-number">${day}</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
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 += `
|
||||
<div class="${className}"
|
||||
title="Click to edit this todo"
|
||||
data-id="${todo.id}"
|
||||
data-todo-id="${todo.id}"
|
||||
onclick="editTodoFromCalendar(${todo.id})">
|
||||
<strong>#${todo.id}</strong>: ${truncateText(todo.task, 25)}
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
// Helper function to truncate text
|
||||
function truncateText(text, maxLength) {
|
||||
if (!text) return '';
|
||||
return text.length > maxLength ? text.substring(0, maxLength) + '...' : text;
|
||||
}
|
||||
10
index.html
10
index.html
@ -18,6 +18,7 @@
|
||||
<div class="tab-bar">
|
||||
<button class="tab-btn active" id="tab-list-btn" type="button">Todos List</button>
|
||||
<button class="tab-btn" id="tab-form-btn" type="button">Add/Edit Todo</button>
|
||||
<button class="tab-btn" id="tab-calendar-btn" type="button">Calendar</button>
|
||||
<button class="tab-btn" id="tab-admin-btn" type="button">Admin</button>
|
||||
</div>
|
||||
<div id="tab-content-list" class="tab-content active">
|
||||
@ -49,7 +50,16 @@
|
||||
<div id="password-modal-error" style="color:#d33; margin-top:8px; display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="calendar-modal" class="modal" style="display:none;">
|
||||
<div class="modal-content calendar-modal-content">
|
||||
<button class="modal-close-btn" id="calendar-close-btn">×</button>
|
||||
<h3 id="calendar-modal-title">Todo Calendar</h3>
|
||||
<div id="calendar-container"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="app.js"></script>
|
||||
<script src="modal.js"></script>
|
||||
<script src="calendar.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
2
modal.js
2
modal.js
@ -129,7 +129,7 @@ function showEditTodoModal(rowIdx, key) {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="modal-urgent">Urgent (1-7)</label>
|
||||
<label for="modal-urgent">Urgency</label>
|
||||
<input type="number" id="modal-urgent" name="urgent" min="1" max="7" value="${todo.urgent || ''}">
|
||||
</div>
|
||||
<div>
|
||||
|
||||
65
style.css
65
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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user