From f4d04e90a9c4400215f1e74ec608f85d2f6da415 Mon Sep 17 00:00:00 2001 From: Casey Wittrock Date: Fri, 7 Nov 2025 07:50:19 -0600 Subject: [PATCH] finish client table for now --- custom_ui/api/db.py | 94 +++++++++++++++++-- custom_ui/db_utils.py | 13 ++- frontend/src/api.js | 35 ++++--- frontend/src/components/common/DataTable.vue | 98 +++++++++++++------- frontend/src/components/pages/Clients.vue | 34 ++++--- frontend/src/stores/filters.js | 86 ++++++++++++----- 6 files changed, 254 insertions(+), 106 deletions(-) diff --git a/custom_ui/api/db.py b/custom_ui/api/db.py index 659f4c3..04dc2f1 100644 --- a/custom_ui/api/db.py +++ b/custom_ui/api/db.py @@ -1,9 +1,10 @@ import frappe, json -from custom_ui.db_utils import calculate_appointment_scheduled_status, calculate_estimate_sent_status, calculate_payment_recieved_status, calculate_job_scheduled_status +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_clients(options): options = json.loads(options) + print("DEBUG: Raw options received:", options) defaultOptions = { "fields": ["*"], "filters": {}, @@ -13,26 +14,81 @@ def get_clients(options): "for_table": False } options = {**defaultOptions, **options} + print("DEBUG: Final options:", options) clients = [] tableRows = [] - count = frappe.db.count("Address", filters=options["filters"]) + # Map frontend field names to backend field names + def map_field_name(frontend_field): + field_mapping = { + "addressTitle": "address_title", + "addressName": "address_title", # Legacy support + "appointmentScheduledStatus": "address_title", # These are computed fields, sort by address_title + "estimateSentStatus": "address_title", + "paymentReceivedStatus": "address_title", + "jobStatus": "address_title" + } + return field_mapping.get(frontend_field, frontend_field) + + # Process filters from PrimeVue format to Frappe format + processed_filters = {} + if options["filters"]: + for field_name, filter_obj in options["filters"].items(): + if isinstance(filter_obj, dict) and "value" in filter_obj: + if filter_obj["value"] is not None and filter_obj["value"] != "": + # Map frontend field names to backend field names + backend_field = map_field_name(field_name) + + # Handle different match modes + match_mode = filter_obj.get("matchMode", "contains") + if isinstance(match_mode, str): + match_mode = match_mode.lower() + + if match_mode in ("contains", "contains"): + processed_filters[backend_field] = ["like", f"%{filter_obj['value']}%"] + elif match_mode in ("startswith", "startsWith"): + processed_filters[backend_field] = ["like", f"{filter_obj['value']}%"] + elif match_mode in ("endswith", "endsWith"): + processed_filters[backend_field] = ["like", f"%{filter_obj['value']}"] + elif match_mode in ("equals", "equals"): + processed_filters[backend_field] = filter_obj["value"] + else: + # Default to contains + processed_filters[backend_field] = ["like", f"%{filter_obj['value']}%"] + + # Process sorting + order_by = None + if options.get("sorting") and options["sorting"]: + sorting_str = options["sorting"] + if sorting_str and sorting_str.strip(): + # Parse "field_name asc/desc" format + parts = sorting_str.strip().split() + if len(parts) >= 2: + sort_field = parts[0] + sort_direction = parts[1].lower() + # Map frontend field to backend field + backend_sort_field = map_field_name(sort_field) + order_by = f"{backend_sort_field} {sort_direction}" + + print("DEBUG: Processed filters:", processed_filters) + print("DEBUG: Order by:", order_by) + + count = frappe.db.count("Address", filters=processed_filters) print("DEBUG: Total addresses count:", count) addresses = frappe.db.get_all( "Address", fields=options["fields"], - filters=options["filters"], + filters=processed_filters, limit=options["page_size"], start=(options["page"] - 1) * options["page_size"], - order_by=(options["sorting"]) + order_by=order_by ) for address in addresses: client = {} tableRow = {} - print("DEBUG: Processing address:", address) on_site_meetings = frappe.db.get_all( "On-Site Meeting", @@ -40,14 +96,26 @@ def get_clients(options): 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"]} ) - quotations = frappe.db.get_all( - "Quotation", + payment_entries = frappe.db.get_all( + "Payment Entry", fields=["*"], filters={"custom_installation_address": address["address_title"]} ) @@ -60,13 +128,19 @@ def get_clients(options): "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_name"] = address.get("address_title", "") + 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]) if sales_invvoices else "Not Started" - tableRow["job_scheduled_status"] = calculate_job_scheduled_status(jobs[0]) if jobs 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 diff --git a/custom_ui/db_utils.py b/custom_ui/db_utils.py index 677848e..ecf4701 100644 --- a/custom_ui/db_utils.py +++ b/custom_ui/db_utils.py @@ -2,8 +2,6 @@ def calculate_appointment_scheduled_status(on_site_meeting): if not on_site_meeting: return "Not Started" - # if on_site_meeting["end_time"] < today: - # return "In Progress" return "Completed" def calculate_estimate_sent_status(quotation): @@ -13,16 +11,17 @@ def calculate_estimate_sent_status(quotation): return "Completed" return "In Progress" -def calculate_payment_recieved_status(sales_invoice): +def calculate_payment_recieved_status(sales_invoice, payment_entries): if not sales_invoice: return "Not Started" - if sales_invoice and sales_invoice["status"] == "Paid": + payment_sum = sum(entry["paid_amount"] for entry in payment_entries) if payment_entries else 0 + if (sales_invoice and sales_invoice["status"] == "Paid") or sales_invoice["grand_total"] <= payment_sum: return "Completed" return "In Progress" -def calculate_job_scheduled_status(project): - if not project: +def calculate_job_status(project, tasks=[]): + if not project or not tasks: return "Not Started" - if not project["start_time"]: + if any(task["status"] != "Completed" for task in tasks): return "In Progress" return "Completed" \ No newline at end of file diff --git a/frontend/src/api.js b/frontend/src/api.js index 5e5b221..a3d61ed 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -1,4 +1,3 @@ -import { da } from "vuetify/locale"; import DataUtils from "./utils"; const ZIPPOPOTAMUS_BASE_URL = "https://api.zippopotam.us/us"; @@ -8,6 +7,7 @@ const FRAPPE_UPSERT_CLIENT_METHOD = "custom_ui.api.db.upsert_client"; class Api { static async request(frappeMethod, args = {}) { args = DataUtils.toSnakeCaseObject(args); + console.log("DEBUG: API - Request Args: ", { method: frappeMethod, args }); try { let response = await frappe.call({ method: frappeMethod, @@ -207,8 +207,7 @@ class Api { const routes = getDocList("Pre-Built Routes"); for (const rt of routes) { route = getDetailedDoc("Pre-Built Routes", rt.name); - let tableRow = { - }; + let tableRow = {}; } console.log("DEBUG: API - getRouteData result: ", data); @@ -234,8 +233,8 @@ class Api { customer: timesheet.customer, totalHours: timesheet.total_hours, status: timesheet.status, - totalPayFormatted: timesheet.total_costing_amount - } + totalPayFormatted: timesheet.total_costing_amount, + }; data.push(tableRow); } console.log("DEBUG: API - getTimesheetData result: ", data); @@ -246,31 +245,31 @@ class Api { * Get paginated client 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 getPaginatedClientDetails(paginationParams = {}, filters = {}) { + static async getPaginatedClientDetails(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: sortField ? `${sortField} ${sortOrder === -1 ? "desc" : "asc"}` : null, + sorting: + actualSortField && actualSortOrder + ? `${actualSortField} ${actualSortOrder === -1 ? "desc" : "asc"}` + : null, for_table: true, }; - const result = await this.request("custom_ui.api.db.get_clients", { options }); + console.log("DEBUG: API - Sending options to backend:", options); - // Transform the response to match what the frontend expects - return { - data: result.data, - pagination: { - total: result.pagination.total, - page: result.pagination.page, - pageSize: result.pagination.page_size, - totalPages: result.pagination.total_pages, - }, - }; + const result = await this.request("custom_ui.api.db.get_clients", { options }); + return result; } /** diff --git a/frontend/src/components/common/DataTable.vue b/frontend/src/components/common/DataTable.vue index 3453be2..c078a04 100644 --- a/frontend/src/components/common/DataTable.vue +++ b/frontend/src/components/common/DataTable.vue @@ -82,12 +82,13 @@ :rowsPerPageOptions="[5, 10, 20, 50]" :paginator="true" :rows="currentRows" + :first="currentFirst" :lazy="lazy" :totalRecords="lazy ? totalRecords : getFilteredDataLength" @page="handlePage" - @sort="handleSort" + @sort="triggerLazyLoad" @filter="handleFilter" - sortMode="multiple" + sortMode="single" removableSort filterDisplay="none" v-model:filters="filterRef" @@ -252,9 +253,18 @@ const currentRows = computed(() => { return 10; // Default for non-lazy tables }); +// Get current first index for pagination synchronization +const currentFirst = computed(() => { + if (props.lazy) { + return paginationStore.getTablePagination(props.tableName).first; + } + return currentPageState.value.first; +}); + // Initialize filters and pagination in store when component mounts onMounted(() => { filtersStore.initializeTableFilters(props.tableName, props.columns); + filtersStore.initializeTableSorting(props.tableName); if (props.lazy) { paginationStore.initializeTablePagination(props.tableName, { rows: 10, @@ -348,10 +358,7 @@ const hasFilters = computed(() => { }); const hasActiveFilters = computed(() => { - const currentFilters = filtersStore.getTableFilters(props.tableName); - return Object.values(currentFilters).some( - (filter) => filter.value && filter.value.trim() !== "", - ); + return filtersStore.getActiveFiltersCount(props.tableName) > 0; }); const hasFilterChanges = computed(() => { @@ -406,6 +413,7 @@ const applyFilters = () => { // Reset to first page when filters change (for both lazy and non-lazy) currentPageState.value = { page: 0, first: 0 }; + selectedPageJump.value = ""; // Reset page jump dropdown // For both lazy and non-lazy tables, trigger reload with new filters if (props.lazy) { @@ -423,14 +431,16 @@ const clearFilters = () => { pendingFilters.value[col.fieldName] = ""; }); - // Clear store filters + // Clear store filters (but keep sorting) filtersStore.clearTableFilters(props.tableName); // Reset to first page when filters are cleared (for both lazy and non-lazy) currentPageState.value = { page: 0, first: 0 }; + selectedPageJump.value = ""; // Reset page jump dropdown // For both lazy and non-lazy tables, trigger reload with cleared filters if (props.lazy) { + paginationStore.clearTableCache(props.tableName); paginationStore.resetToFirstPage(props.tableName); triggerLazyLoad(); } else { @@ -459,11 +469,11 @@ const jumpToPage = () => { const pageNumber = parseInt(selectedPageJump.value) - 1; // Convert to 0-based if (props.lazy) { + // Update pagination store - the currentFirst computed property will automatically sync paginationStore.setPage(props.tableName, pageNumber); triggerLazyLoad(); } else { // For non-lazy tables, update our internal state - // The DataTable will handle the actual pagination currentPageState.value = { page: pageNumber, first: pageNumber * currentRows.value, @@ -514,23 +524,13 @@ const handlePage = (event) => { }; // Handle sorting events -const handleSort = (event) => { - console.log("Sort event:", event); - if (props.lazy) { - paginationStore.setSorting(props.tableName, event.sortField, event.sortOrder); - // Reset to first page when sorting changes - paginationStore.resetToFirstPage(props.tableName); - triggerLazyLoad(); - } - emit("sort-change", event); -}; - // Handle filter events const handleFilter = (event) => { console.log("Filter event:", event); // Reset to first page when filters change (for both lazy and non-lazy) currentPageState.value = { page: 0, first: 0 }; + selectedPageJump.value = ""; // Reset page jump dropdown if (props.lazy) { paginationStore.resetToFirstPage(props.tableName); @@ -540,31 +540,60 @@ const handleFilter = (event) => { }; // Trigger lazy load event -const triggerLazyLoad = () => { - if (props.lazy && props.onLazyLoad) { +const triggerLazyLoad = (event = {}) => { + if (props.lazy) { const paginationParams = paginationStore.getPaginationParams(props.tableName); const filters = filtersStore.getTableFilters(props.tableName); + // Use sort information from the event if provided (from DataTable sort event) + // Otherwise fall back to stored sorting state + const storedSorting = filtersStore.getTableSorting(props.tableName); + const lazyEvent = { - first: paginationParams.offset, - rows: paginationParams.limit, - page: paginationParams.page, - sortField: paginationParams.sortField, - sortOrder: paginationParams.sortOrder, - filters: filters, + first: event.first !== undefined ? event.first : paginationParams.offset, + rows: event.rows !== undefined ? event.rows : paginationParams.limit, + page: event.page !== undefined ? event.page : paginationParams.page, + sortField: + event.sortField !== undefined + ? event.sortField + : storedSorting.field || paginationParams.sortField, + sortOrder: + event.sortOrder !== undefined + ? event.sortOrder + : storedSorting.order || paginationParams.sortOrder, + filters: { ...filters, ...(event.filters || {}) }, }; + // If this is a sort event, update the stored sorting state + if (event.sortField !== undefined || event.sortOrder !== undefined) { + filtersStore.updateTableSorting( + props.tableName, + lazyEvent.sortField, + lazyEvent.sortOrder, + ); + + // Reset to first page when sorting changes + currentPageState.value = { page: 0, first: 0 }; + selectedPageJump.value = ""; // Reset page jump dropdown + paginationStore.clearTableCache(props.tableName); + paginationStore.resetToFirstPage(props.tableName); + lazyEvent.page = 0; + lazyEvent.first = 0; + } + console.log("Triggering lazy load with:", lazyEvent); emit("lazy-load", lazyEvent); - props.onLazyLoad(lazyEvent); } }; // Trigger data refresh for non-lazy tables when filters change const triggerDataRefresh = () => { const filters = filtersStore.getTableFilters(props.tableName); + const sorting = filtersStore.getTableSorting(props.tableName); const refreshEvent = { filters: filters, + sortField: sorting.field, + sortOrder: sorting.order, page: currentPageState.value.page, first: currentPageState.value.first, rows: currentRows.value, @@ -583,10 +612,15 @@ const handleFilterInput = (fieldName, value, filterCallback) => { filterCallback(); } - // For non-lazy tables, also trigger a data refresh when individual filters change - if (!props.lazy) { - // Reset to first page when filters change - currentPageState.value = { page: 0, first: 0 }; + // Reset to first page when filters change (for both lazy and non-lazy) + currentPageState.value = { page: 0, first: 0 }; + + // For lazy tables, reset pagination and trigger lazy load + if (props.lazy) { + paginationStore.resetToFirstPage(props.tableName); + triggerLazyLoad(); + } else { + // For non-lazy tables, also trigger a data refresh when individual filters change triggerDataRefresh(); } }; diff --git a/frontend/src/components/pages/Clients.vue b/frontend/src/components/pages/Clients.vue index b10bd6f..f300c35 100644 --- a/frontend/src/components/pages/Clients.vue +++ b/frontend/src/components/pages/Clients.vue @@ -14,7 +14,6 @@ :lazy="true" :totalRecords="totalRecords" :loading="isLoading" - :onLazyLoad="handleLazyLoad" @lazy-load="handleLazyLoad" /> @@ -44,13 +43,13 @@ const onClick = () => { }; const filters = { - addressName: { value: null, matchMode: FilterMatchMode.CONTAINS }, + addressTitle: { value: null, matchMode: FilterMatchMode.CONTAINS }, }; const columns = [ { label: "Name", - fieldName: "addressName", + fieldName: "addressTitle", type: "text", sortable: true, filterable: true, @@ -68,7 +67,7 @@ const columns = [ type: "status", sortable: true, }, - { label: "Job Status", fieldName: "jobScheduledStatus", type: "status", sortable: true }, + { label: "Job Status", fieldName: "jobStatus", type: "status", sortable: true }, ]; // Handle lazy loading events from DataTable const handleLazyLoad = async (event) => { @@ -77,6 +76,10 @@ const handleLazyLoad = async (event) => { try { isLoading.value = true; + // Get sorting information from filters store first (needed for cache key) + const sorting = filtersStore.getTableSorting("clients"); + console.log("Current sorting state:", sorting); + // Get pagination parameters const paginationParams = { page: event.page || 0, @@ -95,9 +98,10 @@ const handleLazyLoad = async (event) => { }); } - // Clear cache when filters are active to ensure fresh data + // Clear cache when filters or sorting are active to ensure fresh data const hasActiveFilters = Object.keys(filters).length > 0; - if (hasActiveFilters) { + const hasActiveSorting = paginationParams.sortField && paginationParams.sortOrder; + if (hasActiveFilters || hasActiveSorting) { paginationStore.clearTableCache("clients"); } @@ -106,8 +110,8 @@ const handleLazyLoad = async (event) => { "clients", paginationParams.page, paginationParams.pageSize, - paginationParams.sortField, - paginationParams.sortOrder, + sorting.field || paginationParams.sortField, + sorting.order || paginationParams.sortOrder, filters, ); @@ -127,8 +131,8 @@ const handleLazyLoad = async (event) => { console.log("Making API call with:", { paginationParams, filters }); - // Call API with pagination and filters - const result = await Api.getPaginatedClientDetails(paginationParams, filters); + // Call API with pagination, filters, and sorting + const result = await Api.getPaginatedClientDetails(paginationParams, filters, sorting); // Update local state - extract from pagination structure tableData.value = result.data; @@ -149,8 +153,8 @@ const handleLazyLoad = async (event) => { "clients", paginationParams.page, paginationParams.pageSize, - paginationParams.sortField, - paginationParams.sortOrder, + sorting.field || paginationParams.sortField, + sorting.order || paginationParams.sortOrder, filters, { records: result.data, @@ -178,17 +182,19 @@ onMounted(async () => { // Initialize pagination and filters paginationStore.initializeTablePagination("clients", { rows: 10 }); filtersStore.initializeTableFilters("clients", columns); + filtersStore.initializeTableSorting("clients"); // Load first page const initialPagination = paginationStore.getTablePagination("clients"); const initialFilters = filtersStore.getTableFilters("clients"); + const initialSorting = filtersStore.getTableSorting("clients"); await handleLazyLoad({ page: initialPagination.page, rows: initialPagination.rows, first: initialPagination.first, - sortField: initialPagination.sortField, - sortOrder: initialPagination.sortOrder, + sortField: initialSorting.field || initialPagination.sortField, + sortOrder: initialSorting.order || initialPagination.sortOrder, filters: initialFilters, }); }); diff --git a/frontend/src/stores/filters.js b/frontend/src/stores/filters.js index 275f09f..a4b9cf6 100644 --- a/frontend/src/stores/filters.js +++ b/frontend/src/stores/filters.js @@ -4,29 +4,9 @@ import { FilterMatchMode } from "@primevue/core"; export const useFiltersStore = defineStore("filters", { state: () => ({ // Store filters by table/component name - tableFilters: { - clients: { - addressName: { value: null, matchMode: FilterMatchMode.CONTAINS }, - }, - jobs: { - customer: { value: null, matchMode: FilterMatchMode.CONTAINS }, - jobId: { value: null, matchMode: FilterMatchMode.CONTAINS }, - }, - timesheets: { - employee: { value: null, matchMode: FilterMatchMode.CONTAINS }, - customer: { value: null, matchMode: FilterMatchMode.CONTAINS }, - }, - warranties: { - customer: { value: null, matchMode: FilterMatchMode.CONTAINS }, - warrantyId: { value: null, matchMode: FilterMatchMode.CONTAINS }, - address: { value: null, matchMode: FilterMatchMode.CONTAINS }, - assignedTechnician: { value: null, matchMode: FilterMatchMode.CONTAINS }, - }, - routes: { - technician: { value: null, matchMode: FilterMatchMode.CONTAINS }, - routeId: { value: null, matchMode: FilterMatchMode.CONTAINS }, - }, - }, + tableFilters: {}, + // Store sorting by table/component name + tableSorting: {}, }), actions: { // Generic method to get filters for a specific table @@ -34,6 +14,11 @@ export const useFiltersStore = defineStore("filters", { return this.tableFilters[tableName] || {}; }, + // Generic method to get sorting for a specific table + getTableSorting(tableName) { + return this.tableSorting[tableName] || { field: null, order: null }; + }, + // Generic method to update a specific filter updateTableFilter(tableName, fieldName, value, matchMode = null) { if (!this.tableFilters[tableName]) { @@ -52,6 +37,15 @@ export const useFiltersStore = defineStore("filters", { } }, + // Generic method to update sorting for a table + updateTableSorting(tableName, field, order) { + if (!this.tableSorting[tableName]) { + this.tableSorting[tableName] = { field: null, order: null }; + } + this.tableSorting[tableName].field = field; + this.tableSorting[tableName].order = order; + }, + // Method to clear all filters for a table clearTableFilters(tableName) { if (this.tableFilters[tableName]) { @@ -61,6 +55,19 @@ export const useFiltersStore = defineStore("filters", { } }, + // Method to clear sorting for a table + clearTableSorting(tableName) { + if (this.tableSorting[tableName]) { + this.tableSorting[tableName] = { field: null, order: null }; + } + }, + + // Method to clear both filters and sorting for a table + clearTableState(tableName) { + this.clearTableFilters(tableName); + this.clearTableSorting(tableName); + }, + // Method to initialize filters for a table if they don't exist initializeTableFilters(tableName, columns) { if (!this.tableFilters[tableName]) { @@ -77,14 +84,43 @@ export const useFiltersStore = defineStore("filters", { }); }, + // Method to initialize sorting for a table + initializeTableSorting(tableName) { + if (!this.tableSorting[tableName]) { + this.tableSorting[tableName] = { field: null, order: null }; + } + }, + + // Method to get active filters count + getActiveFiltersCount(tableName) { + const filters = this.getTableFilters(tableName); + return Object.values(filters).filter( + (filter) => filter.value && filter.value.trim() !== "", + ).length; + }, + + // Method to check if sorting is active + isSortingActive(tableName) { + const sorting = this.getTableSorting(tableName); + return sorting.field !== null && sorting.order !== null; + }, + + // Method to get all table state (filters + sorting) + getTableState(tableName) { + return { + filters: this.getTableFilters(tableName), + sorting: this.getTableSorting(tableName), + }; + }, + // Legacy method for backward compatibility setClientNameFilter(filterValue) { - this.updateTableFilter("clients", "addressName", filterValue); + this.updateTableFilter("clients", "addressTitle", filterValue); }, // Getter for legacy compatibility get clientNameFilter() { - return this.tableFilters?.clients?.addressName?.value || ""; + return this.tableFilters?.clients?.addressTitle?.value || ""; }, }, });