timetrack-pro-ru / index.html
007m's picture
сделай все на русском все заголовки все и добавь возможность добавить коментарий за смену
97f13b3 verified
Raw
History Blame Contribute Delete
24.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>TimeTrack Pro RU</title>
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/feather-icons"></script>
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
<style>
body {
font-family: 'Roboto', sans-serif;
-webkit-tap-highlight-color: transparent;
}
button, input, select, textarea {
font-size: 16px !important; /* Prevent zoom on focus in mobile browsers */
}
</style>
<style>
.time-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.pulse-animation {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
</head>
<body class="bg-gray-50">
<div id="app" class="min-h-screen">
<!-- Header -->
<header class="bg-indigo-600 text-white shadow-lg">
<div class="container mx-auto px-4 py-6">
<div class="flex justify-between items-center">
<div class="flex items-center space-x-3">
<i data-feather="clock" class="w-8 h-8"></i>
<h1 class="text-2xl font-bold">ТаймТрек Про RU</h1>
</div>
<div class="flex items-center space-x-4">
<button class="px-4 py-2 bg-indigo-700 rounded-lg hover:bg-indigo-800 transition flex items-center">
<i data-feather="user" class="mr-2"></i>
Профиль
</button>
</div>
</div>
</div>
</header>
<!-- Main Content -->
<main class="container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Time Tracking Card -->
<div class="lg:col-span-2">
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-xl font-semibold mb-6 flex items-center">
<i data-feather="watch" class="mr-2 text-indigo-600"></i>
Текущая смена
</h2>
<div class="flex flex-col items-center justify-center py-8">
<div class="text-5xl font-bold mb-4" id="current-time">--:--:--</div>
<div class="text-gray-500 mb-8" id="current-date">Loading date...</div>
<div class="flex space-x-4 mb-8">
<button id="clock-in-btn" class="px-6 py-3 bg-green-500 text-white rounded-lg hover:bg-green-600 transition flex items-center pulse-animation">
<i data-feather="log-in" class="mr-2"></i>
Начать смену
</button>
<button id="clock-out-btn" disabled class="px-6 py-3 bg-red-500 text-white rounded-lg hover:bg-red-600 transition flex items-center opacity-50">
<i data-feather="log-out" class="mr-2"></i>
Закончить смену
</button>
<div id="comment-section" class="w-full mt-4 hidden">
<textarea id="shift-comment" placeholder="Добавьте комментарий к смене" class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500"></textarea>
<button id="save-comment" class="mt-2 px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition">
Сохранить комментарий
</button>
</div>
</div>
<div class="w-full bg-gray-200 rounded-full h-2.5 mb-4">
<div id="progress-bar" class="bg-indigo-600 h-2.5 rounded-full" style="width: 0%"></div>
</div>
<p class="text-sm text-gray-600">Прогресс смены: <span id="progress-text">0%</span></p>
</div>
</div>
<!-- Recent Shifts -->
<div class="bg-white rounded-xl shadow-md p-6 mt-6">
<h2 class="text-xl font-semibold mb-6 flex items-center">
<i data-feather="list" class="mr-2 text-indigo-600"></i>
Последние смены
</h2>
<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 id="shifts-table" class="bg-white divide-y divide-gray-200">
<!-- Data will be inserted here by JavaScript -->
</tbody>
</table>
</div>
</div>
</div>
<!-- Stats and Summary -->
<div class="flex flex-col space-y-6">
<!-- Today's Summary -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i data-feather="bar-chart-2" class="mr-2 text-indigo-600"></i>
Сегодняшний отчет
</h2>
<div class="space-y-4">
<div class="flex justify-between items-center">
<span class="text-gray-600">Всего времени:</span>
<span id="today-hours" class="font-bold">0h 0m</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-600">Заработок (₽):</span>
<span id="today-earnings" class="font-bold">$0.00</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-600">Статус:</span>
<span id="today-status" class="font-bold text-red-500">Не на смене</span>
</div>
</div>
</div>
<!-- Weekly Summary -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i data-feather="calendar" class="mr-2 text-indigo-600"></i>
Недельный отчет
</h2>
<div class="space-y-4">
<div class="flex justify-between items-center">
<span class="text-gray-600">Всего часов:</span>
<span id="weekly-hours" class="font-bold">0h 0m</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-600">Ожидаемая оплата (₽):</span>
<span id="weekly-earnings" class="font-bold">$0.00</span>
</div>
<div class="flex justify-between items-center">
<span class="text-gray-600">Отработано дней:</span>
<span id="weekly-days" class="font-bold">0</span>
</div>
</div>
</div>
<!-- Settings Card -->
<div class="bg-white rounded-xl shadow-md p-6">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<i data-feather="settings" class="mr-2 text-indigo-600"></i>
Настройки
</h2>
<div class="space-y-4">
<div>
<label for="hourly-rate" class="block text-sm font-medium text-gray-700 mb-1">Ставка (₽/час)</label>
<input type="number" id="hourly-rate" value="15.00"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<div>
<label for="work-hours" class="block text-sm font-medium text-gray-700 mb-1">Часов в день</label>
<input type="number" id="work-hours" value="8"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500">
</div>
<button id="save-settings" class="w-full px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700 transition">
Сохранить
</button>
</div>
</div>
</div>
</div>
</main>
<!-- Footer -->
<footer class="bg-gray-800 text-white py-6 mt-12">
<div class="container mx-auto px-4 text-center">
<p>© 2023 ТаймТрек Про RU. Все права защищены.</p>
<p class="text-gray-400 text-sm mt-2">Учитывайте время, увеличивайте заработок!</p>
</div>
</footer>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
feather.replace();
// Update current time every second
function updateClock() {
const now = new Date();
const timeElement = document.getElementById('current-time');
const dateElement = document.getElementById('current-date');
timeElement.textContent = now.toLocaleTimeString();
dateElement.textContent = now.toLocaleDateString('ru-RU', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
updateClock();
setInterval(updateClock, 1000);
// Sample data for shifts (in a real app, this would come from localStorage or a database)
let shifts = JSON.parse(localStorage.getItem('shifts')) || [];
let shiftComments = JSON.parse(localStorage.getItem('shiftComments')) || {};
let isClockedIn = false;
let currentShift = null;
// Check if there's an active shift
if (shifts.length > 0 && !shifts[shifts.length - 1].clockOut) {
isClockedIn = true;
currentShift = shifts[shifts.length - 1];
updateClockInUI(true);
}
// Populate shifts table
function populateShiftsTable() {
const tableBody = document.getElementById('shifts-table');
tableBody.innerHTML = '';
// Show only last 5 shifts for simplicity
const recentShifts = shifts.slice(-5).reverse();
recentShifts.forEach(shift => {
const row = document.createElement('tr');
const dateCell = document.createElement('td');
dateCell.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-900';
dateCell.textContent = new Date(shift.clockIn).toLocaleDateString();
const inCell = document.createElement('td');
inCell.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500';
inCell.textContent = new Date(shift.clockIn).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
const outCell = document.createElement('td');
outCell.className = 'px-6 py-4 whitespace-nowrap text-sm text-gray-500';
outCell.textContent = shift.clockOut ?
new Date(shift.clockOut).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}) : '--:--';
const hoursCell = document.createElement('td');
hoursCell.className = 'px-6 py-4 whitespace-nowrap text-sm font-medium';
if (shift.clockOut) {
const hours = (new Date(shift.clockOut) - new Date(shift.clockIn)) / (1000 * 60 * 60);
hoursCell.textContent = hours.toFixed(2) + 'h';
hoursCell.classList.add('text-green-600');
} else {
hoursCell.textContent = 'Active';
hoursCell.classList.add('text-indigo-600');
}
row.appendChild(dateCell);
row.appendChild(inCell);
row.appendChild(outCell);
row.appendChild(hoursCell);
tableBody.appendChild(row);
addCommentToRow(row, shift);
});
updateSummaryCards();
}
// Update summary cards with calculations
function updateSummaryCards() {
// Today's hours
const today = new Date().toLocaleDateString();
const todayShifts = shifts.filter(shift =>
new Date(shift.clockIn).toLocaleDateString() === today && shift.clockOut
);
let todayMinutes = 0;
todayShifts.forEach(shift => {
todayMinutes += (new Date(shift.clockOut) - new Date(shift.clockIn)) / (1000 * 60);
});
const todayHours = Math.floor(todayMinutes / 60);
const todayMins = Math.floor(todayMinutes % 60);
document.getElementById('today-hours').textContent = `${todayHours}h ${todayMins}m`;
// Weekly hours (last 7 days)
const oneWeekAgo = new Date();
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
const weeklyShifts = shifts.filter(shift =>
new Date(shift.clockIn) >= oneWeekAgo && shift.clockOut
);
let weeklyMinutes = 0;
const uniqueDays = new Set();
weeklyShifts.forEach(shift => {
weeklyMinutes += (new Date(shift.clockOut) - new Date(shift.clockIn)) / (1000 * 60);
uniqueDays.add(new Date(shift.clockIn).toLocaleDateString());
});
const weeklyHours = Math.floor(weeklyMinutes / 60);
const weeklyMins = Math.floor(weeklyMinutes % 60);
document.getElementById('weekly-hours').textContent = `${weeklyHours}h ${weeklyMins}m`;
document.getElementById('weekly-days').textContent = uniqueDays.size;
// Earnings calculations
const hourlyRate = parseFloat(document.getElementById('hourly-rate').value) || 15.00;
document.getElementById('today-earnings').textContent = `${(todayMinutes / 60 * hourlyRate).toFixed(2)}₽`;
document.getElementById('weekly-earnings').textContent = `${(weeklyMinutes / 60 * hourlyRate).toFixed(2)}₽`;
// Update status
const statusElement = document.getElementById('today-status');
if (isClockedIn) {
statusElement.textContent = 'На смене';
statusElement.classList.remove('text-red-500');
statusElement.classList.add('text-green-500');
} else {
statusElement.textContent = 'Не на смене';
statusElement.classList.remove('text-green-500');
statusElement.classList.add('text-red-500');
}
}
// Update UI when clocked in/out
function updateClockInUI(clockedIn) {
const clockInBtn = document.getElementById('clock-in-btn');
const clockOutBtn = document.getElementById('clock-out-btn');
if (clockedIn) {
clockInBtn.disabled = true;
clockInBtn.classList.remove('bg-green-500', 'hover:bg-green-600', 'pulse-animation');
clockInBtn.classList.add('bg-gray-400', 'hover:bg-gray-500');
clockOutBtn.disabled = false;
clockOutBtn.classList.remove('opacity-50');
// Start tracking shift progress
updateShiftProgress();
setInterval(updateShiftProgress, 60000); // Update every minute
} else {
clockInBtn.disabled = false;
clockInBtn.classList.add('bg-green-500', 'hover:bg-green-600', 'pulse-animation');
clockInBtn.classList.remove('bg-gray-400', 'hover:bg-gray-500');
clockOutBtn.disabled = true;
clockOutBtn.classList.add('opacity-50');
}
}
// Update progress bar for current shift
function updateShiftProgress() {
if (!isClockedIn) return;
const workHours = parseFloat(document.getElementById('work-hours').value) || 8;
const clockInTime = new Date(currentShift.clockIn);
const now = new Date();
const elapsedHours = (now - clockInTime) / (1000 * 60 * 60);
const progressPercent = Math.min(100, (elapsedHours / workHours) * 100);
document.getElementById('progress-bar').style.width = `${progressPercent}%`;
document.getElementById('progress-text').textContent = `${progressPercent.toFixed(1)}%`;
// Change color based on progress
const progressBar = document.getElementById('progress-bar');
progressBar.classList.remove('bg-red-500', 'bg-yellow-500', 'bg-green-500');
if (progressPercent < 50) {
progressBar.classList.add('bg-red-500');
} else if (progressPercent < 90) {
progressBar.classList.add('bg-yellow-500');
} else {
progressBar.classList.add('bg-green-500');
}
}
// Clock In button click
document.getElementById('clock-in-btn').addEventListener('click', function() {
const now = new Date();
currentShift = {
clockIn: now.toISOString(),
clockOut: null,
comment: ''
};
shifts.push(currentShift);
localStorage.setItem('shifts', JSON.stringify(shifts));
document.getElementById('comment-section').classList.add('hidden');
isClockedIn = true;
updateClockInUI(true);
// Add comment display to shift rows
function addCommentToRow(row, shift) {
if (shift.comment) {
const commentRow = document.createElement('tr');
commentRow.className = 'bg-gray-50';
const commentCell = document.createElement('td');
commentCell.colSpan = 4;
commentCell.className = 'px-6 py-4 text-sm text-gray-500';
commentCell.textContent = `Комментарий: ${shift.comment}`;
commentRow.appendChild(commentCell);
row.parentNode.insertBefore(commentRow, row.nextSibling);
}
}
function populateShiftsTable() {
});
// Clock Out button click
document.getElementById('clock-out-btn').addEventListener('click', function() {
document.getElementById('comment-section').classList.remove('hidden');
document.getElementById('clock-out-btn').disabled = true;
});
document.getElementById('save-comment').addEventListener('click', function() {
const comment = document.getElementById('shift-comment').value;
if (currentShift) {
currentShift.comment = comment;
shiftComments[currentShift.clockIn] = comment;
localStorage.setItem('shiftComments', JSON.stringify(shiftComments));
}
if (!isClockedIn || !currentShift) return;
const now = new Date();
currentShift.clockOut = now.toISOString();
// Update in shifts array
const index = shifts.findIndex(s => s.clockIn === currentShift.clockIn && !s.clockOut);
if (index !== -1) {
shifts[index] = currentShift;
}
localStorage.setItem('shifts', JSON.stringify(shifts));
isClockedIn = false;
currentShift = null;
updateClockInUI(false);
populateShiftsTable();
});
// Save settings
document.getElementById('save-settings').addEventListener('click', function() {
const hourlyRate = document.getElementById('hourly-rate').value;
const workHours = document.getElementById('work-hours').value;
localStorage.setItem('hourlyRate', hourlyRate);
localStorage.setItem('workHours', workHours);
updateSummaryCards();
// Show confirmation
const button = this;
button.textContent = 'Сохранено!';
setTimeout(() => {
button.textContent = 'Сохранить';
}, 2000);
});
// Load saved settings
if (localStorage.getItem('hourlyRate')) {
document.getElementById('hourly-rate').value = localStorage.getItem('hourlyRate');
}
if (localStorage.getItem('workHours')) {
document.getElementById('work-hours').value = localStorage.getItem('workHours');
}
// Initialize the UI
populateShiftsTable();
});
</script>
</body>
</html>