application-tracker / index.html
kasramojallal's picture
Add 2 files
19e8cf5 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Job Application Tracker Pro</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.progress-bar {
transition: width 0.5s ease-in-out;
}
.chart-bar {
transition: height 0.5s ease-in-out;
}
.date-picker {
position: relative;
}
.date-picker input[type="date"] {
appearance: none;
-webkit-appearance: none;
}
.date-picker::after {
content: '\f073';
font-family: 'Font Awesome 6 Free';
font-weight: 900;
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
pointer-events: none;
color: #6b7280;
}
.chart-container {
height: 180px;
}
.chart-bar {
width: 100%;
border-radius: 4px 4px 0 0;
position: relative;
}
.chart-bar::after {
content: attr(data-count);
position: absolute;
top: -22px;
left: 50%;
transform: translateX(-50%);
font-size: 12px;
font-weight: bold;
color: #4b5563;
}
.chart-goal-line {
position: absolute;
width: 100%;
height: 2px;
background-color: #6b7280;
z-index: 10;
}
.btn-primary {
background-color: #4f46e5;
color: white;
transition: all 0.2s;
}
.btn-primary:hover {
background-color: #4338ca;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.btn-danger {
background-color: #dc2626;
color: white;
transition: all 0.2s;
}
.btn-danger:hover {
background-color: #b91c1c;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.btn-success {
background-color: #059669;
color: white;
transition: all 0.2s;
}
.btn-success:hover {
background-color: #047857;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.btn-secondary {
background-color: #4b5563;
color: white;
transition: all 0.2s;
}
.btn-secondary:hover {
background-color: #374151;
transform: translateY(-1px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.count-btn {
width: 44px;
height: 44px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s;
}
.count-btn:hover {
transform: scale(1.05);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
.input-field {
transition: all 0.2s;
border: 1px solid #d1d5db;
}
.input-field:focus {
border-color: #818cf8;
box-shadow: 0 0 0 3px rgba(129, 140, 248, 0.2);
}
.compact-section {
background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
}
.edit-input {
width: 50px;
padding: 2px 5px;
border: 1px solid #d1d5db;
border-radius: 4px;
text-align: center;
}
.heatmap-day {
width: 14px;
height: 14px;
border-radius: 2px;
margin: 1px;
position: relative;
}
.heatmap-day:hover::after {
content: attr(data-tooltip);
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
background: #333;
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
z-index: 10;
}
.timeline-item {
position: relative;
padding-left: 20px;
margin-bottom: 15px;
}
.timeline-item::before {
content: '';
position: absolute;
left: 0;
top: 5px;
width: 10px;
height: 10px;
border-radius: 50%;
background: #4f46e5;
}
.timeline-item::after {
content: '';
position: absolute;
left: 4px;
top: 15px;
bottom: -15px;
width: 2px;
background: #e5e7eb;
}
.timeline-item:last-child::after {
display: none;
}
.status-badge {
font-size: 10px;
padding: 2px 6px;
border-radius: 10px;
font-weight: 600;
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-6 max-w-6xl">
<!-- Header -->
<header class="mb-6">
<h1 class="text-3xl font-bold text-indigo-700 mb-1">Job Application Tracker Pro</h1>
<p class="text-sm text-gray-600">Track and visualize your job search progress</p>
</header>
<!-- Compact User Section -->
<div class="compact-section rounded-lg shadow-sm p-4 mb-4 border border-gray-200">
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
<div>
<label for="username" class="block text-xs font-medium text-gray-700 mb-1">Username</label>
<div class="flex">
<input type="text" id="username" placeholder="Your name"
class="flex-1 px-3 py-2 text-sm input-field rounded-l-md focus:outline-none focus:ring-1 focus:ring-indigo-500">
<button id="saveUserBtn" class="btn-primary px-3 py-2 text-sm rounded-r-md">
<i class="fas fa-check"></i>
</button>
</div>
</div>
<div>
<label for="dailyGoal" class="block text-xs font-medium text-gray-700 mb-1">Daily Goal</label>
<div class="flex">
<input type="number" id="dailyGoal" min="1" value="30"
class="flex-1 px-3 py-2 text-sm input-field rounded-l-md focus:outline-none focus:ring-1 focus:ring-indigo-500">
<button id="saveGoalBtn" class="btn-primary px-3 py-2 text-sm rounded-r-md">
<i class="fas fa-bullseye"></i>
</button>
</div>
</div>
<div>
<label for="selectedDate" class="block text-xs font-medium text-gray-700 mb-1">Select Date</label>
<div class="date-picker relative">
<input type="date" id="selectedDate" class="w-full px-3 py-2 text-sm input-field rounded-md focus:outline-none focus:ring-1 focus:ring-indigo-500">
</div>
</div>
<div>
<label class="block text-xs font-medium text-gray-700 mb-1">Quick Actions</label>
<div class="flex gap-2">
<button id="todayBtn" class="btn-secondary px-3 py-2 text-xs rounded-md">
<i class="fas fa-calendar-day mr-1"></i> Today
</button>
<button id="yesterdayBtn" class="btn-secondary px-3 py-2 text-xs rounded-md">
<i class="fas fa-arrow-left mr-1"></i> Yesterday
</button>
</div>
</div>
</div>
<div id="currentUser" class="mt-3 text-xs text-gray-600 hidden flex items-center justify-between">
<div>
<span class="font-medium">User:</span> <span id="userDisplay"></span> |
<span class="font-medium">Goal:</span> <span id="goalDisplay"></span> |
<span class="font-medium">Streak:</span> <span id="streakDisplay">0 days</span>
</div>
<div id="dateDisplay" class="text-gray-700"></div>
</div>
</div>
<!-- Daily Progress -->
<div class="bg-white rounded-lg shadow-md p-5 mb-6 border border-gray-100">
<h2 class="text-lg font-semibold mb-3 text-gray-800" id="dailyProgressTitle">Today's Progress</h2>
<div class="mb-4">
<div class="flex justify-between mb-1">
<span class="text-xs font-medium text-gray-700">Applications</span>
<span id="progressText" class="text-xs font-medium text-gray-700">0/30</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2">
<div id="progressBar" class="progress-bar bg-indigo-600 h-2 rounded-full" style="width: 0%"></div>
</div>
</div>
<div class="flex items-center justify-center gap-4 mb-4">
<button id="decreaseBtn" class="count-btn bg-red-500 text-white disabled:opacity-50 disabled:cursor-not-allowed" disabled>
<i class="fas fa-minus"></i>
</button>
<div class="text-3xl font-bold w-16 text-center text-gray-800" id="countDisplay">0</div>
<button id="increaseBtn" class="count-btn bg-green-500 text-white">
<i class="fas fa-plus"></i>
</button>
</div>
<div class="flex gap-3">
<button id="saveBtn" class="btn-primary flex-1 py-2 px-4 text-sm rounded-md">
<i class="fas fa-save mr-1"></i> Save
</button>
<button id="deleteBtn" class="btn-danger flex-1 py-2 px-4 text-sm rounded-md hidden">
<i class="fas fa-trash mr-1"></i> Delete
</button>
<button id="addNoteBtn" class="btn-secondary flex-1 py-2 px-4 text-sm rounded-md">
<i class="fas fa-sticky-note mr-1"></i> Add Note
</button>
</div>
</div>
<!-- Visualization Section -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<!-- Weekly Summary -->
<div class="bg-white rounded-lg shadow-md p-5 border border-gray-100">
<h2 class="text-lg font-semibold mb-3 text-gray-800">Weekly Summary</h2>
<div class="chart-container flex justify-between items-end mb-5 border-b border-gray-200 pb-4">
<!-- Chart bars will be inserted here by JavaScript -->
</div>
<div class="grid grid-cols-3 gap-3">
<div class="bg-indigo-50 p-3 rounded-lg border border-indigo-100">
<div class="text-xs font-medium text-indigo-700">Today</div>
<div class="text-xl font-bold text-indigo-900" id="todayCount">0</div>
</div>
<div class="bg-purple-50 p-3 rounded-lg border border-purple-100">
<div class="text-xs font-medium text-purple-700">This Week</div>
<div class="text-xl font-bold text-purple-900" id="weeklyCount">0</div>
</div>
<div class="bg-green-50 p-3 rounded-lg border border-green-100">
<div class="text-xs font-medium text-green-700">Total</div>
<div class="text-xl font-bold text-green-900" id="totalCount">0</div>
</div>
</div>
</div>
<!-- Heatmap -->
<div class="bg-white rounded-lg shadow-md p-5 border border-gray-100">
<h2 class="text-lg font-semibold mb-3 text-gray-800">Activity Heatmap</h2>
<div class="flex justify-between mb-2">
<span class="text-xs text-gray-500">Last 3 Months</span>
<div class="flex items-center">
<span class="text-xs text-gray-500 mr-2">Less</span>
<div class="flex">
<div class="heatmap-day bg-gray-100"></div>
<div class="heatmap-day bg-green-100"></div>
<div class="heatmap-day bg-green-300"></div>
<div class="heatmap-day bg-green-500"></div>
<div class="heatmap-day bg-green-700"></div>
</div>
<span class="text-xs text-gray-500 ml-2">More</span>
</div>
</div>
<div id="heatmap" class="flex flex-wrap">
<!-- Heatmap will be inserted here by JavaScript -->
</div>
</div>
<!-- Streak & Stats -->
<div class="bg-white rounded-lg shadow-md p-5 border border-gray-100">
<h2 class="text-lg font-semibold mb-3 text-gray-800">Your Stats</h2>
<div class="grid grid-cols-2 gap-4 mb-4">
<div class="bg-blue-50 p-3 rounded-lg border border-blue-100">
<div class="text-xs font-medium text-blue-700">Current Streak</div>
<div class="text-xl font-bold text-blue-900" id="currentStreak">0 days</div>
</div>
<div class="bg-yellow-50 p-3 rounded-lg border border-yellow-100">
<div class="text-xs font-medium text-yellow-700">Best Streak</div>
<div class="text-xl font-bold text-yellow-900" id="bestStreak">0 days</div>
</div>
</div>
<div class="bg-pink-50 p-3 rounded-lg border border-pink-100 mb-4">
<div class="text-xs font-medium text-pink-700">Completion Rate</div>
<div class="text-xl font-bold text-pink-900" id="completionRate">0%</div>
</div>
<div class="text-xs text-gray-500">
<div class="flex justify-between mb-1">
<span>Applications per day:</span>
<span id="avgPerDay">0</span>
</div>
<div class="flex justify-between">
<span>Days applied:</span>
<span id="daysApplied">0</span>
</div>
</div>
</div>
</div>
<!-- Application History -->
<div class="bg-white rounded-lg shadow-md p-5 mb-6 border border-gray-100">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-semibold text-gray-800">Application History</h2>
<div class="flex gap-2">
<button id="sortDateBtn" class="btn-secondary px-3 py-1 text-xs rounded-md">
<i class="fas fa-sort-amount-down mr-1"></i> Sort by Date
</button>
<button id="sortCountBtn" class="btn-secondary px-3 py-1 text-xs rounded-md">
<i class="fas fa-sort-numeric-down mr-1"></i> Sort by Count
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Date</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Applications</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Goal</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Notes</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="historyTable" class="bg-white divide-y divide-gray-200">
<!-- History rows will be inserted here by JavaScript -->
</tbody>
</table>
</div>
</div>
<!-- Timeline View -->
<div class="bg-white rounded-lg shadow-md p-5 mb-6 border border-gray-100">
<h2 class="text-lg font-semibold mb-3 text-gray-800">Application Timeline</h2>
<div id="timeline" class="space-y-4">
<!-- Timeline items will be inserted here by JavaScript -->
</div>
</div>
<!-- All Users Data (Admin View) -->
<div id="adminSection" class="bg-white rounded-lg shadow-md p-5 border border-gray-100 hidden">
<div class="flex justify-between items-center mb-4">
<h2 class="text-lg font-semibold text-gray-800">All Users Progress</h2>
<div class="flex gap-2">
<button id="refreshUsersBtn" class="btn-secondary px-3 py-1 text-sm rounded-md">
<i class="fas fa-sync-alt mr-1"></i> Refresh
</button>
<button id="exportDataBtn" class="btn-success px-3 py-1 text-sm rounded-md">
<i class="fas fa-file-export mr-1"></i> Export Data
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">User</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Today</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Weekly Avg</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Total</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Streak</th>
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody id="usersTable" class="bg-white divide-y divide-gray-200">
<!-- Users data will be inserted here by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Edit Modal -->
<div id="editModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-800" id="editModalTitle">Edit Entry</h3>
<button id="closeEditModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Date</label>
<div class="date-picker relative">
<input type="date" id="editDate" class="w-full px-3 py-2 text-sm input-field rounded-md focus:outline-none focus:ring-1 focus:ring-indigo-500">
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Applications</label>
<input type="number" id="editCount" min="0" class="w-full px-3 py-2 text-sm input-field rounded-md focus:outline-none focus:ring-1 focus:ring-indigo-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Goal</label>
<input type="number" id="editGoal" min="1" class="w-full px-3 py-2 text-sm input-field rounded-md focus:outline-none focus:ring-1 focus:ring-indigo-500">
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Notes</label>
<textarea id="editNotes" class="w-full px-3 py-2 text-sm input-field rounded-md focus:outline-none focus:ring-1 focus:ring-indigo-500" rows="3"></textarea>
</div>
</div>
<div class="mt-6 flex justify-end space-x-3">
<button id="cancelEdit" class="btn-secondary px-4 py-2 text-sm rounded-md">
Cancel
</button>
<button id="saveEdit" class="btn-primary px-4 py-2 text-sm rounded-md">
Save Changes
</button>
</div>
</div>
</div>
<!-- Note Modal -->
<div id="noteModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-lg p-6 w-full max-w-md">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-800" id="noteModalTitle">Add Note</h3>
<button id="closeNoteModal" class="text-gray-500 hover:text-gray-700">
<i class="fas fa-times"></i>
</button>
</div>
<div class="space-y-4">
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Note</label>
<textarea id="noteContent" class="w-full px-3 py-2 text-sm input-field rounded-md focus:outline-none focus:ring-1 focus:ring-indigo-500" rows="5" placeholder="Add details about your applications today..."></textarea>
</div>
</div>
<div class="mt-6 flex justify-end space-x-3">
<button id="cancelNote" class="btn-secondary px-4 py-2 text-sm rounded-md">
Cancel
</button>
<button id="saveNote" class="btn-primary px-4 py-2 text-sm rounded-md">
Save Note
</button>
</div>
</div>
</div>
<script>
// DOM Elements
const usernameInput = document.getElementById('username');
const saveUserBtn = document.getElementById('saveUserBtn');
const dailyGoalInput = document.getElementById('dailyGoal');
const saveGoalBtn = document.getElementById('saveGoalBtn');
const currentUserDiv = document.getElementById('currentUser');
const userDisplay = document.getElementById('userDisplay');
const goalDisplay = document.getElementById('goalDisplay');
const dateDisplay = document.getElementById('dateDisplay');
const streakDisplay = document.getElementById('streakDisplay');
const decreaseBtn = document.getElementById('decreaseBtn');
const increaseBtn = document.getElementById('increaseBtn');
const countDisplay = document.getElementById('countDisplay');
const progressText = document.getElementById('progressText');
const progressBar = document.getElementById('progressBar');
const saveBtn = document.getElementById('saveBtn');
const deleteBtn = document.getElementById('deleteBtn');
const addNoteBtn = document.getElementById('addNoteBtn');
const historyTable = document.getElementById('historyTable');
const adminSection = document.getElementById('adminSection');
const usersTable = document.getElementById('usersTable');
const selectedDateInput = document.getElementById('selectedDate');
const dailyProgressTitle = document.getElementById('dailyProgressTitle');
const todayCountEl = document.getElementById('todayCount');
const weeklyCountEl = document.getElementById('weeklyCount');
const totalCountEl = document.getElementById('totalCount');
const refreshUsersBtn = document.getElementById('refreshUsersBtn');
const exportDataBtn = document.getElementById('exportDataBtn');
const editModal = document.getElementById('editModal');
const closeEditModal = document.getElementById('closeEditModal');
const editModalTitle = document.getElementById('editModalTitle');
const editDate = document.getElementById('editDate');
const editCount = document.getElementById('editCount');
const editGoal = document.getElementById('editGoal');
const editNotes = document.getElementById('editNotes');
const cancelEdit = document.getElementById('cancelEdit');
const saveEdit = document.getElementById('saveEdit');
const noteModal = document.getElementById('noteModal');
const closeNoteModal = document.getElementById('closeNoteModal');
const noteModalTitle = document.getElementById('noteModalTitle');
const noteContent = document.getElementById('noteContent');
const cancelNote = document.getElementById('cancelNote');
const saveNote = document.getElementById('saveNote');
const todayBtn = document.getElementById('todayBtn');
const yesterdayBtn = document.getElementById('yesterdayBtn');
const sortDateBtn = document.getElementById('sortDateBtn');
const sortCountBtn = document.getElementById('sortCountBtn');
const heatmap = document.getElementById('heatmap');
const timeline = document.getElementById('timeline');
const currentStreakEl = document.getElementById('currentStreak');
const bestStreakEl = document.getElementById('bestStreak');
const completionRateEl = document.getElementById('completionRate');
const avgPerDayEl = document.getElementById('avgPerDay');
const daysAppliedEl = document.getElementById('daysApplied');
// State
let currentUser = localStorage.getItem('currentUser') || '';
let dailyGoal = parseInt(localStorage.getItem(`${currentUser}_dailyGoal`)) || 30;
let currentCount = 0;
let selectedDate = new Date().toISOString().split('T')[0]; // Today's date in YYYY-MM-DD format
let applicationsData = JSON.parse(localStorage.getItem('applicationsData')) || {};
let allUsersData = JSON.parse(localStorage.getItem('allUsersData')) || {};
let currentEditEntry = null;
let sortOrder = 'dateDesc'; // dateDesc, dateAsc, countDesc, countAsc
// Initialize
function init() {
// Set today's date as default
selectedDateInput.value = selectedDate;
selectedDateInput.max = selectedDate; // Don't allow future dates
if (currentUser) {
usernameInput.value = currentUser;
dailyGoalInput.value = dailyGoal;
showCurrentUser();
loadDateData();
loadHistory();
loadAllUsersData();
updateStats();
}
// Check if current user is admin (loliloli)
if (currentUser === 'loliloli') {
adminSection.classList.remove('hidden');
}
}
// Show current user info
function showCurrentUser() {
currentUserDiv.classList.remove('hidden');
userDisplay.textContent = currentUser;
goalDisplay.textContent = dailyGoal;
progressText.textContent = `0/${dailyGoal}`;
updateDateDisplay();
}
// Update date display
function updateDateDisplay() {
const dateObj = new Date(selectedDate);
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'];
const day = dateObj.getDate();
const month = monthNames[dateObj.getMonth()];
const dayOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][dateObj.getDay()];
// Get ordinal suffix for day
let suffix = 'th';
if (day % 10 === 1 && day !== 11) suffix = 'st';
else if (day % 10 === 2 && day !== 12) suffix = 'nd';
else if (day % 10 === 3 && day !== 13) suffix = 'rd';
dateDisplay.textContent = `${dayOfWeek}, ${day}${suffix} ${month}`;
}
// Load data for selected date
function loadDateData() {
if (!currentUser || !applicationsData[currentUser]) {
currentCount = 0;
updateCountDisplay();
deleteBtn.classList.add('hidden');
return;
}
const dateData = applicationsData[currentUser][selectedDate];
if (dateData) {
currentCount = dateData.count;
deleteBtn.classList.remove('hidden');
} else {
currentCount = 0;
deleteBtn.classList.add('hidden');
}
updateCountDisplay();
updateTitle();
updateDateDisplay();
}
// Update the title based on selected date
function updateTitle() {
const today = new Date().toISOString().split('T')[0];
const yesterday = new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().split('T')[0];
if (selectedDate === today) {
dailyProgressTitle.textContent = "Today's Progress";
} else if (selectedDate === yesterday) {
dailyProgressTitle.textContent = "Yesterday's Progress";
} else {
const dateObj = new Date(selectedDate);
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const day = dateObj.getDate();
const month = monthNames[dateObj.getMonth()];
dailyProgressTitle.textContent = `${day} ${month} Progress`;
}
}
// Update count display
function updateCountDisplay() {
countDisplay.textContent = currentCount;
progressText.textContent = `${currentCount}/${dailyGoal}`;
const progressPercentage = Math.min((currentCount / dailyGoal) * 100, 100);
progressBar.style.width = `${progressPercentage}%`;
// Change progress bar color based on completion
if (progressPercentage >= 100) {
progressBar.classList.remove('bg-indigo-600', 'bg-yellow-500', 'bg-red-500');
progressBar.classList.add('bg-green-500');
} else if (progressPercentage >= 75) {
progressBar.classList.remove('bg-green-500', 'bg-yellow-500', 'bg-red-500');
progressBar.classList.add('bg-indigo-600');
} else if (progressPercentage >= 50) {
progressBar.classList.remove('bg-indigo-600', 'bg-green-500', 'bg-red-500');
progressBar.classList.add('bg-yellow-500');
} else {
progressBar.classList.remove('bg-indigo-600', 'bg-yellow-500', 'bg-green-500');
progressBar.classList.add('bg-red-500');
}
// Enable/disable buttons
decreaseBtn.disabled = currentCount <= 0;
}
// Load history
function loadHistory() {
if (!currentUser || !applicationsData[currentUser]) {
historyTable.innerHTML = '<tr><td colspan="6" class="px-4 py-3 text-center text-xs text-gray-500">No data available</td></tr>';
todayCountEl.textContent = '0';
weeklyCountEl.textContent = '0';
totalCountEl.textContent = '0';
return;
}
// Clear existing rows
historyTable.innerHTML = '';
// Get sorted dates based on current sort order
const userData = applicationsData[currentUser];
let dates = Object.keys(userData);
if (sortOrder === 'dateDesc') {
dates.sort((a, b) => new Date(b) - new Date(a));
} else if (sortOrder === 'dateAsc') {
dates.sort((a, b) => new Date(a) - new Date(b));
} else if (sortOrder === 'countDesc') {
dates.sort((a, b) => userData[b].count - userData[a].count);
} else if (sortOrder === 'countAsc') {
dates.sort((a, b) => userData[a].count - userData[b].count);
}
// Calculate summary counts
const today = new Date().toISOString().split('T')[0];
const todayCount = userData[today] ? userData[today].count : 0;
// Calculate weekly count (last 7 days)
const weeklyDates = dates.filter(date => {
const dateObj = new Date(date);
const todayObj = new Date(today);
return dateObj >= new Date(todayObj.setDate(todayObj.getDate() - 7));
});
const weeklyCount = weeklyDates.reduce((sum, date) => sum + userData[date].count, 0);
// Calculate total count
const totalCount = dates.reduce((sum, date) => sum + userData[date].count, 0);
// Update summary cards
todayCountEl.textContent = todayCount;
weeklyCountEl.textContent = weeklyCount;
totalCountEl.textContent = totalCount;
// Create chart
createWeeklyChart(dates.slice(0, 7).reverse());
// Create heatmap
createHeatmap();
// Create timeline
createTimeline(dates);
// Add rows
dates.forEach(date => {
const entry = userData[date];
const row = document.createElement('tr');
const dateCell = document.createElement('td');
dateCell.className = 'px-4 py-3 whitespace-nowrap text-xs text-gray-500';
dateCell.textContent = formatDate(date);
const countCell = document.createElement('td');
countCell.className = 'px-4 py-3 whitespace-nowrap text-xs text-gray-500';
countCell.textContent = entry.count;
const goalCell = document.createElement('td');
goalCell.className = 'px-4 py-3 whitespace-nowrap text-xs text-gray-500';
goalCell.textContent = entry.goal;
const statusCell = document.createElement('td');
statusCell.className = 'px-4 py-3 whitespace-nowrap text-xs';
const statusSpan = document.createElement('span');
statusSpan.className = entry.count >= entry.goal ?
'status-badge bg-green-100 text-green-800' :
'status-badge bg-red-100 text-red-800';
statusSpan.textContent = entry.count >= entry.goal ? 'Completed' : 'Pending';
statusCell.appendChild(statusSpan);
const notesCell = document.createElement('td');
notesCell.className = 'px-4 py-3 text-xs text-gray-500 max-w-xs truncate';
notesCell.textContent = entry.notes || '—';
notesCell.title = entry.notes || '';
const actionCell = document.createElement('td');
actionCell.className = 'px-4 py-3 whitespace-nowrap text-xs text-gray-500 flex gap-2';
const editButton = document.createElement('button');
editButton.className = 'text-indigo-600 hover:text-indigo-900';
editButton.innerHTML = '<i class="fas fa-edit"></i>';
editButton.title = 'Edit this entry';
editButton.addEventListener('click', () => {
openEditModal(date, entry);
});
const deleteButton = document.createElement('button');
deleteButton.className = 'text-red-600 hover:text-red-900';
deleteButton.innerHTML = '<i class="fas fa-trash"></i>';
deleteButton.title = 'Delete this entry';
deleteButton.addEventListener('click', () => {
if (confirm(`Are you sure you want to delete the entry for ${formatDate(date)}?`)) {
delete applicationsData[currentUser][date];
localStorage.setItem('applicationsData', JSON.stringify(applicationsData));
if (allUsersData[currentUser] && allUsersData[currentUser].applications[date]) {
delete allUsersData[currentUser].applications[date];
localStorage.setItem('allUsersData', JSON.stringify(allUsersData));
}
loadHistory();
updateStats();
// If current date is selected, reset count
if (selectedDate === date) {
currentCount = 0;
updateCountDisplay();
deleteBtn.classList.add('hidden');
}
}
});
actionCell.appendChild(editButton);
actionCell.appendChild(deleteButton);
row.appendChild(dateCell);
row.appendChild(countCell);
row.appendChild(goalCell);
row.appendChild(statusCell);
row.appendChild(notesCell);
row.appendChild(actionCell);
historyTable.appendChild(row);
});
}
// Create weekly chart
function createWeeklyChart(dates) {
const chartContainer = document.querySelector('.chart-container');
chartContainer.innerHTML = '';
if (!dates.length) return;
// Find max value for scaling
const maxValue = Math.max(...dates.map(date => {
return applicationsData[currentUser][date].count;
}), dailyGoal);
dates.forEach(date => {
const entry = applicationsData[currentUser][date];
const day = new Date(date).getDate();
const dayOfWeek = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][new Date(date).getDay()];
const count = entry.count;
const goal = entry.goal;
const barContainer = document.createElement('div');
barContainer.className = 'flex flex-col items-center w-full';
const barWrapper = document.createElement('div');
barWrapper.className = 'relative flex-1 w-full flex items-end';
barWrapper.style.height = '100%';
// Count bar (foreground)
const countBar = document.createElement('div');
countBar.className = 'chart-bar';
// Color based on count
if (count === 0) {
countBar.classList.add('bg-gray-200');
} else if (count < 5) {
countBar.classList.add('bg-green-100');
} else if (count < 10) {
countBar.classList.add('bg-green-300');
} else if (count < 20) {
countBar.classList.add('bg-green-500');
} else {
countBar.classList.add('bg-green-700');
}
countBar.style.height = `${(count / maxValue) * 100}%`;
countBar.setAttribute('data-count', count);
// Goal line
const goalLine = document.createElement('div');
goalLine.className = 'chart-goal-line';
goalLine.style.bottom = `${(goal / maxValue) * 100}%`;
// Label
const label = document.createElement('div');
label.className = 'text-xs text-gray-500 mt-1';
label.textContent = dayOfWeek;
barWrapper.appendChild(countBar);
barWrapper.appendChild(goalLine);
barContainer.appendChild(barWrapper);
barContainer.appendChild(label);
chartContainer.appendChild(barContainer);
});
}
// Create heatmap
function createHeatmap() {
heatmap.innerHTML = '';
if (!currentUser || !applicationsData[currentUser]) return;
const today = new Date();
const startDate = new Date(today);
startDate.setDate(today.getDate() - 90); // Last 3 months
// Create a map of all dates in the last 90 days
const dateMap = {};
for (let d = new Date(startDate); d <= today; d.setDate(d.getDate() + 1)) {
const dateStr = d.toISOString().split('T')[0];
dateMap[dateStr] = applicationsData[currentUser][dateStr] ? applicationsData[currentUser][dateStr].count : 0;
}
// Group by weeks
const weeks = [];
let currentWeek = [];
let currentDay = new Date(startDate);
while (currentDay <= today) {
currentWeek.push({
date: currentDay.toISOString().split('T')[0],
count: dateMap[currentDay.toISOString().split('T')[0]] || 0
});
if (currentDay.getDay() === 6 || currentDay.toISOString().split('T')[0] === today.toISOString().split('T')[0]) {
weeks.push(currentWeek);
currentWeek = [];
}
currentDay = new Date(currentDay);
currentDay.setDate(currentDay.getDate() + 1);
}
// Create heatmap
weeks.forEach(week => {
const weekContainer = document.createElement('div');
weekContainer.className = 'flex flex-col';
week.forEach(day => {
const dayElement = document.createElement('div');
dayElement.className = 'heatmap-day';
// Color based on count
if (day.count === 0) {
dayElement.classList.add('bg-gray-100');
} else if (day.count < 5) {
dayElement.classList.add('bg-green-100');
} else if (day.count < 10) {
dayElement.classList.add('bg-green-300');
} else if (day.count < 20) {
dayElement.classList.add('bg-green-500');
} else {
dayElement.classList.add('bg-green-700');
}
dayElement.setAttribute('data-tooltip', `${formatDate(day.date)}: ${day.count} applications`);
weekContainer.appendChild(dayElement);
});
heatmap.appendChild(weekContainer);
});
}
// Create timeline
function createTimeline(dates) {
timeline.innerHTML = '';
if (!dates.length) {
timeline.innerHTML = '<p class="text-sm text-gray-500">No applications yet. Start applying to see your timeline!</p>';
return;
}
// Show only last 10 entries in timeline
const recentDates = dates.slice(0, 10);
recentDates.forEach(date => {
const entry = applicationsData[currentUser][date];
const timelineItem = document.createElement('div');
timelineItem.className = 'timeline-item';
const content = document.createElement('div');
content.className = 'text-sm';
const dateSpan = document.createElement('span');
dateSpan.className = 'font-medium text-gray-900';
dateSpan.textContent = formatDate(date);
const countSpan = document.createElement('span');
countSpan.className = 'text-gray-600 ml-2';
countSpan.textContent = `${entry.count} applications`;
const statusSpan = document.createElement('span');
statusSpan.className = entry.count >= entry.goal ?
'status-badge bg-green-100 text-green-800 ml-2' :
'status-badge bg-red-100 text-red-800 ml-2';
statusSpan.textContent = entry.count >= entry.goal ? 'Goal met' : 'Goal not met';
content.appendChild(dateSpan);
content.appendChild(countSpan);
content.appendChild(statusSpan);
if (entry.notes) {
const notesDiv = document.createElement('div');
notesDiv.className = 'text-xs text-gray-500 mt-1';
notesDiv.textContent = entry.notes;
content.appendChild(notesDiv);
}
timelineItem.appendChild(content);
timeline.appendChild(timelineItem);
});
}
// Update stats
function updateStats() {
if (!currentUser || !applicationsData[currentUser]) return;
const userData = applicationsData[currentUser];
const dates = Object.keys(userData).sort((a, b) => new Date(a) - new Date(b));
const today = new Date().toISOString().split('T')[0];
// Calculate streaks
let currentStreak = 0;
let bestStreak = 0;
let tempStreak = 0;
// Check if today has data
const todayHasData = userData[today] !== undefined;
// Start from yesterday if today has no data yet
let checkDate = todayHasData ? today : new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().split('T')[0];
while (userData[checkDate] !== undefined) {
currentStreak++;
tempStreak++;
bestStreak = Math.max(bestStreak, tempStreak);
// Move to previous day
checkDate = new Date(new Date(checkDate).setDate(new Date(checkDate).getDate() - 1)).toISOString().split('T')[0];
}
// Calculate completion rate
const totalEntries = dates.length;
const completedEntries = dates.filter(date => userData[date].count >= userData[date].goal).length;
const completionRate = totalEntries > 0 ? Math.round((completedEntries / totalEntries) * 100) : 0;
// Calculate average applications per day
const totalApplications = dates.reduce((sum, date) => sum + userData[date].count, 0);
const avgPerDay = totalEntries > 0 ? (totalApplications / totalEntries).toFixed(1) : 0;
// Update UI
currentStreakEl.textContent = `${currentStreak} day${currentStreak !== 1 ? 's' : ''}`;
bestStreakEl.textContent = `${bestStreak} day${bestStreak !== 1 ? 's' : ''}`;
completionRateEl.textContent = `${completionRate}%`;
avgPerDayEl.textContent = avgPerDay;
daysAppliedEl.textContent = totalEntries;
streakDisplay.textContent = `${currentStreak} day${currentStreak !== 1 ? 's' : ''}`;
}
// Open edit modal
function openEditModal(date, entry) {
currentEditEntry = { date, entry };
editModalTitle.textContent = `Edit Entry for ${formatDate(date)}`;
editDate.value = date;
editCount.value = entry.count;
editGoal.value = entry.goal;
editNotes.value = entry.notes || '';
editModal.classList.remove('hidden');
}
// Close edit modal
function closeEditModalHandler() {
editModal.classList.add('hidden');
currentEditEntry = null;
}
// Save edited entry
function saveEditHandler() {
const newCount = parseInt(editCount.value);
const newGoal = parseInt(editGoal.value);
const newDate = editDate.value;
const newNotes = editNotes.value;
if (isNaN(newCount) || newCount < 0) {
alert('Please enter a valid application count');
return;
}
if (isNaN(newGoal) || newGoal < 1) {
alert('Please enter a valid goal (minimum 1)');
return;
}
const { date: oldDate, entry } = currentEditEntry;
// If date changed, we need to delete old entry and create new one
if (newDate !== oldDate) {
delete applicationsData[currentUser][oldDate];
if (allUsersData[currentUser] && allUsersData[currentUser].applications[oldDate]) {
delete allUsersData[currentUser].applications[oldDate];
}
}
// Update applicationsData
applicationsData[currentUser][newDate] = {
count: newCount,
goal: newGoal,
notes: newNotes
};
localStorage.setItem('applicationsData', JSON.stringify(applicationsData));
// Update allUsersData
if (!allUsersData[currentUser]) {
allUsersData[currentUser] = {
goal: dailyGoal,
applications: {}
};
}
allUsersData[currentUser].applications[newDate] = {
count: newCount,
goal: newGoal,
notes: newNotes
};
localStorage.setItem('allUsersData', JSON.stringify(allUsersData));
// Reload data
loadHistory();
updateStats();
// If current date is selected, update count
if (selectedDate === newDate || selectedDate === oldDate) {
if (selectedDate === newDate) {
currentCount = newCount;
} else {
currentCount = 0;
}
updateCountDisplay();
if (selectedDate === newDate && newCount > 0) {
deleteBtn.classList.remove('hidden');
} else if (selectedDate === oldDate) {
deleteBtn.classList.add('hidden');
}
}
closeEditModalHandler();
}
// Open note modal
function openNoteModal() {
noteModalTitle.textContent = `Add Note for ${formatDate(selectedDate)}`;
noteContent.value = '';
noteModal.classList.remove('hidden');
}
// Close note modal
function closeNoteModalHandler() {
noteModal.classList.add('hidden');
}
// Save note
function saveNoteHandler() {
const note = noteContent.value.trim();
// Initialize data structures if they don't exist
if (!applicationsData[currentUser]) {
applicationsData[currentUser] = {};
}
if (!applicationsData[currentUser][selectedDate]) {
applicationsData[currentUser][selectedDate] = {
count: currentCount,
goal: dailyGoal,
notes: note
};
} else {
applicationsData[currentUser][selectedDate].notes = note;
}
// Update allUsersData
if (!allUsersData[currentUser]) {
allUsersData[currentUser] = {
goal: dailyGoal,
applications: {}
};
}
if (!allUsersData[currentUser].applications[selectedDate]) {
allUsersData[currentUser].applications[selectedDate] = {
count: currentCount,
goal: dailyGoal,
notes: note
};
} else {
allUsersData[currentUser].applications[selectedDate].notes = note;
}
localStorage.setItem('applicationsData', JSON.stringify(applicationsData));
localStorage.setItem('allUsersData', JSON.stringify(allUsersData));
// Reload history to show the new note
loadHistory();
closeNoteModalHandler();
}
// Load all users data (for admin)
function loadAllUsersData() {
if (currentUser !== 'loliloli') return;
usersTable.innerHTML = '';
const users = Object.keys(allUsersData);
if (users.length === 0) {
usersTable.innerHTML = '<tr><td colspan="6" class="px-4 py-3 text-center text-xs text-gray-500">No user data available</td></tr>';
return;
}
users.forEach(user => {
if (user === 'loliloli') return;
const userData = allUsersData[user];
const today = new Date().toISOString().split('T')[0];
const todayCount = userData.applications[today] ? userData.applications[today].count : 0;
// Calculate weekly average
const dates = Object.keys(userData.applications);
const last7Days = dates
.filter(date => {
const dateObj = new Date(date);
const todayObj = new Date(today);
return dateObj >= new Date(todayObj.setDate(todayObj.getDate() - 7));
})
.map(date => userData.applications[date].count);
const weeklyAvg = last7Days.length > 0 ?
Math.round(last7Days.reduce((a, b) => a + b, 0) / last7Days.length) : 0;
// Calculate total
const total = dates.reduce((sum, date) => sum + userData.applications[date].count, 0);
// Calculate streak
let currentStreak = 0;
let checkDate = today;
while (userData.applications[checkDate] !== undefined) {
currentStreak++;
checkDate = new Date(new Date(checkDate).setDate(new Date(checkDate).getDate() - 1)).toISOString().split('T')[0];
}
const row = document.createElement('tr');
const userCell = document.createElement('td');
userCell.className = 'px-4 py-3 whitespace-nowrap text-xs font-medium text-gray-900';
userCell.textContent = user;
const todayCell = document.createElement('td');
todayCell.className = 'px-4 py-3 whitespace-nowrap text-xs text-gray-500';
todayCell.textContent = todayCount;
const weeklyCell = document.createElement('td');
weeklyCell.className = 'px-4 py-3 whitespace-nowrap text-xs text-gray-500';
weeklyCell.textContent = weeklyAvg;
const totalCell = document.createElement('td');
totalCell.className = 'px-4 py-3 whitespace-nowrap text-xs text-gray-500';
totalCell.textContent = total;
const streakCell = document.createElement('td');
streakCell.className = 'px-4 py-3 whitespace-nowrap text-xs text-gray-500';
streakCell.textContent = `${currentStreak} day${currentStreak !== 1 ? 's' : ''}`;
const actionCell = document.createElement('td');
actionCell.className = 'px-4 py-3 whitespace-nowrap text-xs text-gray-500';
const viewButton = document.createElement('button');
viewButton.className = 'text-indigo-600 hover:text-indigo-900';
viewButton.innerHTML = '<i class="fas fa-eye"></i>';
viewButton.title = 'View user details';
viewButton.addEventListener('click', () => {
alert(`Viewing details for ${user}\nToday: ${todayCount}\nWeekly Avg: ${weeklyAvg}\nTotal: ${total}\nCurrent Streak: ${currentStreak} days`);
});
actionCell.appendChild(viewButton);
row.appendChild(userCell);
row.appendChild(todayCell);
row.appendChild(weeklyCell);
row.appendChild(totalCell);
row.appendChild(streakCell);
row.appendChild(actionCell);
usersTable.appendChild(row);
});
}
// Export data as JSON
function exportData() {
const dataStr = JSON.stringify(allUsersData, null, 2);
const dataUri = 'data:application/json;charset=utf-8,'+ encodeURIComponent(dataStr);
const exportFileDefaultName = 'job-application-tracker-data.json';
const linkElement = document.createElement('a');
linkElement.setAttribute('href', dataUri);
linkElement.setAttribute('download', exportFileDefaultName);
linkElement.click();
}
// Format date for display (just month and day)
function formatDate(dateString) {
const dateObj = new Date(dateString);
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const day = dateObj.getDate();
const month = monthNames[dateObj.getMonth()];
const year = dateObj.getFullYear();
return `${day} ${month} ${year}`;
}
// Event Listeners
saveUserBtn.addEventListener('click', () => {
const newUser = usernameInput.value.trim();
if (!newUser) {
alert('Please enter a username');
return;
}
currentUser = newUser;
localStorage.setItem('currentUser', currentUser);
// Initialize user data if it doesn't exist
if (!applicationsData[currentUser]) {
applicationsData[currentUser] = {};
}
// Initialize allUsersData if it doesn't exist
if (!allUsersData[currentUser]) {
allUsersData[currentUser] = {
goal: dailyGoal,
applications: {}
};
localStorage.setItem('allUsersData', JSON.stringify(allUsersData));
}
// Load user's daily goal
dailyGoal = allUsersData[currentUser].goal || 30;
dailyGoalInput.value = dailyGoal;
showCurrentUser();
loadDateData();
loadHistory();
updateStats();
// Show admin section if user is loliloli
if (currentUser === 'loliloli') {
adminSection.classList.remove('hidden');
loadAllUsersData();
} else {
adminSection.classList.add('hidden');
}
});
saveGoalBtn.addEventListener('click', () => {
const newGoal = parseInt(dailyGoalInput.value);
if (isNaN(newGoal) || newGoal < 1) {
alert('Please enter a valid goal (minimum 1)');
return;
}
dailyGoal = newGoal;
goalDisplay.textContent = dailyGoal;
progressText.textContent = `${currentCount}/${dailyGoal}`;
// Save goal for current user
if (currentUser) {
if (!allUsersData[currentUser]) {
allUsersData[currentUser] = {
goal: dailyGoal,
applications: {}
};
} else {
allUsersData[currentUser].goal = dailyGoal;
}
localStorage.setItem('allUsersData', JSON.stringify(allUsersData));
localStorage.setItem(`${currentUser}_dailyGoal`, dailyGoal);
}
updateCountDisplay();
});
increaseBtn.addEventListener('click', () => {
currentCount++;
updateCountDisplay();
});
decreaseBtn.addEventListener('click', () => {
if (currentCount > 0) {
currentCount--;
updateCountDisplay();
}
});
saveBtn.addEventListener('click', () => {
if (!currentUser) {
alert('Please set a username first');
return;
}
// Save to applicationsData
if (!applicationsData[currentUser]) {
applicationsData[currentUser] = {};
}
applicationsData[currentUser][selectedDate] = {
count: currentCount,
goal: dailyGoal
};
localStorage.setItem('applicationsData', JSON.stringify(applicationsData));
// Save to allUsersData
if (!allUsersData[currentUser]) {
allUsersData[currentUser] = {
goal: dailyGoal,
applications: {}
};
}
allUsersData[currentUser].applications[selectedDate] = {
count: currentCount,
goal: dailyGoal
};
localStorage.setItem('allUsersData', JSON.stringify(allUsersData));
// Reload history and stats
loadHistory();
updateStats();
// If admin, reload all users data
if (currentUser === 'loliloli') {
loadAllUsersData();
}
// Show delete button if this is an existing entry
deleteBtn.classList.remove('hidden');
alert('Applications saved successfully for ' + formatDate(selectedDate) + '!');
});
deleteBtn.addEventListener('click', () => {
if (!confirm('Are you sure you want to delete this entry for ' + formatDate(selectedDate) + '?')) {
return;
}
// Delete from applicationsData
if (applicationsData[currentUser] && applicationsData[currentUser][selectedDate]) {
delete applicationsData[currentUser][selectedDate];
localStorage.setItem('applicationsData', JSON.stringify(applicationsData));
}
// Delete from allUsersData
if (allUsersData[currentUser] && allUsersData[currentUser].applications[selectedDate]) {
delete allUsersData[currentUser].applications[selectedDate];
localStorage.setItem('allUsersData', JSON.stringify(allUsersData));
}
// Reset count for this date
currentCount = 0;
updateCountDisplay();
deleteBtn.classList.add('hidden');
// Reload history and stats
loadHistory();
updateStats();
// If admin, reload all users data
if (currentUser === 'loliloli') {
loadAllUsersData();
}
alert('Entry deleted successfully for ' + formatDate(selectedDate) + '!');
});
// Date selection handling
selectedDateInput.addEventListener('change', () => {
selectedDate = selectedDateInput.value;
loadDateData();
});
// Quick date buttons
todayBtn.addEventListener('click', () => {
selectedDate = new Date().toISOString().split('T')[0];
selectedDateInput.value = selectedDate;
loadDateData();
});
yesterdayBtn.addEventListener('click', () => {
selectedDate = new Date(new Date().setDate(new Date().getDate() - 1)).toISOString().split('T')[0];
selectedDateInput.value = selectedDate;
loadDateData();
});
// Sort buttons
sortDateBtn.addEventListener('click', () => {
sortOrder = sortOrder === 'dateDesc' ? 'dateAsc' : 'dateDesc';
loadHistory();
});
sortCountBtn.addEventListener('click', () => {
sortOrder = sortOrder === 'countDesc' ? 'countAsc' : 'countDesc';
loadHistory();
});
// Edit modal handlers
closeEditModal.addEventListener('click', closeEditModalHandler);
cancelEdit.addEventListener('click', closeEditModalHandler);
saveEdit.addEventListener('click', saveEditHandler);
// Note modal handlers
addNoteBtn.addEventListener('click', openNoteModal);
closeNoteModal.addEventListener('click', closeNoteModalHandler);
cancelNote.addEventListener('click', closeNoteModalHandler);
saveNote.addEventListener('click', saveNoteHandler);
// Refresh users data
refreshUsersBtn.addEventListener('click', loadAllUsersData);
// Export data
exportDataBtn.addEventListener('click', exportData);
// 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=kasramojallal/application-tracker" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>