diff --git a/images/calendar.svg b/images/calendar.svg new file mode 100644 index 0000000..89b1812 --- /dev/null +++ b/images/calendar.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/cancel.svg b/images/cancel.svg new file mode 100644 index 0000000..971b2e0 --- /dev/null +++ b/images/cancel.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/images/delete.svg b/images/delete.svg new file mode 100644 index 0000000..5618f76 --- /dev/null +++ b/images/delete.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/images/edit.svg b/images/edit.svg new file mode 100644 index 0000000..074aecd --- /dev/null +++ b/images/edit.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/images/tick.svg b/images/tick.svg new file mode 100644 index 0000000..eb038a3 --- /dev/null +++ b/images/tick.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/index.html b/index.html index 09c6dc0..62a6f0f 100644 --- a/index.html +++ b/index.html @@ -3,11 +3,198 @@ - Simple HTML Page + To-Do List + +
+
+

To-Do List

+ +
+
+
+ + +
+ +
+ +
+
+
+ + +
+ Complete the project + Medium +
+
+
+ 2025-05-01 +
+ + +
+
+
+ +
+
+ + +
+ Read the book + Low +
+
+
+ 2025-04-25 +
+ + +
+
+
+ +
+
+ + +
+ Go to the gym + High +
+
+
+ 2025-04-23 +
+ + +
+
+
+ +
+
+ + +
+ Make a doctor's appointment + Medium +
+
+
+ 2025-04-20 +
+ + +
+
+
+
+
+ +
+ Filter by date +
+ +
+ +
+ + Calendar +
+ +
+ +
+
+ +
+
+ Task + +
+
+ + +
+
+ +
+ + Calerdar +
+
+
+ +
+ + + + + +
+
+
+ + +
+
\ No newline at end of file diff --git a/src/main.js b/src/main.js index e69de29..4db4814 100644 --- a/src/main.js +++ b/src/main.js @@ -0,0 +1,423 @@ +const taskList = document.querySelector('.task-list'); +const taskForm = document.querySelector('.task-form'); +const addTaskButton = document.querySelector('.add-button'); +const cancelTaskButton = document.querySelector('.cancel-button'); +const cancelImageButton = document.querySelector('.cancel-image-button'); +const taskNameInput = document.querySelector('#task-name'); +const taskDateInput = document.querySelector('#task-date'); +const priorityInputs = document.querySelectorAll('input[name="priority"]'); + +const filterButton = document.querySelector('.filter-button'); +const filterContainer = document.querySelector('.filter-container'); +const applyFilterButton = document.querySelector('#apply-filter'); +const dateFilterSelect = document.querySelector('.date-filter'); +const filterDateInput = document.querySelector('#filter-date-pick'); +const customDatePicker = document.querySelector('.custom-date-picker'); + +let currentFilter = 'all'; // all, current, last, next +let selectedFilterDate = ''; // 'YYYY-MM-DD' +let currentSort = 'none'; // none, date, priority + +let taskToEdit = null; +let elementToEdit = null; + +let tasks = []; + +// Завантажує задачі з JSON-файлу +fetch('./task.json') + .then((response) => response.json()) + .then((json) => { + tasks = json.map((task, index) => ({...task, taskIndex: index})); + renderTasks(tasks); + }); + +document.querySelector('.add-form-button').addEventListener('click', showTaskForm); // Відриває форму + +// Показує форму для додавання/редагування задачі +function showTaskForm() { + taskForm.style.display = 'flex'; + document.querySelector('.to-do-container').classList.add('disabled'); +} + +// Приховує форму для додавання/редагування задачі +function hideTaskForm() { + taskForm.style.display = 'none'; + taskForm.reset(); + taskToEdit = null; + elementToEdit = null; + document.querySelector('.to-do-container').classList.remove('disabled'); +} + +// Відображає список задач +function renderTasks(taskArray) { + taskList.innerHTML = ''; + + taskArray.forEach((task) => { + const taskElement = generateTask(task); + taskList.appendChild(taskElement); + }); +} + +// При наатисканні кнопки приховує форму для додавання/редагування задачі +[cancelTaskButton, cancelImageButton].forEach(button => { + button.addEventListener('click', (e) => { + hideTaskForm(); + }); +}); + +//Слухач зміни сортування задач +document.querySelector('#sort-by').addEventListener('change', (e) => { + currentSort = e.target.value; + updateTasksView(); +}); + +// Показує контейнер для фільтрації задач +filterButton.addEventListener('click', () => { + filterContainer.style.display = 'flex'; + document.querySelector('.to-do-container').classList.add('disabled'); +}); + +// Застосовує фільтр задач +applyFilterButton.addEventListener('click', () => { + currentFilter = dateFilterSelect.value; + selectedFilterDate = filterDateInput.value; + updateTasksView(); + filterContainer.style.display = 'none'; + document.querySelector('.to-do-container').classList.remove('disabled'); +}); + +//Оновлює список задач після застосування фільтру або сортування +function updateTasksView() { + const filtered = filterTasks(tasks); + const sorted = sortTasks(filtered); + renderTasks(sorted); +} + +// Сортує задачі за обраним критерієм +function sortTasks(taskArray) { + if (currentSort === 'none') return taskArray; + const sorted = [...taskArray]; + + if (currentSort === 'date') { + sorted.sort((task1, task2) => new Date(task2.date) - new Date(task1.date)); + } else if (currentSort === 'priority') { + const order = { high: 1, medium: 2, low: 3 }; + sorted.sort((task1, task2) => order[task1.priority] - order[task2.priority]); + } + return sorted; +} + +// Слухач зміни вибору дати фільтру +dateFilterSelect.addEventListener('change', () => { + const today = new Date(); + const year = today.getFullYear(); + const month = today.getMonth(); + + const firstDayCurrent = new Date(year, month, 1); + const lastDayCurrent = new Date(year, month + 1, 0); + + const lastDayLast = new Date(year, month, 0); + const firstDayNext = new Date(year, month + 1, 1); + + const valueNext = new Date(year, month + 1, today.getDate()) + const valueLast = new Date(year, month - 1, today.getDate()); + + const selected = dateFilterSelect.value; + + if (selected === 'current') { + filterDateInput.min = formatDate(firstDayCurrent); + filterDateInput.max = formatDate(lastDayCurrent); + filterDateInput.value = formatDate(today); + customDatePicker.style.display = 'flex'; + } else if (selected === 'last') { + filterDateInput.min = ''; + filterDateInput.max = formatDate(lastDayLast); + filterDateInput.value = formatDate(valueLast); + customDatePicker.style.display = 'flex'; + } else if (selected === 'next') { + filterDateInput.min = formatDate(firstDayNext); + filterDateInput.max = ''; + filterDateInput.value = formatDate(valueNext); + customDatePicker.style.display = 'flex'; + } else { + filterDateInput.min = ''; + filterDateInput.max = ''; + filterDateInput.value = ''; + customDatePicker.style.display = 'none'; + } +}); + +// Форматує дату у формат YYYY-MM-DD +function formatDate(date) { + const yyyy = date.getFullYear(); + let mm = date.getMonth() + 1; + let dd = date.getDate(); + + if (mm < 10) { + mm = '0' + mm; + } + + if (dd < 10) { + dd = '0' + dd; + } + + return yyyy + '-' + mm + '-' + dd; +} + +// Фільтрує задачі за обраним критерієм +function filterTasks(taskArray) { + const today = new Date(); + const year = today.getFullYear(); + const month = today.getMonth(); + const selectedDay = new Date(selectedFilterDate); + const first = new Date(year, month, 1); // Перший день поточного місяця + const last = new Date(year, month + 1, 0); // Останній день поточного місяця + + if (currentFilter === 'all') return taskArray; + + if (currentFilter === 'current') { + if (!selectedFilterDate) { + return taskArray.filter(task => { + const day = new Date(task.date); + return day >= first && day <= last; + }); + + } else { + return taskArray.filter(task => new Date(task.date).toDateString() === selectedDay.toDateString()); + } + + } else if (currentFilter === 'last') { + if(!selectedFilterDate) { + return taskArray.filter(task => { + const d = new Date(task.date); + return d < first; + }); + + } else { + return taskArray.filter(task => new Date(task.date).toDateString() === selectedDay.toDateString()); + } + + } else if (currentFilter === 'next') { + if(!selectedFilterDate) { + return taskArray.filter(task => { + const d = new Date(task.date); + return d > last; + }); + + } else { + return taskArray.filter(task => new Date(task.date).toDateString() === selectedDay.toDateString()); + } + } + return taskArray; +} + +// Слухач події відправки форми для додавання/редагування задачі +taskForm.addEventListener('submit', function (e) { + e.preventDefault(); + + const name = taskNameInput.value.trim(); + const date = taskDateInput.value; + const priority = Array.from(priorityInputs).find(input => input.checked)?.value; + + if (!name || !date || !priority) return; + + if (taskToEdit) { + taskToEdit.name = name; + taskToEdit.date = date; + taskToEdit.priority = priority; + + const newElement = generateTask(taskToEdit); + taskList.replaceChild(newElement, elementToEdit); + + taskToEdit = null; + elementToEdit = null; + } else { + const newTask = { + name, + priority, + completed: false, + date, + taskIndex: tasks.length + }; + + tasks.push(newTask); + + const taskElement = generateTask(newTask); + const taskItems = Array.from(taskList.children); + + let added = false; + for (let i = 0; i < taskItems.length; i++) { + const checkbox = taskItems[i].querySelector('input[type="checkbox"]'); + if (checkbox && checkbox.checked) { + taskList.insertBefore(taskElement, taskItems[i]); + added = true; + break; + } + } + if (!added) { + taskList.appendChild(taskElement); + } + } + updateTasksView(); + hideTaskForm(); +}); + +// Створення елемента задачі +function generateTask(task) { + const taskItem = document.createElement('div'); + taskItem.classList.add('task-item'); + + if (task.taskIndex !== undefined) { + taskItem.dataset.index = task.taskIndex; + } + + const leftSide = document.createElement('div'); + leftSide.classList.add('left-side'); + + const customCheckbox = document.createElement('label'); + customCheckbox.classList.add('custom-checkbox'); + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.checked = task.completed; + + if (task.completed) { + setTimeout(() => { + changeTaskPosition(taskItem, true); + }); + } + + checkbox.addEventListener('change', () => { + task.completed = checkbox.checked; + changeTaskPosition(taskItem, checkbox.checked); + }); + + const checkmark = document.createElement('span'); + checkmark.classList.add('checkmark'); + + customCheckbox.append(checkbox, checkmark); + + const textBlock = document.createElement('div'); + textBlock.classList.add('text-block'); + + const taskText = document.createElement('span'); + taskText.classList.add('task-text'); + taskText.textContent = task.name; + + const tag = document.createElement('span'); + tag.classList.add('tag', task.priority); + tag.textContent = capitalize(task.priority.toLowerCase()); + + textBlock.append(taskText, tag); + leftSide.append(customCheckbox, textBlock); + + const rightSide = document.createElement('div'); + rightSide.classList.add('right-side'); + + const taskDate = document.createElement('span'); + taskDate.classList.add('task-date'); + taskDate.textContent = task.date; + + rightSide.appendChild(taskDate); + + if(!taskItem.querySelector('.edit-and-delete-buttons')) { + const buttons = document.createElement('div'); + buttons.classList.add('edit-and-delete-buttons'); + + const editButton = document.createElement('button'); + editButton.classList.add('edit-button'); + editButton.addEventListener('click', () => { + taskForm.style.display = 'flex'; + taskNameInput.value = task.name; + taskDateInput.value = task.date; + + priorityInputs.forEach(input => { + input.checked = input.value === task.priority; + }); + + taskToEdit = task; + elementToEdit = taskItem; + document.querySelector('.to-do-container').classList.add('disabled') + }); + + const editImage = document.createElement('img'); + editImage.src = './images/edit.svg'; + editImage.alt = 'Edit'; + editButton.appendChild(editImage); + + const deleteButton = document.createElement('button'); + deleteButton.classList.add('delete-button'); + deleteButton.addEventListener('click', () => { + const index = parseInt(taskItem.dataset.index); + tasks = tasks.filter(task => task.taskIndex !== index); + taskItem.remove(); + }); + + const deleteImage = document.createElement('img'); + deleteImage.src = './images/delete.svg'; + deleteImage.alt = 'Delete'; + deleteButton.appendChild(deleteImage); + + buttons.appendChild(editButton); + buttons.appendChild(deleteButton); + + rightSide.appendChild(buttons); + } + + taskItem.append(leftSide, rightSide); + + return taskItem; +} + +// Функція для капіталізації першої літери +function capitalize(str) { + return str.charAt(0).toUpperCase() + str.slice(1); +} + +// Функція для зміни позиції задачі в списку +function changeTaskPosition(taskItem, isCompleted) { + const taskItems = Array.from(taskList.children); + taskList.removeChild(taskItem); + + const taskIndex = parseInt(taskItem.dataset.index); + + if (isCompleted) { + // Якщо задача виконана, додаємо її в кінець списку + taskList.appendChild(taskItem); + } else { + // Якщо задача знову не виконана, спробуємо знайти, куди її вставити серед інших невиконаних задач + let added = false; + for (let i = 0; i < taskItems.length; i++) { + const element = taskItems[i]; + const elementIndex = parseInt(element.dataset.index); + const checkbox = element.querySelector('input[type="checkbox"]'); + + // Якщо ця задача ще не виконана і була пізніше за поточну, вставляємо нашу задачу перед цією + if (!checkbox.checked && elementIndex > taskIndex) { + taskList.insertBefore(taskItem, element); + added = true; + break; + } + } + + // Якщо не знайшли підходящу невиконану задачу + if (!added) { + let placed = false; + for (let i = 0; i < taskItems.length; i++) { + const element = taskItems[i]; + const checkbox = element.querySelector('input[type="checkbox"]'); + + // Якщо ця задача виконана, вставляємо нашу задачу перед нею + if (checkbox.checked) { + taskList.insertBefore(taskItem, element); + placed = true; + break; + } + } + // Якщо взагалі не знайшли жодної виконаної + if (!placed) { + taskList.appendChild(taskItem); + } + } + } +} \ No newline at end of file diff --git a/style/style.css b/style/style.css index e69de29..182f932 100644 --- a/style/style.css +++ b/style/style.css @@ -0,0 +1,423 @@ +* { + box-sizing: border-box; + font-family: Arial, sans-serif; +} + +body { + display: flex; + background-color: #f4f4f4; +} + +.to-do-container { + width: 600px; + margin: 0 auto; +} + +.to-do-container.disabled { + opacity: 0.5; + pointer-events: none; +} + +.add-form-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.add-form-button { + width: 100%; + padding: 10px 0; + margin-bottom: 20px; + border: none; + border-radius: 8px; + font-size: 18px; + color: white; + background-color: #3b82f6; + text-align: center; + cursor: pointer; +} + +.add-form-button:active { + background-color: #0f59a8 +} + +.sort-and-filter-container { + display: flex; + align-items: center; + margin-bottom: 20px; + justify-content: space-between; +} + +.sort, .filter-button { + display: flex; + gap: 10px; + padding: 10px; + background-color: #ffffff; + border: 1px solid #ccc; + border-radius: 8px; +} + +.sort-label, .filter-button { + font-size: 16px; + color: black; +} + +.filter-button { + cursor: pointer; +} + +.sort select { + font-size: 16px; + border: none; + color: #555; + cursor: pointer; + outline: none; +} + +.task-list { + display: flex; + flex-direction: column; + background-color: #ffffff; + border: 1px solid #ccc; + border-radius: 8px; + padding: 5px 15px; +} + +.task-item { + display: flex; + justify-content: space-between; + align-items: stretch; + padding: 10px 0px +} + +.task-item:not(:last-child) { + border-bottom: 1px solid #ddd; +} + +.left-side { + display: flex; + align-items: flex-start; + gap: 10px; +} + +.text-block, .edit-and-delete-buttons { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: space-between; + height: 100%; +} + +.task-text { + max-width: 100%; + word-break: break-word; + overflow-wrap: break-word; + font-size: 18px; +} + +.tag { + display: inline-block; + font-size: 16px; + padding: 3px 8px; + border-radius: 8px; +} + +.tag.low { + background-color: #e9e9e9; + color: #555; +} + +.tag.medium { + background-color: #f1820b; + color: #fff; +} + +.tag.high { + background-color: #f25c54; + color: #fff; +} + +.right-side { + display: flex; + align-items: flex-start; + gap: 18px; +} + +.task-date { + font-size: 18px; + color: #888; + white-space: nowrap; +} + +.edit-and-delete-buttons { + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.edit-button, .delete-button { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + padding: 0; + border: none; + background: none; + cursor: pointer; +} + +.edit-button img, .delete-button img { + width: 100%; + height: 100%; + object-fit: contain; +} + +.custom-checkbox input { + display: none; +} + +.custom-checkbox .checkmark { + display: inline-block; + position: relative; + width: 20px; + height: 20px; + border-radius: 50%; + border: 2px solid #888; + cursor: pointer; +} + +.custom-checkbox input:checked + .checkmark::after { + content: ''; + position: absolute; + width: 100%; + height: 100%; + background-image: url('../images/tick.svg'); + background-repeat: no-repeat; + background-size: contain; + background-position: center; +} + +.task-form { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: flex; + flex-direction: column; + gap: 16px; + width: 100%; + max-width: 400px; + padding: 20px; + background-color: #fdfdfd; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 1px 6px rgba(0,0,0,0.1); +} + +.form-name-container { + display: flex; + justify-content: space-between; + align-items: center; +} + +.task { + font-size: 22px; + font-weight: bold; +} + +.cancel-image-button { + display: flex; + align-items: center; + justify-content: center; + width: 20px; + height: 20px; + padding: 0; + border: none; + background: none; + cursor: pointer; +} + +.cancel-image-button img { + width: 100%; + height: 100%; + object-fit: contain; +} + +.add-task-container, .add-date-container, .add-priority-container { + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 5px; +} + +.task-name-container label, .task-date-container label, .task-priority-container label { + font-size: 14px; +} + +.add-task-container input, .add-date-container input { + width: 100%; + padding: 10px; + border-radius: 4px; + border: 1px solid #ccc; + color: #626262; + outline: none; +} + +.form-buttons { + display: flex; + gap: 5px; +} + +.add-button, .cancel-button { + flex: 1; + padding: 8px 0px; + font-size: 16px; +} + +.add-button { + background-color: #3b82f6; + color: white; + border: none; + border-radius: 6px; + cursor: pointer; +} + +.cancel-button { + background-color: #fdfdfd; + color: #626262; + border: 2px solid #ccc; + border-radius: 6px; + cursor: pointer; +} + +.add-button:active { + background-color: #0f59a8; +} + +.cancel-button:active { + background-color: #f0f0f0;; +} + +.custom-radio { + display: flex; + align-items: center; + cursor: pointer; +} + +.priority-options { + display:flex; + justify-content: space-between; + width: 100%; +} + +.custom-radio input { + display: none; +} + +.custom-radio .radio-mark { + width: 20px; + height: 20px; + border-radius: 50%; + border: 2px solid #888; + margin-right: 8px; +} + +.custom-radio input:checked + .radio-mark { + border: 4px solid #3b82f6; +} + +.filter-container { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + display: flex; + flex-direction: column; + background-color: #ffffff; + border: 1px solid #e3e3e3; + gap: 10px; + padding: 15px; + border-radius: 8px; + width: 100%; + max-width: 250px; + box-shadow: 0 1px 6px rgba(0,0,0,0.1); +} + +.filter-container span { + margin: 0; + font-size: 18px; + font-weight: bold; + color: #333; +} + +.filter-container input[type="date"] { + width: 100%; + padding: 10px; + border-radius: 6px; + background-color: #ffffff; + border: 1px solid #ccc; + font-size: 14px; + outline: none; +} + +.container-date-filter { + width: 100%; + padding: 10px; + border: none; + border-radius: 6px; + background-color: #e9e9e9; +} + +.filter-container select { + width: 100%; + background-color: #e9e9e9; + font-size: 14px; + border: none; + outline: none; +} + +.date-filter-actions { + display: flex; + justify-content: flex-end; +} + +#apply-filter { + padding: 8px 16px; + background-color: #3b82f6; + color: white; + font-size: 14px; + border: none; + border-radius: 6px; + cursor: pointer; +} + +#apply-filter:active { + background-color: #0f59a8; +} + +.filter-container .custom-date-picker { + display: none; +} + +.custom-date-picker { + position: relative; + width: 100%; +} + +.custom-date-picker input[type="date"]::-webkit-calendar-picker-indicator { + opacity: 0; +} + +.custom-date-picker .calendar-icon { + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + pointer-events: none; +} + +.task-form, .filter-container { + display: none; +} \ No newline at end of file diff --git a/task.json b/task.json new file mode 100644 index 0000000..cbc123a --- /dev/null +++ b/task.json @@ -0,0 +1,26 @@ +[ + { + "name": "Complete the project", + "priority": "medium", + "completed": false, + "date": "2025-05-01" + }, + { + "name": "Read the book", + "priority": "low", + "completed": true, + "date": "2025-04-25" + }, + { + "name": "Go to the gym", + "priority": "high", + "completed": false, + "date": "2025-04-23" + }, + { + "name": "Make a doctor's appointment", + "priority": "medium", + "completed": false, + "date": "2025-04-20" + } +] \ No newline at end of file