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 @@
+
+
+
Admin Tasks
+
+
+
+
+
+
+
+
+
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 },