diff --git a/custom_ui/api/db/estimates.py b/custom_ui/api/db/estimates.py index 3496a06..208057e 100644 --- a/custom_ui/api/db/estimates.py +++ b/custom_ui/api/db/estimates.py @@ -1,4 +1,5 @@ import frappe, json +from frappe.utils.pdf import get_pdf from custom_ui.db_utils import process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, build_error_response # =============================================================================== @@ -88,6 +89,69 @@ def get_estimate_from_address(full_address): # return build_success_response(quotation) # else: # return build_error_response("No quotation found for the given address.", 404) + +# @frappe.whitelist() +# def send_estimate_email(estimate_name): +# print("DEBUG: Queuing email send job for estimate:", estimate_name) +# frappe.enqueue( +# "custom_ui.api.db.estimates.send_estimate_email_job", +# estimate_name=estimate_name, +# queue="long", # or "default" +# timeout=600, +# ) +# return build_success_response("Email queued for sending.") + +@frappe.whitelist() +def send_estimate_email(estimate_name): +# def send_estimate_email_job(estimate_name): + print("DEBUG: Sending estimate email for:", estimate_name) + quotation = frappe.get_doc("Quotation", estimate_name) + + party_exists = frappe.db.exists(quotation.quotation_to, quotation.party_name) + if not party_exists: + return build_error_response("No email found for the customer.", 400) + party = frappe.get_doc(quotation.quotation_to, quotation.party_name) + + email = None + if (getattr(party, 'email_id', None)): + email = party.email_id + elif (getattr(party, 'contact_ids', None) and len(party.email_ids) > 0): + primary = next((e for e in party.email_ids if e.is_primary), None) + email = primary.email_id if primary else party.email_ids[0].email_id + + if not email and quotation.custom_installation_address: + address = frappe.get_doc("Address", quotation.custom_installation_address) + email = getattr(address, 'email_id', None) + if not email: + return build_error_response("No email found for the customer or address.", 400) + + # email = "casey@shilohcode.com" + template_name = "Quote with Actions - SNW" + template = frappe.get_doc("Email Template", template_name) + message = frappe.render_template(template.response, {"doc": quotation}) + subject = frappe.render_template(template.subject, {"doc": quotation}) + html = frappe.get_print("Quotation", quotation.name, print_format="Quotation - SNW - Standard", letterhead=True) + print("DEBUG: Generated HTML for PDF.") + pdf = get_pdf(html) + print("DEBUG: Generated PDF for email attachment.") + frappe.sendmail( + recipients=email, + subject=subject, + content=message, + doctype="Quotation", + name=quotation.name, + read_receipt=1, + print_letterhead=1, + attachments=[{"fname": f"{quotation.name}.pdf", "fcontent": pdf}] + ) + print(f"DEBUG: Email sent to {email} successfully.") + quotation.custom_current_status = "Submitted" + quotation.custom_sent = 1 + quotation.save() + quotation.submit() + updated_quotation = frappe.get_doc("Quotation", estimate_name) + print("DEBUG: Quotation submitted successfully.") + return build_success_response(updated_quotation.as_dict()) @frappe.whitelist() def upsert_estimate(data): @@ -129,6 +193,7 @@ def upsert_estimate(data): "custom_installation_address": data.get("address_name"), "contact_email": data.get("contact_email"), "party_name": data.get("contact_name"), + "company": "Sprinklers Northwest", "customer_name": data.get("customer_name"), }) for item in data.get("items", []): @@ -142,4 +207,15 @@ def upsert_estimate(data): return build_success_response(new_estimate.as_dict()) except Exception as e: print(f"DEBUG: Error in upsert_estimate: {str(e)}") + return build_error_response(str(e), 500) + +@frappe.whitelist() +def lock_estimate(estimate_name): + """Lock an estimate to prevent further edits.""" + try: + estimate = frappe.get_doc("Quotation", estimate_name) + estimate.submit() + final_estimate = frappe.get_doc("Quotation", estimate_name) + return build_success_response(final_estimate.as_dict()) + except Exception as e: return build_error_response(str(e), 500) \ No newline at end of file diff --git a/custom_ui/events/estimate.py b/custom_ui/events/estimate.py index 185c3d5..1991c05 100644 --- a/custom_ui/events/estimate.py +++ b/custom_ui/events/estimate.py @@ -9,7 +9,6 @@ def after_insert(doc, method): address_doc = frappe.get_doc("Address", doc.custom_installation_address) address_doc.custom_estimate_sent_status = "In Progress" address_doc.save() - print("DEBUG: Address status updated successfully") except Exception as e: print("ERROR in after_insert hook:", str(e)) frappe.log_error(f"Error in estimate after_insert: {str(e)}", "Estimate Hook Error") \ No newline at end of file diff --git a/custom_ui/events/onsite_meeting.py b/custom_ui/events/onsite_meeting.py index 296a3e6..0bb316d 100644 --- a/custom_ui/events/onsite_meeting.py +++ b/custom_ui/events/onsite_meeting.py @@ -10,4 +10,11 @@ def after_insert(doc, method): if doc.status == "Completed": address_doc = frappe.get_doc("Address", doc.address) address_doc.custom_onsite_meeting_scheduled = "Completed" - address_doc.save() \ No newline at end of file + address_doc.save() + +def on_update(doc, method): + print("DEBUG: On Update Triggered for On-Site Meeting") + if doc.status == "Completed": + print("DEBUG: Meeting marked as Completed, updating Address status") + address_doc = frappe.get_doc("Address", doc.address) + address_doc.custom_onsite_meeting_scheduled = "Completed" \ No newline at end of file diff --git a/custom_ui/events/quotation.py b/custom_ui/events/quotation.py index cf4ec86..da1cf48 100644 --- a/custom_ui/events/quotation.py +++ b/custom_ui/events/quotation.py @@ -10,9 +10,9 @@ def after_insert(doc, method): def after_save(doc, method): - address_title = doc.custom_installation_address - address_name = frappe.db.get_value("Address", fieldname="name", filters={"address_title": address_title}) - if doc.custome_sent and address_name: - address_doc = frappe.get_doc("Address", address_name) - address_doc.custom_quotation_sent = "Completed" - address_doc.save() \ No newline at end of file + if not doc.custom_sent: + return + print("DEBUG: Quotation has been sent, updating Address status") + address_doc = frappe.get_doc("Address", doc.custom_installation_address) + address_doc.custom_quotation_sent = "Completed" + address_doc.save() \ No newline at end of file diff --git a/custom_ui/install.py b/custom_ui/install.py index 1c7e98e..57dba1d 100644 --- a/custom_ui/install.py +++ b/custom_ui/install.py @@ -6,7 +6,6 @@ from .utils import create_module def after_install(): create_module() add_custom_fields() - update_onsite_meeting_fields() frappe.db.commit() # Proper way to refresh metadata @@ -14,6 +13,7 @@ def after_install(): frappe.reload_doctype("Address") frappe.clear_cache(doctype="On-Site Meeting") frappe.reload_doctype("On-Site Meeting") + update_onsite_meeting_fields() update_address_fields() build_frontend() diff --git a/frontend/src/api.js b/frontend/src/api.js index 5f8548d..7a4072b 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -8,6 +8,8 @@ const FRAPPE_PROXY_METHOD = "custom_ui.api.proxy.request"; const FRAPPE_UPSERT_ESTIMATE_METHOD = "custom_ui.api.db.estimates.upsert_estimate"; const FRAPPE_GET_ESTIMATES_METHOD = "custom_ui.api.db.estimates.get_estimate_table_data"; const FRAPPE_GET_ESTIMATE_BY_ADDRESS_METHOD = "custom_ui.api.db.estimates.get_estimate_from_address"; +const FRAPPE_SEND_ESTIMATE_EMAIL_METHOD = "custom_ui.api.db.estimates.send_estimate_email"; +const FRAPPE_LOCK_ESTIMATE_METHOD = "custom_ui.api.db.estimates.lock_estimate"; // Job methods const FRAPPE_GET_JOBS_METHOD = "custom_ui.api.db.get_jobs"; const FRAPPE_UPSERT_JOB_METHOD = "custom_ui.api.db.jobs.upsert_job"; @@ -30,6 +32,10 @@ const FRAPPE_GET_CLIENT_METHOD = "custom_ui.api.db.clients.get_client"; const FRAPPE_GET_CLIENT_NAMES_METHOD = "custom_ui.api.db.clients.get_client_names"; class Api { + // ============================================================================ + // CORE REQUEST METHOD + // ============================================================================ + static async request(frappeMethod, args = {}) { const errorStore = useErrorStore(); args = ApiUtils.toSnakeCaseObject(args); @@ -51,52 +57,61 @@ class Api { } } - static async getAddressByFullAddress(fullAddress) { - return await this.request("custom_ui.api.db.addresses.get_address_by_full_address", { - full_address: fullAddress, + // ============================================================================ + // CLIENT METHODS + // ============================================================================ + + static async getClientStatusCounts(params = {}) { + return await this.request(FRAPPE_GET_CLIENT_STATUS_COUNTS_METHOD, params); + } + + static async getClientDetails(clientName) { + return await this.request(FRAPPE_GET_CLIENT_METHOD, { clientName }); + } + + static async getClient(clientName) { + return await this.request(FRAPPE_GET_CLIENT_METHOD, { clientName }); + } + + /** + * 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 = {}, sortings = []) { + const { page = 0, pageSize = 10 } = paginationParams; + const result = await this.request(FRAPPE_GET_CLIENT_TABLE_DATA_METHOD, { + filters, + sortings, + page: page + 1, + pageSize, }); + return result; } - static async getQuotationItems() { - return await this.request("custom_ui.api.db.estimates.get_quotation_items"); + static async getCustomerNames(type) { + return await this.request(FRAPPE_GET_CLIENT_NAMES_METHOD, { type }); } - static async getEstimateFromAddress(fullAddress) { - return await this.request(FRAPPE_GET_ESTIMATE_BY_ADDRESS_METHOD, { - full_address: fullAddress, - }); + static async getClientNames(clientName) { + return await this.request(FRAPPE_GET_CLIENT_NAMES_METHOD, { searchTerm: clientName }); } - static async getAddress(fullAddress) { - return await this.request("custom_ui.api.db.addresses.get_address", { fullAddress }); + static async searchClientNames(searchTerm) { + return await this.request("custom_ui.api.db.clients.search_client_names", { searchTerm }); } - static async getContactsForAddress(fullAddress) { - return await this.request("custom_ui.api.db.addresses.get_contacts_for_address", { - fullAddress, - }); + static async createClient(clientData) { + const result = await this.request(FRAPPE_UPSERT_CLIENT_METHOD, { data: clientData }); + console.log("DEBUG: API - Created/Updated Client: ", result); + return result; } - static async getEstimate(estimateName) { - return await this.request("custom_ui.api.db.estimates.get_estimate", { - estimate_name: estimateName, - }); - } - - static async getEstimateItems() { - return await this.request("custom_ui.api.db.estimates.get_estimate_items"); - } - - static async searchAddresses(searchTerm) { - const filters = { - full_address: ["like", `%${searchTerm}%`], - }; - return await this.getAddresses(["full_address"], filters); - } - - static async getAddresses(fields = ["*"], filters = {}) { - return await this.request(FRAPPE_GET_ADDRESSES_METHOD, { fields, filters }); - } + // ============================================================================ + // ON-SITE MEETING METHODS + // ============================================================================ static async getUnscheduledOnSiteMeetings() { return await this.request( @@ -126,95 +141,28 @@ class Api { }); } - static async getClientStatusCounts(params = {}) { - return await this.request(FRAPPE_GET_CLIENT_STATUS_COUNTS_METHOD, params); + // ============================================================================ + // ESTIMATE / QUOTATION METHODS + // ============================================================================ + + static async getQuotationItems() { + return await this.request("custom_ui.api.db.estimates.get_quotation_items"); } - static async getClientDetails(clientName) { - return await this.request(FRAPPE_GET_CLIENT_METHOD, { clientName }); - } - - static async getJobDetails() { - const projects = await this.getDocsList("Project"); - const data = []; - for (let prj of projects) { - let project = await this.getDetailedDoc("Project", prj.name); - const tableRow = { - name: project.name, - customInstallationAddress: project.custom_installation_address, - customer: project.customer, - status: project.status, - percentComplete: project.percent_complete, - }; - data.push(tableRow); - } - return data; - } - - static async getServiceData() { - const data = DataUtils.dummyServiceData; - return data; - } - - static async getRouteData() { - const data = DataUtils.dummyRouteData; - //const data = []; - const routes = getDocList("Pre-Built Routes"); - for (const rt of routes) { - route = getDetailedDoc("Pre-Built Routes", rt.name); - let tableRow = {}; - } - return data; - } - - static async getWarrantyData() { - const data = await this.request(FRAPPE_GET_WARRANTY_CLAIMS_METHOD); - console.log("DEBUG: API - getWarrantyData result: ", data); - return data; - } - - static async getTimesheetData() { - //const data = DataUtils.dummyTimesheetData; - const data = []; - const timesheets = await this.getDocsList("Timesheet"); - for (const ts of timesheets) { - const timesheet = await this.getDetailedDoc("Timesheet", ts.name); - const tableRow = { - timesheetId: timesheet.name, - employee: timesheet.employee_name, - date: timesheet.date, - customer: timesheet.customer, - totalHours: timesheet.total_hours, - status: timesheet.status, - totalPay: timesheet.total_costing_amount, - }; - console.log("Timesheet Row: ", tableRow); - data.push(tableRow); - } - console.log("DEBUG: API - getTimesheetData result: ", data); - return data; - } - - static async getClient(clientName) { - return await this.request(FRAPPE_GET_CLIENT_METHOD, { clientName }); - } - - /** - * 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 = {}, sortings = []) { - const { page = 0, pageSize = 10 } = paginationParams; - const result = await this.request(FRAPPE_GET_CLIENT_TABLE_DATA_METHOD, { - filters, - sortings, - page: page + 1, - pageSize, + static async getEstimateFromAddress(fullAddress) { + return await this.request(FRAPPE_GET_ESTIMATE_BY_ADDRESS_METHOD, { + full_address: fullAddress, }); - return result; + } + + static async getEstimate(estimateName) { + return await this.request("custom_ui.api.db.estimates.get_estimate", { + estimate_name: estimateName, + }); + } + + static async getEstimateItems() { + return await this.request("custom_ui.api.db.estimates.get_estimate_items"); } static async getPaginatedEstimateDetails(paginationParams = {}, filters = {}, sorting = null) { @@ -241,29 +189,38 @@ class Api { return result; } - static async getPaginatedInvoiceDetails(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 invoice options to backend:", options); - - const result = await this.request(FRAPPE_GET_INVOICES_METHOD, { options }); + static async createEstimate(estimateData) { + const result = await this.request(FRAPPE_UPSERT_ESTIMATE_METHOD, { data: estimateData }); return result; + } + static async sendEstimateEmail(estimateName) { + return await this.request(FRAPPE_SEND_ESTIMATE_EMAIL_METHOD, { estimateName }); + } + + static async lockEstimate(estimateName) { + return await this.request(FRAPPE_LOCK_ESTIMATE_METHOD, { estimateName }); + } + + // ============================================================================ + // JOB / PROJECT METHODS + // ============================================================================ + + static async getJobDetails() { + const projects = await this.getDocsList("Project"); + const data = []; + for (let prj of projects) { + let project = await this.getDetailedDoc("Project", prj.name); + const tableRow = { + name: project.name, + customInstallationAddress: project.custom_installation_address, + customer: project.customer, + status: project.status, + percentComplete: project.percent_complete, + }; + data.push(tableRow); + } + return data; } /** @@ -297,6 +254,59 @@ class Api { return result; } + static async createJob(jobData) { + const payload = DataUtils.toSnakeCaseObject(jobData); + const result = await this.request(FRAPPE_UPSERT_JOB_METHOD, { data: payload }); + console.log("DEBUG: API - Created Job: ", result); + return result; + } + + // ============================================================================ + // INVOICE / PAYMENT METHODS + // ============================================================================ + + static async getPaginatedInvoiceDetails(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 invoice options to backend:", options); + + const result = await this.request(FRAPPE_GET_INVOICES_METHOD, { options }); + return result; + + } + + static async createInvoice(invoiceData) { + const payload = DataUtils.toSnakeCaseObject(invoiceData); + const result = await this.request(FRAPPE_UPSERT_INVOICE_METHOD, { data: payload }); + console.log("DEBUG: API - Created Invoice: ", result); + return result; + } + + // ============================================================================ + // WARRANTY METHODS + // ============================================================================ + + static async getWarrantyData() { + const data = await this.request(FRAPPE_GET_WARRANTY_CLAIMS_METHOD); + console.log("DEBUG: API - getWarrantyData result: ", data); + return data; + } + /** * Get paginated warranty claims data with filtering and sorting * @param {Object} paginationParams - Pagination parameters from store @@ -328,6 +338,90 @@ class Api { return result; } + static async createWarranty(warrantyData) { + const payload = DataUtils.toSnakeCaseObject(warrantyData); + const result = await this.request(FRAPPE_UPSERT_INVOICE_METHOD, { data: payload }); + console.log("DEBUG: API - Created Warranty: ", result); + return result; + } + + // ============================================================================ + // ADDRESS METHODS + // ============================================================================ + + static async getAddressByFullAddress(fullAddress) { + return await this.request("custom_ui.api.db.addresses.get_address_by_full_address", { + full_address: fullAddress, + }); + } + + static async getAddress(fullAddress) { + return await this.request("custom_ui.api.db.addresses.get_address", { fullAddress }); + } + + static async getContactsForAddress(fullAddress) { + return await this.request("custom_ui.api.db.addresses.get_contacts_for_address", { + fullAddress, + }); + } + + static async searchAddresses(searchTerm) { + const filters = { + full_address: ["like", `%${searchTerm}%`], + }; + return await this.getAddresses(["full_address"], filters); + } + + static async getAddresses(fields = ["*"], filters = {}) { + return await this.request(FRAPPE_GET_ADDRESSES_METHOD, { fields, filters }); + } + + // ============================================================================ + // SERVICE / ROUTE / TIMESHEET METHODS + // ============================================================================ + + static async getServiceData() { + const data = DataUtils.dummyServiceData; + return data; + } + + static async getRouteData() { + const data = DataUtils.dummyRouteData; + //const data = []; + const routes = getDocList("Pre-Built Routes"); + for (const rt of routes) { + route = getDetailedDoc("Pre-Built Routes", rt.name); + let tableRow = {}; + } + return data; + } + + static async getTimesheetData() { + //const data = DataUtils.dummyTimesheetData; + const data = []; + const timesheets = await this.getDocsList("Timesheet"); + for (const ts of timesheets) { + const timesheet = await this.getDetailedDoc("Timesheet", ts.name); + const tableRow = { + timesheetId: timesheet.name, + employee: timesheet.employee_name, + date: timesheet.date, + customer: timesheet.customer, + totalHours: timesheet.total_hours, + status: timesheet.status, + totalPay: timesheet.total_costing_amount, + }; + console.log("Timesheet Row: ", tableRow); + data.push(tableRow); + } + console.log("DEBUG: API - getTimesheetData result: ", data); + return data; + } + + // ============================================================================ + // GENERIC DOCTYPE METHODS + // ============================================================================ + /** * Fetch a list of documents from a specific doctype. * @@ -386,18 +480,6 @@ class Api { return doc; } - static async getCustomerNames(type) { - return await this.request(FRAPPE_GET_CLIENT_NAMES_METHOD, { type }); - } - - static async getClientNames(clientName) { - return await this.request(FRAPPE_GET_CLIENT_NAMES_METHOD, { searchTerm: clientName }); - } - - static async searchClientNames(searchTerm) { - return await this.request("custom_ui.api.db.clients.search_client_names", { searchTerm }); - } - static async getCompanyNames() { const companies = await this.getDocsList("Company", ["name"]); const companyNames = companies.map((company) => company.name); @@ -405,41 +487,9 @@ class Api { return companyNames; } - // Create methods - - static async createClient(clientData) { - const result = await this.request(FRAPPE_UPSERT_CLIENT_METHOD, { data: clientData }); - console.log("DEBUG: API - Created/Updated Client: ", result); - return result; - } - - static async createEstimate(estimateData) { - const result = await this.request(FRAPPE_UPSERT_ESTIMATE_METHOD, { data: estimateData }); - return result; - } - - static async createJob(jobData) { - const payload = DataUtils.toSnakeCaseObject(jobData); - const result = await this.request(FRAPPE_UPSERT_JOB_METHOD, { data: payload }); - console.log("DEBUG: API - Created Job: ", result); - return result; - } - - static async createInvoice(invoiceData) { - const payload = DataUtils.toSnakeCaseObject(invoiceData); - const result = await this.request(FRAPPE_UPSERT_INVOICE_METHOD, { data: payload }); - console.log("DEBUG: API - Created Invoice: ", result); - return result; - } - - static async createWarranty(warrantyData) { - const payload = DataUtils.toSnakeCaseObject(warrantyData); - const result = await this.request(FRAPPE_UPSERT_INVOICE_METHOD, { data: payload }); - console.log("DEBUG: API - Created Warranty: ", result); - return result; - } - - // External API calls + // ============================================================================ + // EXTERNAL API METHODS + // ============================================================================ /** * Fetch a list of places (city/state) by zipcode using Zippopotamus API. diff --git a/frontend/src/components/pages/Clients.vue b/frontend/src/components/pages/Clients.vue index 3e12294..35bb7e0 100644 --- a/frontend/src/components/pages/Clients.vue +++ b/frontend/src/components/pages/Clients.vue @@ -35,7 +35,9 @@ import { usePaginationStore } from "../../stores/pagination"; import { useFiltersStore } from "../../stores/filters"; import { useModalStore } from "../../stores/modal"; import { useRouter } from "vue-router"; +import { useNotificationStore } from "../../stores/notifications-primevue"; +const notifications = useNotificationStore(); const loadingStore = useLoadingStore(); const paginationStore = usePaginationStore(); const filtersStore = useFiltersStore(); @@ -128,7 +130,7 @@ const columns = [ sortable: true, buttonVariant: "outlined", onStatusClick: (status, rowData) => handleAppointmentClick(status, rowData), - disableCondition: (status) => status?.toLowerCase() !== "not started", + // disableCondition: (status) => status?.toLowerCase() !== "not started", }, { label: "Estimate Sent", @@ -137,7 +139,7 @@ const columns = [ sortable: true, buttonVariant: "outlined", onStatusClick: (status, rowData) => handleEstimateClick(status, rowData), - disableCondition: (status) => status?.toLowerCase() !== "not started", + // disableCondition: (status) => status?.toLowerCase() !== "not started", }, { label: "Payment Received", @@ -146,7 +148,7 @@ const columns = [ sortable: true, buttonVariant: "outlined", onStatusClick: (status, rowData) => handlePaymentClick(status, rowData), - disableCondition: (status) => status?.toLowerCase() !== "not started", + // disableCondition: (status) => status?.toLowerCase() !== "not started", }, { label: "Job Status", @@ -155,7 +157,7 @@ const columns = [ sortable: true, buttonVariant: "outlined", onStatusClick: (status, rowData) => handleJobClick(status, rowData), - disableCondition: (status) => status?.toLowerCase() !== "not started", + // disableCondition: (status) => status?.toLowerCase() !== "not started", }, ]; @@ -203,36 +205,6 @@ const tableActions = [ // variant: "filled", // }, // }, - // { - // label: "Edit", - // action: (rowData) => { - // console.log("Editing client:", rowData); - // // Implementation would open edit modal - // }, - // type: "button", - // style: "secondary", - // icon: "pi pi-pencil", - // rowAction: true, // Row action - appears in each row's actions column - // layout: { - // priority: "primary", - // variant: "outlined", - // }, - // }, - // { - // label: "Quick View", - // action: (rowData) => { - // console.log("Quick view for:", rowData.addressTitle); - // // Implementation would show quick preview - // }, - // type: "button", - // style: "info", - // icon: "pi pi-search", - // rowAction: true, // Row action - appears in each row's actions column - // layout: { - // priority: "secondary", - // variant: "compact", - // }, - // }, ]; // Handle lazy loading events from DataTable const handleLazyLoad = async (event) => { @@ -306,35 +278,50 @@ const handleLazyLoad = async (event) => { }; // Status button click handlers const handleAppointmentClick = (status, rowData) => { + const address = encodeURIComponent(rowData.address); if (status?.toLowerCase() === "not started") { // Navigate to schedule on-site meeting - const address = encodeURIComponent(rowData.address); router.push(`/schedule-onsite?new=true&address=${address}`); + } else { + // Navigate to view appointment details + router.push('/schedule-onsite?address=' + address); } }; const handleEstimateClick = (status, rowData) => { + const address = encodeURIComponent(rowData.address); if (status?.toLowerCase() === "not started") { // Navigate to create quotation/estimate - const address = encodeURIComponent(rowData.address); router.push(`/estimate?new=true&address=${address}`); + } else { + // Navigate to view estimate details + router.push('/estimate?address=' + address); } }; const handlePaymentClick = (status, rowData) => { - if (status?.toLowerCase() === "not started") { - // Navigate to payment processing - const address = encodeURIComponent(rowData.address); - router.push(`/payments?new=true&address=${address}`); - } + notifications.addWarning("Payment view/create coming soon!"); + // const address = encodeURIComponent(rowData.address); + // if (status?.toLowerCase() === "not started") { + // // Navigate to payment processing + // router.push(`/payments?new=true&address=${address}`); + // } + // else { + // // Navigate to view payment details + // router.push('/payments?address=' + address); + // } }; const handleJobClick = (status, rowData) => { - if (status?.toLowerCase() === "not started") { - // Navigate to job creation - const address = encodeURIComponent(rowData.address); - router.push(`/jobs?new=true&address=${address}`); - } + notifications.addWarning("Job view/create coming soon!"); + // const address = encodeURIComponent(rowData.address); + // if (status?.toLowerCase() === "not started") { + // // Navigate to job creation + // router.push(`/job?new=true&address=${address}`); + // } else { + // // Navigate to view job details + // router.push('/job?address=' + address); + // } }; // Watch for filters change to update status counts diff --git a/frontend/src/components/pages/Estimate.vue b/frontend/src/components/pages/Estimate.vue index 779eeff..1a014a7 100644 --- a/frontend/src/components/pages/Estimate.vue +++ b/frontend/src/components/pages/Estimate.vue @@ -93,7 +93,7 @@ />
-
@@ -196,7 +196,7 @@ @click="showConfirmationModal = false" severity="secondary" /> -