From 94e1c4dfedc55e0593b2f3bd04527bea21af9051 Mon Sep 17 00:00:00 2001 From: rocketdebris Date: Mon, 12 Jan 2026 15:50:56 -0500 Subject: [PATCH] Added Tasks Page, Routing, SideBar entry, and API methods. --- custom_ui/api/db/tasks.py | 88 +++++++++ frontend/src/api.js | 37 +++- frontend/src/components/SideBar.vue | 2 + frontend/src/components/pages/Tasks.vue | 231 ++++++++++++++++++++++++ frontend/src/router.js | 2 + 5 files changed, 359 insertions(+), 1 deletion(-) create mode 100644 custom_ui/api/db/tasks.py create mode 100644 frontend/src/components/pages/Tasks.vue diff --git a/custom_ui/api/db/tasks.py b/custom_ui/api/db/tasks.py new file mode 100644 index 0000000..5e611c2 --- /dev/null +++ b/custom_ui/api/db/tasks.py @@ -0,0 +1,88 @@ +import frappe +from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response + + +@frappe.whitelist() +def get_job_task_table_data(filters={}, sortings={}, page=1, page_size=10): + """Get paginated job tasks table data with filtering and sorting support.""" + print("DEBUG: raw task options received:", filters, sortings, page, page_size) + + processed_filters, processed_sortings, is_or, page, page_size = process_query_conditions(filters, sortings, page, page_size) + + print("DEBUG: Processed Filters:", processed_filters) + + if is_or: + count = frappe.db.sql(*get_count_or_filters("Task", filters))[0][0] + else: + count = frappe.db.count("Task", filters=filters) + + print(f"DEBUG: Number of tasks returned: {count}") + + tasks = frappe.db.get_all( + "Task", + fields=["*"], + filters=filters, + limit=page_size, + start=(page - 1) * page_size, + order_by=processed_sortings + ) + + tableRows = [] + for task in tasks: + tableRow = {} + tableRow["id"] = task["name"] + tableRow["subject"] = task["subject"] + tableRow["address"] = task.get("custom_property", "") + tableRow["status"] = task.get("status", "") + tableRow["type"] = task.get("type", "") + tableRows.append(tableRow) + + table_data_dict = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size) + return build_success_response(table_data_dict) + + +@frappe.whitelist() +def get_job_task_list(job_id=""): + if job_id: + try: + tasks = frappe.get_all('Task', filters={"project": job_id}) + task_docs = {task_id: frappe.get_doc(task_id) for task_id in tasks} + return build_success_response(task_docs) + except Exception as e: + return build_error_response(str(e), 500) + + +@frappe.whitelist() +def get_tasks_table_data(filters={}, sortings=[], page=1, page_size=10): + """Get paginated job table data with filtering and sorting support.""" + print("DEBUG: Raw job options received:", filters, sortings, page, page_size) + + processed_filters, processed_sortings, is_or, page, page_size = process_query_conditions(filters, sortings, page, page_size) + + # Handle count with proper OR filter support + if is_or: + count = frappe.db.sql(*get_count_or_filters("Task", processed_filters))[0][0] + else: + count = frappe.db.count("Task", filters=processed_filters) + + tasks = frappe.db.get_all( + "Task", + fields=["*"], + filters=processed_filters if not is_or else None, + or_filters=processed_filters if is_or else None, + limit=page_size, + start=(page - 1) * page_size, + order_by=processed_sortings + ) + + tableRows = [] + for task in tasks: + tableRow = {} + tableRow["id"] = task["name"] + tableRow["subject"] = task["subject"] + tableRow["address"] = task.get("custom_property", "") + tableRow["status"] = task.get("status", "") + tableRows.append(tableRow) + + data_table_dict = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size) + return build_success_response(data_table_dict) diff --git a/frontend/src/api.js b/frontend/src/api.js index 979ff86..d7a3798 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -21,6 +21,8 @@ const FRAPPE_UPSERT_JOB_METHOD = "custom_ui.api.db.jobs.upsert_job"; const FRAPPE_GET_JOB_TASK_LIST_METHOD = "custom_ui.api.db.jobs.get_job_task_table_data"; const FRAPPE_GET_INSTALL_PROJECTS_METHOD = "custom_ui.api.db.jobs.get_install_projects"; const FRAPPE_GET_JOB_TEMPLATES_METHOD = "custom_ui.api.db.jobs.get_job_templates"; +// Task methods +const FRAPPE_GET_TASKS_METHOD = "custom_ui.api.db.tasks.get_tasks_table_data"; // Invoice methods const FRAPPE_GET_INVOICES_METHOD = "custom_ui.api.db.invoices.get_invoice_table_data"; const FRAPPE_UPSERT_INVOICE_METHOD = "custom_ui.api.db.invoices.upsert_invoice"; @@ -240,7 +242,7 @@ class Api { static async getJobTemplates(company) { return await this.request(FRAPPE_GET_JOB_TEMPLATES_METHOD, { company }); } - + static async getJobDetails() { const projects = await this.getDocsList("Project"); const data = []; @@ -339,7 +341,40 @@ class Api { return result; } + // ============================================================================ + // TASK METHODS + // ============================================================================ + /** + * Get paginated job data with filtering and sorting + * @param {Object} paginationParams - Pagination parameters from store + * @param {Object} filters - Filter parameters from store + * @param {Object} sorting - Sorting parameters from store (optional) + * @returns {Promise<{data: Array, pagination: Object}>} + */ + static async getPaginatedTaskDetails(paginationParams = {}, filters = {}, sorting = null) { + const { page = 0, pageSize = 10, sortField = null, sortOrder = null } = paginationParams; + + // Use sorting from the dedicated sorting parameter first, then fall back to pagination params + const actualSortField = sorting?.field || sortField; + const actualSortOrder = sorting?.order || sortOrder; + + const options = { + page: page + 1, // Backend expects 1-based pages + page_size: pageSize, + filters, + sorting: + actualSortField && actualSortOrder + ? `${actualSortField} ${actualSortOrder === -1 ? "desc" : "asc"}` + : null, + for_table: true, + }; + + console.log("DEBUG: API - Sending task options to backend:", options); + + const result = await this.request(FRAPPE_GET_TASKS_METHOD, { options }); + return result; + } // ============================================================================ // INVOICE / PAYMENT METHODS diff --git a/frontend/src/components/SideBar.vue b/frontend/src/components/SideBar.vue index 6bf38b2..a995252 100644 --- a/frontend/src/components/SideBar.vue +++ b/frontend/src/components/SideBar.vue @@ -19,6 +19,7 @@ import { ReceiveDollars, NavArrowLeft, NavArrowRight, + ClipboardCheck, } from "@iconoir/vue"; import SidebarSpeedDial from "./SidebarSpeedDial.vue"; @@ -139,6 +140,7 @@ const categories = ref([ // { name: "Bids", icon: Neighbourhood, url: "/schedule-bid" }, { name: "Estimates", icon: Calculator, url: "/estimates" }, { name: "Jobs", icon: Hammer, url: "/jobs" }, + { name: "Tasks", icon: ClipboardCheck, url: "/tasks" }, { name: "Payments/Invoices", icon: ReceiveDollars, url: "/invoices" }, { name: "Routes", icon: PathArrowSolid, url: "/routes" }, { name: "Time Sheets", icon: Clock, url: "/timesheets" }, diff --git a/frontend/src/components/pages/Tasks.vue b/frontend/src/components/pages/Tasks.vue new file mode 100644 index 0000000..155fda8 --- /dev/null +++ b/frontend/src/components/pages/Tasks.vue @@ -0,0 +1,231 @@ + + + diff --git a/frontend/src/router.js b/frontend/src/router.js index 9e9294f..e81fb07 100644 --- a/frontend/src/router.js +++ b/frontend/src/router.js @@ -13,6 +13,7 @@ import Client from "./components/pages/Client.vue"; import Property from "./components/pages/Property.vue"; import Estimate from "./components/pages/Estimate.vue"; import Job from "./components/pages/Job.vue"; +import Tasks from "./components/pages/Tasks.vue"; const routes = [ { @@ -25,6 +26,7 @@ const routes = [ { path: "/property", component: Property }, { path: "/jobs", component: Jobs }, { path: "/job", component: Job }, + { path: "/tasks", component: Tasks }, { path: "/invoices", component: Invoices }, { path: "/estimates", component: Estimates }, { path: "/estimate", component: Estimate },