420 lines
No EOL
13 KiB
HTML
420 lines
No EOL
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en" x-data="appData()" x-init="init()">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Edit Time Entry</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
|
<link href="/frontend/dist/styles.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
|
|
<script src="https://unpkg.com/alpinejs" defer></script>
|
|
|
|
<style>
|
|
/* Default (Light Mode) Styles */
|
|
body {
|
|
background-color: #f5f5f5;
|
|
color: #333;
|
|
transition: background-color 0.3s, color 0.3s;
|
|
text-align: center;
|
|
height: 100vh;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
margin: 0;
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Time Tracker Card */
|
|
.time-tracker {
|
|
background-color: #fff;
|
|
color: #333;
|
|
transition: background-color 0.3s, color 0.3s;
|
|
max-width: 800px;
|
|
z-index: 10;
|
|
position: relative;
|
|
margin: 0 auto;
|
|
padding: 20px;
|
|
border-radius: 15px;
|
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
/* Dark Mode Styles */
|
|
body.dark-mode {
|
|
background-color: #1a202c;
|
|
color: #cbd5e0;
|
|
}
|
|
|
|
.dark-mode .time-tracker {
|
|
background-color: #2d3748;
|
|
color: #cbd5e0;
|
|
}
|
|
|
|
/* SVG Background */
|
|
.svg-background {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
z-index: 1;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* Buttons */
|
|
.btn {
|
|
display: inline-block;
|
|
padding: 0.5em 1.5em;
|
|
font-size: 1em;
|
|
font-weight: 500;
|
|
color: white;
|
|
background-color: #4CAF50;
|
|
border: none;
|
|
border-radius: 0.375em;
|
|
cursor: pointer;
|
|
transition: background-color 0.3s ease, transform 0.1s ease;
|
|
}
|
|
|
|
.btn:hover {
|
|
background-color: #45a049;
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.btn-danger {
|
|
background-color: #e53e3e;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
background-color: #c53030;
|
|
}
|
|
|
|
/* Dark Mode for Buttons */
|
|
.dark-mode .btn {
|
|
background-color: #3182ce;
|
|
}
|
|
|
|
.dark-mode .btn:hover {
|
|
background-color: #2b6cb0;
|
|
}
|
|
|
|
/* DateTime Input Styles */
|
|
input[type="datetime-local"] {
|
|
width: 100%;
|
|
padding: 0.5em;
|
|
margin: 0.5em 0;
|
|
border: 1px solid #ccc;
|
|
border-radius: 0.375em;
|
|
font-size: 1em;
|
|
transition: border-color 0.2s ease;
|
|
}
|
|
|
|
input[type="datetime-local"]:focus {
|
|
outline: none;
|
|
border-color: #3182ce;
|
|
}
|
|
|
|
.dark-mode input[type="datetime-local"] {
|
|
background-color: #2d3748;
|
|
color: #cbd5e0;
|
|
border-color: #4a5568;
|
|
}
|
|
|
|
.dark-mode input[type="datetime-local"]:focus {
|
|
border-color: #63b3ed;
|
|
}
|
|
|
|
/* Modal */
|
|
#edit-modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
z-index: 1000;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
|
|
#edit-modal .modal-content {
|
|
background-color: white;
|
|
padding: 20px;
|
|
margin: auto;
|
|
width: 50%;
|
|
border-radius: 10px;
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
/* Dark Mode for Modal */
|
|
.dark-mode #edit-modal .modal-content {
|
|
background-color: #2d3748;
|
|
color: #cbd5e0;
|
|
}
|
|
|
|
/* Table Styles */
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin-top: 1em;
|
|
}
|
|
|
|
thead th {
|
|
background-color: #e2e8f0;
|
|
color: #2d3748;
|
|
padding: 0.75em;
|
|
border-bottom: 2px solid #cbd5e0;
|
|
}
|
|
|
|
tbody tr:nth-child(even) {
|
|
background-color: #f7fafc;
|
|
}
|
|
|
|
tbody tr:nth-child(odd) {
|
|
background-color: #ffffff;
|
|
}
|
|
|
|
tbody td {
|
|
padding: 0.75em;
|
|
border-bottom: 1px solid #cbd5e0;
|
|
}
|
|
|
|
body.dark-mode table {
|
|
background-color: #2d3748;
|
|
color: #cbd5e0;
|
|
}
|
|
|
|
body.dark-mode thead th {
|
|
background-color: #4a5568;
|
|
color: #edf2f7;
|
|
}
|
|
|
|
body.dark-mode tbody tr:nth-child(even) {
|
|
background-color: #2c3440;
|
|
}
|
|
|
|
body.dark-mode tbody tr:nth-child(odd) {
|
|
background-color: #1f2733;
|
|
}
|
|
|
|
body.dark-mode tbody td {
|
|
border-bottom: 1px solid #4a5568;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body :class="{ 'dark-mode': isDarkMode }">
|
|
<!-- SVG Wave Animation Background -->
|
|
<svg class="svg-background" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
|
xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100%" height="100%" viewBox="0 0 1600 900" preserveAspectRatio="xMidYMax slice">
|
|
<defs>
|
|
<linearGradient id="bg">
|
|
<stop offset="0%" style="stop-color:rgba(130, 158, 249, 0.06)"></stop>
|
|
<stop offset="50%" style="stop-color:rgba(76, 190, 255, 0.6)"></stop>
|
|
<stop offset="100%" style="stop-color:rgba(115, 209, 72, 0.2)"></stop>
|
|
</linearGradient>
|
|
<path id="wave" fill="url(#bg)" d="M-363.852,452.589c0,0,236.988-91.997,505.475,0s371.981,88.998,575.971,0s293.985-89.278,505.474,5.859s493.475,98.368,716.963-4.995v560.106H-363.852V452.589z" />
|
|
</defs>
|
|
<g>
|
|
<use xlink:href='#wave' opacity=".3">
|
|
<animateTransform attributeName="transform" attributeType="XML" type="translate" dur="10s" calcMode="spline"
|
|
values="270 230; -334 180; 270 230" keyTimes="0; .5; 1" keySplines="0.42, 0, 0.58, 1.0;0.42, 0, 0.58, 1.0" repeatCount="indefinite" />
|
|
</use>
|
|
<use xlink:href='#wave' opacity=".6">
|
|
<animateTransform attributeName="transform" attributeType="XML" type="translate" dur="8s" calcMode="spline"
|
|
values="-270 230;243 220;-270 230" keyTimes="0; .6; 1" keySplines="0.42, 0, 0.58, 1.0;0.42, 0, 0.58, 1.0" repeatCount="indefinite" />
|
|
</use>
|
|
<use xlink:href='#wave' opacity=".9">
|
|
<animateTransform attributeName="transform" attributeType="XML" type="translate" dur="6s" calcMode="spline"
|
|
values="0 230;-140 200;0 230" keyTimes="0; .4; 1" keySplines="0.42, 0, 0.58, 1.0;0.42, 0, 0.58, 1.0" repeatCount="indefinite" />
|
|
</use>
|
|
</g>
|
|
</svg>
|
|
|
|
<!-- Main Content -->
|
|
<div class="relative max-w-lg w-full time-tracker shadow-2xl rounded-xl p-8 z-10">
|
|
<h1 class="text-4xl font-bold mb-6 text-center">Edit Time Entry</h1>
|
|
|
|
<!-- Go Home Button -->
|
|
<div class="text-center mb-4">
|
|
<a href="/" class="btn">Go Home</a>
|
|
</div>
|
|
|
|
<!-- Form to select a user -->
|
|
<form id="user-form">
|
|
<label for="user">Select User:</label>
|
|
<select id="user" name="user" required></select>
|
|
<button class="btn" type="submit">Fetch Times</button>
|
|
</form>
|
|
|
|
<!-- Table to display clock-in/clock-out times -->
|
|
<table id="time-table" class="time-entry-table mt-6" style="display:none;">
|
|
<thead>
|
|
<tr>
|
|
<th>Date</th>
|
|
<th>Clock In</th>
|
|
<th>Clock Out</th>
|
|
<th>Edit</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody></tbody>
|
|
</table>
|
|
|
|
<!-- Edit Form Modal -->
|
|
<div id="edit-modal" class="hidden flex">
|
|
<div class="modal-content">
|
|
<h2>Edit Clock In/Out Times for <span id="edit-date"></span></h2>
|
|
<form id="edit-form">
|
|
<input type="hidden" id="entry-id">
|
|
<label for="clock_in">Clock In:</label>
|
|
<input type="datetime-local" id="edit-clock-in" name="clock_in">
|
|
<br>
|
|
<label for="clock_out">Clock Out:</label>
|
|
<input type="datetime-local" id="edit-clock-out" name="clock_out">
|
|
<br>
|
|
<button class="btn" type="submit">Save Changes</button>
|
|
<button class="btn btn-danger" type="button" onclick="closeModal()">Cancel</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function appData() {
|
|
return {
|
|
isDarkMode: false,
|
|
|
|
// Initialize the app
|
|
init() {
|
|
this.isDarkMode = localStorage.getItem('darkMode') === 'true';
|
|
if (this.isDarkMode) {
|
|
document.body.classList.add('dark-mode');
|
|
} else {
|
|
document.body.classList.remove('dark-mode');
|
|
}
|
|
},
|
|
|
|
// Toggle between dark mode and light mode
|
|
toggleDarkMode() {
|
|
this.isDarkMode = !this.isDarkMode;
|
|
localStorage.setItem('darkMode', this.isDarkMode);
|
|
document.body.classList.toggle('dark-mode', this.isDarkMode);
|
|
},
|
|
};
|
|
}
|
|
|
|
async function fetchUsers() {
|
|
const response = await fetch('/users');
|
|
const users = await response.json();
|
|
const userSelect = document.getElementById('user');
|
|
users.forEach(user => {
|
|
const option = document.createElement('option');
|
|
option.value = user.name;
|
|
option.text = user.name;
|
|
userSelect.add(option);
|
|
});
|
|
}
|
|
|
|
function formatTime(datetimeString) {
|
|
if (!datetimeString) return 'N/A';
|
|
const date = new Date(datetimeString);
|
|
const options = {
|
|
hour: 'numeric',
|
|
minute: 'numeric',
|
|
hour12: true, // Set to false for 24-hour format
|
|
};
|
|
return date.toLocaleTimeString([], options);
|
|
}
|
|
|
|
async function fetchTimeEntries(user) {
|
|
const response = await fetch(`/time/${user}/recall/month`);
|
|
const data = await response.json();
|
|
const tableBody = document.getElementById('time-table').getElementsByTagName('tbody')[0];
|
|
tableBody.innerHTML = ''; // Clear existing table data
|
|
data.entries.forEach(entry => {
|
|
const row = tableBody.insertRow();
|
|
row.insertCell(0).innerText = entry.date;
|
|
row.insertCell(1).innerText = entry.clock_in ? formatTime(entry.clock_in) : 'N/A';
|
|
row.insertCell(2).innerText = entry.clock_out ? formatTime(entry.clock_out) : 'N/A';
|
|
const editCell = row.insertCell(3);
|
|
const editButton = document.createElement('button');
|
|
editButton.innerText = 'Edit';
|
|
editButton.className = 'btn';
|
|
editButton.onclick = () => openEditModal(user, entry.date, entry.clock_in, entry.clock_out);
|
|
editCell.appendChild(editButton);
|
|
});
|
|
document.getElementById('time-table').style.display = 'table'; // Show table
|
|
}
|
|
|
|
function toLocalDatetimeInputValue(date) {
|
|
if (!date) return '';
|
|
const dt = new Date(date);
|
|
const year = dt.getFullYear();
|
|
const month = ('0' + (dt.getMonth() + 1)).slice(-2);
|
|
const day = ('0' + dt.getDate()).slice(-2);
|
|
const hours = ('0' + dt.getHours()).slice(-2);
|
|
const minutes = ('0' + dt.getMinutes()).slice(-2);
|
|
return `${year}-${month}-${day}T${hours}:${minutes}`;
|
|
}
|
|
|
|
function localDatetimeToUTC(datetimeLocal) {
|
|
if (!datetimeLocal) return null;
|
|
const localDate = new Date(datetimeLocal);
|
|
return localDate.toISOString();
|
|
}
|
|
|
|
function openEditModal(user, date, clockIn, clockOut) {
|
|
document.getElementById('edit-date').innerText = date;
|
|
document.getElementById('entry-id').value = date;
|
|
document.getElementById('edit-clock-in').value = clockIn ? toLocalDatetimeInputValue(clockIn) : '';
|
|
document.getElementById('edit-clock-out').value = clockOut ? toLocalDatetimeInputValue(clockOut) : '';
|
|
document.getElementById('edit-modal').style.display = 'flex';
|
|
}
|
|
|
|
function closeModal() {
|
|
document.getElementById('edit-modal').style.display = 'none';
|
|
}
|
|
|
|
async function submitForm(event) {
|
|
event.preventDefault();
|
|
const user = document.getElementById('user').value;
|
|
const date = document.getElementById('entry-id').value;
|
|
const clockIn = document.getElementById('edit-clock-in').value;
|
|
const clockOut = document.getElementById('edit-clock-out').value;
|
|
|
|
const clockInUTC = clockIn ? localDatetimeToUTC(clockIn) : null;
|
|
const clockOutUTC = clockOut ? localDatetimeToUTC(clockOut) : null;
|
|
|
|
const response = await fetch(`/time/${user}/edit`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
date: date,
|
|
clock_in_time: clockInUTC,
|
|
clock_out_time: clockOutUTC,
|
|
}),
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert('Times updated successfully');
|
|
closeModal();
|
|
fetchTimeEntries(user);
|
|
} else {
|
|
const error = await response.json();
|
|
alert('Error updating times: ' + error.message);
|
|
}
|
|
}
|
|
|
|
document.getElementById('user-form').addEventListener('submit', function(event) {
|
|
event.preventDefault();
|
|
const user = document.getElementById('user').value;
|
|
fetchTimeEntries(user);
|
|
});
|
|
|
|
document.getElementById('edit-form').addEventListener('submit', submitForm);
|
|
|
|
fetchUsers();
|
|
</script>
|
|
</body>
|
|
</html> |