Files
TimeTracker/app/static/global-fab.js
T
Dries Peeters d7acfb9ff2 Add global time FAB, inline time-entry edits, and week comparison chart
Global quick actions (FAB):
- Add a fixed primary FAB with an animated popover (Start Timer, Log Time,
  New Task) for authenticated users in base.html.
- Start Timer uses the dashboard control when present, otherwise opens the
  dashboard with #start-timer so the existing start modal appears.
- Hide the FAB from the md breakpoint up while the header floating timer is
  active (floating-timer-bar.js toggles body.fab-hide-desktop-timer-active).

Time entries overview:
- Make Notes and Duration editable in the table where permissions allow;
  save via same-origin PUT/PATCH /api/entry/<id> (time-entries-inline-edit.js).
- Register PATCH on the session update_entry route alongside PUT.

Dashboard:
- Add a this-week vs last-week hours chart fed by GET /api/reports/week-comparison
  and dashboard-enhancements.js.

Documentation:
- Extend UI_GUIDELINES (FAB, inline edit, file reference) and ARCHITECTURE
  (session JSON endpoints) to match the new behavior.
2026-04-27 17:53:12 +02:00

108 lines
2.7 KiB
JavaScript

/**
* Global FAB: quick Start Timer, Log Time, New Task.
*/
(function () {
'use strict';
function getRoot() {
return document.getElementById('globalTimeFab');
}
function setOpen(open) {
var root = getRoot();
var btn = document.getElementById('globalTimeFabBtn');
var menu = document.getElementById('globalTimeFabMenu');
if (!root || !btn) return;
root.classList.toggle('is-open', open);
btn.setAttribute('aria-expanded', open ? 'true' : 'false');
if (menu) menu.setAttribute('aria-hidden', open ? 'false' : 'true');
}
function close() {
setOpen(false);
}
function open() {
setOpen(true);
}
function toggle() {
var root = getRoot();
if (!root) return;
setOpen(!root.classList.contains('is-open'));
}
function dashboardUrl() {
var root = getRoot();
var init = window.__BASE_INIT__ || {};
return (root && root.getAttribute('data-dashboard-url')) || init.dashboard || '/';
}
function manualUrl() {
var root = getRoot();
var init = window.__BASE_INIT__ || {};
return (root && root.getAttribute('data-manual-entry-url')) || init.manualEntry || '/timer/manual';
}
function newTaskUrl() {
var root = getRoot();
var init = window.__BASE_INIT__ || {};
return (root && root.getAttribute('data-new-task-url')) || init.newTask || '/tasks/create';
}
function onStartTimer() {
close();
var openBtn = document.querySelector('#openStartTimer');
if (openBtn) {
openBtn.click();
return;
}
var dash = dashboardUrl();
var base = dash.split('#')[0];
window.location.href = base + '#start-timer';
}
function onLogTime() {
close();
window.location.href = manualUrl();
}
function onNewTask() {
close();
window.location.href = newTaskUrl();
}
document.addEventListener('DOMContentLoaded', function () {
var root = getRoot();
var btn = document.getElementById('globalTimeFabBtn');
if (!root || !btn) return;
btn.addEventListener('click', function (e) {
e.stopPropagation();
toggle();
});
root.querySelectorAll('[data-fab-action]').forEach(function (el) {
el.addEventListener('click', function () {
var act = el.getAttribute('data-fab-action');
if (act === 'start') onStartTimer();
else if (act === 'log') onLogTime();
else if (act === 'task') onNewTask();
});
});
document.addEventListener('click', function (e) {
if (!root.classList.contains('is-open')) return;
if (root.contains(e.target)) return;
close();
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && root.classList.contains('is-open')) {
close();
btn.focus();
}
});
});
})();