OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

_timer_pill.php

4 KB
<?php
/**
 * Globale Stoppuhr-Pill — wird in jedem Layout (main, admin) eingebunden.
 *
 * Sichtbar nur, wenn der eingeloggte User einen aktiven Timer hat.
 * Tickt sekündlich per JS; Klick führt zum Ticket; "Stop"-Button
 * submittet an /tickets/{id}/timer/stop.
 *
 * Erwartet aus dem View-Scope:
 *   $_global_running_timer  Array oder null
 *   $csrf_token             aus Application::render()
 */
$tim = $_global_running_timer ?? null;
if (!$tim) return;
// Server-autoritative verstrichene Sekunden (zeitzonensicher, via DB-Uhr).
$elapsedSec = (int)($tim['elapsed_seconds'] ?? 0);
$isPaused   = !empty($tim['paused_at']);
$ticketId  = (int)$tim['ticket_id'];
$subj      = (string)($tim['ticket_subject'] ?? '');
// Sehr lange Betreffs kappen, damit die Pill nicht halb durchs Layout
// schießt.
$subjShort = mb_strimwidth($subj, 0, 32, '…');
?>
<style>
/* Inline-CSS, weil die Pill modulübergreifend sichtbar ist und
   tickets.css nicht global geladen wird. Selektoren sind eindeutig
   genug, um nicht in andere Module zu lecken. */
.global-timer-pill {
    display: inline-flex; align-items: center; gap: 0.5rem;
    background: var(--emerald-50); border: 1px solid var(--emerald-300); color: var(--emerald-700);
    padding: 0.3rem 0.7rem; border-radius: 9999px;
    font-size: 0.82rem; font-weight: 600; text-decoration: none;
    transition: background-color .12s, transform .12s;
}
.global-timer-pill:hover { background: var(--emerald-100); transform: translateY(-1px); color: var(--emerald-700); }
.global-timer-pill.is-paused { background: var(--amber-100); border-color: var(--amber-300); color: var(--amber-800); }
.global-timer-pill.is-paused:hover { background: var(--amber-200); color: var(--amber-800); }
.global-timer-pill .dot {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--emerald-500); flex-shrink: 0;
    animation: gtpPulse 1.4s ease-in-out infinite;
}
.global-timer-pill.is-paused .dot { background: var(--amber-500); animation: none; }
.global-timer-pill .clock {
    font-family: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
    font-weight: 700; letter-spacing: 0.02em;
}
.global-timer-pill .stop-btn {
    border: none; background: rgba(220, 38, 38, 0.1); color: var(--red-700);
    border-radius: 9999px; padding: 0.15rem 0.55rem;
    font-size: 0.72rem; cursor: pointer; font-weight: 700;
    margin-left: 0.2rem;
}
.global-timer-pill .stop-btn:hover { background: rgba(220, 38, 38, 0.2); }
@keyframes gtpPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.35; } }
</style>
<a class="global-timer-pill <?= $isPaused ? 'is-paused' : '' ?>"
   href="/tickets/<?= $ticketId ?>"
   title="<?= htmlspecialchars('Timer läuft auf #' . $ticketId . ' · ' . $subj, ENT_QUOTES) ?>"
   id="global-timer-pill"
   data-elapsed-seconds="<?= $elapsedSec ?>"
   data-paused="<?= $isPaused ? '1' : '0' ?>">
    <span class="dot" aria-hidden="true"></span>
    <span>#<?= $ticketId ?></span>
    <span class="clock" id="global-timer-clock">00:00:00</span>
    <form method="POST" action="/tickets/<?= $ticketId ?>/timer/stop"
          style="display:inline;margin:0;padding:0;" data-dialog-keep>
        <input type="hidden" name="_csrf" value="<?= htmlspecialchars($csrf_token ?? '', ENT_QUOTES) ?>">
        <button type="submit" class="stop-btn"
                data-dialog-keep data-confirm-stop="Timer wirklich stoppen?"
                title="<?= $this->te('Timer stoppen') ?>"><?= $this->te('&#9209; Stop') ?></button>
    </form>
</a>

<script<?= $this->scriptNonce() ?>>
(function () {
    var pill = document.getElementById('global-timer-pill');
    if (!pill) return;
    // Server liefert verstrichene Sekunden; clientseitig relativ zum Seitenladen
    // weiterzählen (zeitzonenunabhängig).
    var baseElapsed = parseInt(pill.dataset.elapsedSeconds, 10) || 0;
    var isPaused    = pill.dataset.paused === '1';
    var pageLoadMs  = Date.now();
    var clockEl     = document.getElementById('global-timer-clock');

    function format(sec) {
        if (sec < 0) sec = 0;
        var h = Math.floor(sec / 3600);
        var m = Math.floor((sec % 3600) / 60);
        var s = sec % 60;
        return (h < 10 ? '0' : '') + h + ':' +
               (m < 10 ? '0' : '') + m + ':' +
               (s < 10 ? '0' : '') + s;
    }
    function tick() {
        var elapsed = isPaused
            ? baseElapsed
            : baseElapsed + Math.floor((Date.now() - pageLoadMs) / 1000);
        if (clockEl) clockEl.textContent = format(Math.max(0, elapsed));
    }
    tick();
    setInterval(tick, 1000);
})();
</script>