Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions one_compliance/custom/custom_field/timesheet_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,12 @@ def get_timesheet_detail_custom_fields():
"default": "Pending",
"insert_after": "lag_notification_sent",
},
{
"fieldname": "event",
"fieldtype": "Link",
"label": "Event",
"options": "Event",
"insert_after": "approval_status",
},
]
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"actions": [],
"allow_rename": 1,
"autoname": "full_name:task",
"autoname": "prompt",
"creation": "2026-04-18 11:40:00",
"doctype": "DocType",
"editable_grid": 1,
Expand All @@ -10,6 +10,7 @@
"user",
"full_name",
"task",
"is_ad_hoc_event",
"project",
"subject",
"start_time"
Expand All @@ -29,6 +30,12 @@
"label": "Task",
"options": "Task"
},
{
"default": "0",
"fieldname": "is_ad_hoc_event",
"fieldtype": "Check",
"label": "Is Ad-hoc Event"
},
{
"fieldname": "project",
"fieldtype": "Data",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Copyright (c) 2026, efeone and contributors
# For license information, please see license.txt

import frappe
from frappe.model.document import Document

class ActiveTaskTimer(Document):
pass
def autoname(self):
full_name = self.full_name or frappe.db.get_value("User", self.user, "full_name") or self.user
task_label = self.task if self.task else ("Ad-hoc Event" if self.is_ad_hoc_event else "No Task")
self.name = f"{full_name}: {task_label}"
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ frappe.pages['project-management_tool'].on_page_load = function (wrapper) {

page.main.addClass("frappe-card");

page.add_event_btn = page.add_inner_button(__('Add Event'), function () {
window.start_active_event_timer();
});

$(document).on('one-compliance-refresh-tools', function () {
refresh_projects(page);
});

$(document).on('one-compliance-timer-changed', function (e, timers) {
const has_event_timer = (timers || []).some(t => t.is_ad_hoc_event);
if (page.add_event_btn) {
if (has_event_timer) {
page.add_event_btn.hide();
} else {
page.add_event_btn.show();
}
}
});

make_filters(page);
// Initialize pagination
page.current_page = 1;
Expand Down Expand Up @@ -137,28 +156,31 @@ function refresh_projects(page, page_num = null) {
page_length: page.page_length
},
callback: (r) => {
if (r.message && r.message.length > 0) {
$(frappe.render_template("project_management_tool", { project_list: r.message })).appendTo(page.body);

// Action to redirect to the task management tool
page.body.find(".showTask").on("click", function () {
var project_id = $(this).attr("project");

// Set route options before navigation
frappe.route_options = {
project: project_id
};
// Navigate to the task management tool page
frappe.set_route('task-management-tool');
});

// Attach pagination controls and page-length button logic
render_pagination_controls(page, r.message.length);
setup_page_length_buttons(page);
} else {
// If no projects are found, append a message to the page body
$('<div class="frappe-list"></div>').appendTo(page.body)
.append('<div class="no-result text-muted flex justify-center align-center" style="text-align: center;"><p>No Project found with matching filters.</p></div>');
if (r.message) {
if (r.message.projects && r.message.projects.length > 0) {
$(frappe.render_template("project_management_tool", { project_list: r.message.projects })).appendTo(page.body);

// Action to redirect to the task management tool
page.body.find(".showTask").on("click", function () {
var project_id = $(this).attr("project");

// Set route options before navigation
frappe.route_options = {
project: project_id
};
// Navigate to the task management tool page
frappe.set_route('task-management-tool');
});

// Attach pagination controls and page-length button logic
render_pagination_controls(page, r.message.projects.length);
setup_page_length_buttons(page);
} else {
// If no projects are found, append a message to the page body
$('<div class="frappe-list"></div>').appendTo(page.body)
.append('<div class="no-result text-muted flex justify-center align-center" style="text-align: center;"><p>No Project found with matching filters.</p></div>');
}
$(document).trigger('one-compliance-timer-changed', [r.message.active_timers]);
}
},
freeze: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def get_project(
query += f" ORDER BY p.modified DESC LIMIT {int(page_length)} OFFSET {int(offset)};"

project_list = frappe.db.sql(query, as_dict=1)

# Process employee assignment details
for project in project_list:
project['employee_names'] = []
Expand All @@ -82,4 +83,9 @@ def get_project(
project['_assign'] = []
project['employee_names'] = []

return project_list
from one_compliance.one_compliance.page.task_management_tool.task_management_tool import get_active_timer

return {
"projects": project_list,
"active_timers": get_active_timer()
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,14 @@
<div class="card-body">
<div class="row">
<div class="col-md-2" style="padding: inherit;">
{% if data.is_event %}
<span class="card-title" style="color: orange; font-weight: bold;">{{ data.subject }}</span>
{% else %}
<a class="card-title" style="color: orange;" href="/app/task/{{data.name}}">{{ data.subject
}}</a><br>
<a class="card-subtitle project-link" href="/app/project/{{ data.project }}"
color="{{ data.color }}">{{ data.project_name }}</a>
{% endif %}
{% if data.customer %}
<p class="card-text customer-link">
Customer: <a style="font-weight:bold;" href="/app/customer/{{data.customer}}">{{
Expand Down Expand Up @@ -68,17 +72,24 @@
<button class="btn btn-outline-warning btn-icon startButton" task-id="{{ data.name }}"
project-id="{{ data.project }}" assignees="{{ data.employee_names }}"
task-subject="{{ data.subject }}"
data-toggle="tooltip" title="Start Timer">
data-toggle="tooltip" title="Start Timer" {% if data.is_event %} style="display:none;" {% endif %}>
<i class="fas fa-play"></i>
</button>
<!-- Button for event dialog -->
<button class="btn btn-outline-primary btn-icon eventDialogButton" task-id="{{ data.name }}"
task-subject="{{ data.subject }}" start-time="{{ data.start_time }}"
data-toggle="tooltip" title="Finalize Event" {% if not data.is_event %} style="display:none;" {% endif %}>
<i class="fas fa-clock"></i>
</button>
<!-- Button for timesheet -->
<button class="btn btn-outline-primary btn-icon timeEntryButton" task-id="{{ data.name }}"
project-id="{{ data.project }}" assignees="{{ data.employee_names }}"
task-subject="{{ data.subject }}"
data-toggle="tooltip" title="Time Sheet">
data-toggle="tooltip" title="Time Sheet" {% if data.is_event %} style="display:none;" {% endif %}>
<i class="fas fa-clock"></i>
</button>
<!-- Button for view documents -->
{% if not data.is_event %}
<button class="btn btn-outline-info btn-icon documentButton"
sub-category="{{ data.compliance_sub_category }}" customer="{{ data.customer }}"
data-toggle="tooltip" title="Documents" {% if is_document_icon_hidden %} hidden {% endif
Expand All @@ -102,6 +113,7 @@
is_payment_icon_hidden %} hidden {% endif %}>
<i class="fas fa-wallet"></i>
</button>
{% endif %}
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,25 @@ frappe.pages['task-management-tool'].on_page_load = function (wrapper) {
page.current_page = 1;
page.page_length = 20;

page.add_event_btn = page.add_inner_button(__('Add Event'), function () {
window.start_active_event_timer();
});

$(document).on('one-compliance-refresh-tools', function () {
refresh_tasks(page);
});

$(document).on('one-compliance-timer-changed', function (e, timers) {
const has_event_timer = (timers || []).some(t => t.is_ad_hoc_event);
if (page.add_event_btn) {
if (has_event_timer) {
page.add_event_btn.hide();
} else {
page.add_event_btn.show();
}
}
});

make_filters(page);
if (!frappe.route_options || !frappe.route_options.project) {
refresh_tasks(page, true);
Expand Down Expand Up @@ -159,13 +178,30 @@ function refresh_tasks(page, reset_page = false) {
page_length: page.page_length
},
callback: (r) => {
if (r.message && r.message.tasks.length > 0) {
render_task_list(page, r.message.tasks, r.message.icons);
setup_pagination(page, r.message.total_tasks);
setup_page_length_buttons(page);
initialize_task_actions(page);
} else {
show_no_task_found(page);
if (r.message) {
let tasks = r.message.tasks || [];
const active_timers = r.message.active_timers || [];

const event_timer = active_timers.find(t => t.is_ad_hoc_event);
if (event_timer) {
tasks.unshift({
name: 'EVENT-' + frappe.session.user,
subject: event_timer.subject || 'Ad-hoc Event',
status: 'Working',
is_event: true,
start_time: event_timer.start_time
});
}

if (tasks.length > 0) {
render_task_list(page, tasks, r.message.icons);
setup_pagination(page, r.message.total_tasks);
setup_page_length_buttons(page);
initialize_task_actions(page, active_timers);
} else {
show_no_task_found(page);
}
$(document).trigger('one-compliance-timer-changed', [active_timers]);
}
},
freeze: true,
Expand Down Expand Up @@ -227,7 +263,7 @@ function render_task_list(page, tasks, icons) {
Handles all task-related button bindings and UI updates
*/

function initialize_task_actions(page) {
function initialize_task_actions(page, active_timers = null) {
const body = page.body;

body.find(".paymentEntryButton").off().on("click", function () {
Expand All @@ -247,6 +283,12 @@ function initialize_task_actions(page) {

body.find(".timeEntryButton").hide();

body.find(".eventDialogButton").off().on("click", function () {
const start_time = $(this).attr("start-time");
const subject = $(this).attr("task-subject");
window.show_add_event_dialog(start_time, subject);
});

body.find(".startButton").off().on("click", function () {
const task_name = $(this).attr("task-id");
const project_name = $(this).attr("project-id");
Expand Down Expand Up @@ -284,29 +326,47 @@ function initialize_task_actions(page) {
});
});

frappe.call({
method: "one_compliance.one_compliance.page.task_management_tool.task_management_tool.get_active_timer",
callback: (r) => {
const active_timers = r.message || [];
body.find(".start-time").each(function () {
const task_name = $(this).attr("task-id");
const project_name = $(this).attr("project-id");

const task_timer = active_timers.find(t => t.task === task_name);
const apply_active_timers = (timers) => {
const active_timers_list = timers || [];
body.find(".start-time").each(function () {
const task_name = $(this).attr("task-id");
const project_name = $(this).attr("project-id");
const is_event = task_name && task_name.startsWith('EVENT-');

if (task_timer) {
const formatted_time = frappe.datetime.str_to_user(task_timer.start_time);
$(this).text(formatted_time);
body.find(`.startButton[task-id='${task_name}'][project-id='${project_name}']`).hide();
body.find(`.timeEntryButton[task-id='${task_name}'][project-id='${project_name}']`).show();
const task_timer = is_event ? active_timers_list.find(t => t.is_ad_hoc_event) : active_timers_list.find(t => t.task === task_name);

if (task_timer) {
const formatted_time = frappe.datetime.str_to_user(task_timer.start_time);
$(this).text(formatted_time);
body.find(`.startButton[task-id='${task_name}'][project-id='${project_name}']`).hide();

if (is_event) {
body.find(`.eventDialogButton[task-id='${task_name}']`).show();
body.find(`.timeEntryButton[task-id='${task_name}']`).hide();
} else {
$(this).text("");
body.find(`.timeEntryButton[task-id='${task_name}'][project-id='${project_name}']`).show();
}
} else {
$(this).text("");
if (!is_event) {
body.find(`.startButton[task-id='${task_name}'][project-id='${project_name}']`).show();
body.find(`.timeEntryButton[task-id='${task_name}'][project-id='${project_name}']`).hide();
}
});
}
});
body.find(`.timeEntryButton[task-id='${task_name}'][project-id='${project_name}']`).hide();
body.find(`.eventDialogButton[task-id='${task_name}']`).hide();
}
});
};

if (active_timers) {
apply_active_timers(active_timers);
} else {
frappe.call({
method: "one_compliance.one_compliance.page.task_management_tool.task_management_tool.get_active_timer",
callback: (r) => {
apply_active_timers(r.message);
}
});
}

body.find(".timeEntryButton").off().on("click", function () {
const task_name = $(this).attr("task-id");
Expand Down Expand Up @@ -751,7 +811,10 @@ function set_status_colors(page) {
status_el.css("color", color);
project_el.css("color", color);

if (["Open", "Overdue", "Working", "Pending Review", "Hold", "Pending with Authority"].includes(status)) add_check_icon(status_el[0]);
const task_id = status_el.attr("task-id");
const is_event = task_id && task_id.startsWith('EVENT-');

if (!is_event && ["Open", "Overdue", "Working", "Pending Review", "Hold", "Pending with Authority"].includes(status)) add_check_icon(status_el[0]);
});

function add_check_icon(element) {
Expand Down
Loading