hourly-logger / index.html
raj-da-023's picture
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
<!DOCTYPE html>
<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 !important;
}
.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">&times;</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">&times;</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">&times;</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">&lt;</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">&gt;</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">&times;</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">&times;</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">&times;</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">&times;</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>