Bladeren bron

Audit log: render When in viewer's local time, dd.mm.YYYY HH:mm:ss

Server emits each row's occurred_at inside <time data-local-datetime
datetime="…">, with a UTC fallback already formatted dd.mm.YYYY HH:mm:ss.
A small bit of app.js then rewrites the visible text to the viewer's
local timezone in the same format (also re-runs after htmx swaps).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chiappa 2 dagen geleden
bovenliggende
commit
964a2f50ce
2 gewijzigde bestanden met toevoegingen van 29 en 2 verwijderingen
  1. 25 0
      public/assets/js/app.js
  2. 4 2
      views/audit/index.twig

+ 25 - 0
public/assets/js/app.js

@@ -74,6 +74,31 @@
         }
     });
 
+    // ----- Local-timezone datetime rendering ----------------------------
+    // Server emits each audit row's `occurred_at` as a UTC ISO-8601 string
+    // inside <time data-local-datetime datetime="…">…</time>. Rewrite the
+    // text content to the viewer's local timezone in dd.mm.YYYY HH:mm:ss.
+    function pad2(n) { return n < 10 ? '0' + n : '' + n; }
+    function formatLocalDateTime(d) {
+        return pad2(d.getDate()) + '.' + pad2(d.getMonth() + 1) + '.' + d.getFullYear()
+            + ' ' + pad2(d.getHours()) + ':' + pad2(d.getMinutes()) + ':' + pad2(d.getSeconds());
+    }
+    function localizeDateTimes(root) {
+        const scope = root || document;
+        const els = scope.querySelectorAll('time[data-local-datetime]');
+        els.forEach(function (el) {
+            const iso = el.getAttribute('datetime') || '';
+            if (iso === '') { return; }
+            const d = new Date(iso);
+            if (isNaN(d.getTime())) { return; }
+            el.textContent = formatLocalDateTime(d);
+        });
+    }
+    document.addEventListener('DOMContentLoaded', function () { localizeDateTimes(); });
+    document.addEventListener('htmx:afterSwap', function (ev) {
+        localizeDateTimes(ev.target || document);
+    });
+
     // ----- Alpine component registrations -------------------------------
     document.addEventListener('alpine:init', function () {
         // Hamburger menu — toggle, close on outside / Escape / item click.

+ 4 - 2
views/audit/index.twig

@@ -116,7 +116,7 @@
             <table class="min-w-full text-sm">
                 <thead class="bg-slate-50 text-slate-600 text-xs uppercase tracking-wider dark:bg-slate-700 dark:text-slate-300">
                     <tr>
-                        <th class="text-left px-3 py-2 font-semibold">When (UTC)</th>
+                        <th class="text-left px-3 py-2 font-semibold">When</th>
                         <th class="text-left px-3 py-2 font-semibold">User</th>
                         <th class="text-left px-3 py-2 font-semibold">Action</th>
                         <th class="text-left px-3 py-2 font-semibold">Entity</th>
@@ -127,7 +127,9 @@
                 <tbody class="divide-y divide-slate-100 align-top dark:divide-slate-700">
                     {% for r in rows %}
                         <tr>
-                            <td class="px-3 py-2 font-mono text-xs whitespace-nowrap">{{ r.occurred_at }}</td>
+                            <td class="px-3 py-2 font-mono text-xs whitespace-nowrap">
+                                <time datetime="{{ r.occurred_at }}" data-local-datetime>{{ r.occurred_at|date("d.m.Y H:i:s", "UTC") }}</time>
+                            </td>
                             <td class="px-3 py-2">
                                 {% if r.user_email is not null and r.user_email != '' %}{{ r.user_email }}{% else %}<span class="text-slate-400 dark:text-slate-500">—</span>{% endif %}
                             </td>