forked from extern/egroupware
WIP timesheet timers: show time under each button and allow overwriting it via a click on the time
ToDo: - Stop on paused timer does not behave write, overwriting stop needs testing, as timesheet need to be updated too, because it's already stored - not checks or min/max values and config on overwrite time
This commit is contained in:
parent
c52ac8fbdc
commit
c2fea85c2a
@ -23,6 +23,11 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
||||
* Specific timer state
|
||||
*/
|
||||
let specific = {};
|
||||
/**
|
||||
* Disable config with values "overall", "specific" or "overwrite"
|
||||
* @type {string[]}
|
||||
*/
|
||||
let disable = [];
|
||||
/**
|
||||
* Timer container in top-menu
|
||||
* @type {Element}
|
||||
@ -45,8 +50,11 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
||||
*/
|
||||
function setState(_state)
|
||||
{
|
||||
disable = _state.disable;
|
||||
// initiate overall timer
|
||||
startTimer(overall, _state.overall?.start, _state.overall?.offset); // to show offset / paused time
|
||||
overall.started = _state.overall?.started ? new Date(_state.overall.started) : undefined;
|
||||
overall.started_id = _state.overall?.started_id;
|
||||
if (_state.overall?.paused)
|
||||
{
|
||||
stopTimer(overall, true);
|
||||
@ -61,6 +69,8 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
||||
if (_state.specific?.start || _state.specific?.paused)
|
||||
{
|
||||
startTimer(specific, _state.specific?.start, _state.specific?.offset, _state.specific.app_id); // to show offset / paused time
|
||||
specific.started = _state.specific?.started ? new Date(_state.specific.started) : undefined;
|
||||
specific.started_id = _state.specific?.started_id;
|
||||
specific.id = _state.specific.id;
|
||||
if (_state.specific?.paused)
|
||||
{
|
||||
@ -140,6 +150,18 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
||||
if (_action.substring(0, 8) === 'specific')
|
||||
{
|
||||
specific.id = tse_id;
|
||||
if (_action.substring(9) === 'start')
|
||||
{
|
||||
specific.started_id = tse_id;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
overall.id = tse_id;
|
||||
if (_action.substring(9) === 'start')
|
||||
{
|
||||
overall.started_id = tse_id;
|
||||
}
|
||||
}
|
||||
if (_action === 'specific-stop')
|
||||
{
|
||||
@ -259,13 +281,13 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
||||
*/
|
||||
function startTimer(_timer, _start, _offset)
|
||||
{
|
||||
const time = _start ? new Date(_start) : new Date();
|
||||
if (_timer.last && time.valueOf() < _timer.last.valueOf())
|
||||
_timer.started = _start ? new Date(_start) : new Date();
|
||||
if (_timer.last && _timer.started.valueOf() < _timer.last.valueOf())
|
||||
{
|
||||
throw egw.lang('Start-time can not be before last stop- or pause-time %1!', formatUTCTime(_timer.last));
|
||||
}
|
||||
// update _timer state object
|
||||
_timer.last = _timer.start = time;
|
||||
_timer.start = new Date(_timer.last = _timer.started);
|
||||
|
||||
if (_offset || _timer.offset && _timer.paused)
|
||||
{
|
||||
@ -394,10 +416,9 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
||||
/**
|
||||
* Open the timer dialog to start/stop timers
|
||||
*
|
||||
* @param {Array} disable
|
||||
* @param {string} _title default "Start & stop timer"
|
||||
*/
|
||||
function timerDialog(disable, _title)
|
||||
function timerDialog(_title)
|
||||
{
|
||||
// Pass egw in the constructor
|
||||
dialog = new Et2Dialog(egw);
|
||||
@ -418,6 +439,7 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
||||
Et2Dialog.alert(e, egw.lang('Invalid Input'), Et2Dialog.ERROR_MESSAGE);
|
||||
}
|
||||
setButtonState();
|
||||
updateTimes();
|
||||
return false;
|
||||
}
|
||||
dialog = undefined;
|
||||
@ -429,7 +451,11 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
||||
],
|
||||
value: {
|
||||
content: {
|
||||
disable: disable.join(':')
|
||||
disable: disable.join(':'),
|
||||
times: {
|
||||
specific: getTimes(specific),
|
||||
overall: getTimes(overall)
|
||||
}
|
||||
},
|
||||
sel_options: {}
|
||||
}
|
||||
@ -444,14 +470,92 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update times displayed under buttons
|
||||
*/
|
||||
function updateTimes()
|
||||
{
|
||||
if (!dialog) return;
|
||||
|
||||
const times = {
|
||||
specific: getTimes(specific),
|
||||
overall: getTimes(overall)
|
||||
};
|
||||
|
||||
// disable not matching / available menu-items
|
||||
dialog._overlayContentNode.querySelectorAll('et2-date-time-today').forEach(_widget => {
|
||||
const [,timer, action] = _widget.id.match(/times\[([^\]]+)\]\[([^\]]+)\]/);
|
||||
_widget.value = times[timer][action];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get start, pause and stop time of timer to display in UI
|
||||
*
|
||||
* @param {Object} _timer
|
||||
* @return {Object} with attributes start, pause, stop
|
||||
*/
|
||||
function getTimes(_timer)
|
||||
{
|
||||
const started = _timer.started ? new Date(_timer.started.valueOf() - egw.getTimezoneOffset() * 60000) : undefined;
|
||||
const last = _timer.last ? new Date(_timer.last.valueOf() - egw.getTimezoneOffset() * 60000) : undefined;
|
||||
return {
|
||||
start: started,
|
||||
paused: _timer.paused ? last : undefined,
|
||||
stop: !_timer.start && !_timer.paused ? last : undefined
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Change/overwrite time
|
||||
*
|
||||
* @param {PointerEvent} _ev
|
||||
* @param {Et2DateTimeToday} _widget
|
||||
*/
|
||||
change_timer: function(_ev, _widget)
|
||||
{
|
||||
// if there is no value, or timer overwrite is disabled --> ignore click
|
||||
if (!_widget?.value || disable.indexOf('overwrite') !== -1) {
|
||||
return;
|
||||
}
|
||||
const [, which, action] = _widget.id.match(/times\[([^\]]+)\]\[([^\]]+)\]/);
|
||||
const timer = which === 'overall' ? overall : specific;
|
||||
const tse_id = timer[action === 'start' ? 'started_id' : 'id'];
|
||||
const time = _widget.value;
|
||||
const dialog = new Et2Dialog(egw);
|
||||
|
||||
// Set attributes. They can be set in any way, but this is convenient.
|
||||
dialog.transformAttributes({
|
||||
callback: (_button, _values) => {
|
||||
if (_button === Et2Dialog.OK_BUTTON)
|
||||
{
|
||||
_widget.value = _values.time;
|
||||
// for start we need to take the (not stored) offset into account
|
||||
const offset = action === 'start' ? timer.started.valueOf() - timer.start.valueOf() : 0;
|
||||
timer[action] = new Date((new Date(_values.time)).valueOf() + egw.getTimezoneOffset() * 60000 - offset);
|
||||
egw.request('timesheet.EGroupware\\Timesheet\\Events.ajax_updateTime',
|
||||
[tse_id, new Date((new Date(_values.time)).valueOf() + egw.getTimezoneOffset() * 60000)])
|
||||
}
|
||||
},
|
||||
title: egw.lang('Change time'),
|
||||
template: 'timesheet.timer.change',
|
||||
buttons: Et2Dialog.BUTTONS_OK_CANCEL,
|
||||
value: {
|
||||
content: { time: _widget.value }
|
||||
}
|
||||
});
|
||||
// Add to DOM, dialog will auto-open
|
||||
document.body.appendChild(dialog);
|
||||
},
|
||||
|
||||
/**
|
||||
* Start timer for given app and id
|
||||
*
|
||||
* @param {Object} _action
|
||||
* @param {Array} _senders
|
||||
*/
|
||||
start_timer(_action, _senders)
|
||||
start_timer: function(_action, _senders)
|
||||
{
|
||||
if (_action.parent.data.nextmatch?.getSelection().all || _senders.length !== 1)
|
||||
{
|
||||
@ -503,7 +607,7 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
||||
|
||||
// bind click handler
|
||||
timer_container.addEventListener('click', (ev) => {
|
||||
timerDialog(state.disable);
|
||||
timerDialog();
|
||||
});
|
||||
|
||||
// check if overall working time is not disabled
|
||||
@ -534,7 +638,7 @@ egw.extend('timer', egw.MODULE_GLOBAL, function()
|
||||
// overall timer running for more than 16 hours, ask to stop
|
||||
else if (overall?.start && (((new Date()).valueOf() - overall.start.valueOf()) / 3600000) >= 16)
|
||||
{
|
||||
timerDialog(state.disable, 'Forgot to switch off working time?');
|
||||
timerDialog('Forgot to switch off working time?');
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -5274,6 +5274,11 @@ span.overlayContainer img.overlay {
|
||||
Created on : 23.07.2014, 13:25:11
|
||||
Author : stefanreinhardt
|
||||
*/
|
||||
et2-date-time-today[id^=_times] {
|
||||
text-align: center;
|
||||
color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.timesheet_timer,
|
||||
#egw_fw_topmenu_info_items #topmenu_info_timer #topmenu_timer {
|
||||
text-align: center;
|
||||
|
@ -4542,7 +4542,7 @@ span.overlayContainer img.overlay {
|
||||
#egw_fw_main #egw_fw_tabs .egw_fw_ui_tabs_header .egw_fw_ui_tab_header:hover {
|
||||
background-color: rgba(153, 204, 255, 0.4);
|
||||
padding-bottom: 0px;
|
||||
padding-top: 7px;
|
||||
border-top: 7px solid transparent;
|
||||
transition: none;
|
||||
width: -webkit-fill-available;
|
||||
width: -moz-available;
|
||||
@ -4581,6 +4581,9 @@ span.overlayContainer img.overlay {
|
||||
border-right: 1px solid #bfc0bf;
|
||||
border-bottom: 4px solid white !important;
|
||||
border-top: 4px solid transparent;
|
||||
width: -webkit-fill-available;
|
||||
width: -moz-available;
|
||||
max-width: fit-content !important;
|
||||
background-color: #ffffff;
|
||||
background-image: none !important;
|
||||
}
|
||||
@ -5251,6 +5254,11 @@ span.overlayContainer img.overlay {
|
||||
Created on : 23.07.2014, 13:25:11
|
||||
Author : stefanreinhardt
|
||||
*/
|
||||
et2-date-time-today[id^=_times] {
|
||||
text-align: center;
|
||||
color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.timesheet_timer,
|
||||
#egw_fw_topmenu_info_items #topmenu_info_timer #topmenu_timer {
|
||||
text-align: center;
|
||||
|
@ -5264,6 +5264,11 @@ span.overlayContainer img.overlay {
|
||||
Created on : 23.07.2014, 13:25:11
|
||||
Author : stefanreinhardt
|
||||
*/
|
||||
et2-date-time-today[id^=_times] {
|
||||
text-align: center;
|
||||
color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.timesheet_timer,
|
||||
#egw_fw_topmenu_info_items #topmenu_info_timer #topmenu_timer {
|
||||
text-align: center;
|
||||
|
@ -18,6 +18,11 @@
|
||||
|
||||
@import (reference) "definitions.less";
|
||||
|
||||
et2-date-time-today[id^=_times] {
|
||||
text-align: center;
|
||||
color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.timesheet_timer, #egw_fw_topmenu_info_items #topmenu_info_timer #topmenu_timer {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
|
@ -5285,6 +5285,11 @@ span.overlayContainer img.overlay {
|
||||
Created on : 23.07.2014, 13:25:11
|
||||
Author : stefanreinhardt
|
||||
*/
|
||||
et2-date-time-today[id^=_times] {
|
||||
text-align: center;
|
||||
color: gray;
|
||||
cursor: pointer;
|
||||
}
|
||||
.timesheet_timer,
|
||||
#egw_fw_topmenu_info_items #topmenu_info_timer #topmenu_timer {
|
||||
text-align: center;
|
||||
|
@ -147,7 +147,45 @@ class Events extends Api\Storage\Base
|
||||
$this->db->update(self::TABLE, [
|
||||
'tse_app' => $app,
|
||||
'tse_app_id' => $id,
|
||||
], ['tse_id' => $tse_id], __LINE__, __FILE__, self::APP);
|
||||
], [
|
||||
'tse_id' => $tse_id,
|
||||
'account_id' => $this->user,
|
||||
'ts_id IS NULL',
|
||||
], __LINE__, __FILE__, self::APP);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set tse_time on an already started timer
|
||||
*
|
||||
* @param int $tse_id id of the running timer-event
|
||||
* @param string $time
|
||||
* @return void
|
||||
* @throws Api\Db\Exception\InvalidSql
|
||||
*/
|
||||
public function ajax_updateTime(int $tse_id, string $time)
|
||||
{
|
||||
if ($tse_id > 0 && !empty($time) && ($event = $this->read($tse_id)))
|
||||
{
|
||||
$time = new Api\DateTime($time);
|
||||
$this->db->update(self::TABLE, [
|
||||
'tse_time' => $time,
|
||||
], [
|
||||
'tse_id' => $tse_id,
|
||||
'account_id' => $this->user,
|
||||
], __LINE__, __FILE__, self::APP);
|
||||
|
||||
// if a stop event is overwritten, we need to adjust the timesheet duration and quantity
|
||||
if (!empty($event['ts_id']) && ($diff = round(($time->getTimestamp() - $event['tse_time'].getTimestamp()) / 60)))
|
||||
{
|
||||
$bo = new \timesheet_bo();
|
||||
if ($bo->read($event['ts_id']))
|
||||
{
|
||||
$bo->data['ts_duration'] += $diff;
|
||||
$bo->data['ts_quantity'] += $diff / 60;
|
||||
$bo->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -254,13 +292,12 @@ class Events extends Api\Storage\Base
|
||||
// format start-times in UTZ as JavaScript Date() understands
|
||||
foreach($state as &$timer)
|
||||
{
|
||||
if (isset($timer['start']))
|
||||
foreach(['start', 'started', 'last'] as $name)
|
||||
{
|
||||
$timer['start'] = (new Api\DateTime($timer['start'], new \DateTimeZone('UTC')))->format(Api\DateTime::ET2);
|
||||
}
|
||||
if (isset($timer['last']))
|
||||
{
|
||||
$timer['last'] = (new Api\DateTime($timer['last'], new \DateTimeZone('UTC')))->format(Api\DateTime::ET2);
|
||||
if (isset($timer[$name]))
|
||||
{
|
||||
$timer[$name] = (new Api\DateTime($timer[$name], new \DateTimeZone('UTC')))->format(Api\DateTime::ET2);
|
||||
}
|
||||
}
|
||||
}
|
||||
// send timer configuration to client-side
|
||||
@ -299,7 +336,8 @@ class Events extends Api\Storage\Base
|
||||
{
|
||||
if ($row['tse_type'] & self::START)
|
||||
{
|
||||
$timer['start'] = $row['tse_time'];
|
||||
$timer['start'] = $timer['started'] = $row['tse_time'];
|
||||
$timer['started_id'] = $row['tse_id'];
|
||||
$timer['paused'] = false;
|
||||
}
|
||||
elseif ($timer['start'])
|
||||
|
@ -1,6 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE overlay PUBLIC "-//EGroupware GmbH//eTemplate 2//EN" "http://www.egroupware.org/etemplate2.dtd">
|
||||
<overlay>
|
||||
<template id="timesheet.timer.change" template="" lang="" group="0" version="1.7.002">
|
||||
<et2-date-time id="time" required="true"/>
|
||||
</template>
|
||||
<template id="timesheet.timer" template="" lang="" group="0" version="1.7.002">
|
||||
<grid width="100%" >
|
||||
<columns>
|
||||
@ -11,11 +14,6 @@
|
||||
<column/>
|
||||
</columns>
|
||||
<rows>
|
||||
<row disabled="@disable=/overwrite/">
|
||||
<description value="Overwrite time" span="2"/>
|
||||
<et2-date-time id="time" span="all" placeholder="now"
|
||||
onclick="if (!this.value) this.value = new Date((new Date).valueOf() - egw.getTimezoneOffset() * 60000)"/>
|
||||
</row>
|
||||
<row disabled="@disable=/specific/">
|
||||
<description value="Timer"/>
|
||||
<old-box id="specific_timer" value="00:00" class="timesheet_timer"/>
|
||||
@ -23,6 +21,13 @@
|
||||
<et2-button id="specific[pause]" label="Pause" image="timesheet/pause-orange" disabled="true"/>
|
||||
<et2-button id="specific[stop]" label="Stop" image="timesheet/stop" disabled="true"/>
|
||||
</row>
|
||||
<row disabled="@disable=/specific/">
|
||||
<description />
|
||||
<description/>
|
||||
<et2-date-time-today id="times[specific][start]" onclick="egw.change_timer"/>
|
||||
<et2-date-time-today id="times[specific][paused]" onclick="egw.change_timer"/>
|
||||
<et2-date-time-today id="times[specific][stop]" onclick="egw.change_timer"/>
|
||||
</row>
|
||||
<row disabled="@disable=/overall/">
|
||||
<description value="Working time"/>
|
||||
<old-box id="overall_timer" value="00:00" class="timesheet_timer overall"/>
|
||||
@ -30,6 +35,13 @@
|
||||
<et2-button id="overall[pause]" label="Pause" image="timesheet/pause-orange" disabled="true"/>
|
||||
<et2-button id="overall[stop]" label="Stop" image="timesheet/stop" disabled="true"/>
|
||||
</row>
|
||||
<row disabled="@disable=/overall/">
|
||||
<description />
|
||||
<description/>
|
||||
<et2-date-time-today id="times[overall][start]" onclick="egw.change_timer"/>
|
||||
<et2-date-time-today id="times[overall][paused]" onclick="egw.change_timer"/>
|
||||
<et2-date-time-today id="times[overall][stop]" onclick="egw.change_timer"/>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
</template>
|
||||
|
Loading…
Reference in New Issue
Block a user