diff --git a/custom_ui/api/db.py b/custom_ui/api/db.py
index 9d152f2..167a503 100644
--- a/custom_ui/api/db.py
+++ b/custom_ui/api/db.py
@@ -1,6 +1,5 @@
import frappe, json, re
from datetime import datetime, date
-from custom_ui.db_utils import calculate_appointment_scheduled_status, calculate_estimate_sent_status, calculate_payment_recieved_status, calculate_job_status
@frappe.whitelist()
def get_client_status_counts(weekly=False, week_start_date=None, week_end_date=None):
@@ -16,13 +15,6 @@ def get_client_status_counts(weekly=False, week_start_date=None, week_end_date=N
filters = {status_field: status_value}
filters.update(base_filters)
return filters
-
- def get_status_total(counts_dicts, status_field):
- sum_array = []
- for counts_dict in counts_dicts:
- sum_array.append(counts_dict[status_field])
- return sum(sum_array)
-
onsite_meeting_scheduled_status_counts = {
"label": "On-Site Meeting Scheduled",
@@ -58,6 +50,7 @@ def get_client_status_counts(weekly=False, week_start_date=None, week_end_date=N
job_status_counts,
payment_received_status_counts
]
+
categories = []
for status_dict in status_dicts:
category = {
@@ -85,7 +78,28 @@ def get_client_status_counts(weekly=False, week_start_date=None, week_end_date=N
return categories
@frappe.whitelist()
-def get_clients(options):
+def get_client(client_name):
+ address = frappe.get_doc("Address", client_name)
+ customer_name = [link for link in address.links if link.link_doctype == "Customer"][0].link_name
+ project_names = frappe.db.get_all("Project", fields=["name"], filters=[
+ ["or", [
+ ["custom_installation_address", "=", address.address_title],
+ ["custom_address", "=", address.address_title]
+ ]]
+ ])
+
+ projects = [frappe.get_doc("Project", proj["name"]) for proj in project_names]
+ customer = frappe.get_doc("Customer", customer_name)
+ # get all associated data as needed
+ return {
+ "address": address,
+ "customer": customer,
+ "projects": projects
+ }
+
+
+@frappe.whitelist()
+def get_clients_table_data(options):
options = json.loads(options)
print("DEBUG: Raw options received:", options)
defaultOptions = {
@@ -98,9 +112,6 @@ def get_clients(options):
options = {**defaultOptions, **options}
print("DEBUG: Final options:", options)
- clients = []
- tableRows = []
-
# Map frontend field names to backend field names
def map_field_name(frontend_field):
field_mapping = {
@@ -162,75 +173,23 @@ def get_clients(options):
addresses = frappe.db.get_all(
"Address",
- fields=["address_title", "custom_onsite_meeting_scheduled", "custom_estimate_sent_status", "custom_job_status", "custom_payment_received_status"],
+ fields=["name", "address_title", "custom_onsite_meeting_scheduled", "custom_estimate_sent_status", "custom_job_status", "custom_payment_received_status"],
filters=processed_filters,
limit=options["page_size"],
start=(options["page"] - 1) * options["page_size"],
order_by=order_by
)
-
- # for address in addresses:
- # client = {}
- # tableRow = {}
-
- # on_site_meetings = frappe.db.get_all(
- # "On-Site Meeting",
- # fields=["*"],
- # filters={"address": address["address_title"]}
- # )
-
- # quotations = frappe.db.get_all(
- # "Quotation",
- # fields=["*"],
- # filters={"custom_installation_address": address["address_title"]}
- # )
-
- # sales_orders = frappe.db.get_all(
- # "Sales Order",
- # fields=["*"],
- # filters={"custom_installation_address": address["address_title"]}
- # )
-
- # sales_invvoices = frappe.db.get_all(
- # "Sales Invoice",
- # fields=["*"],
- # filters={"custom_installation_address": address["address_title"]}
- # )
-
- # payment_entries = frappe.db.get_all(
- # "Payment Entry",
- # fields=["*"],
- # filters={"custom_installation_address": address["address_title"]}
- # )
-
- # jobs = frappe.db.get_all(
- # "Project",
- # fields=["*"],
- # filters={
- # "custom_installation_address": address["address_title"],
- # "project_template": "SNW Install"
- # }
- # )
-
- # tasks = frappe.db.get_all(
- # "Task",
- # fields=["*"],
- # filters={"project": jobs[0]["name"]}
- # ) if jobs else []
-
- # tableRow["id"] = address["name"]
- # tableRow["address_title"] = address["address_title"]
- # tableRow["appointment_scheduled_status"] = calculate_appointment_scheduled_status(on_site_meetings[0]) if on_site_meetings else "Not Started"
- # tableRow["estimate_sent_status"] = calculate_estimate_sent_status(quotations[0]) if quotations else "Not Started"
- # tableRow["payment_received_status"] = calculate_payment_recieved_status(sales_invvoices[0], payment_entries) if sales_invvoices and payment_entries else "Not Started"
- # tableRow["job_status"] = calculate_job_status(jobs[0], tasks) if jobs and tasks else "Not Started"
- # tableRows.append(tableRow)
-
- # client["address"] = address
- # client["on_site_meetings"] = on_site_meetings
- # client["jobs"] = jobs
- # client["quotations"] = quotations
- # clients.append(client)
+
+ rows = []
+ for address in addresses:
+ tableRow = {}
+ tableRow["id"] = address["name"]
+ tableRow["address_title"] = address["address_title"]
+ tableRow["appointment_scheduled_status"] = address["custom_onsite_meeting_scheduled"]
+ tableRow["estimate_sent_status"] = address["custom_estimate_sent_status"]
+ tableRow["job_status"] = address["custom_job_status"]
+ tableRow["payment_received_status"] = address["custom_payment_received_status"]
+ rows.append(tableRow)
return {
"pagination": {
@@ -239,7 +198,7 @@ def get_clients(options):
"page_size": options["page_size"],
"total_pages": (count + options["page_size"] - 1) // options["page_size"]
},
- "data": addresses
+ "data": rows
}
diff --git a/frontend/action-behavior-test.html b/frontend/action-behavior-test.html
new file mode 100644
index 0000000..fad3c90
--- /dev/null
+++ b/frontend/action-behavior-test.html
@@ -0,0 +1,278 @@
+
+
+
+
+
+ Updated DataTable Actions Behavior Test
+
+
+
+ Updated DataTable Actions Behavior Test
+
+ ✅ New Action Behavior Summary
+
+
+
Action Type Changes:
+
+ -
+ Global Actions: Default behavior - always available above
+ table
+
+ -
+ Single Selection Actions (requiresSelection: true): Above table, enabled only when exactly one row selected
+
+ -
+ Row Actions (rowAction: true): In
+ actions column, available per row
+
+ -
+ Bulk Actions (requiresMultipleSelection: true): Above table when rows selected
+
+
+
+
+
+
Updated Action Types Matrix:
+
+
+ | Action Type |
+ Property |
+ Location |
+ Enabled When |
+ Data Received |
+
+
+ | Global |
+ None (default) |
+ Above table |
+ Always |
+ None |
+
+
+ | Single Selection |
+ requiresSelection: true |
+ Above table |
+ Exactly 1 row selected |
+ Selected row object |
+
+
+ | Row Action |
+ rowAction: true |
+ Actions column |
+ Always (per row) |
+ Individual row object |
+
+
+ | Bulk |
+ requiresMultipleSelection: true |
+ Above table (when selected) |
+ 1+ rows selected |
+ Array of selected rows |
+
+
+
+
+
+
Implementation Changes Made:
+
+ -
+ Computed Properties Updated:
+
+ -
+ globalActions: Actions with no special
+ properties
+
+ -
+ singleSelectionActions: Actions with
+ requiresSelection: true
+
+ -
+ rowActions: Actions with
+ rowAction: true
+
+ -
+ bulkActions: Actions with
+ requiresMultipleSelection: true
+
+
+
+ -
+ Template Updates:
+
+ - Global Actions section now includes single selection actions
+ -
+ Single selection actions are disabled unless exactly one row is
+ selected
+
+ - Visual feedback shows selection state
+ -
+ Actions column only shows
+ rowAction: true actions
+
+
+
+ -
+ New Handler Added:
+
+ -
+ handleSingleSelectionAction: Passes selected
+ row data to action
+
+
+
+
+
+
+
+
Example Configuration (Clients.vue):
+
+
+const tableActions = [
+ // Global action - always available
+ {
+ label: "Add Client",
+ action: () => modalStore.openModal("createClient"),
+ icon: "pi pi-plus",
+ style: "primary"
+ },
+ // Single selection action - enabled when exactly one row selected
+ {
+ label: "View Details",
+ action: (rowData) => router.push(`/clients/${rowData.id}`),
+ icon: "pi pi-eye",
+ style: "info",
+ requiresSelection: true
+ },
+ // Bulk action - enabled when rows selected
+ {
+ label: "Export Selected",
+ action: (selectedRows) => exportData(selectedRows),
+ icon: "pi pi-download",
+ style: "success",
+ requiresMultipleSelection: true
+ },
+ // Row actions - appear in each row
+ {
+ label: "Edit",
+ action: (rowData) => editClient(rowData),
+ icon: "pi pi-pencil",
+ style: "secondary",
+ rowAction: true
+ },
+ {
+ label: "Quick View",
+ action: (rowData) => showPreview(rowData),
+ icon: "pi pi-search",
+ style: "info",
+ rowAction: true
+ }
+];
+
+
+
+
+
User Experience Improvements:
+
+ -
+ Clearer Action Organization: Actions are logically grouped by
+ their purpose
+
+ -
+ Better Visual Feedback: Users see why certain actions are
+ disabled
+
+ -
+ More Flexible Layout: Actions can be placed where they make
+ most sense
+
+ -
+ Reduced Clutter: Row actions only show contextual actions for
+ that specific row
+
+ -
+ Intuitive Behavior: Single selection actions work like "View
+ Details" - need one item selected
+
+
+
+
+
+
Action Flow Examples:
+
+ -
+ Adding New Item: Global action → Always available → No data
+ needed
+
+ -
+ Viewing Item Details: Single selection action → Select one row
+ → View details of selected item
+
+ -
+ Editing Item: Row action → Click edit in specific row → Edit
+ that item
+
+ -
+ Bulk Operations: Bulk action → Select multiple rows → Operate
+ on all selected
+
+
+
+
+ ✅ Testing Checklist
+
+ - [ ] Global actions are always enabled and visible above table
+ - [ ] Single selection actions are disabled when no rows selected
+ - [ ] Single selection actions are disabled when multiple rows selected
+ - [ ] Single selection actions are enabled when exactly one row is selected
+ - [ ] Single selection actions receive correct row data
+ - [ ] Row actions appear in each row's actions column
+ - [ ] Row actions receive correct individual row data
+ - [ ] Bulk actions appear when rows are selected
+ - [ ] Bulk actions receive array of selected row data
+ - [ ] Visual feedback shows selection state appropriately
+
+
+
diff --git a/frontend/documentation/components/DataTable.md b/frontend/documentation/components/DataTable.md
index c30bacc..c4596a1 100644
--- a/frontend/documentation/components/DataTable.md
+++ b/frontend/documentation/components/DataTable.md
@@ -11,6 +11,7 @@ A feature-rich data table component built with PrimeVue's DataTable. This compon
@@ -41,6 +42,32 @@ const tableData = ref([
{ id: 2, name: "Jane Smith", status: "in progress" },
]);
+const tableActions = ref([
+ {
+ label: "Add Item",
+ action: () => console.log("Add clicked"),
+ icon: "pi pi-plus",
+ style: "primary",
+ // Global action - always available
+ },
+ {
+ label: "View Details",
+ action: (rowData) => console.log("View:", rowData),
+ icon: "pi pi-eye",
+ style: "info",
+ requiresSelection: true,
+ // Single selection action - enabled when exactly one row selected
+ },
+ {
+ label: "Edit",
+ action: (rowData) => console.log("Edit:", rowData),
+ icon: "pi pi-pencil",
+ style: "secondary",
+ rowAction: true,
+ // Row action - appears in each row's actions column
+ },
+]);
+
const handleRowClick = (event) => {
console.log("Row clicked:", event.data);
};
@@ -85,6 +112,12 @@ const handleRowClick = (event) => {
- **Type:** `Object`
- **Default:** `{ global: { value: null, matchMode: FilterMatchMode.CONTAINS } }`
+### `tableActions` (Array)
+
+- **Description:** Array of action objects that define interactive buttons for the table. Actions can be global (always available), single-selection (enabled when exactly one row is selected), row-specific (displayed per row), or bulk (for multiple selected rows).
+- **Type:** `Array