GPT : Timezone and reporting improvements
Add timezone-aware timestamp support and reporting improvements Store timestamps in ISO-8601 format with timezone offsets Accept client-local timestamps from browser and API Normalize legacy naive timestamps during parsing Add migration script for converting old SQLite timestamps Fix mixed naive/aware datetime comparison errors Render timestamps in browser-local timezone Auto-generate default 7-day reports on page load Expand default dashboard report to include all users Preserve manual single-user report generation Add curl-based validation/test script for API and report flows
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Time Clock</title>
|
||||
</head>
|
||||
@@ -13,7 +14,13 @@
|
||||
{% if clocked_in_users %}
|
||||
<ul>
|
||||
{% for user in clocked_in_users %}
|
||||
<li>{{ user.name }}</li>
|
||||
<li>
|
||||
{{ user.name }} —
|
||||
since
|
||||
<span class="tztime" data-ts="{{ user.since }}">
|
||||
{{ user.since }}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
@@ -21,21 +28,58 @@
|
||||
{% endif %}
|
||||
|
||||
<br/>
|
||||
|
||||
{% if all_user_reports %}
|
||||
|
||||
<h2>Last 7 Days Report</h2>
|
||||
|
||||
<table border="1" cellpadding="5">
|
||||
|
||||
<tr>
|
||||
<th>User</th>
|
||||
<th>Total Hours Worked</th>
|
||||
</tr>
|
||||
|
||||
{% for report in all_user_reports %}
|
||||
<tr>
|
||||
<td>{{ report.name }}</td>
|
||||
<td>{{ report.hours }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
</table>
|
||||
|
||||
<br>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<hr/>
|
||||
<br/>
|
||||
|
||||
<form method="POST">
|
||||
<form method="POST" id="timeclock-form">
|
||||
|
||||
<input
|
||||
type="hidden"
|
||||
name="client_timestamp"
|
||||
id="client_timestamp"
|
||||
>
|
||||
|
||||
<table border="1" cellpadding="5">
|
||||
|
||||
<tr>
|
||||
<td>User</td>
|
||||
|
||||
<td>
|
||||
<select name="user_id">
|
||||
|
||||
{% for user in users %}
|
||||
<option value="{{ user.id }}">
|
||||
{{ user.name }}
|
||||
<option
|
||||
value="{{ user.id }}"
|
||||
{% if selected_user_id == user.id %}
|
||||
selected
|
||||
{% endif %}
|
||||
>
|
||||
{{ user.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
|
||||
@@ -47,13 +91,25 @@
|
||||
<td>Clock Actions</td>
|
||||
|
||||
<td>
|
||||
<button type="submit" name="action" value="clock_in">
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
name="action"
|
||||
value="clock_in"
|
||||
onclick="setCurrentTimestamp()"
|
||||
>
|
||||
Clock In
|
||||
</button>
|
||||
|
||||
<button type="submit" name="action" value="clock_out">
|
||||
<button
|
||||
type="submit"
|
||||
name="action"
|
||||
value="clock_out"
|
||||
onclick="setCurrentTimestamp()"
|
||||
>
|
||||
Clock Out
|
||||
</button>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -64,6 +120,7 @@
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="begin_date"
|
||||
id="begin_date"
|
||||
value=""
|
||||
>
|
||||
</td>
|
||||
@@ -76,6 +133,7 @@
|
||||
<input
|
||||
type="datetime-local"
|
||||
name="end_date"
|
||||
id="end_date"
|
||||
value=""
|
||||
>
|
||||
</td>
|
||||
@@ -85,7 +143,11 @@
|
||||
<td>Report</td>
|
||||
|
||||
<td>
|
||||
<button type="submit" name="action" value="report">
|
||||
<button
|
||||
type="submit"
|
||||
name="action"
|
||||
value="report"
|
||||
>
|
||||
Generate Report
|
||||
</button>
|
||||
</td>
|
||||
@@ -99,6 +161,8 @@
|
||||
|
||||
{% if report_hours is not none %}
|
||||
|
||||
<h2>Custom Report</h2>
|
||||
|
||||
<table border="1" cellpadding="5">
|
||||
|
||||
<tr>
|
||||
@@ -113,5 +177,60 @@
|
||||
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
|
||||
function localDatetimeValue(date) {
|
||||
|
||||
const pad = (v) => String(v).padStart(2, "0");
|
||||
|
||||
return (
|
||||
date.getFullYear() + "-" +
|
||||
pad(date.getMonth() + 1) + "-" +
|
||||
pad(date.getDate()) + "T" +
|
||||
pad(date.getHours()) + ":" +
|
||||
pad(date.getMinutes())
|
||||
);
|
||||
}
|
||||
|
||||
function setCurrentTimestamp() {
|
||||
|
||||
document.getElementById("client_timestamp").value =
|
||||
new Date().toISOString();
|
||||
}
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
|
||||
//
|
||||
// Populate default report range
|
||||
//
|
||||
|
||||
const now = new Date();
|
||||
|
||||
const weekAgo = new Date();
|
||||
|
||||
weekAgo.setDate(now.getDate() - 7);
|
||||
|
||||
document.getElementById("begin_date").value =
|
||||
localDatetimeValue(weekAgo);
|
||||
|
||||
document.getElementById("end_date").value =
|
||||
localDatetimeValue(now);
|
||||
|
||||
//
|
||||
// Convert displayed timestamps to local timezone
|
||||
//
|
||||
|
||||
document.querySelectorAll(".tztime").forEach((el) => {
|
||||
|
||||
const ts = el.dataset.ts;
|
||||
|
||||
const d = new Date(ts);
|
||||
|
||||
el.textContent = d.toLocaleString();
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user