work-time / index.html
AliDu14's picture
Add 1 files
cf54b36 verified
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WorkTime Bot</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>
body {
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
background-color: var(--tg-theme-bg-color, #f7f8fc);
color: var(--tg-theme-text-color, #000000);
}
.header {
background-color: var(--tg-theme-header-bg-color, #0088cc);
color: var(--tg-theme-header-text-color, #ffffff);
}
.button-primary {
background-color: var(--tg-theme-button-color, #0088cc);
color: var(--tg-theme-button-text-color, #ffffff);
}
.button-secondary {
background-color: var(--tg-theme-secondary-bg-color, #ebedf0);
color: var(--tg-theme-text-color, #000000);
}
.card {
background-color: var(--tg-theme-bg-color, #ffffff);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.progress-bar {
height: 6px;
border-radius: 3px;
background-color: var(--tg-theme-secondary-bg-color, #ebedf0);
}
.progress-fill {
height: 100%;
border-radius: 3px;
background-color: var(--tg-theme-button-color, #0088cc);
transition: width 0.3s ease;
}
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(0, 136, 204, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(0, 136, 204, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(0, 136, 204, 0);
}
}
.fade-in {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.telegram-input {
border: 1px solid var(--tg-theme-secondary-bg-color, #ebedf0);
background-color: var(--tg-theme-bg-color, #ffffff);
color: var(--tg-theme-text-color, #000000);
}
.telegram-hint {
color: var(--tg-theme-hint-color, #707579);
}
</style>
</head>
<body class="min-h-screen">
<div class="header px-4 py-3 flex items-center">
<button id="back-button" class="mr-3">
<i class="fas fa-chevron-left text-white"></i>
</button>
<h1 class="text-lg font-semibold">WorkTime Bot</h1>
</div>
<div class="container mx-auto px-4 py-3">
<!-- Основной экран -->
<div id="main-screen">
<div class="card rounded-xl p-4 mb-3">
<div class="flex justify-between items-center mb-3">
<div>
<h2 class="font-medium">Текущий статус</h2>
<p class="text-sm telegram-hint" id="current-date">Сегодня, 12 июня</p>
</div>
<div class="px-3 py-1 rounded-full bg-blue-100 text-blue-800 text-sm" id="current-status">
Не начат
</div>
</div>
<div class="grid grid-cols-2 gap-2 mb-3">
<button id="start-btn" class="button-primary pulse rounded-lg py-3 px-4 font-medium flex items-center justify-center">
<i class="fas fa-play mr-2"></i> Старт
</button>
<button id="end-btn" class="button-secondary rounded-lg py-3 px-4 font-medium flex items-center justify-center" disabled>
<i class="fas fa-stop mr-2"></i> Стоп
</button>
</div>
<div class="mb-3">
<div class="flex justify-between mb-1">
<span class="text-sm telegram-hint">Прогресс дня</span>
<span class="text-sm font-medium" 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-2 text-center">
<div class="rounded-lg p-2 bg-blue-50">
<p class="text-xs telegram-hint">Сегодня</p>
<p class="font-bold" id="today-hours">0 ч</p>
</div>
<div class="rounded-lg p-2 bg-blue-50">
<p class="text-xs telegram-hint">Дней</p>
<p class="font-bold" id="worked-days">0</p>
</div>
<div class="rounded-lg p-2 bg-blue-50">
<p class="text-xs telegram-hint">Всего</p>
<p class="font-bold" id="total-hours">0 ч</p>
</div>
</div>
</div>
<div class="card rounded-xl p-4 mb-3">
<div class="flex justify-between items-center mb-3">
<h2 class="font-medium">Статистика</h2>
<div class="flex items-center">
<button id="prev-month" class="p-1 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-left telegram-hint"></i>
</button>
<span class="mx-2 text-sm font-medium" id="current-month">Июнь 2023</span>
<button id="next-month" class="p-1 rounded-full hover:bg-gray-100">
<i class="fas fa-chevron-right telegram-hint"></i>
</button>
</div>
</div>
<div class="overflow-hidden rounded-lg">
<table class="min-w-full">
<thead class="bg-gray-100">
<tr>
<th class="px-3 py-2 text-left text-xs font-medium telegram-hint">Дата</th>
<th class="px-3 py-2 text-left text-xs font-medium telegram-hint">Часы</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200" id="month-table-body">
<!-- Данные будут добавляться сюда -->
</tbody>
</table>
</div>
<div class="mt-3 pt-2 border-t border-gray-200 text-right">
<p class="text-xs telegram-hint">Итого за месяц:</p>
<p class="text-sm font-medium" id="month-total">0 дней, 0 часов</p>
</div>
</div>
<div class="card rounded-xl p-4">
<h2 class="font-medium mb-3">История</h2>
<div class="space-y-2" id="history-list">
<!-- История будет добавляться сюда -->
</div>
</div>
</div>
<!-- Экран настроек -->
<div id="settings-screen" class="hidden">
<div class="card rounded-xl p-4 mb-3">
<h2 class="font-medium mb-3">Настройки</h2>
<div class="mb-3">
<label class="block text-sm font-medium mb-1">Рабочих часов в день</label>
<input type="number" id="work-hours-per-day" class="telegram-input w-full rounded-lg px-3 py-2" value="8" min="1" max="24">
</div>
<div class="mb-3">
<label class="block text-sm font-medium mb-1">Уведомления</label>
<div class="flex items-center justify-between">
<span class="text-sm">Напоминать о перерывах</span>
<label class="relative inline-flex items-center cursor-pointer">
<input type="checkbox" id="break-reminders" class="sr-only peer">
<div class="w-9 h-5 bg-gray-200 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-blue-600"></div>
</label>
</div>
</div>
<button id="save-settings" class="button-primary w-full rounded-lg py-3 px-4 font-medium">
Сохранить
</button>
</div>
<div class="card rounded-xl p-4">
<h2 class="font-medium mb-3">О боте</h2>
<p class="text-sm mb-2">WorkTime Bot v1.0</p>
<p class="text-sm telegram-hint">Отслеживайте свое рабочее время прямо в Telegram</p>
</div>
</div>
</div>
<!-- Нижнее меню -->
<div class="fixed bottom-0 left-0 right-0 bg-white border-t border-gray-200 flex justify-around py-2">
<button id="main-tab" class="flex flex-col items-center px-4 py-1 text-blue-600">
<i class="fas fa-home"></i>
<span class="text-xs mt-1">Главная</span>
</button>
<button id="stats-tab" class="flex flex-col items-center px-4 py-1 text-gray-500">
<i class="fas fa-chart-bar"></i>
<span class="text-xs mt-1">Статистика</span>
</button>
<button id="settings-tab" class="flex flex-col items-center px-4 py-1 text-gray-500">
<i class="fas fa-cog"></i>
<span class="text-xs mt-1">Настройки</span>
</button>
</div>
<script>
// Имитация Telegram WebApp
document.addEventListener('DOMContentLoaded', function() {
// Инициализация хранилища
if (!localStorage.getItem('workTimeBot')) {
localStorage.setItem('workTimeBot', JSON.stringify({
currentSession: null,
workDays: {},
monthlyStats: {},
settings: {
workHoursPerDay: 8,
breakReminders: true
}
}));
}
const appData = JSON.parse(localStorage.getItem('workTimeBot'));
// Элементы интерфейса
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 mainScreen = document.getElementById('main-screen');
const settingsScreen = document.getElementById('settings-screen');
const mainTab = document.getElementById('main-tab');
const settingsTab = document.getElementById('settings-tab');
const workHoursInput = document.getElementById('work-hours-per-day');
const breakRemindersToggle = document.getElementById('break-reminders');
const saveSettingsBtn = document.getElementById('save-settings');
const backButton = document.getElementById('back-button');
// Текущая дата и время
const now = new Date();
const today = formatDate(now);
currentDate.textContent = `Сегодня, ${formatDate(now, true)}`;
// Текущий месяц для статистики
let currentMonthView = new Date(now.getFullYear(), now.getMonth(), 1);
updateMonthView();
// Загрузка настроек
workHoursInput.value = appData.settings.workHoursPerDay;
breakRemindersToggle.checked = appData.settings.breakReminders;
// Проверка активной сессии
if (appData.currentSession) {
const sessionStart = new Date(appData.currentSession.startTime);
if (formatDate(sessionStart) === today) {
// Сегодняшняя сессия активна
startBtn.disabled = true;
endBtn.disabled = false;
currentStatus.textContent = 'Работаю';
startBtn.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();
});
// Переключение экранов
settingsTab.addEventListener('click', function() {
mainScreen.classList.add('hidden');
settingsScreen.classList.remove('hidden');
updateTabStyles('settings');
});
mainTab.addEventListener('click', function() {
mainScreen.classList.remove('hidden');
settingsScreen.classList.add('hidden');
updateTabStyles('main');
});
backButton.addEventListener('click', function() {
// В реальном боте это закрыло бы веб-приложение
alert('В реальном Telegram боте это закрыло бы веб-приложение');
});
// Сохранение настроек
saveSettingsBtn.addEventListener('click', function() {
appData.settings.workHoursPerDay = parseInt(workHoursInput.value) || 8;
appData.settings.breakReminders = breakRemindersToggle.checked;
localStorage.setItem('workTimeBot', JSON.stringify(appData));
// Возврат на главный экран
mainScreen.classList.remove('hidden');
settingsScreen.classList.add('hidden');
updateTabStyles('main');
// Показ уведомления
showTelegramNotification('Настройки сохранены');
});
// Обновление статистики
updateStats();
// Функции
function formatDate(date, short = false) {
if (short) {
return date.toLocaleDateString('ru-RU', { day: 'numeric', month: 'long' });
}
return date.toLocaleDateString('ru-RU');
}
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('workTimeBot', JSON.stringify(appData));
startBtn.disabled = true;
endBtn.disabled = false;
currentStatus.textContent = 'Работаю';
startBtn.classList.remove('pulse');
// Добавление в историю
addHistoryEvent('Начало работы', startTime);
// Запуск таймера
updateTimer();
const timerInterval = setInterval(updateTimer, 1000);
endBtn.addEventListener('click', function() {
clearInterval(timerInterval);
endWorkSession();
});
// Имитация уведомления Telegram
showTelegramNotification('Рабочий день начат');
}
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('workTimeBot', JSON.stringify(appData));
startBtn.disabled = false;
endBtn.disabled = true;
currentStatus.textContent = 'Не начат';
startBtn.classList.add('pulse');
// Сброс прогресса
progressFill.style.width = '0%';
workProgress.textContent = '0%';
todayHours.textContent = '0 ч';
// Добавление в историю
addHistoryEvent('Конец работы', endTime, durationHours);
// Обновление статистики
updateStats();
updateMonthView();
// Имитация уведомления Telegram
showTelegramNotification(`Рабочий день завершен: ${durationHours} ч`);
}
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 workHoursPerDay = appData.settings.workHoursPerDay || 8;
const progress = Math.min((durationMs / (workHoursPerDay * 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-3 py-2 text-sm">${formatDate(day.date)}</td>
<td class="px-3 py-2 text-sm font-medium">${day.duration} ч</td>
`;
monthTableBody.appendChild(row);
});
// Если нет данных за месяц
if (monthWorkDays.length === 0) {
const row = document.createElement('tr');
row.innerHTML = `
<td colspan="2" class="px-3 py-4 text-center text-sm telegram-hint">Нет данных за этот месяц</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 p-3 rounded-lg bg-gray-100';
let eventText = `
<div class="flex justify-between items-center">
<div>
<p class="font-medium">${event}</p>
<p class="text-xs telegram-hint">${formatTime(time)}</p>
</div>
`;
if (duration) {
eventText += `
<span class="bg-blue-100 text-blue-800 text-xs font-medium px-2 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 updateTabStyles(activeTab) {
const tabs = ['main', 'stats', 'settings'];
tabs.forEach(tab => {
const tabElement = document.getElementById(`${tab}-tab`);
if (tab === activeTab) {
tabElement.classList.remove('text-gray-500');
tabElement.classList.add('text-blue-600');
} else {
tabElement.classList.remove('text-blue-600');
tabElement.classList.add('text-gray-500');
}
});
}
function showTelegramNotification(message) {
// В реальном Telegram WebApp можно использовать window.Telegram.WebApp.showAlert(message);
console.log('Telegram notification:', message);
// Визуальная имитация уведомления
const notification = document.createElement('div');
notification.className = 'fixed top-4 left-1/2 transform -translate-x-1/2 bg-gray-800 text-white px-4 py-2 rounded-lg shadow-lg text-sm fade-in';
notification.textContent = message;
document.body.appendChild(notification);
setTimeout(() => {
notification.classList.add('opacity-0', 'transition-opacity', 'duration-300');
setTimeout(() => notification.remove(), 300);
}, 3000);
}
// Загрузка истории
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();
updateTabStyles('main');
});
</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>