work-time / index.html
AliDu14's picture
Add 2 files
512e8e4 verified
raw
history blame
23.4 kB
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Учет рабочего времени</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 {
height: 8px;
border-radius: 4px;
background-color: #e5e7eb;
overflow: hidden;
}
.progress-fill {
height: 100%;
border-radius: 4px;
background-color: #3b82f6;
transition: width 0.3s ease;
}
.floating-btn {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.floating-btn:hover {
transform: translateY(-2px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.floating-btn:active {
transform: translateY(0);
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(59, 130, 246, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(59, 130, 246, 0);
}
}
.fade-in {
animation: fadeIn 0.5s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="container mx-auto px-4 py-8 max-w-3xl">
<header class="text-center mb-8">
<h1 class="text-3xl font-bold text-gray-800 mb-2">Учет рабочего времени</h1>
<p class="text-gray-600">Отслеживайте свои рабочие часы и дни</p>
</header>
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
<div class="flex justify-between items-center mb-6">
<div>
<h2 class="text-xl font-semibold text-gray-800">Текущий статус</h2>
<p class="text-gray-500 text-sm" id="current-date">Сегодня: </p>
</div>
<div class="bg-blue-50 text-blue-800 px-4 py-2 rounded-lg">
<span class="font-medium" id="current-status">Не начат</span>
</div>
</div>
<div class="grid grid-cols-2 gap-4 mb-6">
<button id="start-btn" class="floating-btn pulse bg-green-500 hover:bg-green-600 text-white py-4 px-6 rounded-lg font-medium flex items-center justify-center">
<i class="fas fa-play mr-2"></i> Начало дня
</button>
<button id="end-btn" class="floating-btn bg-red-500 hover:bg-red-600 text-white py-4 px-6 rounded-lg font-medium flex items-center justify-center" disabled>
<i class="fas fa-stop mr-2"></i> Конец дня
</button>
</div>
<div class="mb-6">
<div class="flex justify-between mb-2">
<span class="text-sm font-medium text-gray-700">Прогресс рабочего дня</span>
<span class="text-sm font-medium text-gray-700" id="work-progress">0%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progress-fill" style="width: 0%"></div>
</div>
</div>
<div class="grid grid-cols-3 gap-4 text-center">
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">Отработано сегодня</p>
<p class="text-2xl font-bold text-gray-800" id="today-hours">0 ч</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">Отработано дней</p>
<p class="text-2xl font-bold text-gray-800" id="worked-days">0</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">Всего часов</p>
<p class="text-2xl font-bold text-gray-800" id="total-hours">0 ч</p>
</div>
</div>
</div>
<div class="bg-white rounded-xl shadow-md p-6 mb-8">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-800">Статистика за месяц</h2>
<div class="flex items-center">
<button id="prev-month" class="p-2 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-left text-gray-600"></i>
</button>
<span class="mx-4 font-medium" id="current-month">Июнь 2023</span>
<button id="next-month" class="p-2 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-right text-gray-600"></i>
</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-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Дата</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Начало</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Конец</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Часы</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200" id="month-table-body">
<!-- Данные будут добавляться сюда -->
</tbody>
</table>
</div>
<div class="mt-4 text-right">
<p class="text-sm text-gray-500">Итого за месяц:</p>
<p class="text-lg font-semibold" id="month-total">0 рабочих дней, 0 часов</p>
</div>
</div>
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">История</h2>
<div class="space-y-4" id="history-list">
<!-- История будет добавляться сюда -->
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Инициализация хранилища
if (!localStorage.getItem('workTimeTracker')) {
localStorage.setItem('workTimeTracker', JSON.stringify({
currentSession: null,
workDays: {},
monthlyStats: {}
}));
}
const appData = JSON.parse(localStorage.getItem('workTimeTracker'));
// Элементы интерфейса
const startBtn = document.getElementById('start-btn');
const endBtn = document.getElementById('end-btn');
const currentStatus = document.getElementById('current-status');
const todayHours = document.getElementById('today-hours');
const workedDays = document.getElementById('worked-days');
const totalHours = document.getElementById('total-hours');
const progressFill = document.getElementById('progress-fill');
const workProgress = document.getElementById('work-progress');
const currentDate = document.getElementById('current-date');
const currentMonth = document.getElementById('current-month');
const monthTableBody = document.getElementById('month-table-body');
const monthTotal = document.getElementById('month-total');
const historyList = document.getElementById('history-list');
const prevMonthBtn = document.getElementById('prev-month');
const nextMonthBtn = document.getElementById('next-month');
// Текущая дата и время
const now = new Date();
const today = formatDate(now);
currentDate.textContent = `Сегодня: ${formatDate(now, true)}`;
// Текущий месяц для статистики
let currentMonthView = new Date(now.getFullYear(), now.getMonth(), 1);
updateMonthView();
// Проверка активной сессии
if (appData.currentSession) {
const sessionStart = new Date(appData.currentSession.startTime);
if (formatDate(sessionStart) === today) {
// Сегодняшняя сессия активна
startBtn.disabled = true;
endBtn.disabled = false;
currentStatus.textContent = 'Работаю';
document.getElementById('start-btn').classList.remove('pulse');
// Обновление таймера
updateTimer();
const timerInterval = setInterval(updateTimer, 1000);
endBtn.addEventListener('click', function() {
clearInterval(timerInterval);
endWorkSession();
});
} else {
// Сессия из прошлого дня - завершаем ее
endWorkSession(appData.currentSession.startTime, new Date(appData.currentSession.startTime).setHours(23, 59, 59, 999));
}
}
// Обработчики кнопок
startBtn.addEventListener('click', startWorkSession);
// Навигация по месяцам
prevMonthBtn.addEventListener('click', function() {
currentMonthView.setMonth(currentMonthView.getMonth() - 1);
updateMonthView();
});
nextMonthBtn.addEventListener('click', function() {
currentMonthView.setMonth(currentMonthView.getMonth() + 1);
updateMonthView();
});
// Обновление статистики
updateStats();
// Функции
function formatDate(date, withWeekday = false) {
const options = { year: 'numeric', month: '2-digit', day: '2-digit' };
if (withWeekday) {
options.weekday = 'long';
}
return date.toLocaleDateString('ru-RU', options);
}
function formatTime(date) {
return date.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
}
function startWorkSession() {
const startTime = new Date();
appData.currentSession = {
startTime: startTime.toISOString()
};
localStorage.setItem('workTimeTracker', JSON.stringify(appData));
startBtn.disabled = true;
endBtn.disabled = false;
currentStatus.textContent = 'Работаю';
document.getElementById('start-btn').classList.remove('pulse');
// Добавление в историю
addHistoryEvent('Начало рабочего дня', startTime);
// Запуск таймера
updateTimer();
const timerInterval = setInterval(updateTimer, 1000);
endBtn.addEventListener('click', function() {
clearInterval(timerInterval);
endWorkSession();
});
}
function endWorkSession(customStartTime, customEndTime) {
const endTime = customEndTime ? new Date(customEndTime) : new Date();
const startTime = customStartTime ? new Date(customStartTime) : new Date(appData.currentSession.startTime);
const workDate = formatDate(startTime);
const durationMs = endTime - startTime;
const durationHours = (durationMs / (1000 * 60 * 60)).toFixed(2);
// Сохранение данных о рабочем дне
if (!appData.workDays[workDate]) {
appData.workDays[workDate] = {
startTime: startTime.toISOString(),
endTime: endTime.toISOString(),
duration: durationHours
};
// Обновление месячной статистики
const monthKey = `${startTime.getFullYear()}-${String(startTime.getMonth() + 1).padStart(2, '0')}`;
if (!appData.monthlyStats[monthKey]) {
appData.monthlyStats[monthKey] = {
days: 0,
hours: 0
};
}
appData.monthlyStats[monthKey].days += 1;
appData.monthlyStats[monthKey].hours += parseFloat(durationHours);
}
// Очистка текущей сессии
appData.currentSession = null;
localStorage.setItem('workTimeTracker', JSON.stringify(appData));
startBtn.disabled = false;
endBtn.disabled = true;
currentStatus.textContent = 'Не начат';
document.getElementById('start-btn').classList.add('pulse');
// Сброс прогресса
progressFill.style.width = '0%';
workProgress.textContent = '0%';
todayHours.textContent = '0 ч';
// Добавление в историю
addHistoryEvent('Конец рабочего дня', endTime, durationHours);
// Обновление статистики
updateStats();
updateMonthView();
}
function updateTimer() {
const startTime = new Date(appData.currentSession.startTime);
const now = new Date();
const durationMs = now - startTime;
const durationHours = (durationMs / (1000 * 60 * 60)).toFixed(2);
// Обновление отображаемого времени
todayHours.textContent = `${durationHours} ч`;
// Расчет прогресса (8-часовой рабочий день)
const progress = Math.min((durationMs / (8 * 60 * 60 * 1000)) * 100, 100);
progressFill.style.width = `${progress}%`;
workProgress.textContent = `${Math.round(progress)}%`;
}
function updateStats() {
// Подсчет общего количества рабочих дней и часов
let totalDays = 0;
let totalHours = 0;
for (const month in appData.monthlyStats) {
totalDays += appData.monthlyStats[month].days;
totalHours += appData.monthlyStats[month].hours;
}
workedDays.textContent = totalDays;
totalHours.textContent = `${totalHours.toFixed(1)} ч`;
}
function updateMonthView() {
const monthName = currentMonthView.toLocaleDateString('ru-RU', { month: 'long', year: 'numeric' });
currentMonth.textContent = monthName.charAt(0).toUpperCase() + monthName.slice(1);
const monthKey = `${currentMonthView.getFullYear()}-${String(currentMonthView.getMonth() + 1).padStart(2, '0')}`;
const monthStats = appData.monthlyStats[monthKey] || { days: 0, hours: 0 };
// Очистка таблицы
monthTableBody.innerHTML = '';
// Поиск всех рабочих дней в этом месяце
const monthWorkDays = [];
for (const date in appData.workDays) {
const workDay = appData.workDays[date];
const workDate = new Date(workDay.startTime);
if (workDate.getFullYear() === currentMonthView.getFullYear() &&
workDate.getMonth() === currentMonthView.getMonth()) {
monthWorkDays.push({
date: workDate,
startTime: new Date(workDay.startTime),
endTime: new Date(workDay.endTime),
duration: workDay.duration
});
}
}
// Сортировка по дате
monthWorkDays.sort((a, b) => b.date - a.date);
// Заполнение таблицы
monthWorkDays.forEach(day => {
const row = document.createElement('tr');
row.className = 'fade-in';
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">${formatDate(day.date)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${formatTime(day.startTime)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">${formatTime(day.endTime)}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">${day.duration} ч</td>
`;
monthTableBody.appendChild(row);
});
// Итого за месяц
monthTotal.textContent = `${monthStats.days} рабочих дней, ${monthStats.hours.toFixed(1)} часов`;
}
function addHistoryEvent(event, time, duration = null) {
const eventElement = document.createElement('div');
eventElement.className = 'fade-in bg-gray-50 p-4 rounded-lg';
let eventText = `
<div class="flex justify-between items-center">
<div>
<p class="font-medium text-gray-800">${event}</p>
<p class="text-sm text-gray-500">${formatTime(time)}</p>
</div>
`;
if (duration) {
eventText += `
<span class="bg-blue-100 text-blue-800 text-xs font-medium px-2.5 py-0.5 rounded-full">
${duration} ч
</span>
`;
}
eventText += `</div>`;
eventElement.innerHTML = eventText;
// Добавляем в начало списка
if (historyList.firstChild) {
historyList.insertBefore(eventElement, historyList.firstChild);
} else {
historyList.appendChild(eventElement);
}
// Ограничиваем историю 10 последними событиями
if (historyList.children.length > 10) {
historyList.removeChild(historyList.lastChild);
}
}
// Загрузка истории
function loadHistory() {
historyList.innerHTML = '';
// Собираем все события (начала и концы рабочих дней)
const events = [];
for (const date in appData.workDays) {
const workDay = appData.workDays[date];
events.push({
type: 'start',
time: new Date(workDay.startTime),
duration: null
});
events.push({
type: 'end',
time: new Date(workDay.endTime),
duration: workDay.duration
});
}
// Сортировка по времени (новые сначала)
events.sort((a, b) => b.time - a.time);
// Отображение
events.slice(0, 10).forEach(event => {
addHistoryEvent(
event.type === 'start' ? 'Начало рабочего дня' : 'Конец рабочего дня',
event.time,
event.duration
);
});
}
loadHistory();
});
</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=AliDu14/work-time" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>