Spaces:
Running
Running
add color the the date in calendar logged date are greeen not logged dates are red and ,diffent colour to Today's Log popupdate based on hour example 11.35 : light green ,12:00is blue :12:30:lightblue,1:00 yellow ,add view_category,delete_category,add import data to google calander button with import data to google calenders - Initial Deployment
38bf2ff
verified
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Hourly Logger</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<style> | |
/* Custom styles */ | |
.hidden-window { | |
display: none ; | |
} | |
.selected-category { | |
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.5); | |
} | |
.modal-overlay { | |
background-color: rgba(0, 0, 0, 0.5); | |
} | |
.hour-cell { | |
min-height: 40px; | |
} | |
/* Make window draggable */ | |
#appWindow { | |
-webkit-app-region: drag; | |
} | |
#titleBar { | |
-webkit-app-region: drag; | |
} | |
.no-drag { | |
-webkit-app-region: no-drag; | |
} | |
</style> | |
</head> | |
<body class="bg-gray-100 font-sans"> | |
<!-- Main App Window --> | |
<div id="appWindow" class="fixed top-10 left-10 w-80 bg-white rounded-lg shadow-xl overflow-hidden border border-gray-300 z-50"> | |
<div id="titleBar" class="bg-blue-600 text-white p-2 flex justify-between items-center"> | |
<h2 class="font-bold">Hourly Logger</h2> | |
<div class="no-drag"> | |
<button id="minimizeBtn" class="text-white hover:bg-blue-700 rounded px-2 py-1">_</button> | |
<button id="closeBtn" class="text-white hover:bg-blue-700 rounded px-2 py-1">×</button> | |
</div> | |
</div> | |
<div class="p-4"> | |
<div class="flex flex-wrap gap-2 mb-4"> | |
<button id="startBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded">Start</button> | |
<button id="stopBtn" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded">Stop</button> | |
<button id="viewTodayBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">View Today</button> | |
<button id="calendarBtn" class="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded">Calendar</button> | |
<button id="addCategoryBtn" class="bg-yellow-500 hover:bg-yellow-600 text-white px-4 py-2 rounded">Add Category</button> | |
<button id="viewCategoriesBtn" class="bg-amber-500 hover:bg-amber-600 text-white px-4 py-2 rounded">View Categories</button> | |
<button id="deleteCategoryBtn" class="bg-orange-500 hover:bg-orange-600 text-white px-4 py-2 rounded">Delete Category</button> | |
<button id="addMissedEventBtn" class="bg-indigo-500 hover:bg-indigo-600 text-white px-4 py-2 rounded">Add Missed Event</button> | |
<button id="exportToGoogleBtn" class="bg-teal-500 hover:bg-teal-600 text-white px-4 py-2 rounded">Export to Google</button> | |
</div> | |
<div id="statusText" class="text-sm text-gray-600 mb-4"> | |
Status: Not running | |
</div> | |
</div> | |
</div> | |
<!-- Hourly Popup --> | |
<div id="hourlyPopup" class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-xl p-4 z-50 hidden-window border border-gray-300 w-96"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="font-bold text-lg">What are you doing?</h3> | |
<button id="closePopupBtn" class="text-gray-500 hover:text-gray-700">×</button> | |
</div> | |
<input type="text" id="eventInput" placeholder="Enter your activity..." class="w-full p-2 border border-gray-300 rounded mb-4"> | |
<div id="categoriesContainer" class="flex flex-wrap gap-2 mb-4"> | |
<!-- Categories will be added here dynamically --> | |
</div> | |
<div class="flex justify-between"> | |
<button id="skipBtn" class="bg-gray-300 hover:bg-gray-400 text-gray-800 px-4 py-2 rounded">Skip</button> | |
<button id="saveEventBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">Save</button> | |
</div> | |
</div> | |
<!-- Today's Log View --> | |
<div id="todayLogView" class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-xl p-4 z-50 hidden-window border border-gray-300 w-11/12 max-w-4xl max-h-[80vh] overflow-auto"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="font-bold text-lg">Today's Log - <span id="todayDate"></span></h3> | |
<button id="closeTodayViewBtn" class="text-gray-500 hover:text-gray-700">×</button> | |
</div> | |
<table class="w-full border-collapse"> | |
<thead> | |
<tr class="bg-gray-100"> | |
<th class="p-2 border">Time</th> | |
<th class="p-2 border">Activity</th> | |
<th class="p-2 border">Category</th> | |
</tr> | |
</thead> | |
<tbody id="todayLogTable"> | |
<!-- Log entries will be added here dynamically --> | |
</tbody> | |
</table> | |
</div> | |
<!-- Calendar View --> | |
<div id="calendarView" class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-xl p-4 z-50 hidden-window border border-gray-300 w-11/12 max-w-4xl max-h-[80vh] overflow-auto"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="font-bold text-lg">Calendar</h3> | |
<button id="closeCalendarBtn" class="text-gray-500 hover:text-gray-700">×</button> | |
</div> | |
<div class="flex justify-between items-center mb-4"> | |
<button id="prevMonthBtn" class="bg-gray-200 hover:bg-gray-300 px-3 py-1 rounded"><</button> | |
<h4 id="currentMonthYear" class="font-semibold">Month Year</h4> | |
<button id="nextMonthBtn" class="bg-gray-200 hover:bg-gray-300 px-3 py-1 rounded">></button> | |
</div> | |
<div class="grid grid-cols-7 gap-1 mb-2"> | |
<div class="text-center font-semibold p-2">Sun</div> | |
<div class="text-center font-semibold p-2">Mon</div> | |
<div class="text-center font-semibold p-2">Tue</div> | |
<div class="text-center font-semibold p-2">Wed</div> | |
<div class="text-center font-semibold p-2">Thu</div> | |
<div class="text-center font-semibold p-2">Fri</div> | |
<div class="text-center font-semibold p-2">Sat</div> | |
</div> | |
<div id="calendarGrid" class="grid grid-cols-7 gap-1"> | |
<!-- Calendar days will be added here dynamically --> | |
</div> | |
</div> | |
<!-- Date Events View --> | |
<div id="dateEventsView" class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-xl p-4 z-50 hidden-window border border-gray-300 w-11/12 max-w-4xl max-h-[80vh] overflow-auto"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="font-bold text-lg">Events for <span id="viewedDate"></span></h3> | |
<button id="closeDateEventsBtn" class="text-gray-500 hover:text-gray-700">×</button> | |
</div> | |
<table class="w-full border-collapse"> | |
<thead> | |
<tr class="bg-gray-100"> | |
<th class="p-2 border">Time</th> | |
<th class="p-2 border">Activity</th> | |
<th class="p-2 border">Category</th> | |
</tr> | |
</thead> | |
<tbody id="dateEventsTable"> | |
<!-- Date events will be added here dynamically --> | |
</tbody> | |
</table> | |
</div> | |
<!-- Add Category Popup --> | |
<div id="addCategoryPopup" class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-xl p-4 z-50 hidden-window border border-gray-300 w-96"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="font-bold text-lg">Add New Category</h3> | |
<button id="closeAddCategoryBtn" class="text-gray-500 hover:text-gray-700">×</button> | |
</div> | |
<input type="text" id="newCategoryInput" placeholder="Enter category name..." class="w-full p-2 border border-gray-300 rounded mb-4"> | |
<div class="flex justify-end"> | |
<button id="saveCategoryBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">Save</button> | |
</div> | |
</div> | |
<!-- View/Delete Categories Popup --> | |
<div id="categoriesPopup" class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-xl p-4 z-50 hidden-window border border-gray-300 w-96"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="font-bold text-lg">Categories</h3> | |
<button id="closeCategoriesBtn" class="text-gray-500 hover:text-gray-700">×</button> | |
</div> | |
<div id="categoriesList" class="mb-4 max-h-60 overflow-y-auto"> | |
<!-- Categories will be listed here --> | |
</div> | |
<div class="flex justify-end"> | |
<button id="deleteSelectedCategoryBtn" class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded">Delete Selected</button> | |
</div> | |
</div> | |
<!-- Add Missed Event Popup --> | |
<div id="addMissedEventPopup" class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 bg-white rounded-lg shadow-xl p-4 z-50 hidden-window border border-gray-300 w-96"> | |
<div class="flex justify-between items-center mb-4"> | |
<h3 class="font-bold text-lg">Add Missed Event</h3> | |
<button id="closeMissedEventBtn" class="text-gray-500 hover:text-gray-700">×</button> | |
</div> | |
<div class="mb-4"> | |
<label class="block text-sm font-medium text-gray-700 mb-1">Time</label> | |
<input type="datetime-local" id="missedEventTime" class="w-full p-2 border border-gray-300 rounded"> | |
</div> | |
<input type="text" id="missedEventInput" placeholder="Enter your activity..." class="w-full p-2 border border-gray-300 rounded mb-4"> | |
<div id="missedEventCategories" class="flex flex-wrap gap-2 mb-4"> | |
<!-- Categories will be added here dynamically --> | |
</div> | |
<div class="flex justify-between"> | |
<button id="cancelMissedEventBtn" class="bg-gray-300 hover:bg-gray-400 text-gray-800 px-4 py-2 rounded">Cancel</button> | |
<button id="saveMissedEventBtn" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded">Save</button> | |
</div> | |
</div> | |
<!-- Overlay --> | |
<div id="overlay" class="fixed inset-0 bg-black bg-opacity-50 z-40 hidden-window"></div> | |
<script> | |
// App state | |
const state = { | |
isRunning: false, | |
timer: null, | |
categories: ['Work', 'Play', 'Study', 'Exercise', 'Meal'], | |
events: {}, | |
currentDate: new Date(), | |
calendarDate: new Date(), | |
selectedCategory: null | |
}; | |
// DOM Elements | |
const elements = { | |
appWindow: document.getElementById('appWindow'), | |
startBtn: document.getElementById('startBtn'), | |
stopBtn: document.getElementById('stopBtn'), | |
viewTodayBtn: document.getElementById('viewTodayBtn'), | |
calendarBtn: document.getElementById('calendarBtn'), | |
addCategoryBtn: document.getElementById('addCategoryBtn'), | |
addMissedEventBtn: document.getElementById('addMissedEventBtn'), | |
statusText: document.getElementById('statusText'), | |
hourlyPopup: document.getElementById('hourlyPopup'), | |
eventInput: document.getElementById('eventInput'), | |
categoriesContainer: document.getElementById('categoriesContainer'), | |
skipBtn: document.getElementById('skipBtn'), | |
saveEventBtn: document.getElementById('saveEventBtn'), | |
closePopupBtn: document.getElementById('closePopupBtn'), | |
todayLogView: document.getElementById('todayLogView'), | |
todayDate: document.getElementById('todayDate'), | |
todayLogTable: document.getElementById('todayLogTable'), | |
closeTodayViewBtn: document.getElementById('closeTodayViewBtn'), | |
calendarView: document.getElementById('calendarView'), | |
currentMonthYear: document.getElementById('currentMonthYear'), | |
prevMonthBtn: document.getElementById('prevMonthBtn'), | |
nextMonthBtn: document.getElementById('nextMonthBtn'), | |
calendarGrid: document.getElementById('calendarGrid'), | |
closeCalendarBtn: document.getElementById('closeCalendarBtn'), | |
dateEventsView: document.getElementById('dateEventsView'), | |
viewedDate: document.getElementById('viewedDate'), | |
dateEventsTable: document.getElementById('dateEventsTable'), | |
closeDateEventsBtn: document.getElementById('closeDateEventsBtn'), | |
addCategoryPopup: document.getElementById('addCategoryPopup'), | |
newCategoryInput: document.getElementById('newCategoryInput'), | |
saveCategoryBtn: document.getElementById('saveCategoryBtn'), | |
closeAddCategoryBtn: document.getElementById('closeAddCategoryBtn'), | |
addMissedEventPopup: document.getElementById('addMissedEventPopup'), | |
missedEventTime: document.getElementById('missedEventTime'), | |
missedEventInput: document.getElementById('missedEventInput'), | |
missedEventCategories: document.getElementById('missedEventCategories'), | |
saveMissedEventBtn: document.getElementById('saveMissedEventBtn'), | |
cancelMissedEventBtn: document.getElementById('cancelMissedEventBtn'), | |
closeMissedEventBtn: document.getElementById('closeMissedEventBtn'), | |
overlay: document.getElementById('overlay'), | |
minimizeBtn: document.getElementById('minimizeBtn'), | |
closeBtn: document.getElementById('closeBtn'), | |
viewCategoriesBtn: document.getElementById('viewCategoriesBtn'), | |
deleteCategoryBtn: document.getElementById('deleteCategoryBtn'), | |
exportToGoogleBtn: document.getElementById('exportToGoogleBtn'), | |
categoriesPopup: document.getElementById('categoriesPopup'), | |
categoriesList: document.getElementById('categoriesList'), | |
closeCategoriesBtn: document.getElementById('closeCategoriesBtn'), | |
deleteSelectedCategoryBtn: document.getElementById('deleteSelectedCategoryBtn') | |
}; | |
// Export to Google Calendar | |
function exportToGoogleCalendar() { | |
const events = []; | |
// Collect all events | |
Object.keys(state.events).forEach(dateKey => { | |
Object.keys(state.events[dateKey]).forEach(timeKey => { | |
const event = state.events[dateKey][timeKey]; | |
events.push({ | |
summary: `${event.activity} (${event.category})`, | |
start: event.timestamp, | |
end: event.timestamp | |
}); | |
}); | |
}); | |
// Create ICS file | |
let icsContent = "BEGIN:VCALENDAR\nVERSION:2.0\nPRODID:-//Hourly Logger//EN\n"; | |
events.forEach(event => { | |
icsContent += `BEGIN:VEVENT\n`; | |
icsContent += `DTSTART:${formatICSTimestamp(event.start)}\n`; | |
icsContent += `DTEND:${formatICSTimestamp(event.end)}\n`; | |
icsContent += `SUMMARY:${event.summary}\n`; | |
icsContent += `END:VEVENT\n`; | |
}); | |
icsContent += "END:VCALENDAR"; | |
// Download file | |
const blob = new Blob([icsContent], { type: 'text/calendar' }); | |
const url = URL.createObjectURL(blob); | |
const a = document.createElement('a'); | |
a.href = url; | |
a.download = 'hourly_logger_export.ics'; | |
document.body.appendChild(a); | |
a.click(); | |
document.body.removeChild(a); | |
} | |
// Format timestamp for ICS | |
function formatICSTimestamp(isoString) { | |
return isoString.replace(/[-:]/g, '').replace('.000Z', 'Z'); | |
} | |
// Show categories popup | |
function showCategoriesPopup() { | |
const container = elements.categoriesList; | |
container.innerHTML = ''; | |
state.categories.forEach(category => { | |
const div = document.createElement('div'); | |
div.className = 'flex items-center p-2 hover:bg-gray-100 rounded'; | |
const radio = document.createElement('input'); | |
radio.type = 'radio'; | |
radio.name = 'selectedCategory'; | |
radio.value = category; | |
radio.id = `category-${category}`; | |
radio.className = 'mr-2'; | |
const label = document.createElement('label'); | |
label.htmlFor = `category-${category}`; | |
label.textContent = category; | |
label.className = 'flex-1'; | |
div.appendChild(radio); | |
div.appendChild(label); | |
container.appendChild(div); | |
}); | |
elements.categoriesPopup.classList.remove('hidden-window'); | |
elements.overlay.classList.remove('hidden-window'); | |
} | |
// Close categories popup | |
function closeCategoriesPopup() { | |
elements.categoriesPopup.classList.add('hidden-window'); | |
elements.overlay.classList.add('hidden-window'); | |
} | |
// Delete selected category | |
function deleteSelectedCategory() { | |
const selected = document.querySelector('input[name="selectedCategory"]:checked'); | |
if (!selected) { | |
alert('Please select a category to delete'); | |
return; | |
} | |
const category = selected.value; | |
if (confirm(`Are you sure you want to delete the category "${category}"?`)) { | |
state.categories = state.categories.filter(c => c !== category); | |
saveData(); | |
closeCategoriesPopup(); | |
} | |
} | |
// Initialize the app | |
function init() { | |
// Load saved data from localStorage | |
loadData(); | |
// Set up event listeners | |
setupEventListeners(); | |
// Update UI | |
updateStatus(); | |
// Set current date for today's log | |
updateTodayDate(); | |
// Initialize calendar | |
updateCalendar(); | |
} | |
// Load data from localStorage | |
function loadData() { | |
const savedCategories = localStorage.getItem('hourlyLoggerCategories'); | |
const savedEvents = localStorage.getItem('hourlyLoggerEvents'); | |
if (savedCategories) { | |
state.categories = JSON.parse(savedCategories); | |
} | |
if (savedEvents) { | |
state.events = JSON.parse(savedEvents); | |
} | |
} | |
// Save data to localStorage | |
function saveData() { | |
localStorage.setItem('hourlyLoggerCategories', JSON.stringify(state.categories)); | |
localStorage.setItem('hourlyLoggerEvents', JSON.stringify(state.events)); | |
} | |
// Set up event listeners | |
function setupEventListeners() { | |
// Main buttons | |
elements.startBtn.addEventListener('click', startLogging); | |
elements.stopBtn.addEventListener('click', stopLogging); | |
elements.viewTodayBtn.addEventListener('click', showTodayLog); | |
elements.calendarBtn.addEventListener('click', showCalendar); | |
elements.addCategoryBtn.addEventListener('click', showAddCategoryPopup); | |
elements.viewCategoriesBtn.addEventListener('click', showCategoriesPopup); | |
elements.deleteCategoryBtn.addEventListener('click', showCategoriesPopup); | |
elements.addMissedEventBtn.addEventListener('click', showAddMissedEventPopup); | |
elements.exportToGoogleBtn.addEventListener('click', exportToGoogleCalendar); | |
// Categories popup | |
elements.closeCategoriesBtn.addEventListener('click', closeCategoriesPopup); | |
elements.deleteSelectedCategoryBtn.addEventListener('click', deleteSelectedCategory); | |
// Hourly popup | |
elements.skipBtn.addEventListener('click', skipEvent); | |
elements.saveEventBtn.addEventListener('click', saveEvent); | |
elements.closePopupBtn.addEventListener('click', closeHourlyPopup); | |
// Today's log view | |
elements.closeTodayViewBtn.addEventListener('click', closeTodayLogView); | |
// Calendar view | |
elements.prevMonthBtn.addEventListener('click', prevMonth); | |
elements.nextMonthBtn.addEventListener('click', nextMonth); | |
elements.closeCalendarBtn.addEventListener('click', closeCalendar); | |
// Date events view | |
elements.closeDateEventsBtn.addEventListener('click', closeDateEventsView); | |
// Add category popup | |
elements.saveCategoryBtn.addEventListener('click', saveCategory); | |
elements.closeAddCategoryBtn.addEventListener('click', closeAddCategoryPopup); | |
// Add missed event popup | |
elements.saveMissedEventBtn.addEventListener('click', saveMissedEvent); | |
elements.cancelMissedEventBtn.addEventListener('click', closeAddMissedEventPopup); | |
elements.closeMissedEventBtn.addEventListener('click', closeAddMissedEventPopup); | |
// Window controls | |
elements.minimizeBtn.addEventListener('click', minimizeWindow); | |
elements.closeBtn.addEventListener('click', hideWindow); | |
// Make window draggable | |
let isDragging = false; | |
let offsetX, offsetY; | |
elements.titleBar.addEventListener('mousedown', (e) => { | |
isDragging = true; | |
const rect = elements.appWindow.getBoundingClientRect(); | |
offsetX = e.clientX - rect.left; | |
offsetY = e.clientY - rect.top; | |
}); | |
document.addEventListener('mousemove', (e) => { | |
if (isDragging) { | |
elements.appWindow.style.left = (e.clientX - offsetX) + 'px'; | |
elements.appWindow.style.top = (e.clientY - offsetY) + 'px'; | |
} | |
}); | |
document.addEventListener('mouseup', () => { | |
isDragging = false; | |
}); | |
} | |
// Start logging | |
function startLogging() { | |
if (state.isRunning) return; | |
state.isRunning = true; | |
updateStatus(); | |
// Show initial popup | |
showHourlyPopup(); | |
// Set up hourly timer | |
state.timer = setInterval(() => { | |
showHourlyPopup(); | |
}, 60 * 60 * 1000); // Every hour | |
} | |
// Stop logging | |
function stopLogging() { | |
if (!state.isRunning) return; | |
state.isRunning = false; | |
clearInterval(state.timer); | |
state.timer = null; | |
updateStatus(); | |
} | |
// Update status text | |
function updateStatus() { | |
elements.statusText.textContent = `Status: ${state.isRunning ? 'Running' : 'Not running'}`; | |
elements.startBtn.disabled = state.isRunning; | |
elements.stopBtn.disabled = !state.isRunning; | |
} | |
// Show hourly popup | |
function showHourlyPopup() { | |
// Reset popup state | |
elements.eventInput.value = ''; | |
state.selectedCategory = null; | |
// Update categories buttons | |
updateCategoriesButtons(elements.categoriesContainer); | |
// Show popup and overlay | |
elements.hourlyPopup.classList.remove('hidden-window'); | |
elements.overlay.classList.remove('hidden-window'); | |
// Focus input | |
elements.eventInput.focus(); | |
} | |
// Close hourly popup | |
function closeHourlyPopup() { | |
elements.hourlyPopup.classList.add('hidden-window'); | |
elements.overlay.classList.add('hidden-window'); | |
} | |
// Skip event | |
function skipEvent() { | |
closeHourlyPopup(); | |
} | |
// Save event | |
function saveEvent() { | |
const activity = elements.eventInput.value.trim(); | |
const now = new Date(); | |
const dateKey = formatDateKey(now); | |
const timeKey = formatTimeKey(now); | |
if (!activity && !state.selectedCategory) { | |
alert('Please enter an activity or select a category'); | |
return; | |
} | |
// Initialize date in events if not exists | |
if (!state.events[dateKey]) { | |
state.events[dateKey] = {}; | |
} | |
// Save event | |
state.events[dateKey][timeKey] = { | |
activity: activity || state.selectedCategory, | |
category: state.selectedCategory || 'Uncategorized', | |
timestamp: now.toISOString() | |
}; | |
// Save data | |
saveData(); | |
// Close popup | |
closeHourlyPopup(); | |
} | |
// Show today's log | |
function showTodayLog() { | |
const todayKey = formatDateKey(state.currentDate); | |
// Update date display | |
elements.todayDate.textContent = formatDateDisplay(state.currentDate); | |
// Clear previous entries | |
elements.todayLogTable.innerHTML = ''; | |
// Add entries if they exist | |
if (state.events[todayKey]) { | |
const times = Object.keys(state.events[todayKey]).sort(); | |
times.forEach(time => { | |
const event = state.events[todayKey][time]; | |
const row = document.createElement('tr'); | |
// Color rows based on exact time | |
const [hours, minutes] = time.split(':').map(Number); | |
const totalMinutes = hours * 60 + minutes; | |
let bgColor = 'bg-gray-50'; | |
if (totalMinutes >= 660 && totalMinutes < 720) { // 11:00-12:00 | |
bgColor = 'bg-green-50'; | |
} else if (totalMinutes >= 720 && totalMinutes < 750) { // 12:00-12:30 | |
bgColor = 'bg-blue-50'; | |
} else if (totalMinutes >= 750 && totalMinutes < 780) { // 12:30-13:00 | |
bgColor = 'bg-sky-50'; | |
} else if (totalMinutes >= 780 && totalMinutes < 840) { // 13:00-14:00 | |
bgColor = 'bg-yellow-50'; | |
} else if (totalMinutes >= 840 && totalMinutes < 1080) { // 14:00-18:00 | |
bgColor = 'bg-purple-50'; | |
} else if (totalMinutes >= 1080 && totalMinutes < 1440) { // 18:00-24:00 | |
bgColor = 'bg-indigo-50'; | |
} else { // 00:00-11:00 | |
bgColor = 'bg-gray-50'; | |
} | |
row.className = bgColor; | |
row.innerHTML = ` | |
<td class="p-2 border font-semibold">${time}</td> | |
<td class="p-2 border">${event.activity}</td> | |
<td class="p-2 border">${event.category}</td> | |
`; | |
elements.todayLogTable.appendChild(row); | |
}); | |
} else { | |
const row = document.createElement('tr'); | |
row.innerHTML = ` | |
<td colspan="3" class="p-4 text-center text-gray-500">No events logged today</td> | |
`; | |
elements.todayLogTable.appendChild(row); | |
} | |
// Show view | |
elements.todayLogView.classList.remove('hidden-window'); | |
elements.overlay.classList.remove('hidden-window'); | |
} | |
// Close today's log view | |
function closeTodayLogView() { | |
elements.todayLogView.classList.add('hidden-window'); | |
elements.overlay.classList.add('hidden-window'); | |
} | |
// Show calendar | |
function showCalendar() { | |
updateCalendar(); | |
elements.calendarView.classList.remove('hidden-window'); | |
elements.overlay.classList.remove('hidden-window'); | |
} | |
// Close calendar | |
function closeCalendar() { | |
elements.calendarView.classList.add('hidden-window'); | |
elements.overlay.classList.add('hidden-window'); | |
} | |
// Update calendar display | |
function updateCalendar() { | |
const year = state.calendarDate.getFullYear(); | |
const month = state.calendarDate.getMonth(); | |
// Update month/year display | |
elements.currentMonthYear.textContent = `${new Date(year, month).toLocaleDateString('default', { month: 'long', year: 'numeric' })}`; | |
// Get first day of month and days in month | |
const firstDay = new Date(year, month, 1).getDay(); | |
const daysInMonth = new Date(year, month + 1, 0).getDate(); | |
// Clear calendar | |
elements.calendarGrid.innerHTML = ''; | |
// Add empty cells for days before first day of month | |
for (let i = 0; i < firstDay; i++) { | |
const cell = document.createElement('div'); | |
cell.className = 'p-2 text-center text-gray-400'; | |
elements.calendarGrid.appendChild(cell); | |
} | |
// Add cells for each day of month | |
for (let day = 1; day <= daysInMonth; day++) { | |
const cell = document.createElement('div'); | |
cell.className = 'p-2 text-center border rounded cursor-pointer hover:bg-gray-100 hour-cell'; | |
// Check if this date has events | |
const dateKey = formatDateKey(new Date(year, month, day)); | |
const hasEvents = state.events[dateKey] && Object.keys(state.events[dateKey]).length > 0; | |
if (hasEvents) { | |
cell.classList.add('bg-green-100', 'font-semibold'); | |
} else { | |
cell.classList.add('bg-red-50'); | |
} | |
// Highlight today's date | |
const today = new Date(); | |
if (day === today.getDate() && month === today.getMonth() && year === today.getFullYear()) { | |
cell.classList.add('ring-2', 'ring-blue-500'); | |
} | |
cell.textContent = day; | |
// Add click event to show events for this date | |
cell.addEventListener('click', () => { | |
showDateEvents(new Date(year, month, day)); | |
}); | |
elements.calendarGrid.appendChild(cell); | |
} | |
} | |
// Show previous month | |
function prevMonth() { | |
state.calendarDate.setMonth(state.calendarDate.getMonth() - 1); | |
updateCalendar(); | |
} | |
// Show next month | |
function nextMonth() { | |
state.calendarDate.setMonth(state.calendarDate.getMonth() + 1); | |
updateCalendar(); | |
} | |
// Show events for a specific date | |
function showDateEvents(date) { | |
const dateKey = formatDateKey(date); | |
// Update date display | |
elements.viewedDate.textContent = formatDateDisplay(date); | |
// Clear previous entries | |
elements.dateEventsTable.innerHTML = ''; | |
// Add entries if they exist | |
if (state.events[dateKey]) { | |
const times = Object.keys(state.events[dateKey]).sort(); | |
times.forEach(time => { | |
const event = state.events[dateKey][time]; | |
const row = document.createElement('tr'); | |
row.innerHTML = ` | |
<td class="p-2 border font-semibold">${time}</td> | |
<td class="p-2 border">${event.activity}</td> | |
<td class="p-2 border">${event.category}</td> | |
`; | |
elements.dateEventsTable.appendChild(row); | |
}); | |
} else { | |
const row = document.createElement('tr'); | |
row.innerHTML = ` | |
<td colspan="3" class="p-4 text-center text-gray-500">No events logged for this date</td> | |
`; | |
elements.dateEventsTable.appendChild(row); | |
} | |
// Close calendar and show date events | |
closeCalendar(); | |
elements.dateEventsView.classList.remove('hidden-window'); | |
elements.overlay.classList.remove('hidden-window'); | |
} | |
// Close date events view | |
function closeDateEventsView() { | |
elements.dateEventsView.classList.add('hidden-window'); | |
elements.overlay.classList.add('hidden-window'); | |
} | |
// Show add category popup | |
function showAddCategoryPopup() { | |
elements.newCategoryInput.value = ''; | |
elements.addCategoryPopup.classList.remove('hidden-window'); | |
elements.overlay.classList.remove('hidden-window'); | |
elements.newCategoryInput.focus(); | |
} | |
// Close add category popup | |
function closeAddCategoryPopup() { | |
elements.addCategoryPopup.classList.add('hidden-window'); | |
elements.overlay.classList.add('hidden-window'); | |
} | |
// Save new category | |
function saveCategory() { | |
const categoryName = elements.newCategoryInput.value.trim(); | |
if (!categoryName) { | |
alert('Please enter a category name'); | |
return; | |
} | |
if (!state.categories.includes(categoryName)) { | |
state.categories.push(categoryName); | |
saveData(); | |
closeAddCategoryPopup(); | |
} else { | |
alert('Category already exists'); | |
} | |
} | |
// Show add missed event popup | |
function showAddMissedEventPopup() { | |
// Set default time to now | |
const now = new Date(); | |
const formattedDateTime = formatDateTimeLocal(now); | |
elements.missedEventTime.value = formattedDateTime; | |
// Clear other fields | |
elements.missedEventInput.value = ''; | |
state.selectedCategory = null; | |
// Update categories buttons | |
updateCategoriesButtons(elements.missedEventCategories); | |
// Show popup | |
elements.addMissedEventPopup.classList.remove('hidden-window'); | |
elements.overlay.classList.remove('hidden-window'); | |
elements.missedEventInput.focus(); | |
} | |
// Close add missed event popup | |
function closeAddMissedEventPopup() { | |
elements.addMissedEventPopup.classList.add('hidden-window'); | |
elements.overlay.classList.add('hidden-window'); | |
} | |
// Save missed event | |
function saveMissedEvent() { | |
const activity = elements.missedEventInput.value.trim(); | |
const eventTime = new Date(elements.missedEventTime.value); | |
if (!activity && !state.selectedCategory) { | |
alert('Please enter an activity or select a category'); | |
return; | |
} | |
const dateKey = formatDateKey(eventTime); | |
const timeKey = formatTimeKey(eventTime); | |
// Initialize date in events if not exists | |
if (!state.events[dateKey]) { | |
state.events[dateKey] = {}; | |
} | |
// Save event | |
state.events[dateKey][timeKey] = { | |
activity: activity || state.selectedCategory, | |
category: state.selectedCategory || 'Uncategorized', | |
timestamp: eventTime.toISOString() | |
}; | |
// Save data | |
saveData(); | |
// Close popup | |
closeAddMissedEventPopup(); | |
} | |
// Update categories buttons in a container | |
function updateCategoriesButtons(container) { | |
container.innerHTML = ''; | |
state.categories.forEach(category => { | |
const btn = document.createElement('button'); | |
btn.className = 'px-3 py-1 bg-gray-200 hover:bg-gray-300 rounded text-sm'; | |
btn.textContent = category; | |
btn.addEventListener('click', () => { | |
// Toggle selection | |
if (state.selectedCategory === category) { | |
state.selectedCategory = null; | |
btn.classList.remove('selected-category'); | |
} else { | |
state.selectedCategory = category; | |
// Remove selection from all buttons | |
container.querySelectorAll('button').forEach(b => { | |
b.classList.remove('selected-category'); | |
}); | |
// Add to clicked button | |
btn.classList.add('selected-category'); | |
} | |
}); | |
container.appendChild(btn); | |
}); | |
} | |
// Update today's date display | |
function updateTodayDate() { | |
elements.todayDate.textContent = formatDateDisplay(state.currentDate); | |
} | |
// Format date as key (YYYY-MM-DD) | |
function formatDateKey(date) { | |
return date.toISOString().split('T')[0]; | |
} | |
// Format time as key (HH:MM) | |
function formatTimeKey(date) { | |
return date.toTimeString().substring(0, 5); | |
} | |
// Format date for display | |
function formatDateDisplay(date) { | |
return date.toLocaleDateString('default', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); | |
} | |
// Format datetime-local input value | |
function formatDateTimeLocal(date) { | |
const year = date.getFullYear(); | |
const month = String(date.getMonth() + 1).padStart(2, '0'); | |
const day = String(date.getDate()).padStart(2, '0'); | |
const hours = String(date.getHours()).padStart(2, '0'); | |
const minutes = String(date.getMinutes()).padStart(2, '0'); | |
return `${year}-${month}-${day}T${hours}:${minutes}`; | |
} | |
// Minimize window | |
function minimizeWindow() { | |
elements.appWindow.classList.add('hidden-window'); | |
} | |
// Hide window (run in background) | |
function hideWindow() { | |
elements.appWindow.classList.add('hidden-window'); | |
} | |
// Initialize the app | |
init(); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=raj-da-023/hourly-logger" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |