@ -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> |