| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| <title>PlanSync - Unified Task Planner</title> |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
| <style> |
| :root { |
| --personal: #42A5F5; |
| --shared: #66BB6A; |
| --important: #EF5350; |
| --background: #f8f9fa; |
| --text: #212529; |
| --card-bg: #ffffff; |
| --border: #dee2e6; |
| --shadow: 0 2px 10px rgba(0,0,0,0.05); |
| } |
| |
| [data-theme="dark"] { |
| --background: #121212; |
| --text: #e1e1e1; |
| --card-bg: #1e1e1e; |
| --border: #333; |
| --shadow: 0 2px 10px rgba(0,0,0,0.3); |
| } |
| |
| * { |
| margin: 0; |
| padding: 0; |
| box-sizing: border-box; |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; |
| transition: background-color 0.3s, color 0.3s; |
| } |
| |
| body { |
| background-color: var(--background); |
| color: var(--text); |
| min-height: 100vh; |
| } |
| |
| .container { |
| max-width: 800px; |
| margin: 0 auto; |
| padding: 20px; |
| position: relative; |
| } |
| |
| header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 30px; |
| } |
| |
| .logo { |
| font-size: 24px; |
| font-weight: 600; |
| } |
| |
| .logo span { |
| color: var(--shared); |
| } |
| |
| .theme-toggle { |
| background: none; |
| border: none; |
| color: var(--text); |
| font-size: 20px; |
| cursor: pointer; |
| } |
| |
| .date-controls { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 20px; |
| } |
| |
| .current-date { |
| font-size: 18px; |
| font-weight: 600; |
| } |
| |
| .nav-btn { |
| background: none; |
| border: none; |
| color: var(--text); |
| font-size: 18px; |
| cursor: pointer; |
| padding: 8px; |
| } |
| |
| .view-switcher { |
| display: flex; |
| margin-bottom: 20px; |
| background-color: var(--card-bg); |
| border-radius: 10px; |
| padding: 5px; |
| box-shadow: var(--shadow); |
| } |
| |
| .view-btn { |
| flex: 1; |
| padding: 10px; |
| border: none; |
| background: none; |
| text-align: center; |
| font-weight: 500; |
| color: var(--text); |
| border-radius: 8px; |
| cursor: pointer; |
| } |
| |
| .view-btn.active { |
| background-color: var(--shared); |
| color: white; |
| } |
| |
| .calendar-header { |
| display: grid; |
| grid-template-columns: repeat(7, 1fr); |
| text-align: center; |
| font-weight: 600; |
| margin-bottom: 10px; |
| } |
| |
| .calendar-days { |
| display: grid; |
| grid-template-columns: repeat(7, 1fr); |
| gap: 5px; |
| margin-bottom: 30px; |
| } |
| |
| .calendar-day { |
| aspect-ratio: 1; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: center; |
| border-radius: 8px; |
| cursor: pointer; |
| position: relative; |
| } |
| |
| .calendar-day:hover { |
| background-color: rgba(0,0,0,0.05); |
| } |
| |
| .calendar-day.today { |
| font-weight: bold; |
| color: var(--shared); |
| } |
| |
| .calendar-day.has-events::after { |
| content: ''; |
| width: 6px; |
| height: 6px; |
| background-color: var(--personal); |
| border-radius: 50%; |
| position: absolute; |
| bottom: 5px; |
| } |
| |
| .calendar-day.has-shared::after { |
| background-color: var(--shared); |
| } |
| |
| .calendar-day.has-important::after { |
| background-color: var(--important); |
| } |
| |
| .tabs { |
| display: flex; |
| border-bottom: 1px solid var(--border); |
| margin-bottom: 20px; |
| } |
| |
| .tab { |
| padding: 10px 20px; |
| cursor: pointer; |
| font-weight: 500; |
| position: relative; |
| } |
| |
| .tab.active { |
| color: var(--shared); |
| } |
| |
| .tab.active::after { |
| content: ''; |
| position: absolute; |
| bottom: -1px; |
| left: 0; |
| right: 0; |
| height: 2px; |
| background-color: var(--shared); |
| } |
| |
| .tasks-container { |
| margin-bottom: 80px; |
| } |
| |
| .task-card { |
| background-color: var(--card-bg); |
| border-radius: 12px; |
| padding: 15px; |
| margin-bottom: 15px; |
| box-shadow: var(--shadow); |
| position: relative; |
| overflow: hidden; |
| border-left: 6px solid var(--personal); |
| cursor: pointer; |
| transition: transform 0.2s, box-shadow 0.2s; |
| } |
| |
| .task-card:hover { |
| transform: translateY(-2px); |
| box-shadow: 0 5px 15px rgba(0,0,0,0.1); |
| } |
| |
| .task-card.shared { |
| border-left-color: var(--shared); |
| } |
| |
| .task-card.important { |
| border-left-color: var(--important); |
| } |
| |
| .task-header { |
| display: flex; |
| justify-content: space-between; |
| margin-bottom: 10px; |
| } |
| |
| .task-title { |
| font-weight: 600; |
| font-size: 16px; |
| } |
| |
| .task-time { |
| color: var(--text); |
| opacity: 0.8; |
| font-size: 14px; |
| } |
| |
| .task-desc { |
| font-size: 14px; |
| margin-bottom: 10px; |
| color: var(--text); |
| opacity: 0.7; |
| } |
| |
| .task-participants { |
| display: flex; |
| align-items: center; |
| gap: 5px; |
| margin-top: 10px; |
| } |
| |
| .participant { |
| width: 28px; |
| height: 28px; |
| border-radius: 50%; |
| background-color: var(--shared); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| color: white; |
| font-size: 12px; |
| } |
| |
| .task-actions { |
| position: absolute; |
| top: 15px; |
| right: 15px; |
| display: none; |
| } |
| |
| .task-card:hover .task-actions { |
| display: flex; |
| gap: 10px; |
| } |
| |
| .action-btn { |
| background: none; |
| border: none; |
| color: var(--text); |
| cursor: pointer; |
| opacity: 0.7; |
| } |
| |
| .action-btn:hover { |
| opacity: 1; |
| } |
| |
| .add-btn { |
| position: fixed; |
| bottom: 20px; |
| right: 20px; |
| width: 60px; |
| height: 60px; |
| border-radius: 50%; |
| background-color: var(--shared); |
| color: white; |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| font-size: 24px; |
| border: none; |
| box-shadow: 0 4px 15px rgba(102, 187, 106, 0.3); |
| cursor: pointer; |
| z-index: 100; |
| } |
| |
| .modal-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| right: 0; |
| bottom: 0; |
| background-color: rgba(0,0,0,0.5); |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| z-index: 1000; |
| opacity: 0; |
| pointer-events: none; |
| transition: opacity 0.3s; |
| } |
| |
| .modal-overlay.active { |
| opacity: 1; |
| pointer-events: all; |
| } |
| |
| .modal { |
| background-color: var(--card-bg); |
| border-radius: 12px; |
| width: 90%; |
| max-width: 500px; |
| max-height: 90vh; |
| overflow-y: auto; |
| padding: 20px; |
| transform: translateY(20px); |
| transition: transform 0.3s; |
| } |
| |
| .modal-overlay.active .modal { |
| transform: translateY(0); |
| } |
| |
| .modal-header { |
| display: flex; |
| justify-content: space-between; |
| align-items: center; |
| margin-bottom: 20px; |
| } |
| |
| .modal-title { |
| font-size: 20px; |
| font-weight: 600; |
| } |
| |
| .close-btn { |
| background: none; |
| border: none; |
| font-size: 24px; |
| color: var(--text); |
| cursor: pointer; |
| opacity: 0.7; |
| } |
| |
| .close-btn:hover { |
| opacity: 1; |
| } |
| |
| .form-group { |
| margin-bottom: 15px; |
| } |
| |
| .form-label { |
| display: block; |
| margin-bottom: 5px; |
| font-size: 14px; |
| font-weight: 500; |
| } |
| |
| .form-input { |
| width: 100%; |
| padding: 10px 15px; |
| border-radius: 8px; |
| border: 1px solid var(--border); |
| background-color: var(--card-bg); |
| color: var(--text); |
| font-size: 14px; |
| } |
| |
| .form-select { |
| width: 100%; |
| padding: 10px 15px; |
| border-radius: 8px; |
| border: 1px solid var(--border); |
| background-color: var(--card-bg); |
| color: var(--text); |
| font-size: 14px; |
| appearance: none; |
| background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e"); |
| background-repeat: no-repeat; |
| background-position: right 10px center; |
| background-size: 1em; |
| } |
| |
| .checkbox-group { |
| display: flex; |
| align-items: center; |
| } |
| |
| .checkbox-label { |
| margin-left: 8px; |
| font-size: 14px; |
| } |
| |
| .btn { |
| padding: 10px 20px; |
| border-radius: 8px; |
| border: none; |
| font-weight: 500; |
| cursor: pointer; |
| font-size: 14px; |
| } |
| |
| .btn-primary { |
| background-color: var(--shared); |
| color: white; |
| } |
| |
| .btn-outline { |
| background: none; |
| border: 1px solid var(--border); |
| color: var(--text); |
| } |
| |
| .category-options { |
| display: grid; |
| grid-template-columns: repeat(2, 1fr); |
| gap: 10px; |
| margin-bottom: 15px; |
| } |
| |
| .category-option { |
| padding: 10px; |
| border-radius: 8px; |
| border: 1px solid var(--border); |
| text-align: center; |
| cursor: pointer; |
| transition: all 0.2s; |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| } |
| |
| .category-option:hover { |
| border-color: var(--personal); |
| } |
| |
| .category-option.selected { |
| border-color: var(--personal); |
| background-color: rgba(66, 165, 245, 0.1); |
| } |
| |
| .category-option.shared.selected { |
| border-color: var(--shared); |
| background-color: rgba(102, 187, 106, 0.1); |
| } |
| |
| .category-icon { |
| font-size: 20px; |
| margin-bottom: 5px; |
| } |
| |
| .voice-btn { |
| display: flex; |
| align-items: center; |
| justify-content: center; |
| width: 100%; |
| padding: 10px; |
| background-color: var(--card-bg); |
| border: 1px dashed var(--border); |
| border-radius: 8px; |
| margin-top: 10px; |
| color: var(--text); |
| cursor: pointer; |
| } |
| |
| .empty-state { |
| text-align: center; |
| padding: 40px 0; |
| color: var(--text); |
| opacity: 0.5; |
| } |
| |
| .empty-state i { |
| font-size: 50px; |
| margin-bottom: 15px; |
| } |
| |
| .completed { |
| text-decoration: line-through; |
| opacity: 0.6; |
| } |
| |
| @media (max-width: 600px) { |
| .container { |
| padding: 15px; |
| } |
| } |
| |
| |
| @keyframes fadeIn { |
| from { opacity: 0; } |
| to { opacity: 1; } |
| } |
| |
| .fade-in { |
| animation: fadeIn 0.3s ease-in-out; |
| } |
| |
| @keyframes slideIn { |
| from { transform: translateY(20px); opacity: 0; } |
| to { transform: translateY(0); opacity: 1; } |
| } |
| |
| .slide-in { |
| animation: slideIn 0.3s ease-in-out; |
| } |
| </style> |
| </head> |
| <body> |
| <div class="container"> |
| <header> |
| <div class="logo">Plan<span>Sync</span></div> |
| <button class="theme-toggle" id="themeToggle"> |
| <i class="fas fa-moon"></i> |
| </button> |
| </header> |
|
|
| <div class="date-controls"> |
| <button class="nav-btn" id="prevDay"><i class="fas fa-chevron-left"></i></button> |
| <div class="current-date" id="currentDate">Today, June 15, 2023</div> |
| <button class="nav-btn" id="nextDay"><i class="fas fa-chevron-right"></i></button> |
| </div> |
|
|
| <div class="view-switcher"> |
| <button class="view-btn active" data-view="day">Day</button> |
| <button class="view-btn" data-view="week">Week</button> |
| <button class="view-btn" data-view="month">Month</button> |
| </div> |
|
|
| <div class="calendar-header slide-in"> |
| <div>S</div> |
| <div>M</div> |
| <div>T</div> |
| <div>W</div> |
| <div>T</div> |
| <div>F</div> |
| <div>S</div> |
| </div> |
|
|
| <div class="calendar-days" id="calendarDays"> |
| |
| </div> |
|
|
| <div class="tabs slide-in"> |
| <div class="tab active" data-tab="personal">Personal Tasks</div> |
| <div class="tab" data-tab="shared">Shared Meetings</div> |
| </div> |
|
|
| <div class="tasks-container"> |
| <div class="task-list" id="personalTasks"> |
| |
| </div> |
|
|
| <div class="task-list" id="sharedTasks" style="display: none;"> |
| |
| </div> |
| </div> |
|
|
| <button class="add-btn" id="addTaskBtn"> |
| <i class="fas fa-plus"></i> |
| </button> |
| </div> |
|
|
| |
| <div class="modal-overlay" id="taskModal"> |
| <div class="modal"> |
| <div class="modal-header"> |
| <div class="modal-title">Add New Task</div> |
| <button class="close-btn" id="closeModal">×</button> |
| </div> |
| <form id="taskForm"> |
| <div class="category-options"> |
| <div class="category-option selected" data-type="personal"> |
| <i class="fas fa-user category-icon"></i> |
| Personal |
| </div> |
| <div class="category-option" data-type="shared"> |
| <i class="fas fa-users category-icon"></i> |
| Shared |
| </div> |
| </div> |
|
|
| <div class="form-group"> |
| <label class="form-label" for="taskTitle">Task Title</label> |
| <input type="text" id="taskTitle" class="form-input" placeholder="What needs to be done?" required> |
| </div> |
|
|
| <div class="form-group"> |
| <label class="form-label" for="taskTime">Time</label> |
| <input type="datetime-local" id="taskTime" class="form-input" required> |
| </div> |
|
|
| <div class="form-group"> |
| <label class="form-label" for="taskDescription">Description (Optional)</label> |
| <textarea id="taskDescription" class="form-input" rows="3" placeholder="Add details..."></textarea> |
| </div> |
|
|
| <div class="form-group" id="participantsContainer" style="display: none;"> |
| <label class="form-label">Participants</label> |
| <div class="participant-input"> |
| <input type="text" id="participantEmail" class="form-input" placeholder="Enter email"> |
| <button type="button" class="btn btn-outline" id="addParticipant" style="margin-top: 5px;">Add</button> |
| </div> |
| <div class="task-participants" id="participantsList" style="margin-top: 10px;"> |
| |
| </div> |
| </div> |
|
|
| <div class="form-group"> |
| <div class="checkbox-group"> |
| <input type="checkbox" id="isImportant"> |
| <label class="checkbox-label" for="isImportant">Mark as Important</label> |
| </div> |
| </div> |
|
|
| <button type="button" class="voice-btn" id="voiceInputBtn"> |
| <i class="fas fa-microphone"></i> Voice Input |
| </button> |
|
|
| <div class="form-group" style="margin-top: 20px;"> |
| <button type="submit" class="btn btn-primary" style="width: 100%;">Add Task</button> |
| </div> |
| </form> |
| </div> |
| </div> |
|
|
| <script> |
| document.addEventListener('DOMContentLoaded', function() { |
| |
| const themeToggle = document.getElementById('themeToggle'); |
| const prevDayBtn = document.getElementById('prevDay'); |
| const nextDayBtn = document.getElementById('nextDay'); |
| const currentDateEl = document.getElementById('currentDate'); |
| const viewSwitcherBtns = document.querySelectorAll('.view-btn'); |
| const calendarDaysEl = document.getElementById('calendarDays'); |
| const tabBtns = document.querySelectorAll('.tab'); |
| const personalTasksEl = document.getElementById('personalTasks'); |
| const sharedTasksEl = document.getElementById('sharedTasks'); |
| const addTaskBtn = document.getElementById('addTaskBtn'); |
| const taskModal = document.getElementById('taskModal'); |
| const closeModalBtn = document.getElementById('closeModal'); |
| const categoryOptions = document.querySelectorAll('.category-option'); |
| const participantsContainer = document.getElementById('participantsContainer'); |
| const participantEmail = document.getElementById('participantEmail'); |
| const addParticipantBtn = document.getElementById('addParticipant'); |
| const participantsList = document.getElementById('participantsList'); |
| const taskForm = document.getElementById('taskForm'); |
| const voiceInputBtn = document.getElementById('voiceInputBtn'); |
| |
| |
| let darkMode = localStorage.getItem('darkMode') === 'true'; |
| let currentDate = new Date(); |
| let activeView = 'day'; |
| let activeTab = 'personal'; |
| let taskType = 'personal'; |
| let tasks = [ |
| { |
| id: 1, |
| title: 'Team meeting', |
| description: 'Discuss project milestones', |
| time: '2023-06-15T10:00', |
| type: 'shared', |
| important: true, |
| completed: false, |
| participants: ['jane@example.com', 'mike@example.com'] |
| }, |
| { |
| id: 2, |
| title: 'Gym session', |
| description: 'Cardio and weight training', |
| time: '2023-06-15T07:00', |
| type: 'personal', |
| important: false, |
| completed: true, |
| participants: [] |
| }, |
| { |
| id: 3, |
| title: 'Doctor appointment', |
| description: 'Annual checkup', |
| time: '2023-06-16T14:30', |
| type: 'personal', |
| important: true, |
| completed: false, |
| participants: [] |
| }, |
| { |
| id: 4, |
| title: 'Project deadline', |
| description: 'Submit final report', |
| time: '2023-06-18T17:00', |
| type: 'personal', |
| important: true, |
| completed: false, |
| participants: [] |
| }, |
| { |
| id: 5, |
| title: 'Birthday party', |
| description: 'Alex is turning 30!', |
| time: '2023-06-17T19:00', |
| type: 'shared', |
| important: false, |
| completed: false, |
| participants: ['alex@example.com', 'sarah@example.com'] |
| }, |
| { |
| id: 6, |
| title: 'Grocery shopping', |
| description: 'Milk, eggs, bread, fruits', |
| time: '2023-06-15T18:00', |
| type: 'personal', |
| important: false, |
| completed: false, |
| participants: [] |
| } |
| ]; |
| |
| |
| init(); |
| |
| function init() { |
| |
| updateTheme(); |
| |
| |
| updateDateDisplay(); |
| |
| |
| generateCalendarDays(); |
| |
| |
| renderTasks(); |
| |
| |
| setupEventListeners(); |
| } |
| |
| function setupEventListeners() { |
| |
| themeToggle.addEventListener('click', toggleTheme); |
| |
| |
| prevDayBtn.addEventListener('click', () => { |
| if (activeView === 'day') { |
| currentDate.setDate(currentDate.getDate() - 1); |
| } else if (activeView === 'week') { |
| currentDate.setDate(currentDate.getDate() - 7); |
| } else if (activeView === 'month') { |
| currentDate.setMonth(currentDate.getMonth() - 1); |
| } |
| updateDateDisplay(); |
| generateCalendarDays(); |
| }); |
| |
| nextDayBtn.addEventListener('click', () => { |
| if (activeView === 'day') { |
| currentDate.setDate(currentDate.getDate() + 1); |
| } else if (activeView === 'week') { |
| currentDate.setDate(currentDate.getDate() + 7); |
| } else if (activeView === 'month') { |
| currentDate.setMonth(currentDate.getMonth() + 1); |
| } |
| updateDateDisplay(); |
| generateCalendarDays(); |
| }); |
| |
| |
| viewSwitcherBtns.forEach(btn => { |
| btn.addEventListener('click', () => { |
| viewSwitcherBtns.forEach(b => b.classList.remove('active')); |
| btn.classList.add('active'); |
| activeView = btn.dataset.view; |
| updateDateDisplay(); |
| generateCalendarDays(); |
| }); |
| }); |
| |
| |
| tabBtns.forEach(btn => { |
| btn.addEventListener('click', () => { |
| tabBtns.forEach(b => b.classList.remove('active')); |
| btn.classList.add('active'); |
| activeTab = btn.dataset.tab; |
| |
| if (activeTab === 'personal') { |
| personalTasksEl.style.display = 'block'; |
| sharedTasksEl.style.display = 'none'; |
| } else { |
| personalTasksEl.style.display = 'none'; |
| sharedTasksEl.style.display = 'block'; |
| } |
| }); |
| }); |
| |
| |
| addTaskBtn.addEventListener('click', () => { |
| taskModal.classList.add('active'); |
| }); |
| |
| closeModalBtn.addEventListener('click', () => { |
| taskModal.classList.remove('active'); |
| }); |
| |
| |
| categoryOptions.forEach(option => { |
| option.addEventListener('click', () => { |
| categoryOptions.forEach(opt => opt.classList.remove('selected')); |
| option.classList.add('selected'); |
| |
| taskType = option.dataset.type; |
| |
| if (taskType === 'shared') { |
| participantsContainer.style.display = 'block'; |
| option.classList.add('shared'); |
| option.classList.remove('selected'); |
| option.classList.add('shared.selected'); |
| } else { |
| participantsContainer.style.display = 'none'; |
| } |
| }); |
| }); |
| |
| |
| addParticipantBtn.addEventListener('click', () => { |
| const email = participantEmail.value.trim(); |
| if (email && isValidEmail(email)) { |
| const participant = document.createElement('div'); |
| participant.className = 'participant'; |
| participant.textContent = email.charAt(0).toUpperCase(); |
| participant.title = email; |
| |
| const removeBtn = document.createElement('span'); |
| removeBtn.className = 'action-btn'; |
| removeBtn.innerHTML = '×'; |
| removeBtn.style.marginLeft = '5px'; |
| removeBtn.style.fontSize = '12px'; |
| removeBtn.addEventListener('click', () => { |
| participant.remove(); |
| }); |
| |
| participant.appendChild(removeBtn); |
| participantsList.appendChild(participant); |
| participantEmail.value = ''; |
| } |
| }); |
| |
| |
| taskForm.addEventListener('submit', (e) => { |
| e.preventDefault(); |
| |
| const title = document.getElementById('taskTitle').value; |
| const time = document.getElementById('taskTime').value; |
| const description = document.getElementById('taskDescription').value; |
| const isImportant = document.getElementById('isImportant').checked; |
| |
| |
| const participantElements = participantsList.querySelectorAll('.participant'); |
| const participants = Array.from(participantElements).map(el => el.title); |
| |
| |
| const newTask = { |
| id: Date.now(), |
| title, |
| description, |
| time, |
| type: taskType, |
| important: isImportant, |
| completed: false, |
| participants |
| }; |
| |
| tasks.push(newTask); |
| renderTasks(); |
| |
| |
| taskForm.reset(); |
| participantsList.innerHTML = ''; |
| taskType = 'personal'; |
| categoryOptions[0].classList.add('selected'); |
| categoryOptions[1].classList.remove('selected'); |
| participantsContainer.style.display = 'none'; |
| |
| |
| taskModal.classList.remove('active'); |
| }); |
| |
| |
| voiceInputBtn.addEventListener('click', () => { |
| alert('In a real app, this would activate the speech recognition API. For this demo, we simulate adding a sample task.'); |
| |
| |
| document.getElementById('taskTitle').value = 'Meeting with client'; |
| document.getElementById('taskTime').value = '2023-06-16T15:00'; |
| document.getElementById('taskDescription').value = 'Discuss new project requirements'; |
| }); |
| } |
| |
| function toggleTheme() { |
| darkMode = !darkMode; |
| localStorage.setItem('darkMode', darkMode); |
| updateTheme(); |
| } |
| |
| function updateTheme() { |
| if (darkMode) { |
| document.documentElement.setAttribute('data-theme', 'dark'); |
| themeToggle.innerHTML = '<i class="fas fa-sun"></i>'; |
| } else { |
| document.documentElement.removeAttribute('data-theme'); |
| themeToggle.innerHTML = '<i class="fas fa-moon"></i>'; |
| } |
| } |
| |
| function updateDateDisplay() { |
| const options = { |
| weekday: 'long', |
| month: 'long', |
| day: 'numeric', |
| year: 'numeric' |
| }; |
| |
| if (activeView === 'day') { |
| currentDateEl.textContent = currentDate.toLocaleDateString('en-US', options); |
| } else if (activeView === 'week') { |
| const weekStart = new Date(currentDate); |
| weekStart.setDate(currentDate.getDate() - currentDate.getDay()); |
| const weekEnd = new Date(weekStart); |
| weekEnd.setDate(weekStart.getDate() + 6); |
| |
| currentDateEl.textContent = `${weekStart.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })} - ${weekEnd.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' })}`; |
| } else if (activeView === 'month') { |
| currentDateEl.textContent = currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }); |
| } |
| } |
| |
| function generateCalendarDays() { |
| calendarDaysEl.innerHTML = ''; |
| |
| if (activeView === 'day') { |
| |
| const day = new Date(currentDate); |
| const dayElement = createCalendarDayElement(day); |
| calendarDaysEl.appendChild(dayElement); |
| } else if (activeView === 'week') { |
| |
| const weekStart = new Date(currentDate); |
| weekStart.setDate(currentDate.getDate() - currentDate.getDay()); |
| |
| for (let i = 0; i < 7; i++) { |
| const day = new Date(weekStart); |
| day.setDate(weekStart.getDate() + i); |
| const dayElement = createCalendarDayElement(day); |
| calendarDaysEl.appendChild(dayElement); |
| } |
| } else if (activeView === 'month') { |
| |
| const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1); |
| const lastDay = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0); |
| |
| |
| const startDay = firstDay.getDay(); |
| |
| |
| const daysInMonth = lastDay.getDate(); |
| const daysToShow = Math.ceil((daysInMonth + startDay) / 7) * 7; |
| |
| |
| for (let i = 0; i < daysToShow; i++) { |
| const day = new Date(firstDay); |
| |
| if (i < startDay) { |
| |
| day.setDate(1 - (startDay - i)); |
| } else if (i >= startDay + daysInMonth) { |
| |
| day.setDate(1 + (i - (startDay + daysInMonth))); |
| } else { |
| |
| day.setDate(1 + (i - startDay)); |
| } |
| |
| const dayElement = createCalendarDayElement(day); |
| calendarDaysEl.appendChild(dayElement); |
| } |
| } |
| } |
| |
| function createCalendarDayElement(date) { |
| const today = new Date(); |
| today.setHours(0, 0, 0, 0); |
| |
| const dateCopy = new Date(date); |
| dateCopy.setHours(0, 0, 0, 0); |
| |
| |
| const hasTasks = tasks.some(task => { |
| const taskDate = new Date(task.time); |
| taskDate.setHours(0, 0, 0, 0); |
| return taskDate.getTime() === dateCopy.getTime(); |
| }); |
| |
| const hasPersonalTasks = tasks.some(task => { |
| const taskDate = new Date(task.time); |
| taskDate.setHours(0, 0, 0, 0); |
| return taskDate.getTime() === dateCopy.getTime() && task.type === 'personal'; |
| }); |
| |
| const hasSharedMeetings = tasks.some(task => { |
| const taskDate = new Date(task.time); |
| taskDate.setHours(0, 0, 0, 0); |
| return taskDate.getTime() === dateCopy.getTime() && task.type === 'shared'; |
| }); |
| |
| const hasImportantTasks = tasks.some(task => { |
| const taskDate = new Date(task.time); |
| taskDate.setHours(0, 0, 0, 0); |
| return taskDate.getTime() === dateCopy.getTime() && task.important; |
| }); |
| |
| |
| const dayElement = document.createElement('div'); |
| dayElement.className = 'calendar-day'; |
| |
| if (dateCopy.getTime() === today.getTime()) { |
| dayElement.classList.add('today'); |
| } |
| |
| |
| if (hasTasks) { |
| dayElement.classList.add('has-events'); |
| |
| if (hasPersonalTasks && hasSharedMeetings) { |
| |
| dayElement.classList.add('has-shared'); |
| } else if (hasSharedMeetings) { |
| dayElement.classList.add('has-shared'); |
| } |
| |
| if (hasImportantTasks) { |
| dayElement.classList.add('has-important'); |
| } |
| } |
| |
| dayElement.textContent = date.getDate(); |
| |
| |
| if (activeView !== 'day' && dateCopy.getTime() === currentDate.getTime()) { |
| dayElement.style.backgroundColor = 'rgba(102, 187, 106, 0.2)'; |
| } |
| |
| |
| dayElement.addEventListener('click', () => { |
| currentDate = new Date(date); |
| if (activeView !== 'day') { |
| |
| viewSwitcherBtns.forEach(b => b.classList.remove('active')); |
| document.querySelector('.view-btn[data-view="day"]').classList.add('active'); |
| activeView = 'day'; |
| } |
| updateDateDisplay(); |
| generateCalendarDays(); |
| }); |
| |
| return dayElement; |
| } |
| |
| function renderTasks() { |
| |
| personalTasksEl.innerHTML = ''; |
| sharedTasksEl.innerHTML = ''; |
| |
| |
| let filteredTasks = []; |
| |
| if (activeView === 'day') { |
| const startOfDay = new Date(currentDate); |
| startOfDay.setHours(0, 0, 0, 0); |
| |
| const endOfDay = new Date(currentDate); |
| endOfDay.setHours(23, 59, 59, 999); |
| |
| filteredTasks = tasks.filter(task => { |
| const taskDate = new Date(task.time); |
| return taskDate >= startOfDay && taskDate <= endOfDay; |
| }); |
| } else if (activeView === 'week') { |
| const weekStart = new Date(currentDate); |
| weekStart.setDate(currentDate.getDate() - currentDate.getDay()); |
| weekStart.setHours(0, 0, 0, 0); |
| |
| const weekEnd = new Date(weekStart); |
| weekEnd.setDate(weekStart.getDate() + 7); |
| weekEnd.setHours(0, 0, 0, 0); |
| |
| filteredTasks = tasks.filter(task => { |
| const taskDate = new Date(task.time); |
| return taskDate >= weekStart && taskDate < weekEnd; |
| }); |
| } else if (activeView === 'month') { |
| const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1); |
| const lastDay = new Date(currentDate.getFullYear(), currentDate.getMonth() + 1, 0); |
| |
| filteredTasks = tasks.filter(task => { |
| const taskDate = new Date(task.time); |
| return taskDate >= firstDay && taskDate <= lastDay; |
| }); |
| } |
| |
| |
| filteredTasks.sort((a, b) => new Date(a.time) - new Date(b.time)); |
| |
| |
| const personalTasks = filteredTasks.filter(task => task.type === 'personal'); |
| const sharedTasks = filteredTasks.filter(task => task.type === 'shared'); |
| |
| |
| if (personalTasks.length === 0) { |
| personalTasksEl.innerHTML = ` |
| <div class="empty-state"> |
| <i class="fas fa-tasks"></i> |
| <p>No personal tasks for this period</p> |
| </div> |
| `; |
| } else { |
| personalTasks.forEach(task => { |
| personalTasksEl.appendChild(createTaskCard(task)); |
| }); |
| } |
| |
| |
| if (sharedTasks.length === 0) { |
| sharedTasksEl.innerHTML = ` |
| <div class="empty-state"> |
| <i class="fas fa-users"></i> |
| <p>No shared meetings for this period</p> |
| </div> |
| `; |
| } else { |
| sharedTasks.forEach(task => { |
| sharedTasksEl.appendChild(createTaskCard(task)); |
| }); |
| } |
| } |
| |
| function createTaskCard(task) { |
| const taskDate = new Date(task.time); |
| const timeStr = taskDate.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' }); |
| const dateStr = taskDate.toLocaleDateString('en-US', { weekday: 'short', month: 'short', day: 'numeric' }); |
| |
| const card = document.createElement('div'); |
| card.className = `task-card ${task.type} ${task.important ? 'important' : ''} ${task.completed ? 'completed' : ''} slide-in`; |
| |
| card.innerHTML = ` |
| <div class="task-header"> |
| <div class="task-title">${task.title}</div> |
| <div class="task-time">${dateStr} • ${timeStr}</div> |
| </div> |
| ${task.description ? `<div class="task-desc">${task.description}</div>` : ''} |
| ${task.participants.length > 0 ? ` |
| <div class="task-participants"> |
| ${task.participants.map(p => `<div class="participant" title="${p}">${p.charAt(0).toUpperCase()}</div>`).join('')} |
| </div> |
| ` : ''} |
| <div class="task-actions"> |
| <button class="action-btn complete-btn" title="Mark as ${task.completed ? 'incomplete' : 'completed'}"> |
| <i class="fas fa-${task.completed ? 'undo' : 'check'}"></i> |
| </button> |
| <button class="action-btn delete-btn" title="Delete"> |
| <i class="fas fa-trash"></i> |
| </button> |
| </div> |
| `; |
| |
| |
| const completeBtn = card.querySelector('.complete-btn'); |
| completeBtn.addEventListener('click', (e) => { |
| e.stopPropagation(); |
| task.completed = !task.completed; |
| card.classList.toggle('completed', task.completed); |
| completeBtn.innerHTML = `<i class="fas fa-${task.completed ? 'undo' : 'check'}"></i>`; |
| completeBtn.title = `Mark as ${task.completed ? 'incomplete' : 'completed'}`; |
| }); |
| |
| |
| const deleteBtn = card.querySelector('.delete-btn'); |
| deleteBtn.addEventListener('click', (e) => { |
| e.stopPropagation(); |
| if (confirm('Are you sure you want to delete this task?')) { |
| tasks = tasks.filter(t => t.id !== task.id); |
| card.classList.add('fade-out'); |
| setTimeout(() => { |
| card.remove(); |
| renderTasks(); |
| }, 300); |
| } |
| }); |
| |
| |
| card.addEventListener('click', () => { |
| alert(`Viewing task: ${task.title}\n\nWould open edit modal in a real app.`); |
| }); |
| |
| return card; |
| } |
| |
| function isValidEmail(email) { |
| |
| return email.includes('@') && email.includes('.'); |
| } |
| }); |
| </script> |
| </body> |
| </html> |