mirror of
https://gitlab.com/hibou-io/hibou-odoo/suite.git
synced 2025-01-20 12:37:31 +02:00
[IMP] timesheet_grid_work_entry: dynamic ui for project task timer
WIP for grid views overrides to add work_type_id
This commit is contained in:
@@ -0,0 +1,288 @@
|
||||
odoo.define('timesheet_grid_work_entry.TimerHeaderComponent', function (require) {
|
||||
"use strict";
|
||||
|
||||
console.log('timesheet_grid_work_entry.TimerHeaderComponent v1');
|
||||
|
||||
const fieldUtils = require('web.field_utils');
|
||||
const TimerHeaderM2O = require('timesheet_grid_work_entry.TimerHeaderM2O');
|
||||
const TimerHeaderComponent = require('timesheet_grid.TimerHeaderComponent');
|
||||
|
||||
const { useState, useRef } = owl.hooks;
|
||||
const { ComponentAdapter } = require('web.OwlCompatibility');
|
||||
|
||||
class TimerHeaderM2OAdapter extends ComponentAdapter {
|
||||
async updateWidget(nextProps) {
|
||||
console.log(nextProps);
|
||||
if (this.widget.workTypeId !== nextProps.widgetArgs[2]) {
|
||||
this.widget.workTypeId = nextProps.widgetArgs[2];
|
||||
const workType = this.widget.workTypeId || false;
|
||||
await this.widget.workTypeMany2one.reinitialize(workType);
|
||||
}
|
||||
// Original
|
||||
if (this.widget.projectId !== nextProps.widgetArgs[0] ||
|
||||
this.widget.taskId !== nextProps.widgetArgs[1]) {
|
||||
this.widget.projectId = nextProps.widgetArgs[0];
|
||||
this.widget.taskId = nextProps.widgetArgs[1];
|
||||
this.widget._updateRequiredField();
|
||||
const project = this.widget.projectId || false;
|
||||
await this.widget.projectMany2one.reinitialize(project);
|
||||
this.widget.taskMany2one.field.domain = [['project_id', '=?', project]];
|
||||
const task = this.widget.taskId || false;
|
||||
await this.widget.taskMany2one.reinitialize(task);
|
||||
} else if (nextProps.widgetArgs[3]) {
|
||||
this.widget._updateRequiredField();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
TimerHeaderComponent.props['workTypeId'] = {
|
||||
type: Number,
|
||||
optional: true
|
||||
};
|
||||
TimerHeaderComponent.props['workTypeName'] = {
|
||||
type: String,
|
||||
optional: true
|
||||
};
|
||||
TimerHeaderComponent.components = { TimerHeaderM2OAdapter };
|
||||
|
||||
class TimerHeaderComponentWorkEntry extends TimerHeaderComponent {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.TimerHeaderM2O = TimerHeaderM2O;
|
||||
}
|
||||
}
|
||||
|
||||
return TimerHeaderComponentWorkEntry;
|
||||
|
||||
// class TimerHeaderComponent extends owl.Component {
|
||||
// constructor() {
|
||||
// super(...arguments);
|
||||
|
||||
// this.state = useState({
|
||||
// time: null,
|
||||
// manualTimeInput: false,
|
||||
// errorManualTimeInput: false,
|
||||
// });
|
||||
// this.TimerHeaderM2O = TimerHeaderM2O;
|
||||
// this.manualTimerAmount = "00:00";
|
||||
// this.manualTimeInput = useRef("manualTimerInput");
|
||||
// this.descriptionInput = useRef("inputDescription");
|
||||
// this.startButton = useRef("startButton");
|
||||
// this.stopButton = useRef("stopButton");
|
||||
// this.timerStarted = false;
|
||||
|
||||
// if (this.props.timerRunning === true) {
|
||||
// this.timerStarted = true;
|
||||
// this.state.time = Math.floor(Date.now() / 1000) - this.props.timer;
|
||||
// this.timer = setInterval(() => {
|
||||
// this.state.time = Math.floor(Date.now() / 1000) - this.props.timer;
|
||||
// }, 1000);
|
||||
// }
|
||||
// }
|
||||
// async willUpdateProps(nextProps) {
|
||||
// if (nextProps.description !== this.props.description && this.descriptionInput.el) {
|
||||
// this.descriptionInput.el.value = nextProps.description;
|
||||
// }
|
||||
// return super.willUpdateProps(...arguments);
|
||||
// }
|
||||
// patched() {
|
||||
// if (this.state.manualTimeInput && !this.state.errorManualTimeInput && this.manualTimeInput.el !== document.activeElement) {
|
||||
// this.manualTimeInput.el.focus();
|
||||
// this.manualTimeInput.el.select();
|
||||
// }
|
||||
// if (this.props.timerRunning && !this.timerStarted) {
|
||||
// this.timerStarted = true;
|
||||
// this.state.time = Math.floor(Date.now() / 1000) - this.props.timer;
|
||||
// this.timer = setInterval(() => {
|
||||
// this.state.time = Math.floor(Date.now() / 1000) - this.props.timer;
|
||||
// }, 1000);
|
||||
// this.stopButton.el.focus();
|
||||
// } else if (!this.props.timerRunning && this.timerStarted) {
|
||||
// this.timerStarted = false;
|
||||
// clearInterval(this.timer);
|
||||
// this.startButton.el.focus();
|
||||
// }
|
||||
// }
|
||||
// mounted() {
|
||||
// if (this.stopButton.el) {
|
||||
// this.stopButton.el.focus();
|
||||
// } else {
|
||||
// this.startButton.el.focus();
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
// //----------------------------------------------------------------------
|
||||
// // Getters
|
||||
// //----------------------------------------------------------------------
|
||||
|
||||
// get timerMode() {
|
||||
// return this.props.addTimeMode;
|
||||
// }
|
||||
// get timeInput() {
|
||||
// return this.manualTimerAmount;
|
||||
// }
|
||||
// get _timerIsRunning() {
|
||||
// return this.props.timerRunning;
|
||||
// }
|
||||
// get _timerReadOnly() {
|
||||
// return this.props.timerReadOnly;
|
||||
// }
|
||||
// get _manualTimeInput() {
|
||||
// return this.state.manualTimeInput;
|
||||
// }
|
||||
// get _timerString() {
|
||||
// if (this.state.time) {
|
||||
// const hours = Math.floor(this.state.time / 3600);
|
||||
// const secondsLeft = this.state.time % 3600;
|
||||
// const seconds = this._display2digits(secondsLeft % 60);
|
||||
// const minutes = this._display2digits(Math.floor(secondsLeft / 60));
|
||||
|
||||
// return `${this._display2digits(hours)}:${minutes}:${seconds}`;
|
||||
// }
|
||||
// return "00:00:00";
|
||||
// }
|
||||
// get isMobile() {
|
||||
// return this.env.device.isMobile;
|
||||
// }
|
||||
|
||||
// //--------------------------------------------------------------------------
|
||||
// // Private
|
||||
// //--------------------------------------------------------------------------
|
||||
|
||||
// _display2digits(number) {
|
||||
// return number > 9 ? "" + number : "0" + number;
|
||||
// }
|
||||
|
||||
// //--------------------------------------------------------------------------
|
||||
// // Handlers
|
||||
// //--------------------------------------------------------------------------
|
||||
|
||||
// /**
|
||||
// * @private
|
||||
// * @param {KeyboardEvent} ev
|
||||
// */
|
||||
// async _onKeydown(ev) {
|
||||
// if (ev.key === 'Enter') {
|
||||
// ev.preventDefault();
|
||||
// if (this.state.manualTimeInput) {
|
||||
// this._onFocusoutTimer(ev);
|
||||
// } else {
|
||||
// this.trigger('new-description', ev.target.value);
|
||||
// this.trigger('timer-stopped');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// /**
|
||||
// * @private
|
||||
// * @param {MouseEvent} ev
|
||||
// */
|
||||
// _onFocusoutTimer(ev) {
|
||||
// try {
|
||||
// const value = fieldUtils.parse['float_time'](ev.target.value);
|
||||
// this.state.errorManualTimeInput = (value < 0);
|
||||
// if (!this.state.errorManualTimeInput) {
|
||||
// this.trigger('new-timer-value', value);
|
||||
// this.state.time = value * 3600;
|
||||
// this.state.manualTimeInput = false;
|
||||
// }
|
||||
// } catch (_) {
|
||||
// this.state.errorManualTimeInput = true;
|
||||
// }
|
||||
// }
|
||||
// /**
|
||||
// * @private
|
||||
// * @param {KeyboardEvent} ev
|
||||
// */
|
||||
// _onInputTimer(ev) {
|
||||
// try {
|
||||
// const value = fieldUtils.parse['float_time'](ev.target.value);
|
||||
// this.state.errorManualTimeInput = (value < 0);
|
||||
// } catch (_) {
|
||||
// this.state.errorManualTimeInput = true;
|
||||
// }
|
||||
// }
|
||||
// /**
|
||||
// * @private
|
||||
// * @param {MouseEvent} ev
|
||||
// */
|
||||
// _onClickStopTimer(ev) {
|
||||
// ev.stopPropagation();
|
||||
// this.trigger('timer-stopped');
|
||||
// }
|
||||
// /**
|
||||
// * @private
|
||||
// * @param {MouseEvent} ev
|
||||
// */
|
||||
// _onClickStartTimer(ev) {
|
||||
// ev.stopPropagation();
|
||||
// this.trigger('timer-started');
|
||||
// }
|
||||
// /**
|
||||
// * @private
|
||||
// * @param {Event} ev
|
||||
// */
|
||||
// _onInputDescription(ev) {
|
||||
// this.trigger('new-description', ev.target.value);
|
||||
// }
|
||||
// /**
|
||||
// * @private
|
||||
// * @param {MouseEvent} ev
|
||||
// */
|
||||
// async _onClickManualTime(ev) {
|
||||
// if (this.props.timerReadOnly) {
|
||||
// return;
|
||||
// }
|
||||
// const rounded_minutes = await this.rpc({
|
||||
// model: 'account.analytic.line',
|
||||
// method: 'get_rounded_time',
|
||||
// args: [this.state.time/60],
|
||||
// });
|
||||
// this.manualTimerAmount = fieldUtils.format['float_time'](rounded_minutes);
|
||||
// this.state.manualTimeInput = true;
|
||||
// }
|
||||
// /**
|
||||
// * @private
|
||||
// * @param {MouseEvent} ev
|
||||
// */
|
||||
// async _onUnlinkTimer(ev) {
|
||||
// this.trigger('timer-unlink');
|
||||
// }
|
||||
// }
|
||||
// TimerHeaderComponent.template = 'timesheet_grid.timer_header';
|
||||
// TimerHeaderComponent.props = {
|
||||
// taskId: {
|
||||
// type: Number,
|
||||
// optional: true
|
||||
// },
|
||||
// projectId: {
|
||||
// type: Number,
|
||||
// optional: true
|
||||
// },
|
||||
// taskName: {
|
||||
// type: String,
|
||||
// optional: true
|
||||
// },
|
||||
// projectName: {
|
||||
// type: String,
|
||||
// optional: true
|
||||
// },
|
||||
// stepTimer: Number,
|
||||
// timer: Number,
|
||||
// description: {
|
||||
// type: String,
|
||||
// optional: true
|
||||
// },
|
||||
// timerRunning: Boolean,
|
||||
// addTimeMode: Boolean,
|
||||
// timerReadOnly: {
|
||||
// type: Boolean,
|
||||
// optional: true
|
||||
// },
|
||||
// projectWarning: Boolean,
|
||||
// };
|
||||
// TimerHeaderComponent.components = { TimerHeaderM2OAdapter };
|
||||
|
||||
// return TimerHeaderComponent;
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
odoo.define('timesheet_grid_work_entry.TimerHeaderM2O', function (require) {
|
||||
"use strict";
|
||||
|
||||
console.log('timesheet_grid_work_entry.TimerHeaderM2OWorkEntry v1');
|
||||
|
||||
const config = require('web.config');
|
||||
const core = require('web.core');
|
||||
const relational_fields = require('web.relational_fields');
|
||||
const Widget = require('web.Widget');
|
||||
|
||||
const Many2One = relational_fields.FieldMany2One;
|
||||
const _t = core._t;
|
||||
const TimerHeaderM2O = require('timesheet_grid.TimerHeaderM2O');
|
||||
|
||||
const TimerHeaderM2OWorkEntry = Widget.include(TimerHeaderM2O, {
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @param {Widget} parent
|
||||
* @param {Object} params
|
||||
*/
|
||||
init: function (parent, params) {
|
||||
console.log('TimerHeaderM2OWorkEntry.init v1');
|
||||
this._super(...arguments);
|
||||
// StandaloneFieldManagerMixin.init.call(this);
|
||||
// this.projectId = arguments[1];
|
||||
// this.taskId = arguments[2];
|
||||
this.workTypeId = arguments[3];
|
||||
},
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
willStart: async function () {
|
||||
await this._super(...arguments);
|
||||
|
||||
const workTypeDomain = [['allow_timesheet', '=', true]];
|
||||
this.workType = await this.model.makeRecord('account.analytic.line', [{
|
||||
name: 'work_type_id',
|
||||
relation: 'hr.work.entry.type',
|
||||
type: 'many2one',
|
||||
value: this.projectTypeId,
|
||||
domain: workTypeDomain,
|
||||
}]);
|
||||
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
start: async function () {
|
||||
const _super = this._super.bind(this);
|
||||
let placeholderWorkType;
|
||||
|
||||
if (config.device.isMobile) {
|
||||
placeholderWorkType = _t('Work Type');
|
||||
} else {
|
||||
placeholderWorkType = _t('Select a Work Type');
|
||||
}
|
||||
const workTypeRecord = this.model.get(this.workType);
|
||||
const workTypeMany2one = new Many2One(this, 'work_type_id', workTypeRecord, {
|
||||
attrs: {
|
||||
placeholder: placeholderWorkType,
|
||||
},
|
||||
noOpen: true,
|
||||
noCreate: true,
|
||||
mode: 'edit',
|
||||
required: true,
|
||||
});
|
||||
workTypeMany2one.field['required'] = true;
|
||||
this._registerWidget(this.workType, 'work_type_id', workTypeMany2one);
|
||||
await workTypeMany2one.appendTo(this.$('.timer_work_type_id'));
|
||||
this.workTypeMany2one = workTypeMany2one;
|
||||
|
||||
_super.apply(...arguments);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
* @override
|
||||
* @param {OdooEvent} ev
|
||||
*/
|
||||
_onFieldChanged: async function (ev) {
|
||||
await this._super(...arguments);
|
||||
|
||||
const workType = this.workTypeId;
|
||||
const fieldName = ev.target.name;
|
||||
|
||||
if (fieldName === 'work_type_id') {
|
||||
record = this.model.get(this.workType);
|
||||
var newId = record.data.work_type_id.res_id;
|
||||
if (workType !== newId) {
|
||||
this.workTypeId = newId;
|
||||
}
|
||||
}
|
||||
|
||||
// } else if (fieldName === 'task_id') {
|
||||
// record = this.model.get(this.task);
|
||||
// const newId = record.data.task_id && record.data.task_id.res_id;
|
||||
// if (task !== newId) {
|
||||
// let project_id = this.projectId;
|
||||
// if (!project_id) {
|
||||
// const task_data = await this._rpc({
|
||||
// model: 'project.task',
|
||||
// method: 'search_read',
|
||||
// args: [[['id', '=', newId]], ['project_id']],
|
||||
// });
|
||||
// project_id = task_data[0].project_id[0];
|
||||
// }
|
||||
|
||||
// this.taskId = false;
|
||||
// this.trigger_up('timer-edit-task', {'taskId': newId, 'projectId': project_id});
|
||||
// }
|
||||
// }
|
||||
},
|
||||
});
|
||||
|
||||
return TimerHeaderM2OWorkEntry
|
||||
|
||||
});
|
||||
@@ -0,0 +1,357 @@
|
||||
odoo.define('timesheet_grid_work_entry.TimerGridRenderer', function (require) {
|
||||
"use strict";
|
||||
|
||||
console.log('timesheet_grid_work_entry.TimerGridRenderer v1');
|
||||
|
||||
const utils = require('web.utils');
|
||||
const GridRenderer = require('web_grid.GridRenderer');
|
||||
const TimerHeaderComponent = require('timesheet_grid_work_entry.TimerHeaderComponent');
|
||||
const TimerStartComponent = require('timesheet_grid.TimerStartComponent');
|
||||
const { useState, useExternalListener, useRef } = owl.hooks;
|
||||
|
||||
class TimerGridRenderer extends GridRenderer {
|
||||
constructor(parent, props) {
|
||||
super(...arguments);
|
||||
useExternalListener(window, 'keydown', this._onKeydown);
|
||||
useExternalListener(window, 'keyup', this._onKeyup);
|
||||
|
||||
this.initialGridAnchor = props.context.grid_anchor;
|
||||
this.initialGroupBy = props.groupBy;
|
||||
|
||||
this.stateTimer = useState({
|
||||
taskId: undefined,
|
||||
taskName: '',
|
||||
projectId: undefined,
|
||||
projectName: '',
|
||||
workTypeId: undefined,
|
||||
workTypeName: '',
|
||||
addTimeMode: false,
|
||||
description: '',
|
||||
startSeconds: 0,
|
||||
timerRunning: false,
|
||||
indexRunning: -1,
|
||||
readOnly: false,
|
||||
projectWarning: false,
|
||||
});
|
||||
this.timerHeader = useRef('timerHeader');
|
||||
this.timesheetId = false;
|
||||
this._onChangeProjectTaskDebounce = _.debounce(this._setProjectTask.bind(this), 500);
|
||||
}
|
||||
mounted() {
|
||||
super.mounted(...arguments);
|
||||
if (this.formatType === 'float_time') {
|
||||
this._get_running_timer();
|
||||
}
|
||||
}
|
||||
async willUpdateProps(nextProps) {
|
||||
if (nextProps.data !== this.props.data) {
|
||||
this._match_line(nextProps.data);
|
||||
}
|
||||
return super.willUpdateProps(...arguments);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Getters
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @returns {boolean} returns true if when we need to display the timer button
|
||||
*
|
||||
*/
|
||||
get showTimerButton() {
|
||||
return ((this.formatType === 'float_time') && (
|
||||
this.props.groupBy.includes('project_id')
|
||||
));
|
||||
}
|
||||
/**
|
||||
* @returns {boolean} returns always true if timesheet in hours, that way we know we're on a timesheet grid and
|
||||
* we can show the timer header.
|
||||
*
|
||||
*/
|
||||
get showTimer() {
|
||||
return this.formatType === 'float_time';
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Private
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
_match_line(new_grid) {
|
||||
const grid = (new_grid) ? new_grid[0].rows : this.props.data[0].rows;
|
||||
let current_value;
|
||||
for (let i = 0; i < grid.length; i++) {
|
||||
current_value = grid[i].values;
|
||||
if (current_value.project_id && current_value.project_id[0] === this.stateTimer.projectId
|
||||
&& ((!current_value.task_id && !this.stateTimer.taskId) ||
|
||||
(current_value.task_id && current_value.task_id[0] === this.stateTimer.taskId))) {
|
||||
this.stateTimer.indexRunning = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.stateTimer.indexRunning = -1;
|
||||
}
|
||||
async _get_running_timer() {
|
||||
const result = await this.rpc({
|
||||
model: 'account.analytic.line',
|
||||
method: 'get_running_timer',
|
||||
args: []
|
||||
});
|
||||
if (result.id !== undefined) {
|
||||
this.stateTimer.timerRunning = true;
|
||||
this.timesheetId = result.id;
|
||||
this.stateTimer.readOnly = result.readonly;
|
||||
this.stateTimer.projectId = result.project_id;
|
||||
this.stateTimer.taskId = result.task_id || undefined;
|
||||
|
||||
// In case of read-only timer
|
||||
this.stateTimer.projectName = (result.project_name) ? result.project_name : '';
|
||||
this.stateTimer.taskName = (result.task_name) ? result.task_name : '';
|
||||
|
||||
this.stateTimer.timerRunning = true;
|
||||
this.stateTimer.description = (result.description === '/') ? '' : result.description;
|
||||
this.stateTimer.startSeconds = Math.floor(Date.now() / 1000) - result.start;
|
||||
} else if (this.stateTimer.timerRunning && this.stateTimer.projectId) {
|
||||
this.timesheetId = false;
|
||||
this.stateTimer.readOnly = false;
|
||||
this.stateTimer.projectId = false;
|
||||
this.stateTimer.taskId = undefined;
|
||||
|
||||
this.stateTimer.timerRunning = false;
|
||||
this.stateTimer.description = '';
|
||||
}
|
||||
if (this.timerHeader.comp.startButton.el) {
|
||||
this.timerHeader.comp.startButton.el.focus();
|
||||
}
|
||||
this._match_line();
|
||||
}
|
||||
async _onSetProject(data) {
|
||||
this.stateTimer.projectId = data.detail.projectId;
|
||||
this.stateTimer.taskId = undefined;
|
||||
this._onChangeProjectTaskDebounce(data.detail.projectId, undefined);
|
||||
}
|
||||
async _onSetWorkType(data) {
|
||||
this.stateTimer.workTypeId = data.detail.workTypeId;
|
||||
}
|
||||
async _onSetTask(data) {
|
||||
this.stateTimer.projectId = data.detail.projectId;
|
||||
this.stateTimer.taskId = data.detail.taskId || undefined;
|
||||
this._onChangeProjectTaskDebounce(this.stateTimer.projectId, data.detail.taskId);
|
||||
}
|
||||
async _setProjectTask(projectId, taskId) {
|
||||
if (!this.stateTimer.projectId) {
|
||||
return;
|
||||
}
|
||||
if (this.timesheetId) {
|
||||
const timesheetId = await this.rpc({
|
||||
model: 'account.analytic.line',
|
||||
method: 'action_change_project_task',
|
||||
args: [[this.timesheetId], this.stateTimer.projectId, this.stateTimer.taskId],
|
||||
});
|
||||
if (this.timesheetId !== timesheetId) {
|
||||
this.timesheetId = timesheetId;
|
||||
await this._get_running_timer();
|
||||
}
|
||||
} else {
|
||||
const seconds = Math.floor(Date.now() / 1000) - this.stateTimer.startSeconds;
|
||||
this.timesheetId = await this.rpc({
|
||||
model: 'account.analytic.line',
|
||||
method: 'create',
|
||||
args: [{
|
||||
'name': this.stateTimer.description,
|
||||
'project_id': this.stateTimer.projectId,
|
||||
'task_id': this.stateTimer.taskId,
|
||||
}],
|
||||
});
|
||||
// Add already runned time and start timer if doesn't running yet in DB
|
||||
this.trigger('add_time_timer', {
|
||||
timesheetId: this.timesheetId,
|
||||
time: seconds
|
||||
});
|
||||
}
|
||||
this._match_line();
|
||||
}
|
||||
async _onClickLineButton(taskId, projectId) {
|
||||
// Check that we can create timers for the selected project.
|
||||
// This is an edge case in multi-company environment.
|
||||
const canStartTimerResult = await this.rpc({
|
||||
model: 'project.project',
|
||||
method: 'check_can_start_timer',
|
||||
args: [[projectId]],
|
||||
});
|
||||
if (canStartTimerResult !== true) {
|
||||
this.trigger('do_action', {action: canStartTimerResult})
|
||||
return;
|
||||
}
|
||||
if (this.stateTimer.addTimeMode === true) {
|
||||
this.timesheetId = await this.rpc({
|
||||
model: 'account.analytic.line',
|
||||
method: 'action_add_time_to_timesheet',
|
||||
args: [[this.timesheetId], projectId, taskId, this.props.stepTimer * 60],
|
||||
});
|
||||
this.trigger('update_timer');
|
||||
} else if (! this.timesheetId && this.stateTimer.timerRunning) {
|
||||
this.stateTimer.projectId = projectId;
|
||||
this.stateTimer.taskId = (taskId) ? taskId : undefined;
|
||||
await this._onChangeProjectTaskDebounce(projectId, taskId);
|
||||
} else {
|
||||
if (this.stateTimer.projectId === projectId && this.stateTimer.taskId === taskId) {
|
||||
await this._stop_timer();
|
||||
return;
|
||||
}
|
||||
await this._stop_timer();
|
||||
this.stateTimer.projectId = projectId;
|
||||
this.stateTimer.taskId = (taskId) ? taskId : undefined;
|
||||
await this._onTimerStarted();
|
||||
await this._onChangeProjectTaskDebounce(projectId, taskId);
|
||||
}
|
||||
if (this.timerHeader.comp.stopButton.el) {
|
||||
this.timerHeader.comp.stopButton.el.focus();
|
||||
}
|
||||
}
|
||||
async _onTimerStarted() {
|
||||
this.stateTimer.timerRunning = true;
|
||||
this.stateTimer.addTimeMode = false;
|
||||
this.stateTimer.startSeconds = Math.floor(Date.now() / 1000);
|
||||
if (this.props.defaultProject && ! this.stateTimer.projectId) {
|
||||
this.stateTimer.projectId = this.props.defaultProject;
|
||||
this._onChangeProjectTaskDebounce(this.props.defaultProject, undefined);
|
||||
}
|
||||
}
|
||||
async _stop_timer() {
|
||||
if (!this.timesheetId) {
|
||||
this.stateTimer.projectWarning = true;
|
||||
return;
|
||||
}
|
||||
let timesheetId = this.timesheetId;
|
||||
this.timesheetId = false;
|
||||
this.trigger('stop_timer', {
|
||||
timesheetId: timesheetId,
|
||||
});
|
||||
this.stateTimer.description = '';
|
||||
this.stateTimer.timerRunning = false;
|
||||
this.timesheetId = false;
|
||||
this.stateTimer.projectId = undefined;
|
||||
this.stateTimer.taskId = undefined;
|
||||
|
||||
this.stateTimer.timerRunning = false;
|
||||
this.stateTimer.projectWarning = false;
|
||||
|
||||
this._match_line();
|
||||
this.stateTimer.readOnly = false;
|
||||
}
|
||||
async _onTimerUnlink() {
|
||||
if (this.timesheetId !== false) {
|
||||
this.trigger('unlink_timer', {
|
||||
timesheetId: this.timesheetId,
|
||||
});
|
||||
}
|
||||
this.timesheetId = false;
|
||||
this.stateTimer.projectId = undefined;
|
||||
this.stateTimer.taskId = undefined;
|
||||
|
||||
this.stateTimer.timerRunning = false;
|
||||
this.stateTimer.description = '';
|
||||
this.stateTimer.manualTimeInput = false;
|
||||
this._match_line();
|
||||
this.stateTimer.readOnly = false;
|
||||
this.stateTimer.projectWarning = false;
|
||||
}
|
||||
_onNewDescription(data) {
|
||||
this.stateTimer.description = data.detail;
|
||||
if (this.timesheetId) {
|
||||
this.trigger('update_timer_description', {
|
||||
timesheetId: this.timesheetId,
|
||||
description: data.detail
|
||||
});
|
||||
}
|
||||
}
|
||||
async _onNewTimerValue(data) {
|
||||
const seconds = Math.floor(Date.now() / 1000) - this.stateTimer.startSeconds;
|
||||
const toAdd = data.detail * 3600 - seconds;
|
||||
this.stateTimer.startSeconds = this.stateTimer.startSeconds - toAdd;
|
||||
if (this.timesheetId && typeof toAdd === 'number') {
|
||||
this.trigger('add_time_timer', {
|
||||
timesheetId: this.timesheetId,
|
||||
time: toAdd
|
||||
});
|
||||
}
|
||||
this.timerHeader.comp.stopButton.el.focus();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Handlers
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @param {MouseEvent} ev
|
||||
*/
|
||||
async _onClickStartTimerFromLine(ev) {
|
||||
if (! ev.detail) {
|
||||
return;
|
||||
}
|
||||
const cell_path = ev.detail.split('.');
|
||||
const grid_path = cell_path.slice(0, -2);
|
||||
const row_path = grid_path.concat(['rows'], cell_path.slice(-1));
|
||||
const row = utils.into(this.props.data, row_path);
|
||||
const data = row.values;
|
||||
const task = (data.task_id) ? data.task_id[0] : undefined;
|
||||
this._onClickLineButton(task, data.project_id[0]);
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {KeyboardEvent} ev
|
||||
*/
|
||||
async _onKeydown(ev) {
|
||||
if (ev.key === 'Shift' && !this.stateTimer.timerRunning && !this.state.editMode) {
|
||||
this.stateTimer.addTimeMode = true;
|
||||
} else if (!ev.altKey && !ev.ctrlKey && !ev.metaKey && this.showTimerButton && ! ['input', 'textarea'].includes(ev.target.tagName.toLowerCase())) {
|
||||
if (ev.key === 'Escape' && this.stateTimer.timerRunning) {
|
||||
this._onTimerUnlink();
|
||||
}
|
||||
const index = ev.keyCode - 65;
|
||||
if (index >= 0 && index <= 26 && index < this.props.data[0].rows.length) {
|
||||
const data = this.props.data[0].rows[index].values;
|
||||
const projectId = data.project_id[0];
|
||||
const taskId = data.task_id && data.task_id[0];
|
||||
this._onClickLineButton(taskId, projectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @private
|
||||
* @param {KeyboardEvent} ev
|
||||
*/
|
||||
_onKeyup(ev) {
|
||||
if (ev.key === 'Shift' && !this.state.editMode) {
|
||||
this.stateTimer.addTimeMode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimerGridRenderer.props = Object.assign({}, GridRenderer.props, {
|
||||
serverTime: {
|
||||
type: String,
|
||||
optional: true
|
||||
},
|
||||
stepTimer: {
|
||||
type: Number,
|
||||
optional: true
|
||||
},
|
||||
defaultProject: {
|
||||
type: [Boolean, Number],
|
||||
optional: true
|
||||
},
|
||||
Component: {
|
||||
type: Object,
|
||||
optional: true
|
||||
},
|
||||
});
|
||||
|
||||
TimerGridRenderer.components = {
|
||||
TimerHeaderComponent,
|
||||
TimerStartComponent,
|
||||
};
|
||||
|
||||
return TimerGridRenderer;
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
odoo.define('timesheet_grid_work_entry.TimerGridView', function (require) {
|
||||
"use strict";
|
||||
|
||||
console.log('timesheet_grid_work_entry.TimerGridView v1');
|
||||
|
||||
const viewRegistry = require('web.view_registry');
|
||||
const WebGridView = require('web_grid.GridView');
|
||||
const TimerGridController = require('timesheet_grid.TimerGridController');
|
||||
const TimerGridModel = require('timesheet_grid.TimerGridModel');
|
||||
const GridRenderer = require('timesheet_grid_work_entry.TimerGridRenderer');
|
||||
const TimesheetConfigQRCodeMixin = require('timesheet_grid.TimesheetConfigQRCodeMixin');
|
||||
const { onMounted, onPatched } = owl.hooks;
|
||||
|
||||
class TimerGridRenderer extends GridRenderer {
|
||||
constructor() {
|
||||
console.log('TimerGridRenderer constructor called');
|
||||
super(...arguments);
|
||||
onMounted(() => this._bindPlayStoreIcon());
|
||||
onPatched(() => this._bindPlayStoreIcon());
|
||||
}
|
||||
}
|
||||
|
||||
// QRCode mixin to bind event on play store icon
|
||||
Object.assign(TimerGridRenderer.prototype, TimesheetConfigQRCodeMixin);
|
||||
|
||||
const TimerGridView = WebGridView.extend({
|
||||
config: Object.assign({}, WebGridView.prototype.config, {
|
||||
Model: TimerGridModel,
|
||||
Controller: TimerGridController,
|
||||
Renderer: TimerGridRenderer
|
||||
})
|
||||
});
|
||||
|
||||
viewRegistry.add('timesheet_timer_grid', TimerGridView);
|
||||
|
||||
return TimerGridView;
|
||||
});
|
||||
15
timesheet_grid_work_entry/static/src/xml/timer_m2o.xml
Normal file
15
timesheet_grid_work_entry/static/src/xml/timer_m2o.xml
Normal file
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<!-- Refactor this into widgets.xml for consistency ? -->
|
||||
|
||||
<templates>
|
||||
|
||||
<template t-name="timesheet_grid_work_entry.timer_project_task" t-inherit="timesheet_grid.timer_project_task" class="d-inline-flex timer_m2o">
|
||||
<xpath expr="//div[hasclass('px-2')]" position="after">
|
||||
<div class="px-2 flex-grow-1">
|
||||
<div class="timer_work_type_id my-auto"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</template>
|
||||
|
||||
</templates>
|
||||
23
timesheet_grid_work_entry/static/src/xml/timesheet_grid.xml
Normal file
23
timesheet_grid_work_entry/static/src/xml/timesheet_grid.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<templates>
|
||||
|
||||
<t t-name="timesheet_grid_work_entry.GridRenderer" t-inherit="web_grid.GridRenderer" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//TimerHeaderComponent" position="attributes">
|
||||
<attribute name="workTypeId">stateTimer.workTypeId</attribute>
|
||||
<attribute name="t-on-timer-edit-work-type">_onSetWorkType</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
<t t-name="timesheet_grid_work_entry.timer_header" t-inherit="timesheet_grid.timer_header" t-inherit-mode="extension" owl="1">
|
||||
<xpath expr="//div[hasclass('input_m2o')]/div" position="after">
|
||||
<div class="px-2">
|
||||
<span t-esc="props.workTypeName"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//TimerHeaderM2OAdapter" position="attributes">
|
||||
<attribute name="Component">TimerHeaderM2OWorkEntry</attribute>
|
||||
<attribute name="widgetArgs">[props.projectId, props.taskId, props.workTypeId, props.projectWarning]</attribute>
|
||||
</xpath>
|
||||
</t>
|
||||
|
||||
</templates>
|
||||
Reference in New Issue
Block a user