Skip to content

Commit

Permalink
Add Multi List function
Browse files Browse the repository at this point in the history
  • Loading branch information
abiteman committed Jan 25, 2025
1 parent 4d65bd5 commit 8da319c
Show file tree
Hide file tree
Showing 3 changed files with 208 additions and 10 deletions.
123 changes: 114 additions & 9 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,112 @@ const pinModal = document.getElementById('pinModal');
const pinInputs = [...document.querySelectorAll('.pin-input')];
const pinError = document.getElementById('pinError');
const clearCompletedBtn = document.getElementById('clearCompleted');
const listSelector = document.getElementById('listSelector');
const renameListBtn = document.getElementById('renameList');
const addListBtn = document.getElementById('addList');

// State
let todos = [];
let todos = {};
let currentList = 'List 1';
let verifiedPin = null;

// List Management
function initializeLists(data) {
if (!data || Object.keys(data).length === 0) {
// Only create List 1 when there are no lists at all
todos = { 'List 1': [] };
currentList = 'List 1';
} else {
// Convert only numeric keys, preserve custom names
const convertedData = {};
Object.entries(data).forEach(([key, value]) => {
// Only convert numeric keys
if (/^\d+$/.test(key)) {
const newKey = `List ${Object.keys(convertedData).length + 1}`;
convertedData[newKey] = value;
} else {
convertedData[key] = value;
}
});

todos = convertedData;
currentList = Object.keys(convertedData)[0];
}

updateListSelector();
renderTodos();
}

function updateListSelector() {
listSelector.innerHTML = '';
// Sort the list keys to ensure List 1 comes first
const sortedKeys = Object.keys(todos).sort((a, b) => {
if (a === 'List 1') return -1;
if (b === 'List 1') return 1;
return a.localeCompare(b);
});

sortedKeys.forEach(listId => {
const option = document.createElement('option');
option.value = listId;
option.textContent = listId;
option.selected = listId === currentList;
listSelector.appendChild(option);
});
}

function switchList(listId) {
currentList = listId;
renderTodos();
}

function addNewList() {
const listCount = Object.keys(todos).length + 1;
const newListId = `List ${listCount}`;
todos[newListId] = [];
currentList = newListId;
updateListSelector();
renderTodos();
saveTodos();
showToast('New list added');
}

async function renameCurrentList() {
const newName = prompt('Enter new list name:', currentList);
if (newName && newName.trim() && newName !== currentList && !todos[newName]) {
const oldName = currentList;
const oldTodos = { ...todos }; // Keep a full backup

try {
// Update the data structure
todos[newName] = todos[currentList];
delete todos[currentList];
currentList = newName;

// Update UI
updateListSelector();

// Save changes
await saveTodos();
showToast('List renamed');
} catch (error) {
// Revert all changes on failure
todos = oldTodos;
currentList = oldName;
updateListSelector();
showToast('Failed to save list name change');
}
}
}

// Event Listeners for List Management
listSelector.addEventListener('change', (e) => {
switchList(e.target.value);
});

renameListBtn.addEventListener('click', renameCurrentList);
addListBtn.addEventListener('click', addNewList);

// PIN Management
async function checkPinRequired() {
try {
Expand Down Expand Up @@ -200,8 +301,8 @@ async function loadTodos() {
try {
const response = await fetchWithPin('/api/todos');
if (!response.ok) throw new Error('Failed to load todos');
todos = await response.json();
renderTodos();
const data = await response.json();
initializeLists(data);
} catch (error) {
showToast('Failed to load todos');
console.error(error);
Expand All @@ -218,9 +319,11 @@ async function saveTodos() {
body: JSON.stringify(todos)
});
if (!response.ok) throw new Error('Failed to save todos');
return true;
} catch (error) {
showToast('Failed to save todos');
console.error(error);
throw error; // Re-throw to handle in calling function
}
}

Expand All @@ -244,7 +347,7 @@ function createTodoElement(todo) {
const deleteBtn = li.querySelector('.delete-btn');
deleteBtn.addEventListener('click', () => {
li.remove();
todos = todos.filter(t => t !== todo);
todos[currentList] = todos[currentList].filter(t => t !== todo);
saveTodos();
showToast('Task deleted');
});
Expand All @@ -254,10 +357,11 @@ function createTodoElement(todo) {

function renderTodos() {
todoList.innerHTML = '';
const currentTodos = todos[currentList] || [];

// Separate todos into active and completed
const activeTodos = todos.filter(todo => !todo.completed);
const completedTodos = todos.filter(todo => todo.completed);
const activeTodos = currentTodos.filter(todo => !todo.completed);
const completedTodos = currentTodos.filter(todo => todo.completed);

// Render active todos
activeTodos.forEach(todo => {
Expand Down Expand Up @@ -285,7 +389,7 @@ todoForm.addEventListener('submit', (e) => {

if (text) {
const todo = { text, completed: false };
todos.push(todo);
todos[currentList].push(todo);
renderTodos();
saveTodos();
todoInput.value = '';
Expand All @@ -295,15 +399,16 @@ todoForm.addEventListener('submit', (e) => {

// Clear completed tasks
clearCompletedBtn.addEventListener('click', () => {
const completedCount = todos.filter(todo => todo.completed).length;
const currentTodos = todos[currentList];
const completedCount = currentTodos.filter(todo => todo.completed).length;

if (completedCount === 0) {
showToast('No completed tasks to clear');
return;
}

if (confirm(`Are you sure you want to delete ${completedCount} completed task${completedCount === 1 ? '' : 's'}?`)) {
todos = todos.filter(todo => !todo.completed);
todos[currentList] = currentTodos.filter(todo => !todo.completed);
renderTodos();
saveTodos();
showToast(`Cleared ${completedCount} completed task${completedCount === 1 ? '' : 's'}`);
Expand Down
19 changes: 19 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@
<body>
<div class="app">
<header>
<div class="list-controls">
<select id="listSelector" aria-label="Select todo list">
<option value="List 1">List 1</option>
</select>
<div class="list-buttons">
<button type="button" id="renameList" class="icon-btn" aria-label="Rename current list">
<svg viewBox="0 0 24 24" width="14" height="14">
<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
<path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
</svg>
</button>
<button type="button" id="addList" class="icon-btn" aria-label="Add new list">
<svg viewBox="0 0 24 24" width="14" height="14">
<line x1="12" y1="5" x2="12" y2="19"></line>
<line x1="5" y1="12" x2="19" y2="12"></line>
</svg>
</button>
</div>
</div>
<h1>DumbDo</h1>
<button id="themeToggle" aria-label="Toggle theme">
<svg class="moon" viewBox="0 0 24 24">
Expand Down
76 changes: 75 additions & 1 deletion styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -348,4 +348,78 @@ input:focus {
display: block;
}

/* Remove logo styles as they're no longer needed */
/* Remove logo styles as they're no longer needed */

.list-controls {
position: absolute;
left: 0;
display: flex;
gap: 0.5rem;
align-items: center;
}

.list-buttons {
display: flex;
gap: 0.25rem;
}

.icon-btn {
background: transparent;
padding: 0.5rem;
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid var(--border);
}

.icon-btn svg {
stroke: var(--text);
stroke-width: 2;
fill: none;
}

.icon-btn:hover {
background: rgba(33, 150, 243, 0.1);
border-color: var(--primary);
}

.icon-btn:hover svg {
stroke: var(--primary);
}

#listSelector {
background: var(--container);
color: var(--text);
border: 1px solid var(--border);
border-radius: 8px;
padding: 0.5rem;
font-size: 0.9rem;
cursor: pointer;
transition: all var(--transition);
min-width: 120px;
}

#listSelector:focus {
outline: none;
border-color: var(--primary);
}

@media (max-width: 600px) {
.list-controls {
position: static;
margin-bottom: 1rem;
justify-content: center;
}

header {
flex-direction: column;
gap: 1rem;
}

#themeToggle {
position: static;
margin-left: auto;
}
}

0 comments on commit 8da319c

Please sign in to comment.