From cb60bb7059080e898c5861011f4571fa45b019c7 Mon Sep 17 00:00:00 2001 From: Jumana-K Date: Sat, 6 Jun 2026 10:37:07 +0530 Subject: [PATCH 1/4] feat:add timer for events --- .../custom/custom_field/timesheet_detail.py | 7 + .../project_management_tool.js | 53 ++++++ .../task_management_tool.html | 4 +- .../task_management_tool.js | 67 +++++++ .../task_management_tool.py | 143 ++++++++++++-- one_compliance/one_compliance/utils.py | 38 ++-- one_compliance/public/js/one_compliance.js | 175 ++++++++++++++++++ 7 files changed, 457 insertions(+), 30 deletions(-) diff --git a/one_compliance/custom/custom_field/timesheet_detail.py b/one_compliance/custom/custom_field/timesheet_detail.py index eafc0438..0833bb59 100644 --- a/one_compliance/custom/custom_field/timesheet_detail.py +++ b/one_compliance/custom/custom_field/timesheet_detail.py @@ -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" + }, ] } diff --git a/one_compliance/one_compliance/page/project_management_tool/project_management_tool.js b/one_compliance/one_compliance/page/project_management_tool/project_management_tool.js index e6367316..e5628395 100644 --- a/one_compliance/one_compliance/page/project_management_tool/project_management_tool.js +++ b/one_compliance/one_compliance/page/project_management_tool/project_management_tool.js @@ -8,6 +8,33 @@ frappe.pages['project-management_tool'].on_page_load = function (wrapper) { // Button to refresh the page let $button = page.set_secondary_action('Refresh', () => location.reload()) + page.add_button(__('Add Event'), function () { + const current_time = frappe.datetime.now_datetime(); + frappe.call({ + method: "one_compliance.one_compliance.page.task_management_tool.task_management_tool.start_active_timer", + args: { + task: "EVENT-" + frappe.session.user, + project: "", + subject: "Event Tracking", + start_time: current_time + }, + callback: (r) => { + if (r.message) { + const user = frappe.session.user; + if (user) { + localStorage.setItem('one-compliance-active-timer-' + user, JSON.stringify(r.message)); + } + frappe.show_alert({ + message: __("Event tracking started. Go to Task Management Tool to stop it."), + indicator: "orange" + }); + toggle_add_event_button(page); + $(document).trigger('one-compliance-timer-changed', [r.message]); + } + } + }); + }); + page.main.addClass("frappe-card"); make_filters(page); @@ -16,6 +43,30 @@ frappe.pages['project-management_tool'].on_page_load = function (wrapper) { page.page_length = 20; refresh_projects(page); + toggle_add_event_button(page); + +} + +frappe.pages['project-management_tool'].on_page_show = function (wrapper) { + var page = wrapper.page; + toggle_add_event_button(page); +} + +/** + * Toggles the visibility of the "Add Event" button based on active event timers. + * Uses localStorage for instant feedback. + */ +function toggle_add_event_button(page) { + const user = frappe.session.user; + const active_timers = JSON.parse(localStorage.getItem('one-compliance-active-timer-' + user) || '[]'); + const event_timer = active_timers.find(t => t.task && t.task.startsWith("EVENT-")); + const $btn = page.wrapper.find('.page-actions button:contains("Add Event")'); + + if (event_timer) { + $btn.hide(); + } else { + $btn.show(); + } } /* @@ -204,3 +255,5 @@ function setup_page_length_buttons(page) { refresh_projects(page); }); } + + diff --git a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.html b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.html index b144162b..5b95de12 100644 --- a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.html +++ b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.html @@ -14,7 +14,7 @@ {% for data in task_list %} -
+
@@ -78,6 +78,7 @@ data-toggle="tooltip" title="Time Sheet"> + {% if not data.is_event_timer %} + {% endif %}
diff --git a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.js b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.js index b8f293a6..91a74c89 100644 --- a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.js +++ b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.js @@ -7,6 +7,34 @@ frappe.pages['task-management-tool'].on_page_load = function (wrapper) { page.main.addClass("frappe-card"); + page.add_button(__('Add Event'), function () { + const current_time = frappe.datetime.now_datetime(); + frappe.call({ + method: "one_compliance.one_compliance.page.task_management_tool.task_management_tool.start_active_timer", + args: { + task: "EVENT-" + frappe.session.user, + project: "", + subject: "Event Tracking", + start_time: current_time + }, + callback: (r) => { + if (r.message) { + const user = frappe.session.user; + if (user) { + localStorage.setItem('one-compliance-active-timer-' + user, JSON.stringify(r.message)); + } + frappe.show_alert({ + message: __("Event tracking started"), + indicator: "orange" + }); + toggle_add_event_button(page); + refresh_tasks(page); + $(document).trigger('one-compliance-timer-changed', [r.message]); + } + } + }); + }); + page.current_page = 1; page.page_length = 20; @@ -14,10 +42,39 @@ frappe.pages['task-management-tool'].on_page_load = function (wrapper) { if (!frappe.route_options || !frappe.route_options.project) { refresh_tasks(page, true); } + toggle_add_event_button(page); +} + +/** + * Toggles the visibility of the "Add Event" button based on active event timers. + * Uses localStorage for instant feedback. + */ +function toggle_add_event_button(page) { + const user = frappe.session.user; + const active_timers = JSON.parse(localStorage.getItem('one-compliance-active-timer-' + user) || '[]'); + const event_timer = active_timers.find(t => t.task && t.task.startsWith("EVENT-")); + const $btn = page.wrapper.find('.page-actions button:contains("Add Event")'); + + if (event_timer) { + $btn.hide(); + } else { + $btn.show(); + } } frappe.pages['task-management-tool'].on_page_show = function (wrapper) { var page = wrapper.page; + toggle_add_event_button(page); + + // Ensure synthetic event row is visible if timer exists in localStorage + const user = frappe.session.user; + const active_timers = JSON.parse(localStorage.getItem('one-compliance-active-timer-' + user) || '[]'); + const event_timer = active_timers.find(t => t.task && t.task.startsWith("EVENT-")); + + // Check if page body is already populated with list + if (event_timer && !page.body.find('[data-is-event-timer="1"]').length) { + refresh_tasks(page); + } if (frappe.route_options && frappe.route_options.project) { page.fields_dict.project.set_value(frappe.route_options.project); @@ -320,6 +377,14 @@ function initialize_task_actions(page) { const task_timer = active_timers.find(t => t.task === task_name); const start_time = task_timer ? task_timer.start_time : null; + if (task_name && task_name.startsWith("EVENT-")) { + show_add_event_dialog({ + timer_task_id: task_name, + start_time: start_time + }); + return; + } + frappe.db.get_value("Task", task_name, "has_external_dependencies") .then(({ message }) => { const show_lag = !!message?.has_external_dependencies; @@ -957,6 +1022,8 @@ function update_task_status(page, task_name, status) { }); } + + /** Hide start buttons if no assignees are set OR if task already has a start time. */ diff --git a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.py b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.py index 2d051767..7fd7f4f8 100644 --- a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.py +++ b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.py @@ -19,7 +19,7 @@ def get_task(status=None, task=None, project=None, customer=None, department=Non if status: if status in ["Completed", "Cancelled", "Template"]: - return + return {"tasks": [], "total_tasks": 0, "icons": get_icon_hidden_status()} else: values["status"] = status conditions.append("t.status = %(status)s") @@ -73,7 +73,8 @@ def get_task(status=None, task=None, project=None, customer=None, department=Non LEFT JOIN `tabCompliance Sub Category` c ON t.compliance_sub_category = c.name {where_clause} """ - total_tasks = frappe.db.sql(count_query, values.copy(), as_dict=False)[0][0] + res = frappe.db.sql(count_query, values.copy(), as_dict=False) + total_tasks = res[0][0] if res else 0 data_query = f""" SELECT @@ -90,7 +91,7 @@ def get_task(status=None, task=None, project=None, customer=None, department=Non values['page_length'] = int(page_length) values['offset'] = (int(page) - 1) * int(page_length) - task_list = frappe.db.sql(data_query, values, as_dict=1) + task_list = frappe.db.sql(data_query, values, as_dict=1) or [] for task_item in task_list: task_item['employee_names'] = [] @@ -114,14 +115,54 @@ def get_task(status=None, task=None, project=None, customer=None, department=Non if task_item['completed_by']: if task_item['completed_by'] == 'Administrator': task_item['completed_by_name'] = 'Administrator' + task_item['completed_by_id'] = 'Administrator' else: completed_by = frappe.get_value("Employee", {"user_id": task_item['completed_by']}, ["name", "employee_name"], as_dict=True) if completed_by: task_item['completed_by_name'] = completed_by.get("employee_name") task_item['completed_by_id'] = completed_by.get("name") + else: + task_item['completed_by_name'] = "" + task_item['completed_by_id'] = "" else: - task_item['completed_by_name'] = [] - task_item['completed_by_id'] = [] + task_item['completed_by_name'] = "" + task_item['completed_by_id'] = "" + + # Check for active event timers for the current user and inject synthetic tasks + try: + active_event_timers = frappe.get_all("Active Task Timer", filters={ + "user": current_user, + "task": ["like", "EVENT-%"] + }, fields=["task", "subject", "start_time"]) + + for timer in active_event_timers: + employee = frappe.get_value("Employee", {"user_id": current_user}, ["name", "employee_name"], as_dict=True) + synthetic_task = { + "name": timer.task, + "subject": timer.subject or "Event Tracking", + "status": "Working", + "is_event_timer": 1, + "start_time": timer.start_time, + "employee_names": [employee.employee_name] if employee else [current_user], + "_assign": [{"employee_name": employee.employee_name, "employee_id": employee.name}] if employee else [], + "assigned_to": "", + "project": "", + "project_name": "Event", + "customer": "", + "department": "", + "compliance_sub_category": "", + "exp_start_date": timer.start_time, + "exp_end_date": "", + "custom_is_payable": 0, + "color": "orange", + "completed_by": "", + "completed_by_name": "", + "completed_by_id": "", + "readiness_status": "" + } + task_list.insert(0, synthetic_task) + except Exception: + pass return { "tasks": task_list, @@ -289,10 +330,13 @@ def start_active_timer(task, project, subject, start_time): user = frappe.session.user if not user or user == 'Guest': frappe.throw(_("User authentication required. Please login first.")) - if not frappe.db.exists("Task", task): - frappe.throw(_("Task {0} not found").format(task)) - if not frappe.has_permission("Task", "read", task): - frappe.throw(_("No permission to access this task")) + + is_event = task.startswith("EVENT-") + if not is_event: + if not frappe.db.exists("Task", task): + frappe.throw(_("Task {0} not found").format(task)) + if not frappe.has_permission("Task", "read", task): + frappe.throw(_("No permission to access this task")) if project and not frappe.db.exists("Project", project): frappe.throw(_("Project {0} not found").format(project)) try: @@ -307,11 +351,12 @@ def start_active_timer(task, project, subject, start_time): ignore_overlap = (int(val1 or 0) == 1) or (int(val2 or 0) == 1) if not ignore_overlap: - existing_timer = frappe.db.sql(""" - SELECT task, subject FROM `tabActive Task Timer` WHERE user = %s AND task != %s - """, (user, task), as_dict=True) + # Check for ANY existing timer for the same user, regardless of task ID + existing_timer = frappe.get_all("Active Task Timer", filters={ + "user": user + }, fields=["task", "subject"], limit=1) - if existing_timer: + if existing_timer and existing_timer[0].task != task: existing_timer = existing_timer[0] frappe.throw(_("Another task is already running: {0}. Please stop it before starting a new one.").format(existing_timer.subject or existing_timer.task)) @@ -329,7 +374,13 @@ def start_active_timer(task, project, subject, start_time): doc.project = project doc.subject = subject doc.start_time = start_time - doc.save(ignore_permissions=True) + if is_event: + doc.flags.ignore_links = True + + if doc.is_new(): + doc.insert(ignore_permissions=True) + else: + doc.save(ignore_permissions=True) frappe.db.commit() all_timers = get_active_timer() @@ -366,8 +417,64 @@ def get_active_timer(): if not user or user == 'Guest': return [] - timers = frappe.db.sql(""" - SELECT task, project, subject, start_time FROM `tabActive Task Timer` WHERE user = %s - """, (user,), as_dict=True) + timers = frappe.get_all("Active Task Timer", filters={"user": user}, fields=["task", "project", "subject", "start_time"]) + + return timers + +@frappe.whitelist() +def check_active_timer(): + """ + Check if any task is already running for the current user. + """ + user = frappe.session.user + if not user or user == 'Guest': + return {"status": "ok"} + + val1 = frappe.db.get_value("Projects Settings", "Projects Settings", "ignore_employee_time_overlap") + val2 = frappe.db.get_value("Projects Settings", "Projects Settings", "ignore_user_time_overlap") + ignore_overlap = (int(val1 or 0) == 1) or (int(val2 or 0) == 1) + + if not ignore_overlap: + existing_timer = frappe.get_all("Active Task Timer", filters={"user": user}, fields=["task", "subject"], limit=1) + + if existing_timer: + existing_timer = existing_timer[0] + return { + "status": "warning", + "message": _("Another task is already running: {0}. Please stop it before starting a new one.").format(existing_timer.subject or existing_timer.task) + } + + return {"status": "ok"} + +@frappe.whitelist() +def create_event_from_tool(subject, starts_on, client=None, event_category=None, company=None, ends_on=None, add_timesheet=False): + """ + Create an Event from Task/Project Management Tool. + """ + event = frappe.new_doc("Event") + event.subject = subject + event.starts_on = starts_on + event.ends_on = ends_on + event.custom_customer = client + event.event_category = event_category + event.company = company + + # Add current user as participant (Employee) + employee = frappe.get_value("Employee", {"user_id": frappe.session.user}, ["name", "employee_name"], as_dict=True) + if employee: + event.append("event_participants", { + "reference_doctype": "Employee", + "reference_docname": employee.name, + "custom_participant_name": employee.employee_name + }) + + if add_timesheet: + event.status = "Completed" + + event.insert(ignore_permissions=True) + + if add_timesheet: + from one_compliance.one_compliance.utils import make_time_sheet_entry + make_time_sheet_entry(event.name) - return timers \ No newline at end of file + return event.name diff --git a/one_compliance/one_compliance/utils.py b/one_compliance/one_compliance/utils.py index 162b830f..b3e9e169 100644 --- a/one_compliance/one_compliance/utils.py +++ b/one_compliance/one_compliance/utils.py @@ -365,29 +365,45 @@ def make_time_sheet_entry(event): if participant.reference_doctype == "Employee": employee_id = participant.reference_docname if employee_id: - create_timesheet(employee_id, activity_type, from_time, to_time) + create_timesheet(employee_id, activity_type, from_time, to_time, event) @frappe.whitelist() -def create_timesheet(employee, activity_type, from_time, to_time): +def create_timesheet(employee, activity_type, from_time, to_time, event=None): from_time = get_datetime(from_time) to_time = get_datetime(to_time) employee_id = frappe.get_value("Employee", {"name": employee}, "name") + # If event is provided, check if it's already added to any timesheet for this employee + if event: + has_entry = frappe.db.get_value("Timesheet Detail", + {"event": event, "parenttype": "Timesheet", "docstatus": ["<", 2]}, + "parent" + ) + if has_entry: + # Verify if the parent timesheet belongs to this employee + ts_employee = frappe.db.get_value("Timesheet", has_entry, "employee") + if ts_employee == employee_id: + frappe.throw(_("Timesheet already created for this Event")) + # Check if a timesheet already exists for the employee within the given date range - if frappe.db.exists("Timesheet", {"employee": employee_id, "start_date": from_time.date(), "end_date": to_time.date()}): - frappe.throw(_("Timesheet already Created")) + existing_ts = frappe.db.get_value("Timesheet", {"employee": employee_id, "start_date": from_time.date(), "end_date": to_time.date()}, "name") + + if existing_ts: + timesheet = frappe.get_doc("Timesheet", existing_ts) else: timesheet = frappe.new_doc("Timesheet") timesheet.employee = employee_id - timesheet.append("time_logs",{ - "activity_type": activity_type, - "from_time": from_time, - "to_time": to_time - }) - timesheet.insert(ignore_permissions=True) - frappe.db.commit() + timesheet.append("time_logs",{ + "activity_type": activity_type, + "from_time": from_time, + "to_time": to_time, + "event": event + }) + + timesheet.save(ignore_permissions=True) + frappe.db.commit() @frappe.whitelist() def get_employee_list_for_hod(): diff --git a/one_compliance/public/js/one_compliance.js b/one_compliance/public/js/one_compliance.js index 0031cd5e..64503905 100644 --- a/one_compliance/public/js/one_compliance.js +++ b/one_compliance/public/js/one_compliance.js @@ -41,10 +41,17 @@ text-decoration: none; white-space: nowrap; + min-width: 50px; + min-height: 20px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; } + #oc-timer-box:empty { + display: none !important; + } + #oc-timer-wrap::before { content: ''; position: absolute; @@ -199,8 +206,10 @@ if (timers.length > 0) { link.innerHTML = getHTML(timers); + wrap.style.display = 'block'; } else { link.style.display = 'none'; + wrap.style.display = 'none'; } wrap.appendChild(link); @@ -237,6 +246,7 @@ */ function update(data) { const el = document.getElementById('oc-timer-box'); + const wrap = document.getElementById('oc-timer-wrap'); if (!el) return; const timers = Array.isArray(data) @@ -261,12 +271,14 @@ el.innerHTML = getHTML(timers); el.style.display = 'flex'; + if (wrap) wrap.style.display = 'block'; if (user) { localStorage.setItem(PREFIX + user, JSON.stringify(timers)); } } else { el.style.display = 'none'; + if (wrap) wrap.style.display = 'none'; if (user) { localStorage.removeItem(PREFIX + user); @@ -294,6 +306,169 @@ }); } + /** + * Shows a dialog to create a new Event from the tool. + * Shared between Task Management Tool and Project Management Tool. + */ + window.show_add_event_dialog = function (opts = {}) { + frappe.model.with_doctype('Event', function () { + frappe.call({ + method: 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.check_active_timer', + callback: function (r) { + // If starting a NEW event tracking session (no timer_task_id provided), check for overlap + if (!opts.timer_task_id && r.message && r.message.status === 'warning') { + frappe.msgprint({ + title: __('Active Task Running'), + indicator: 'orange', + message: r.message.message + }); + return; + } + + const meta = frappe.get_meta('Event'); + const category_field = meta.fields.find(f => f.fieldname === 'event_category'); + const category_options = category_field ? category_field.options : 'Events\nMeeting\nCall\nSent/Received Email\nOther'; + + const dialog = new frappe.ui.Dialog({ + title: __('Add Event'), + fields: [ + { + label: __('Subject'), + fieldname: 'subject', + fieldtype: 'Data', + reqd: 1 + }, + { + label: __('Starts On'), + fieldname: 'starts_on', + fieldtype: 'Datetime', + default: opts.start_time || frappe.datetime.now_datetime(), + reqd: 1 + }, + { + label: __('Client'), + fieldname: 'client', + fieldtype: 'Link', + options: 'Customer' + }, + { + fieldtype: 'Column Break' + }, + { + label: __('Event Category'), + fieldname: 'event_category', + fieldtype: 'Select', + options: category_options, + default: 'Meeting' + }, + { + label: __('Company'), + fieldname: 'company', + fieldtype: 'Link', + options: 'Company', + default: frappe.defaults.get_user_default('company') + }, + { + label: __('End On'), + fieldname: 'ends_on', + fieldtype: 'Datetime', + default: opts.timer_task_id ? frappe.datetime.now_datetime() : null + } + ], + primary_action_label: __('Add Timesheet'), + primary_action(values) { + frappe.call({ + method: 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.create_event_from_tool', + args: { + ...values, + add_timesheet: true + }, + callback: function (res) { + if (res.message) { + frappe.show_alert({ + message: __('Event and Timesheet created successfully'), + indicator: 'green' + }); + if (opts.timer_task_id) { + frappe.call({ + method: 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.stop_active_timer', + args: { task: opts.timer_task_id }, + callback: (r) => { + if (r.message) { + $(document).trigger('one-compliance-timer-changed', [r.message]); + if (frappe.get_route()[0] === 'task-management-tool') { + location.reload(); + } + } + } + }); + } + dialog.hide(); + } + } + }); + } + }); + + dialog.set_secondary_action_label(__('Close')); + dialog.set_secondary_action(() => { + if (opts.timer_task_id) { + frappe.confirm( + __('Are you sure you want to close this dialog? Your tracking session will continue until stopped from the list.'), + () => { + dialog.hide(); + } + ); + } else { + dialog.hide(); + } + }); + + dialog.add_custom_button(__('Edit Full Form'), function () { + const values = dialog.get_values(true); + if (opts.timer_task_id) { + frappe.call({ + method: 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.stop_active_timer', + args: { task: opts.timer_task_id }, + callback: (r) => { + if (r.message) { + $(document).trigger('one-compliance-timer-changed', [r.message]); + } + } + }); + } + dialog.hide(); + + frappe.model.with_doctype('Event', function () { + const doc = frappe.model.get_new_doc('Event'); + doc.subject = values.subject; + doc.starts_on = values.starts_on; + doc.ends_on = values.ends_on; + doc.custom_customer = values.client; + doc.event_category = values.event_category; + doc.company = values.company; + + // Add current employee as participant + frappe.db.get_value('Employee', { user_id: frappe.session.user }, ['name', 'employee_name']).then(r => { + let employee = r.message ? r.message.name : null; + let employee_name = r.message ? r.message.employee_name : null; + if (employee) { + const row = frappe.model.add_child(doc, 'event_participants'); + row.reference_doctype = 'Employee'; + row.reference_docname = employee; + row.custom_participant_name = employee_name; + } + frappe.set_route('Form', 'Event', doc.name); + }); + }); + }); + + dialog.show(); + } + }); + }); + }; + // ========================= // Init // ========================= From cba79ae62b9bd295a4ef17338e947a7d70a7bbec Mon Sep 17 00:00:00 2001 From: Jumana-K Date: Sat, 6 Jun 2026 22:02:25 +0530 Subject: [PATCH 2/4] feat:event timer feature --- .../custom/custom_field/timesheet_detail.py | 2 +- .../project_management_tool.js | 67 +--- .../project_management_tool.py | 6 +- .../task_management_tool.html | 18 +- .../task_management_tool.js | 189 ++++++----- .../task_management_tool.py | 140 +++----- one_compliance/one_compliance/utils.py | 51 +-- one_compliance/public/js/one_compliance.js | 299 ++++++++---------- 8 files changed, 335 insertions(+), 437 deletions(-) diff --git a/one_compliance/custom/custom_field/timesheet_detail.py b/one_compliance/custom/custom_field/timesheet_detail.py index 0833bb59..13f87f7e 100644 --- a/one_compliance/custom/custom_field/timesheet_detail.py +++ b/one_compliance/custom/custom_field/timesheet_detail.py @@ -36,7 +36,7 @@ def get_timesheet_detail_custom_fields(): "fieldtype": "Link", "label": "Event", "options": "Event", - "insert_after": "approval_status" + "insert_after": "approval_status", }, ] } diff --git a/one_compliance/one_compliance/page/project_management_tool/project_management_tool.js b/one_compliance/one_compliance/page/project_management_tool/project_management_tool.js index e5628395..5a18f9e4 100644 --- a/one_compliance/one_compliance/page/project_management_tool/project_management_tool.js +++ b/one_compliance/one_compliance/page/project_management_tool/project_management_tool.js @@ -8,34 +8,15 @@ frappe.pages['project-management_tool'].on_page_load = function (wrapper) { // Button to refresh the page let $button = page.set_secondary_action('Refresh', () => location.reload()) - page.add_button(__('Add Event'), function () { - const current_time = frappe.datetime.now_datetime(); - frappe.call({ - method: "one_compliance.one_compliance.page.task_management_tool.task_management_tool.start_active_timer", - args: { - task: "EVENT-" + frappe.session.user, - project: "", - subject: "Event Tracking", - start_time: current_time - }, - callback: (r) => { - if (r.message) { - const user = frappe.session.user; - if (user) { - localStorage.setItem('one-compliance-active-timer-' + user, JSON.stringify(r.message)); - } - frappe.show_alert({ - message: __("Event tracking started. Go to Task Management Tool to stop it."), - indicator: "orange" - }); - toggle_add_event_button(page); - $(document).trigger('one-compliance-timer-changed', [r.message]); - } - } - }); + page.main.addClass("frappe-card"); + + page.add_inner_button(__('Add Event'), function () { + window.start_active_event_timer(); }); - page.main.addClass("frappe-card"); + $(document).on('one-compliance-refresh-tools', function () { + refresh_projects(page); + }); make_filters(page); // Initialize pagination @@ -43,30 +24,6 @@ frappe.pages['project-management_tool'].on_page_load = function (wrapper) { page.page_length = 20; refresh_projects(page); - toggle_add_event_button(page); - -} - -frappe.pages['project-management_tool'].on_page_show = function (wrapper) { - var page = wrapper.page; - toggle_add_event_button(page); -} - -/** - * Toggles the visibility of the "Add Event" button based on active event timers. - * Uses localStorage for instant feedback. - */ -function toggle_add_event_button(page) { - const user = frappe.session.user; - const active_timers = JSON.parse(localStorage.getItem('one-compliance-active-timer-' + user) || '[]'); - const event_timer = active_timers.find(t => t.task && t.task.startsWith("EVENT-")); - const $btn = page.wrapper.find('.page-actions button:contains("Add Event")'); - - if (event_timer) { - $btn.hide(); - } else { - $btn.show(); - } } /* @@ -188,8 +145,12 @@ 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); + if (r.message && r.message.projects && r.message.projects.length > 0) { + $(frappe.render_template("project_management_tool", { project_list: r.message.projects })).appendTo(page.body); + + if (r.message.active_timers) { + $(document).trigger('one-compliance-timer-changed', [r.message.active_timers]); + } // Action to redirect to the task management tool page.body.find(".showTask").on("click", function () { @@ -255,5 +216,3 @@ function setup_page_length_buttons(page) { refresh_projects(page); }); } - - diff --git a/one_compliance/one_compliance/page/project_management_tool/project_management_tool.py b/one_compliance/one_compliance/page/project_management_tool/project_management_tool.py index 4a151e42..8e4785cc 100644 --- a/one_compliance/one_compliance/page/project_management_tool/project_management_tool.py +++ b/one_compliance/one_compliance/page/project_management_tool/project_management_tool.py @@ -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) + from one_compliance.one_compliance.page.task_management_tool.task_management_tool import get_active_timer # Process employee assignment details for project in project_list: project['employee_names'] = [] @@ -82,4 +83,7 @@ def get_project( project['_assign'] = [] project['employee_names'] = [] - return project_list + return { + "projects": project_list, + "active_timers": get_active_timer() + } diff --git a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.html b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.html index 5b95de12..c1dc384b 100644 --- a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.html +++ b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.html @@ -14,10 +14,13 @@ {% for data in task_list %} -
+
+ {% if data.is_event %} + {{ data.subject }} + {% else %} {{ data.subject }}

{% endif %} + {% endif %}
{{ data.exp_start_date }}
- {{ data.exp_end_date }} @@ -68,18 +72,24 @@ + + - {% if not data.is_event_timer %} + {% if not data.is_event %}
{{ data.exp_start_date }}
- {{ data.exp_end_date }} diff --git a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.js b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.js index e05b2cb7..24f1ee4f 100644 --- a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.js +++ b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.js @@ -19,7 +19,7 @@ frappe.pages['task-management-tool'].on_page_load = function (wrapper) { }); $(document).on('one-compliance-timer-changed', function (e, timers) { - const has_event_timer = (timers || []).some(t => t.task && t.task.startsWith('EVENT-')); + 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(); @@ -182,10 +182,10 @@ function refresh_tasks(page, reset_page = false) { let tasks = r.message.tasks || []; const active_timers = r.message.active_timers || []; - const event_timer = active_timers.find(t => t.task && t.task.startsWith('EVENT-')); + const event_timer = active_timers.find(t => t.is_ad_hoc_event); if (event_timer) { tasks.unshift({ - name: event_timer.task, + name: 'EVENT-' + frappe.session.user, subject: event_timer.subject || 'Ad-hoc Event', status: 'Working', is_event: true, @@ -333,7 +333,7 @@ function initialize_task_actions(page, active_timers = null) { const project_name = $(this).attr("project-id"); const is_event = task_name && task_name.startsWith('EVENT-'); - const task_timer = active_timers_list.find(t => t.task === task_name); + 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); diff --git a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.py b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.py index d903cdaf..e96880fb 100644 --- a/one_compliance/one_compliance/page/task_management_tool/task_management_tool.py +++ b/one_compliance/one_compliance/page/task_management_tool/task_management_tool.py @@ -283,17 +283,21 @@ def get_icon_hidden_status(): return data @frappe.whitelist() -def start_active_timer(task, project, subject, start_time): +def start_active_timer(task, project, subject, start_time, is_ad_hoc_event=0): """ Start a timer for a specific task, ensuring no overlapping timers for the same user. """ user = frappe.session.user + is_ad_hoc_event = int(is_ad_hoc_event) + if not user or user == 'Guest': frappe.throw(_("User authentication required. Please login first.")) - if not task.startswith("EVENT-") and not frappe.db.exists("Task", task): - frappe.throw(_("Task {0} not found").format(task)) - if not task.startswith("EVENT-") and not frappe.has_permission("Task", "read", task): - frappe.throw(_("No permission to access this task")) + + if not is_ad_hoc_event: + if not task or not frappe.db.exists("Task", task): + frappe.throw(_("Task {0} not found").format(task)) + if not frappe.has_permission("Task", "read", task): + frappe.throw(_("No permission to access this task")) if project and not frappe.db.exists("Project", project): frappe.throw(_("Project {0} not found").format(project)) try: @@ -303,9 +307,8 @@ def start_active_timer(task, project, subject, start_time): except Exception: frappe.throw(_("Invalid start_time format")) - val1 = frappe.db.get_value("Projects Settings", "Projects Settings", "ignore_employee_time_overlap") - val2 = frappe.db.get_value("Projects Settings", "Projects Settings", "ignore_user_time_overlap") - ignore_overlap = (int(val1 or 0) == 1) or (int(val2 or 0) == 1) + settings = frappe.db.get_value("Projects Settings", "Projects Settings", ["ignore_employee_time_overlap", "ignore_user_time_overlap"], as_dict=True) or {} + ignore_overlap = (int(settings.get("ignore_employee_time_overlap") or 0) == 1) or (int(settings.get("ignore_user_time_overlap") or 0) == 1) if not ignore_overlap: existing_timer = frappe.db.sql(""" @@ -316,18 +319,24 @@ def start_active_timer(task, project, subject, start_time): existing_timer = existing_timer[0] frappe.throw(_("Another task is already running: {0}. Please stop it before starting a new one.").format(existing_timer.subject or existing_timer.task)) - timer_name = frappe.db.get_value("Active Task Timer", {"user": user, "task": task}) + filters = {"user": user} + if is_ad_hoc_event: + filters["is_ad_hoc_event"] = 1 + else: + filters["task"] = task + + timer_name = frappe.db.get_value("Active Task Timer", filters) if timer_name: doc = frappe.get_doc("Active Task Timer", timer_name) else: doc = frappe.new_doc("Active Task Timer") doc.user = user - doc.task = task + if not is_ad_hoc_event: + doc.task = task doc.flags.ignore_permissions = True - if task.startswith("EVENT-"): - doc.flags.ignore_links = True + doc.is_ad_hoc_event = is_ad_hoc_event doc.project = project doc.subject = subject @@ -341,13 +350,17 @@ def start_active_timer(task, project, subject, start_time): return all_timers @frappe.whitelist() -def stop_active_timer(task=None): +def stop_active_timer(task=None, is_ad_hoc_event=0): """ - Stop the active timer for the current user, optionally filtering by task. + Stop the active timer for the current user, optionally filtering by task or ad-hoc status. """ user = frappe.session.user + is_ad_hoc_event = int(is_ad_hoc_event) filters = {"user": user} - if task: + + if is_ad_hoc_event: + filters["is_ad_hoc_event"] = 1 + elif task: filters["task"] = task timer_names = frappe.get_all("Active Task Timer", filters=filters, pluck="name", ignore_permissions=True) @@ -370,7 +383,7 @@ def get_active_timer(): return [] timers = frappe.db.sql(""" - SELECT task, project, subject, start_time FROM `tabActive Task Timer` WHERE user = %s + SELECT task, project, subject, start_time, is_ad_hoc_event FROM `tabActive Task Timer` WHERE user = %s """, (user,), as_dict=True) return timers @@ -381,9 +394,8 @@ def check_active_timer(): Check if an active timer exists for the current user, respecting overlap settings. """ user = frappe.session.user - val1 = frappe.db.get_value("Projects Settings", "Projects Settings", "ignore_employee_time_overlap") - val2 = frappe.db.get_value("Projects Settings", "Projects Settings", "ignore_user_time_overlap") - ignore_overlap = (int(val1 or 0) == 1) or (int(val2 or 0) == 1) + settings = frappe.db.get_value("Projects Settings", "Projects Settings", ["ignore_employee_time_overlap", "ignore_user_time_overlap"], as_dict=True) or {} + ignore_overlap = (int(settings.get("ignore_employee_time_overlap") or 0) == 1) or (int(settings.get("ignore_user_time_overlap") or 0) == 1) if not ignore_overlap: existing_timer = frappe.db.sql(""" @@ -412,6 +424,7 @@ def create_event_from_tool(subject, event_category, start_time, company, ends_on event.custom_customer = customer event.event_type = "Private" event.status = "Completed" + event.send_reminder = 0 if employee: event.append("event_participants", { @@ -427,6 +440,6 @@ def create_event_from_tool(subject, event_category, start_time, company, ends_on make_time_sheet_entry(event.name) # Stop the ad-hoc event timer - stop_active_timer(f"EVENT-{user}") + stop_active_timer(is_ad_hoc_event=1) return event.name \ No newline at end of file diff --git a/one_compliance/public/js/one_compliance.js b/one_compliance/public/js/one_compliance.js index c1264099..cd1ec9f5 100644 --- a/one_compliance/public/js/one_compliance.js +++ b/one_compliance/public/js/one_compliance.js @@ -1,12 +1,12 @@ (function () { - 'use strict'; + 'use strict'; - const PREFIX = 'one-compliance-active-timer-'; + const PREFIX = 'one-compliance-active-timer-'; - // ========================= - // CSS Injection - // ========================= - function injectStyles() { + // ========================= + // CSS Injection + // ========================= + function injectStyles() { const Z_INDEX = 1040; const style = document.createElement('style'); @@ -79,366 +79,366 @@ document.documentElement.appendChild(style); } - // ========================= - // Utilities - // ========================= - - /** - * Escape HTML to prevent XSS - * @param {string} str - * @returns {string} - */ - function esc(str) { - if (!str) return ''; - return String(str) - .replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"'); - } - - /** - * Get active timer data from localStorage (fallback/cache) - * @returns {Array} - */ - function getActiveData() { - const match = document.cookie.match(/(?:^|; )user_id=([^;]*)/); - const user = match ? decodeURIComponent(match[1]) : null; - - if (user) { - const data = localStorage.getItem(PREFIX + user); - if (data) { - try { - const parsed = JSON.parse(data); - return Array.isArray(parsed) ? parsed : [parsed]; - } catch (e) { - console.warn('Invalid timer data in localStorage', e); - } - } - } - - for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); - if (key && key.startsWith(PREFIX)) { - try { - const data = JSON.parse(localStorage.getItem(key)); - return Array.isArray(data) ? data : [data]; - } catch (e) { - console.warn('Error parsing fallback timer data', e); - } - } - } - - return []; - } - - /** - * Generate HTML for timer display - * @param {Array} timers - * @returns {string} - */ - function getHTML(timers) { - if (!timers || timers.length === 0) return ''; - - const icon = - ''; - - let content = ''; - - if (timers.length <= 3) { - const lines = timers.map((d) => { - const project = d.project ? esc(d.project) + ': ' : ''; - const time = esc(d.formatted_start_time || d.start_time); - - return ( - '
' + - 'Timer is ON: ' + - project + - esc(d.subject) + - ' (' + - time + - ')' + - '
' - ); - }); - - content = - '
' + - lines.join('') + - '
'; - } else { - content = - 'Timer is ON: ' + - timers.length + - ' Tasks running'; - } - - return icon + '
' + content + '
'; - } - - // ========================= - // Render - // ========================= - - /** - * Render floating timer UI - * @returns {boolean} - */ - function render() { - if (document.getElementById('oc-timer-wrap')) return true; - if (!document.body) return false; - - const wrap = document.createElement('div'); - wrap.id = 'oc-timer-wrap'; - - const link = document.createElement('a'); - link.id = 'oc-timer-box'; - link.href = '/app/task-management-tool'; - - const timers = getActiveData(); - - if (timers.length > 0) { - link.innerHTML = getHTML(timers); - wrap.style.display = 'block'; - } else { - link.style.display = 'none'; - wrap.style.display = 'none'; - } - - wrap.appendChild(link); - document.body.appendChild(wrap); - - return true; - } - - /** - * Boot renderer safely - */ - function boot() { - if (!render()) { - setTimeout(boot, 10); - } - } - - // ========================= - // Sync with Frappe Backend - // ========================= - - /** - * Start real-time sync with backend - */ - function startSync() { - if (!window.frappe || !frappe.call || !window.jQuery) { - setTimeout(startSync, 100); - return; - } - - /** - * Update UI with timer data - * @param {Array|Object|null} data - */ - function update(data) { - const el = document.getElementById('oc-timer-box'); - const wrap = document.getElementById('oc-timer-wrap'); - if (!el) return; - - const timers = Array.isArray(data) - ? data - : data - ? [data] - : []; - - const user = - (frappe.session && frappe.session.user) || - (frappe.boot && frappe.boot.user && frappe.boot.user.name); - - if (timers.length > 0) { - if (frappe.datetime) { - timers.forEach((t) => { - if (t.start_time) { - t.formatted_start_time = - frappe.datetime.str_to_user(t.start_time); - } - }); - } - - el.innerHTML = getHTML(timers); - el.style.display = 'flex'; - if (wrap) wrap.style.display = 'block'; - - if (user) { - localStorage.setItem(PREFIX + user, JSON.stringify(timers)); - } - } else { - el.style.display = 'none'; - if (wrap) wrap.style.display = 'none'; - - if (user) { - localStorage.removeItem(PREFIX + user); - } - } - } - - // Custom event - $(document).on('one-compliance-timer-changed', function (e, data) { - update(data); - }); - - // Realtime event - if (frappe.realtime) { - frappe.realtime.on('one_compliance_timer_update', update); - } - - // Initial fetch - frappe.call({ - method: - 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.get_active_timer', - callback: function (r) { - update(r.message); - }, - }); - } - - // ========================= - // Ad-hoc Events - // ========================= - - /** - * Start a synthetic task timer for an ad-hoc event - */ - window.start_active_event_timer = function () { - frappe.call({ - method: - 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.check_active_timer', - callback: function (r) { - if (r.message) { - frappe.msgprint(r.message); - } else { - const user = frappe.session.user; - const task_id = 'EVENT-' + user; - const start_time = frappe.datetime.now_datetime(); - - frappe.call({ - method: - 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.start_active_timer', - args: { - task: task_id, - project: '', - subject: 'Ad-hoc Event', - start_time: start_time, - }, - callback: function (r) { - $(document).trigger('one-compliance-timer-changed', [ - r.message, - ]); - $(document).trigger('one-compliance-refresh-tools'); - }, - }); - } - }, - }); - }; - - /** - * Show dialog to finalize an ad-hoc event - * @param {string} start_time - * @param {string} subject - */ - window.show_add_event_dialog = function (start_time, subject) { - frappe.model.with_doctype('Event', function () { - const meta = frappe.get_meta('Event'); - const catField = meta.fields.find( - (f) => f.fieldname === 'event_category' - ); - const options = catField ? catField.options.split('\n') : []; - - const d = new frappe.ui.Dialog({ - title: __('Add Event'), - fields: [ - { - label: __('Subject'), - fieldname: 'subject', - fieldtype: 'Data', - reqd: 1, - default: subject || 'Ad-hoc Event', - }, - { - label: __('Starts On'), - fieldname: 'start_time', - fieldtype: 'Datetime', - read_only: 1, - default: start_time, - }, - { - label: __('Company'), - fieldname: 'company', - fieldtype: 'Link', - options: 'Company', - reqd: 1, - default: frappe.defaults.get_user_default('company'), - }, - { - label: __('Client'), - fieldname: 'customer', - fieldtype: 'Link', - options: 'Customer', - }, - { - label: __('Event Category'), - fieldname: 'event_category', - fieldtype: 'Select', - options: options, - reqd: 1, - }, - { - label: __('Description'), - fieldname: 'description', - fieldtype: 'Small Text', - }, - { - label: __('Ends On'), - fieldname: 'ends_on', - fieldtype: 'Datetime', - reqd: 1, - default: frappe.datetime.now_datetime(), - }, - ], - primary_action_label: __('Add Timesheet'), - primary_action(values) { - if (values.ends_on <= values.start_time) { - frappe.msgprint(__('Ends On must be after Starts On')); - return; - } - d.disable_primary_action(); - frappe.call({ - method: - 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.create_event_from_tool', - args: values, - callback: function (r) { - if (r.message) { - frappe.show_alert({ - message: __('Event and Timesheet created successfully'), - indicator: 'green', - }); - d.hide(); - $(document).trigger('one-compliance-timer-changed', [[]]); - $(document).trigger('one-compliance-refresh-tools'); - } - }, - always: function () { - d.enable_primary_action(); - }, - }); - }, - }); - d.show(); - }); - }; - - // ========================= - // Init - // ========================= - injectStyles(); - boot(); - startSync(); -})(); \ No newline at end of file + // ========================= + // Utilities + // ========================= + + /** + * Escape HTML to prevent XSS + * @param {string} str + * @returns {string} + */ + function esc(str) { + if (!str) return ''; + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"'); + } + + /** + * Get active timer data from localStorage (fallback/cache) + * @returns {Array} + */ + function getActiveData() { + const match = document.cookie.match(/(?:^|; )user_id=([^;]*)/); + const user = match ? decodeURIComponent(match[1]) : null; + + if (user) { + const data = localStorage.getItem(PREFIX + user); + if (data) { + try { + const parsed = JSON.parse(data); + return Array.isArray(parsed) ? parsed : [parsed]; + } catch (e) { + console.warn('Invalid timer data in localStorage', e); + } + } + } + + for (let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key && key.startsWith(PREFIX)) { + try { + const data = JSON.parse(localStorage.getItem(key)); + return Array.isArray(data) ? data : [data]; + } catch (e) { + console.warn('Error parsing fallback timer data', e); + } + } + } + + return []; + } + + /** + * Generate HTML for timer display + * @param {Array} timers + * @returns {string} + */ + function getHTML(timers) { + if (!timers || timers.length === 0) return ''; + + const icon = + ''; + + let content = ''; + + if (timers.length <= 3) { + const lines = timers.map((d) => { + const project = d.project ? esc(d.project) + ': ' : ''; + const time = esc(d.formatted_start_time || d.start_time); + + return ( + '
' + + 'Timer is ON: ' + + project + + esc(d.subject) + + ' (' + + time + + ')' + + '
' + ); + }); + + content = + '
' + + lines.join('') + + '
'; + } else { + content = + 'Timer is ON: ' + + timers.length + + ' Tasks running'; + } + + return icon + '
' + content + '
'; + } + + // ========================= + // Render + // ========================= + + /** + * Render floating timer UI + * @returns {boolean} + */ + function render() { + if (document.getElementById('oc-timer-wrap')) return true; + if (!document.body) return false; + + const wrap = document.createElement('div'); + wrap.id = 'oc-timer-wrap'; + + const link = document.createElement('a'); + link.id = 'oc-timer-box'; + link.href = '/app/task-management-tool'; + + const timers = getActiveData(); + + if (timers.length > 0) { + link.innerHTML = getHTML(timers); + wrap.style.display = 'block'; + } else { + link.style.display = 'none'; + wrap.style.display = 'none'; + } + + wrap.appendChild(link); + document.body.appendChild(wrap); + + return true; + } + + /** + * Boot renderer safely + */ + function boot() { + if (!render()) { + setTimeout(boot, 10); + } + } + + // ========================= + // Sync with Frappe Backend + // ========================= + + /** + * Start real-time sync with backend + */ + function startSync() { + if (!window.frappe || !frappe.call || !window.jQuery) { + setTimeout(startSync, 100); + return; + } + + /** + * Update UI with timer data + * @param {Array|Object|null} data + */ + function update(data) { + const el = document.getElementById('oc-timer-box'); + const wrap = document.getElementById('oc-timer-wrap'); + if (!el) return; + + const timers = Array.isArray(data) + ? data + : data + ? [data] + : []; + + const user = + (frappe.session && frappe.session.user) || + (frappe.boot && frappe.boot.user && frappe.boot.user.name); + + if (timers.length > 0) { + if (frappe.datetime) { + timers.forEach((t) => { + if (t.start_time) { + t.formatted_start_time = + frappe.datetime.str_to_user(t.start_time); + } + }); + } + + el.innerHTML = getHTML(timers); + el.style.display = 'flex'; + if (wrap) wrap.style.display = 'block'; + + if (user) { + localStorage.setItem(PREFIX + user, JSON.stringify(timers)); + } + } else { + el.style.display = 'none'; + if (wrap) wrap.style.display = 'none'; + + if (user) { + localStorage.removeItem(PREFIX + user); + } + } + } + + // Custom event + $(document).on('one-compliance-timer-changed', function (e, data) { + update(data); + }); + + // Realtime event + if (frappe.realtime) { + frappe.realtime.on('one_compliance_timer_update', update); + } + + // Initial fetch + frappe.call({ + method: + 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.get_active_timer', + callback: function (r) { + update(r.message); + }, + }); + } + + // ========================= + // Ad-hoc Events + // ========================= + + /** + * Start a synthetic task timer for an ad-hoc event + */ + window.start_active_event_timer = function () { + frappe.call({ + method: + 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.check_active_timer', + callback: function (r) { + if (r.message) { + frappe.msgprint(r.message); + } else { + const user = frappe.session.user; + const start_time = frappe.datetime.now_datetime(); + + frappe.call({ + method: + 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.start_active_timer', + args: { + task: '', + project: '', + subject: 'Ad-hoc Event', + start_time: start_time, + is_ad_hoc_event: 1, + }, + callback: function (r) { + $(document).trigger('one-compliance-timer-changed', [ + r.message, + ]); + $(document).trigger('one-compliance-refresh-tools'); + }, + }); + } + }, + }); + }; + + /** + * Show dialog to finalize an ad-hoc event + * @param {string} start_time + * @param {string} subject + */ + window.show_add_event_dialog = function (start_time, subject) { + frappe.model.with_doctype('Event', function () { + const meta = frappe.get_meta('Event'); + const catField = meta.fields.find( + (f) => f.fieldname === 'event_category' + ); + const options = catField ? catField.options.split('\n') : []; + + const d = new frappe.ui.Dialog({ + title: __('Add Event'), + fields: [ + { + label: __('Subject'), + fieldname: 'subject', + fieldtype: 'Data', + reqd: 1, + default: subject || 'Ad-hoc Event', + }, + { + label: __('Starts On'), + fieldname: 'start_time', + fieldtype: 'Datetime', + read_only: 1, + default: start_time, + }, + { + label: __('Company'), + fieldname: 'company', + fieldtype: 'Link', + options: 'Company', + reqd: 1, + default: frappe.defaults.get_user_default('company'), + }, + { + label: __('Client'), + fieldname: 'customer', + fieldtype: 'Link', + options: 'Customer', + }, + { + label: __('Event Category'), + fieldname: 'event_category', + fieldtype: 'Select', + options: options, + reqd: 1, + }, + { + label: __('Description'), + fieldname: 'description', + fieldtype: 'Small Text', + }, + { + label: __('Ends On'), + fieldname: 'ends_on', + fieldtype: 'Datetime', + reqd: 1, + default: frappe.datetime.now_datetime(), + }, + ], + primary_action_label: __('Add Timesheet'), + primary_action(values) { + if (values.ends_on <= values.start_time) { + frappe.msgprint(__('Ends On must be after Starts On')); + return; + } + d.disable_primary_action(); + frappe.call({ + method: + 'one_compliance.one_compliance.page.task_management_tool.task_management_tool.create_event_from_tool', + args: values, + callback: function (r) { + if (r.message) { + frappe.show_alert({ + message: __('Event and Timesheet created successfully'), + indicator: 'green', + }); + d.hide(); + $(document).trigger('one-compliance-timer-changed', [[]]); + $(document).trigger('one-compliance-refresh-tools'); + } + }, + always: function () { + d.enable_primary_action(); + }, + }); + }, + }); + d.show(); + }); + }; + + // ========================= + // Init + // ========================= + injectStyles(); + boot(); + startSync(); +})();