@ -0,0 +1,48 @@ |
|||||
|
.. image:: https://img.shields.io/badge/license-AGPL--3-blue.svg |
||||
|
:target: https://www.gnu.org/licenses/agpl-3.0-standalone.html |
||||
|
:alt: License: AGPL-3 |
||||
|
|
||||
|
Quick Add Timesheet |
||||
|
==================== |
||||
|
This module helps to add timesheets from systray by selecting Project, Task and Description for Timesheets. |
||||
|
|
||||
|
Configuration |
||||
|
============ |
||||
|
* No additional configurations needed. |
||||
|
|
||||
|
Company |
||||
|
------- |
||||
|
* `Cybrosys Techno Solutions <https://cybrosys.com/>`__ |
||||
|
|
||||
|
License |
||||
|
------- |
||||
|
Affero General Public License, Version 3 (AGPL V3). |
||||
|
(https://www.gnu.org/licenses/agpl-3.0-standalone.html) |
||||
|
|
||||
|
Credits |
||||
|
------- |
||||
|
* Developers: (V17) Mohammed Dilshad Tk, |
||||
|
(V18) Aysha Shalin |
||||
|
Contact: odoo@cybrosys.com |
||||
|
|
||||
|
Contacts |
||||
|
-------- |
||||
|
* Mail Contact : odoo@cybrosys.com |
||||
|
* Website : https://cybrosys.com |
||||
|
|
||||
|
Bug Tracker |
||||
|
----------- |
||||
|
Bugs are tracked on GitHub Issues. In case of trouble, please check there if your issue has already been reported. |
||||
|
|
||||
|
Maintainer |
||||
|
========== |
||||
|
.. image:: https://cybrosys.com/images/logo.png |
||||
|
:target: https://cybrosys.com |
||||
|
|
||||
|
This module is maintained by Cybrosys Technologies. |
||||
|
|
||||
|
For support and more information, please visit `Our Website <https://cybrosys.com/>`__ |
||||
|
|
||||
|
Further information |
||||
|
=================== |
||||
|
HTML Description: `<static/description/index.html>`__ |
@ -0,0 +1,30 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Aysha Shalin (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC |
||||
|
# LICENSE (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import models |
||||
|
|
||||
|
|
||||
|
def _pre_init_project(env): |
||||
|
""" Enable project stages settings """ |
||||
|
env["res.config.settings"].create({ |
||||
|
'group_project_stages': True |
||||
|
}).execute() |
||||
|
return True |
@ -0,0 +1,46 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Aysha Shalin (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE |
||||
|
# (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
{ |
||||
|
'name': 'Quick Add Timesheet', |
||||
|
'version': '18.0.1.0.0', |
||||
|
'category': 'Project', |
||||
|
'summary': """Easily Add Timesheets via Systray.""", |
||||
|
'description': """This module helps to add timesheets from systray by |
||||
|
selecting Project, Task and Description for Timesheets.""", |
||||
|
'author': 'Cybrosys Techno Solutions', |
||||
|
'company': 'Cybrosys Techno Solutions', |
||||
|
'maintainer': 'Cybrosys Techno Solutions', |
||||
|
'website': 'https://www.cybrosys.com', |
||||
|
'depends': ['hr_timesheet', 'project'], |
||||
|
'assets': { |
||||
|
'web.assets_backend': [ |
||||
|
'easy_timesheet_add/static/src/js/TimesheetSystray.js', |
||||
|
'easy_timesheet_add/static/src/xml/TimesheetSystray.xml', |
||||
|
] |
||||
|
}, |
||||
|
'pre_init_hook': '_pre_init_project', |
||||
|
'images': ['static/description/banner.jpg'], |
||||
|
'license': 'AGPL-3', |
||||
|
'installable': True, |
||||
|
'auto_install': False, |
||||
|
'application': False, |
||||
|
} |
@ -0,0 +1,6 @@ |
|||||
|
## Module <easy_timesheet_add> |
||||
|
|
||||
|
#### 29.01.2025 |
||||
|
#### Version 18.0.1.0.0 |
||||
|
##### ADD |
||||
|
- Initial commit for Quick Add Timesheet |
@ -0,0 +1,23 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Aysha Shalin (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC |
||||
|
# LICENSE (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from . import account_analytic_line |
||||
|
from . import project_project |
@ -0,0 +1,35 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Aysha Shalin (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC |
||||
|
# LICENSE (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import fields, models |
||||
|
|
||||
|
|
||||
|
class AccountAnalyticLine(models.Model): |
||||
|
""" Inherits account_analytic_line to store timesheet adding via systray """ |
||||
|
_inherit = 'account.analytic.line' |
||||
|
|
||||
|
start_time = fields.Datetime(help="To store start time of timesheet") |
||||
|
end_time = fields.Datetime(help="To store end time of timesheet") |
||||
|
current_state = fields.Selection([('play', 'play'), ('pause', 'pause')], |
||||
|
help="To manage the timesheet adding via " |
||||
|
"systray") |
||||
|
pausing_time = fields.Char(help="To store time on pausing") |
||||
|
is_current = fields.Boolean(help="Check is timesheet completed or not") |
@ -0,0 +1,42 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
############################################################################### |
||||
|
# |
||||
|
# Cybrosys Technologies Pvt. Ltd. |
||||
|
# |
||||
|
# Copyright (C) 2025-TODAY Cybrosys Technologies(<https://www.cybrosys.com>) |
||||
|
# Author: Aysha Shalin (odoo@cybrosys.com) |
||||
|
# |
||||
|
# You can modify it under the terms of the GNU AFFERO |
||||
|
# GENERAL PUBLIC LICENSE (AGPL v3), Version 3. |
||||
|
# |
||||
|
# This program is distributed in the hope that it will be useful, |
||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
|
# GNU AFFERO GENERAL PUBLIC LICENSE (AGPL v3) for more details. |
||||
|
# |
||||
|
# You should have received a copy of the GNU AFFERO GENERAL PUBLIC |
||||
|
# LICENSE (AGPL v3) along with this program. |
||||
|
# If not, see <http://www.gnu.org/licenses/>. |
||||
|
# |
||||
|
############################################################################### |
||||
|
from odoo import models |
||||
|
|
||||
|
|
||||
|
class ProjectProject(models.Model): |
||||
|
""" Fetch Project Records """ |
||||
|
_inherit = 'project.project' |
||||
|
|
||||
|
def project_records(self): |
||||
|
""" Fetch all project details """ |
||||
|
projects = self.search_read([], ['id', 'name']) |
||||
|
return projects |
||||
|
|
||||
|
|
||||
|
class ProjectTask(models.Model): |
||||
|
""" Fetch task records """ |
||||
|
_inherit = 'project.task' |
||||
|
|
||||
|
def task_records(self): |
||||
|
""" Fetch all task details """ |
||||
|
tasks = self.search_read([], ['id', 'name']) |
||||
|
return tasks |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 210 KiB |
After Width: | Height: | Size: 209 KiB |
After Width: | Height: | Size: 109 KiB |
After Width: | Height: | Size: 495 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 624 B |
After Width: | Height: | Size: 136 KiB |
After Width: | Height: | Size: 214 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 310 B |
After Width: | Height: | Size: 929 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 542 B |
After Width: | Height: | Size: 576 B |
After Width: | Height: | Size: 733 B |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 4.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 738 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 911 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 600 B |
After Width: | Height: | Size: 673 B |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 462 B |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 926 B |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 7.0 KiB |
After Width: | Height: | Size: 878 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 653 B |
After Width: | Height: | Size: 800 B |
After Width: | Height: | Size: 905 B |
After Width: | Height: | Size: 189 KiB |
After Width: | Height: | Size: 4.3 KiB |
After Width: | Height: | Size: 839 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 427 B |
After Width: | Height: | Size: 627 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 988 B |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 875 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 91 KiB |
After Width: | Height: | Size: 130 KiB |
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 94 KiB |
After Width: | Height: | Size: 723 KiB |
After Width: | Height: | Size: 139 KiB |
After Width: | Height: | Size: 363 KiB |
After Width: | Height: | Size: 262 KiB |
After Width: | Height: | Size: 181 KiB |
After Width: | Height: | Size: 168 KiB |
After Width: | Height: | Size: 205 KiB |
After Width: | Height: | Size: 45 KiB |
After Width: | Height: | Size: 960 KiB |
After Width: | Height: | Size: 699 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 2.4 KiB |
@ -0,0 +1,194 @@ |
|||||
|
/** @odoo-module **/ |
||||
|
import { registry } from "@web/core/registry"; |
||||
|
import { Component, useState, useRef } from "@odoo/owl"; |
||||
|
import { Dropdown } from "@web/core/dropdown/dropdown"; |
||||
|
import { user } from "@web/core/user"; |
||||
|
import { _t } from "@web/core/l10n/translation"; |
||||
|
import { useService } from "@web/core/utils/hooks"; |
||||
|
/** @extends Component to add methods of timesheet view */ |
||||
|
export class TimesheetSystray extends Component { |
||||
|
static template = "TimesheetSystray"; |
||||
|
static components = { Dropdown }; |
||||
|
async setup() { |
||||
|
this.orm = useService("orm"); |
||||
|
this.notification = useService("notification") |
||||
|
this.timesheet = useRef("Timesheet") |
||||
|
this.user = user; |
||||
|
this.state = useState({ |
||||
|
project: [], |
||||
|
task: [], |
||||
|
start: false, |
||||
|
WorkTime: null, |
||||
|
timer: false, |
||||
|
Project: 0, |
||||
|
Task: 0, |
||||
|
time: '', |
||||
|
paused: null, |
||||
|
pausedTime: null, |
||||
|
distance: null, |
||||
|
current_task : [], |
||||
|
Access : null |
||||
|
}) |
||||
|
if (await user.hasGroup('hr_timesheet.group_hr_timesheet_user') || |
||||
|
await user.hasGroup('hr_timesheet.group_hr_timesheet_approver') || |
||||
|
await user.hasGroup('hr_timesheet.group_timesheet_manager')) { |
||||
|
this.state.Access = true |
||||
|
this.SearchReadTimeSheet() |
||||
|
this.state.project = await this.orm.call('project.project', 'project_records', [[]]) |
||||
|
this.state.Task = await this.orm.call('project.task', 'task_records', [[]]) |
||||
|
} else { |
||||
|
this.state.Access = false |
||||
|
} |
||||
|
if (user) { |
||||
|
this.state.Access = true |
||||
|
this.SearchReadTimeSheet() |
||||
|
this.state.project = await this.orm.searchRead('project.project', []) |
||||
|
this.state.Task = await this.orm.searchRead('project.task', []) |
||||
|
} |
||||
|
else{ |
||||
|
this.state.Access = false |
||||
|
} |
||||
|
} |
||||
|
currentDT(){ |
||||
|
const currentDT = new Date(); |
||||
|
const year = currentDT.getFullYear().toString().padStart(4, '0'); |
||||
|
const month = String(currentDT.getMonth() + 1).padStart(2, '0'); //Months are zero-indexed. so,add 1
|
||||
|
const day = currentDT.getDate().toString().padStart(2, '0'); |
||||
|
const hours = currentDT.getHours().toString().padStart(2, '0'); |
||||
|
const minutes = currentDT.getMinutes().toString().padStart(2, '0'); |
||||
|
const seconds = currentDT.getSeconds().toString().padStart(2, '0'); |
||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; |
||||
|
} |
||||
|
async SearchReadTimeSheet(){ |
||||
|
this.user = await this.orm.searchRead('res.users', |
||||
|
[["id", "=", user.userId]]) |
||||
|
this.state.current_task = await this.orm.searchRead('account.analytic.line', |
||||
|
[["employee_id", "=", this.user[0].employee_id[0]], |
||||
|
['is_current', '=', true]]) |
||||
|
if(this.state.current_task[0]){ |
||||
|
if(this.state.current_task[0]['end_time']){ |
||||
|
if(this.state.current_task[0]['current_state'] == 'pause'){ |
||||
|
var datetime = new Date(new Date().getTime()) |
||||
|
this.state.time = this.state.current_task[0]['pausing_time'] |
||||
|
this.state.paused = true; |
||||
|
this.state.WorkTime = true |
||||
|
}else { |
||||
|
this.state.WorkTime = true |
||||
|
this.state.time = this.state.current_task[0]['pausing_time'] |
||||
|
const newDate = new Date() |
||||
|
const timeParts = this.state.time.split(/[hms\s]+/); |
||||
|
this.state.paused = false; |
||||
|
newDate.setHours(newDate.getHours() - parseInt(timeParts[0])); |
||||
|
newDate.setMinutes(newDate.getMinutes() - parseInt(timeParts[1])); |
||||
|
newDate.setSeconds(newDate.getSeconds() - parseInt(timeParts[2])); |
||||
|
this.StartCountTime(new Date(newDate).getTime()) |
||||
|
} |
||||
|
}else { |
||||
|
this.current_time = new Date(this.state.current_task[0]['start_time']) |
||||
|
this.state.WorkTime = true |
||||
|
this.StartCountTime(this.current_time.getTime()) |
||||
|
} |
||||
|
}else { |
||||
|
return |
||||
|
} |
||||
|
} |
||||
|
/** Start counting the time **/ |
||||
|
async StartTime() { |
||||
|
this.SearchReadTimeSheet() |
||||
|
this.MainDiv = this.timesheet.el |
||||
|
if (!this.MainDiv.querySelector(".Project").value || |
||||
|
!this.MainDiv.querySelector(".Task").value) { |
||||
|
return this.notification.add(_t("Choose Project and Task"), { type:'warning'}) |
||||
|
} |
||||
|
this.state.start = true; |
||||
|
this.state.WorkTime = true; |
||||
|
this.state.time = this.MainDiv.querySelector(".time").innerHTML; |
||||
|
this.state.Project = this.MainDiv.querySelector(".Project").value |
||||
|
this.state.Task = this.MainDiv.querySelector(".Task").value |
||||
|
this.state.timer = new Date().getTime() |
||||
|
await this.orm.create("account.analytic.line", [{ |
||||
|
project_id: parseInt(this.state.Project), |
||||
|
task_id: parseInt(this.state.Task), |
||||
|
start_time: await this.currentDT(), |
||||
|
name: " ", |
||||
|
is_current: true |
||||
|
}]); |
||||
|
this.SearchReadTimeSheet() |
||||
|
} |
||||
|
/** Pause the time and store the time **/ |
||||
|
async PauseTime(){ |
||||
|
await this.orm.write("account.analytic.line", [this.state.current_task[0].id], { |
||||
|
end_time: await this.currentDT(), |
||||
|
pausing_time: this.state.time, |
||||
|
current_state: 'pause' |
||||
|
}) |
||||
|
await clearInterval(this.StopWatch); |
||||
|
this.state.paused = true; |
||||
|
this.StopWatch = null |
||||
|
this.SearchReadTimeSheet(); |
||||
|
window.location.reload(); |
||||
|
} |
||||
|
/** Start after pause and change the state **/ |
||||
|
async PlayTime() { |
||||
|
this.state.pausedTime = this.state.current_task[0].pausing_time |
||||
|
await this.orm.write("account.analytic.line", [this.state.current_task[0].id], { |
||||
|
current_state: 'play' |
||||
|
}) |
||||
|
this.SearchReadTimeSheet() |
||||
|
} |
||||
|
/** Add timesheet based on project, task and time **/ |
||||
|
async AddTimesheet() { |
||||
|
if (!this.timesheet.el.querySelector(".Description").value) { |
||||
|
return this.notification.add(_t("Add a Description"), { type: 'warning'}); |
||||
|
}else { |
||||
|
/** Convert 02h 30m format time => 02:30 and create timesheet**/ |
||||
|
const timeParts = this.state.time.split(/h |m/); |
||||
|
const hours = Number(timeParts[0]); |
||||
|
const minutes = Number(timeParts[1]); |
||||
|
await this.orm.write("account.analytic.line", [this.state.current_task[0].id], { |
||||
|
end_time: await this.currentDT(), |
||||
|
unit_amount: hours + minutes / 60, |
||||
|
name: this.timesheet.el.querySelector(".Description").value, |
||||
|
is_current : false |
||||
|
}) |
||||
|
} |
||||
|
clearInterval(this.StopWatch); |
||||
|
this.state.time = '' |
||||
|
this.state.WorkTime = false; |
||||
|
window.location.reload(); |
||||
|
} |
||||
|
/** Running the count down using setInterval function */ |
||||
|
StartCountTime(start_time) { |
||||
|
var self = this |
||||
|
self.StopWatch = setInterval(function() { |
||||
|
var currentTime = new Date().getTime() |
||||
|
self.state.distance = currentTime - start_time |
||||
|
var days = Math.floor(self.state.distance / (1000 * 60 * 60 * 24)); |
||||
|
var hours = Math.floor((self.state.distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); |
||||
|
var minutes = Math.floor((self.state.distance % (1000 * 60 * 60)) / (1000 * 60)); |
||||
|
var seconds = Math.floor((self.state.distance % (1000 * 60)) / 1000); |
||||
|
self.state.time = [hours ? hours + 'h' : '00h', minutes ? minutes + 'm' : '00m', seconds ? seconds + 's' : '00s'].join(' '); |
||||
|
self.orm.write("account.analytic.line", [self.state.current_task[0].id], { |
||||
|
pausing_time: self.state.time |
||||
|
}) |
||||
|
}, 1000); |
||||
|
} |
||||
|
/** Filter the tasks bases on selected project **/ |
||||
|
async ChangeProject(project_id) { |
||||
|
if (project_id){ |
||||
|
this.state.Task = await this.orm.searchRead('project.task', |
||||
|
[["project_id", "=", parseInt(project_id)]]) |
||||
|
} |
||||
|
} |
||||
|
/** Filter the tasks bases on selected project **/ |
||||
|
async ChangeTask(task_id) { |
||||
|
if(task_id){ |
||||
|
var project = await this.orm.searchRead('project.project', [["task_ids", "in", [parseInt(task_id)]]]) |
||||
|
this.timesheet.el.querySelector(".Project").value = project[0].id |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
export const systrayItem = { |
||||
|
Component: TimesheetSystray, |
||||
|
}; |
||||
|
registry.category("systray").add("TimesheetSystray", systrayItem, { sequence: 105 }); |
@ -0,0 +1,92 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8" ?> |
||||
|
<template xml:space="preserve"> |
||||
|
<!-- Template of timesheet add view template from systray --> |
||||
|
<t t-name="TimesheetSystray" owl="1"> |
||||
|
<Dropdown menuClass="`p-3 b-3`" t-if="this.state.Access"> |
||||
|
<button class="btn btn-secondary"> |
||||
|
<img src="./easy_timesheet_add/static/src/img/stopwatch.svg" |
||||
|
width="30px" height="18px"/> |
||||
|
</button> |
||||
|
<t t-set-slot="content"> |
||||
|
<div t-ref="Timesheet" |
||||
|
style="width: 300px;min-height: 50px;max-height: 630px"> |
||||
|
<div t-esc="state.time" class="time"/> |
||||
|
<t t-if="!this.state.WorkTime"> |
||||
|
<div class="row"> |
||||
|
<div class="col-3">Project</div> |
||||
|
<div class="col-8"> |
||||
|
<select class="form-select Project" |
||||
|
t-on-change="(ev) => this.ChangeProject(ev.target.value)"> |
||||
|
<option class="bg-secondary" value="">Select a |
||||
|
Project</option> |
||||
|
<t t-if="this.state.project"> |
||||
|
<t t-foreach="this.state.project" |
||||
|
t-as="project" |
||||
|
t-key="project_index"> |
||||
|
<option t-att-value="project.id" |
||||
|
t-att-data="project.name" |
||||
|
t-esc="project.name"/> |
||||
|
</t> |
||||
|
</t> |
||||
|
</select> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="row mt-2"> |
||||
|
<div class="col-3">Task</div> |
||||
|
<div class="col-8"> |
||||
|
<select class="form-select Task" |
||||
|
t-on-change="(ev) => this.ChangeTask(ev.target.value)"> |
||||
|
<option class="bg-secondary" value="">Select a |
||||
|
Task</option> |
||||
|
<t t-if="this.state.Task"> |
||||
|
<t t-foreach="this.state.Task" |
||||
|
t-as="task" t-key="task_index"> |
||||
|
<option t-att-value="task.id" |
||||
|
t-att-data="task.name" |
||||
|
t-esc="task.name"/> |
||||
|
</t> |
||||
|
</t> |
||||
|
</select> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
<t t-if="this.state.WorkTime"> |
||||
|
<div class="row d-flex"> |
||||
|
<div> |
||||
|
<small class="">Description</small> |
||||
|
</div> |
||||
|
<div class="fs-3 text-info text-end"> |
||||
|
<textarea class="form-control Description" |
||||
|
rows="3"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</t> |
||||
|
</div> |
||||
|
<div class="mt-2"> |
||||
|
<button t-if="!this.state.WorkTime" |
||||
|
class="btn btn-success start" |
||||
|
t-on-click="StartTime"> |
||||
|
<span>Start</span> |
||||
|
</button> |
||||
|
<button t-if="this.state.WorkTime" |
||||
|
class="btn btn-danger stop" |
||||
|
t-on-click="AddTimesheet"> |
||||
|
<span>Stop</span> |
||||
|
</button> |
||||
|
<button t-if="this.state.WorkTime and !this.state.paused" |
||||
|
class="btn btn-primary pause" |
||||
|
style="margin-left: 6px" |
||||
|
t-on-click="PauseTime"> |
||||
|
<span>Pause</span> |
||||
|
</button> |
||||
|
<button t-if="this.state.WorkTime and this.state.paused" |
||||
|
class="btn btn-primary pause" |
||||
|
style="margin-left: 6px" |
||||
|
t-on-click="PlayTime"> |
||||
|
<span>Restart</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
</t> |
||||
|
</Dropdown> |
||||
|
</t> |
||||
|
</template> |