updates for company effects
This commit is contained in:
parent
98ec082394
commit
7710a7c8fe
@ -4,7 +4,7 @@ from custom_ui.db_utils import build_error_response, build_success_response, pro
|
||||
from custom_ui.services import DbService, ClientService, AddressService, ContactService
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_week_bid_meetings(week_start, week_end):
|
||||
def get_week_bid_meetings(week_start, week_end, company):
|
||||
"""Get On-Site Meetings scheduled within a specific week."""
|
||||
try:
|
||||
meetings = frappe.db.get_all(
|
||||
@ -12,7 +12,8 @@ def get_week_bid_meetings(week_start, week_end):
|
||||
fields=["*"],
|
||||
filters=[
|
||||
["start_time", ">=", week_start],
|
||||
["start_time", "<=", week_end]
|
||||
["start_time", "<=", week_end],
|
||||
["company", "=", company]
|
||||
],
|
||||
order_by="start_time asc"
|
||||
)
|
||||
@ -27,7 +28,7 @@ def get_week_bid_meetings(week_start, week_end):
|
||||
return build_error_response(str(e), 500)
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_bid_meetings(fields=["*"], filters={}):
|
||||
def get_bid_meetings(fields=["*"], filters={}, company=None):
|
||||
"""Get paginated On-Site Meetings with filtering and sorting support."""
|
||||
try:
|
||||
print("DEBUG: Raw bid meeting options received:", filters)
|
||||
@ -53,13 +54,13 @@ def get_bid_meetings(fields=["*"], filters={}):
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_unscheduled_bid_meetings():
|
||||
def get_unscheduled_bid_meetings(company):
|
||||
"""Get On-Site Meetings that are unscheduled."""
|
||||
try:
|
||||
meetings = frappe.db.get_all(
|
||||
"On-Site Meeting",
|
||||
fields=["*"],
|
||||
filters={"status": "Unscheduled"},
|
||||
filters={"status": "Unscheduled", "company": company},
|
||||
order_by="creation desc"
|
||||
)
|
||||
for meeting in meetings:
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import frappe, json
|
||||
from custom_ui.db_utils import build_error_response, process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, map_lead_client, build_address_title
|
||||
from custom_ui.db_utils import build_error_response, process_query_conditions, build_datatable_dict, get_count_or_filters, build_success_response, map_lead_client, build_address_title, normalize_name
|
||||
from erpnext.crm.doctype.lead.lead import make_customer
|
||||
from custom_ui.api.db.addresses import address_exists
|
||||
from custom_ui.api.db.contacts import check_and_get_contact, create_contact, create_contact_links
|
||||
@ -167,6 +167,81 @@ def get_client_v2(client_name):
|
||||
return build_error_response(str(ve), 400)
|
||||
except Exception as e:
|
||||
return build_error_response(str(e), 500)
|
||||
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_clients_table_data_v2(filters={}, sortings=[], page=1, page_size=10):
|
||||
"""Get paginated client table data with filtering and sorting support."""
|
||||
try:
|
||||
filters = json.loads(filters) if isinstance(filters, str) else filters
|
||||
sortings = json.loads(sortings) if isinstance(sortings, str) else sortings
|
||||
page = int(page)
|
||||
page_size = int(page_size)
|
||||
print("DEBUG: Raw client table query received:", {
|
||||
"filters": filters,
|
||||
"sortings": sortings,
|
||||
"page": page,
|
||||
"page_size": page_size
|
||||
})
|
||||
where_clauses = []
|
||||
values = []
|
||||
if filters.get("company"):
|
||||
where_clauses.append("c.company = %s")
|
||||
values.append(filters["company"]["value"])
|
||||
|
||||
if filters.get("address"):
|
||||
where_clauses.append("a.full_address LIKE %s")
|
||||
values.append(f"%{filters['address']['value']}%")
|
||||
|
||||
if filters.get("customer_name"):
|
||||
where_clauses.append("a.customer_name LIKE %s")
|
||||
values.append(f"%{filters['customer_name']['value']}%")
|
||||
|
||||
where_sql = ""
|
||||
if where_clauses:
|
||||
where_sql = "WHERE " + " AND ".join(where_clauses)
|
||||
|
||||
offset = (page - 1) * page_size
|
||||
|
||||
address_names = frappe.db.sql(f"""
|
||||
SELECT DISTINCT a.name
|
||||
FROM `tabAddress` a
|
||||
LEFT JOIN `tabAddress Company Link` c ON c.parent = a.name
|
||||
{where_sql}
|
||||
ORDER BY a.modified DESC
|
||||
LIMIT %s OFFSET %s
|
||||
""", values + [page_size, offset], as_dict=True)
|
||||
print("DEBUG: Address names retrieved:", address_names)
|
||||
|
||||
count = frappe.db.sql(f"""
|
||||
SELECT COUNT(DISTINCT a.name) as count
|
||||
FROM `tabAddress` a
|
||||
LEFT JOIN `tabAddress Company Link` c ON c.parent = a.name
|
||||
{where_sql}
|
||||
""", values, as_dict=True)[0]["count"]
|
||||
tableRows = []
|
||||
for address_name in address_names:
|
||||
address = AddressService.get_or_throw(address_name["name"])
|
||||
tableRow = {}
|
||||
tableRow["id"] = address.name
|
||||
tableRow["address"] = address.full_address
|
||||
tableRow["client_type"] = address.customer_type
|
||||
tableRow["customer_name"] = normalize_name(address.customer_name, "-#-")
|
||||
tableRow["companies"] = ", ".join([link.company for link in address.get("companies", [])])
|
||||
tableRows.append(tableRow)
|
||||
|
||||
table_data = build_datatable_dict(data=tableRows, count=count, page=page, page_size=page_size)
|
||||
|
||||
return build_success_response(table_data)
|
||||
except frappe.ValidationError as ve:
|
||||
return build_error_response(str(ve), 400)
|
||||
except Exception as e:
|
||||
print("ERROR in get_clients_table_data_v2:", str(e))
|
||||
return build_error_response(str(e), 500)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@frappe.whitelist()
|
||||
|
||||
@ -50,6 +50,8 @@ def get_job(job_id=""):
|
||||
project = project.as_dict()
|
||||
project["job_address"] = address_doc
|
||||
project["client"] = ClientService.get_client_or_throw(project.customer)
|
||||
task_names = frappe.get_all("Task", filters={"project": job_id})
|
||||
project["tasks"] = [frappe.get_doc("Task", task_name).as_dict() for task_name in task_names]
|
||||
return build_success_response(project)
|
||||
except Exception as e:
|
||||
return build_error_response(str(e), 500)
|
||||
@ -167,55 +169,32 @@ def upsert_job(data):
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_install_projects(start_date=None, end_date=None):
|
||||
def get_projects_for_calendar(date, company=None, project_templates=[]):
|
||||
"""Get install projects for the calendar."""
|
||||
# Parse project_templates if it's a JSON string
|
||||
if isinstance(project_templates, str):
|
||||
project_templates = json.loads(project_templates)
|
||||
|
||||
# put some emojis in the print to make it stand out
|
||||
print("📅📅📅", date, "company:", company, "project_templates:", project_templates, "type:", type(project_templates))
|
||||
try:
|
||||
filters = {"project_template": "SNW Install"}
|
||||
filters = {"company": company} if company else {}
|
||||
if project_templates and len(project_templates) > 0:
|
||||
filters["project_template"] = ["in", project_templates]
|
||||
unscheduled_filters = filters.copy()
|
||||
unscheduled_filters["is_scheduled"] = 0
|
||||
filters["expected_start_date"] = date
|
||||
# If date range provided, we could filter, but for now let's fetch all open/active ones
|
||||
# or maybe filter by status not Closed/Completed if we want active ones.
|
||||
# The user said "unscheduled" are those with status "Open" (and no date).
|
||||
# extend filters into unscheduled_filters
|
||||
|
||||
projects = frappe.get_all("Project", fields=["*"], filters=filters)
|
||||
|
||||
calendar_events = []
|
||||
for project in projects:
|
||||
# Determine status
|
||||
status = "unscheduled"
|
||||
if project.get("expected_start_date"):
|
||||
status = "scheduled"
|
||||
|
||||
# Map to calendar event format
|
||||
event = {
|
||||
"id": project.name,
|
||||
"serviceType": project.project_name, # Using project name as service type/title
|
||||
"customer": project.customer,
|
||||
"status": status,
|
||||
"scheduledDate": project.expected_start_date,
|
||||
"scheduledTime": "08:00", # Default time if not specified? Project doesn't seem to have time.
|
||||
"duration": 480, # Default 8 hours?
|
||||
"foreman": project.get("custom_install_crew"),
|
||||
"crew": [], # Need to map crew
|
||||
"estimatedCost": project.estimated_costing,
|
||||
"priority": project.priority.lower() if project.priority else "medium",
|
||||
"notes": project.notes,
|
||||
"address": project.custom_installation_address
|
||||
}
|
||||
|
||||
calendar_events.append(event)
|
||||
|
||||
return {"status": "success", "data": calendar_events}
|
||||
project_names = frappe.get_all("Project", pluck="name", filters=filters)
|
||||
print("DEBUG: Found scheduled project names:", project_names)
|
||||
unscheduled_project_names = frappe.get_all("Project", pluck="name", filters=unscheduled_filters)
|
||||
print("DEBUG: Found unscheduled project names:", unscheduled_project_names)
|
||||
projects = [frappe.get_doc("Project", name).as_dict() for name in project_names]
|
||||
unscheduled_projects = [frappe.get_doc("Project", name).as_dict() for name in unscheduled_project_names]
|
||||
return build_success_response({ "projects": projects, "unscheduled_projects": unscheduled_projects })
|
||||
except Exception as e:
|
||||
return {"status": "error", "message": str(e)}
|
||||
|
||||
@frappe.whitelist()
|
||||
def get_project_templates_for_company(company_name):
|
||||
"""Get project templates for a specific company."""
|
||||
try:
|
||||
templates = frappe.get_all(
|
||||
"Project Template",
|
||||
fields=["*"],
|
||||
filters={"company": company_name}
|
||||
)
|
||||
return build_success_response(templates)
|
||||
except Exception as e:
|
||||
return build_error_response(str(e), 500),
|
||||
return build_error_response(str(e), 500)
|
||||
@ -229,3 +229,7 @@ def build_history_entries(comments, versions):
|
||||
# Sort by timestamp descending
|
||||
history.sort(key=lambda x: x["timestamp"], reverse=True)
|
||||
return history
|
||||
|
||||
def normalize_name(name: str, split_target: str = "_") -> str:
|
||||
"""Normalize a name by splitting off anything after and including the split_target."""
|
||||
return name.split(split_target)[0] if split_target in name else name
|
||||
|
||||
@ -25,4 +25,27 @@ def after_insert(doc, method):
|
||||
|
||||
def before_insert(doc, method):
|
||||
# This is where we will add logic to set tasks and other properties of a job based on it's project_template
|
||||
pass
|
||||
pass
|
||||
|
||||
def before_save(doc, method):
|
||||
print("DEBUG: Before Save Triggered for Project:", doc.name)
|
||||
if doc.expected_start_date and doc.expected_end_date:
|
||||
doc.is_scheduled = 1
|
||||
else:
|
||||
doc.is_scheduled = 0
|
||||
|
||||
def after_save(doc, method):
|
||||
print("DEBUG: After Save Triggered for Project:", doc.name)
|
||||
if doc.project_template == "SNW Install":
|
||||
print("DEBUG: Project template is SNW Install, updating Address Job Status based on Project status")
|
||||
status_mapping = {
|
||||
"Open": "In Progress",
|
||||
"Completed": "Completed",
|
||||
"Closed": "Completed"
|
||||
}
|
||||
new_status = status_mapping.get(doc.status, "In Progress")
|
||||
AddressService.update_value(
|
||||
doc.job_address,
|
||||
"job_status",
|
||||
new_status
|
||||
)
|
||||
@ -1,7 +1,24 @@
|
||||
import frappe
|
||||
from custom_ui.services import AddressService, ClientService
|
||||
|
||||
def before_insert(doc, method):
|
||||
"""Set values before inserting a Task."""
|
||||
print("DEBUG: Before Insert Triggered for Task")
|
||||
project_doc = frappe.get_doc("Project", doc.project)
|
||||
if project_doc.custom_installation_address:
|
||||
doc.custom_property = project_doc.custom_installation_address
|
||||
doc.project_template = project_doc.project_template
|
||||
if project_doc.job_address:
|
||||
doc.custom_property = project_doc.job_address
|
||||
|
||||
def after_insert(doc, method):
|
||||
print("DEBUG: After Insert Triggered for Task")
|
||||
print("DEBUG: Linking Task to Customer and Address")
|
||||
AddressService.append_link_v2(
|
||||
doc.custom_property, "tasks", {"task": doc.name, "project_template": doc.project_template }
|
||||
)
|
||||
AddressService.append_link_v2(
|
||||
doc.custom_property, "links", {"link_doctype": "Task", "link_name": doc.name}
|
||||
)
|
||||
ClientService.append_link_v2(
|
||||
doc.customer, "tasks", {"task": doc.name, "project_template": doc.project_template }
|
||||
)
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
"documentation_url": null,
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "company",
|
||||
"fieldname": "task",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
@ -58,7 +58,7 @@
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"is_virtual": 0,
|
||||
"label": "Company",
|
||||
"label": "Task",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"make_attachment_public": 0,
|
||||
@ -68,8 +68,8 @@
|
||||
"non_negative": 0,
|
||||
"oldfieldname": null,
|
||||
"oldfieldtype": null,
|
||||
"options": "Company",
|
||||
"parent": "Lead Company Link",
|
||||
"options": "Task",
|
||||
"parent": "Customer Task Link",
|
||||
"parentfield": "fields",
|
||||
"parenttype": "DocType",
|
||||
"permlevel": 0,
|
||||
@ -93,6 +93,70 @@
|
||||
"trigger": null,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"documentation_url": null,
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "project_template",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"is_virtual": 0,
|
||||
"label": "Project Template",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"make_attachment_public": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"max_height": null,
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"oldfieldname": null,
|
||||
"oldfieldtype": null,
|
||||
"options": "Project Template",
|
||||
"parent": "Customer Task Link",
|
||||
"parentfield": "fields",
|
||||
"parenttype": "DocType",
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"show_dashboard": 0,
|
||||
"show_on_timeline": 0,
|
||||
"show_preview_popup": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"trigger": null,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
}
|
||||
],
|
||||
"force_re_route_to_default_view": 0,
|
||||
@ -114,10 +178,228 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "5f481f64a0f53ad40b09d8b5694265c1",
|
||||
"modified": "2026-01-15 00:40:39.197431",
|
||||
"module": "Custom",
|
||||
"name": "Lead Company Link",
|
||||
"migration_hash": null,
|
||||
"modified": "2026-01-19 18:10:16.782664",
|
||||
"module": "Custom UI",
|
||||
"name": "Customer Task Link",
|
||||
"naming_rule": "",
|
||||
"nsm_parent_field": null,
|
||||
"parent_node": null,
|
||||
"permissions": [],
|
||||
"print_outline": null,
|
||||
"protect_attached_files": 0,
|
||||
"queue_in_background": 0,
|
||||
"quick_entry": 0,
|
||||
"read_only": 0,
|
||||
"recipient_account_field": null,
|
||||
"restrict_to_domain": null,
|
||||
"route": null,
|
||||
"row_format": "Dynamic",
|
||||
"rows_threshold_for_grid_search": 20,
|
||||
"search_fields": null,
|
||||
"sender_field": null,
|
||||
"sender_name_field": null,
|
||||
"show_name_in_global_search": 0,
|
||||
"show_preview_popup": 0,
|
||||
"show_title_field_in_link": 0,
|
||||
"smallicon": null,
|
||||
"sort_field": "modified",
|
||||
"sort_order": "DESC",
|
||||
"states": [],
|
||||
"subject": null,
|
||||
"subject_field": null,
|
||||
"tag_fields": null,
|
||||
"timeline_field": null,
|
||||
"title_field": null,
|
||||
"track_changes": 0,
|
||||
"track_seen": 0,
|
||||
"track_views": 0,
|
||||
"translated_doctype": 0,
|
||||
"website_search_field": null
|
||||
},
|
||||
{
|
||||
"_assign": null,
|
||||
"_comments": null,
|
||||
"_last_update": null,
|
||||
"_liked_by": null,
|
||||
"_user_tags": null,
|
||||
"actions": [],
|
||||
"allow_auto_repeat": 0,
|
||||
"allow_copy": 0,
|
||||
"allow_events_in_timeline": 0,
|
||||
"allow_guest_to_view": 0,
|
||||
"allow_import": 0,
|
||||
"allow_rename": 1,
|
||||
"app": null,
|
||||
"autoname": null,
|
||||
"beta": 0,
|
||||
"color": null,
|
||||
"colour": null,
|
||||
"custom": 1,
|
||||
"default_email_template": null,
|
||||
"default_print_format": null,
|
||||
"default_view": null,
|
||||
"description": null,
|
||||
"docstatus": 0,
|
||||
"doctype": "DocType",
|
||||
"document_type": "",
|
||||
"documentation": null,
|
||||
"editable_grid": 1,
|
||||
"email_append_to": 0,
|
||||
"engine": "InnoDB",
|
||||
"fields": [
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"documentation_url": null,
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "task",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 1,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"is_virtual": 0,
|
||||
"label": "Task",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"make_attachment_public": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"max_height": null,
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"oldfieldname": null,
|
||||
"oldfieldtype": null,
|
||||
"options": "Task",
|
||||
"parent": "Address Task Link",
|
||||
"parentfield": "fields",
|
||||
"parenttype": "DocType",
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 1,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"show_dashboard": 0,
|
||||
"show_on_timeline": 0,
|
||||
"show_preview_popup": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"trigger": null,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
},
|
||||
{
|
||||
"allow_bulk_edit": 0,
|
||||
"allow_in_quick_entry": 0,
|
||||
"allow_on_submit": 0,
|
||||
"bold": 0,
|
||||
"collapsible": 0,
|
||||
"collapsible_depends_on": null,
|
||||
"columns": 0,
|
||||
"default": null,
|
||||
"depends_on": null,
|
||||
"description": null,
|
||||
"documentation_url": null,
|
||||
"fetch_from": null,
|
||||
"fetch_if_empty": 0,
|
||||
"fieldname": "project_template",
|
||||
"fieldtype": "Link",
|
||||
"hidden": 0,
|
||||
"hide_border": 0,
|
||||
"hide_days": 0,
|
||||
"hide_seconds": 0,
|
||||
"ignore_user_permissions": 0,
|
||||
"ignore_xss_filter": 0,
|
||||
"in_filter": 0,
|
||||
"in_global_search": 0,
|
||||
"in_list_view": 0,
|
||||
"in_preview": 0,
|
||||
"in_standard_filter": 0,
|
||||
"is_virtual": 0,
|
||||
"label": "Project Template",
|
||||
"length": 0,
|
||||
"link_filters": null,
|
||||
"make_attachment_public": 0,
|
||||
"mandatory_depends_on": null,
|
||||
"max_height": null,
|
||||
"no_copy": 0,
|
||||
"non_negative": 0,
|
||||
"oldfieldname": null,
|
||||
"oldfieldtype": null,
|
||||
"options": "Project Template",
|
||||
"parent": "Address Task Link",
|
||||
"parentfield": "fields",
|
||||
"parenttype": "DocType",
|
||||
"permlevel": 0,
|
||||
"placeholder": null,
|
||||
"precision": "",
|
||||
"print_hide": 0,
|
||||
"print_hide_if_no_value": 0,
|
||||
"print_width": null,
|
||||
"read_only": 0,
|
||||
"read_only_depends_on": null,
|
||||
"remember_last_selected_value": 0,
|
||||
"report_hide": 0,
|
||||
"reqd": 0,
|
||||
"search_index": 0,
|
||||
"set_only_once": 0,
|
||||
"show_dashboard": 0,
|
||||
"show_on_timeline": 0,
|
||||
"show_preview_popup": 0,
|
||||
"sort_options": 0,
|
||||
"translatable": 0,
|
||||
"trigger": null,
|
||||
"unique": 0,
|
||||
"width": null
|
||||
}
|
||||
],
|
||||
"force_re_route_to_default_view": 0,
|
||||
"grid_page_length": 50,
|
||||
"has_web_view": 0,
|
||||
"hide_toolbar": 0,
|
||||
"icon": null,
|
||||
"image_field": null,
|
||||
"in_create": 0,
|
||||
"index_web_pages_for_search": 1,
|
||||
"is_calendar_and_gantt": 0,
|
||||
"is_published_field": null,
|
||||
"is_submittable": 0,
|
||||
"is_tree": 0,
|
||||
"is_virtual": 0,
|
||||
"issingle": 0,
|
||||
"istable": 1,
|
||||
"links": [],
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": null,
|
||||
"modified": "2026-01-19 18:10:02.359022",
|
||||
"module": "Custom UI",
|
||||
"name": "Address Task Link",
|
||||
"naming_rule": "",
|
||||
"nsm_parent_field": null,
|
||||
"parent_node": null,
|
||||
@ -268,8 +550,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:34.521684",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.097017",
|
||||
"module": "Custom",
|
||||
"name": "Lead Companies Link",
|
||||
"naming_rule": "",
|
||||
@ -486,8 +768,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:34.576521",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.150584",
|
||||
"module": "Custom",
|
||||
"name": "Address Project Link",
|
||||
"naming_rule": "",
|
||||
@ -704,8 +986,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:34.628136",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.203403",
|
||||
"module": "Custom",
|
||||
"name": "Address Quotation Link",
|
||||
"naming_rule": "",
|
||||
@ -922,8 +1204,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:34.681893",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.255846",
|
||||
"module": "Custom",
|
||||
"name": "Address On-Site Meeting Link",
|
||||
"naming_rule": "",
|
||||
@ -1140,8 +1422,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:34.737017",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.309600",
|
||||
"module": "Custom",
|
||||
"name": "Address Sales Order Link",
|
||||
"naming_rule": "",
|
||||
@ -1294,8 +1576,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:34.787995",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.361237",
|
||||
"module": "Custom",
|
||||
"name": "Contact Address Link",
|
||||
"naming_rule": "",
|
||||
@ -1448,8 +1730,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:34.837721",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.412683",
|
||||
"module": "Custom",
|
||||
"name": "Lead On-Site Meeting Link",
|
||||
"naming_rule": "",
|
||||
@ -2050,8 +2332,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:34.906370",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.483924",
|
||||
"module": "Selling",
|
||||
"name": "Quotation Template",
|
||||
"naming_rule": "",
|
||||
@ -2548,8 +2830,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:34.977831",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.558008",
|
||||
"module": "Selling",
|
||||
"name": "Quotation Template Item",
|
||||
"naming_rule": "",
|
||||
@ -2702,8 +2984,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.031029",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.609372",
|
||||
"module": "Custom UI",
|
||||
"name": "Customer Company Link",
|
||||
"naming_rule": "",
|
||||
@ -2856,8 +3138,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.084461",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.660893",
|
||||
"module": "Custom UI",
|
||||
"name": "Customer Address Link",
|
||||
"naming_rule": "",
|
||||
@ -3010,8 +3292,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.135851",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.712878",
|
||||
"module": "Custom UI",
|
||||
"name": "Customer Contact Link",
|
||||
"naming_rule": "",
|
||||
@ -3164,8 +3446,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.184768",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.765849",
|
||||
"module": "Custom",
|
||||
"name": "Address Contact Link",
|
||||
"naming_rule": "",
|
||||
@ -3318,8 +3600,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.236428",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.818352",
|
||||
"module": "Custom",
|
||||
"name": "Customer On-Site Meeting Link",
|
||||
"naming_rule": "",
|
||||
@ -3472,8 +3754,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.287145",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.870984",
|
||||
"module": "Custom",
|
||||
"name": "Customer Project Link",
|
||||
"naming_rule": "",
|
||||
@ -3626,8 +3908,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.338967",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.922695",
|
||||
"module": "Custom",
|
||||
"name": "Customer Quotation Link",
|
||||
"naming_rule": "",
|
||||
@ -3780,8 +4062,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.388711",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:17.975165",
|
||||
"module": "Custom",
|
||||
"name": "Customer Sales Order Link",
|
||||
"naming_rule": "",
|
||||
@ -3934,8 +4216,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.441876",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:18.027046",
|
||||
"module": "Custom",
|
||||
"name": "Lead Address Link",
|
||||
"naming_rule": "",
|
||||
@ -4088,8 +4370,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.492936",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:18.078476",
|
||||
"module": "Custom",
|
||||
"name": "Lead Contact Link",
|
||||
"naming_rule": "",
|
||||
@ -4242,8 +4524,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.545465",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:18.170095",
|
||||
"module": "Custom",
|
||||
"name": "Lead Quotation Link",
|
||||
"naming_rule": "",
|
||||
@ -4396,8 +4678,8 @@
|
||||
"make_attachments_public": 0,
|
||||
"max_attachments": 0,
|
||||
"menu_index": null,
|
||||
"migration_hash": "7c3c71cf20b258daa783e541cb045a4b",
|
||||
"modified": "2026-01-16 04:11:35.604415",
|
||||
"migration_hash": "0df0ede31f640435231ba887f40eca91",
|
||||
"modified": "2026-01-19 20:52:18.238066",
|
||||
"module": "Custom",
|
||||
"name": "Address Company Link",
|
||||
"naming_rule": "",
|
||||
|
||||
1
custom_ui/fixtures/property_setter.json
Normal file
1
custom_ui/fixtures/property_setter.json
Normal file
@ -0,0 +1 @@
|
||||
[]
|
||||
@ -181,10 +181,13 @@ doc_events = {
|
||||
},
|
||||
"Project": {
|
||||
"before_insert": "custom_ui.events.jobs.before_insert",
|
||||
"after_insert": "custom_ui.events.jobs.after_insert"
|
||||
"after_insert": "custom_ui.events.jobs.after_insert",
|
||||
"before_save": "custom_ui.events.jobs.before_save",
|
||||
"on_update": "custom_ui.events.jobs.after_save"
|
||||
},
|
||||
"Task": {
|
||||
"before_insert": "custom_ui.events.task.before_insert"
|
||||
"before_insert": "custom_ui.events.task.before_insert",
|
||||
"after_insert": "custom_ui.events.task.after_insert"
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,6 +219,8 @@ fixtures = [
|
||||
"Address Contact Link",
|
||||
"Address Company Link",
|
||||
"Contact Address Link",
|
||||
"Address Task Link",
|
||||
"Customer Task Link"
|
||||
]]
|
||||
]
|
||||
},
|
||||
@ -249,7 +254,15 @@ fixtures = [
|
||||
["dt", "=", "Project Template"],
|
||||
["fieldname", "=", "company"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"dt": "Property Setter",
|
||||
"filters": [
|
||||
["doc_type", "=", "Lead"],
|
||||
["doc_type", "=", "Project"]
|
||||
]
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ def after_migrate():
|
||||
frappe.clear_cache(doctype=doctype)
|
||||
frappe.reload_doctype(doctype)
|
||||
|
||||
update_address_fields()
|
||||
# update_address_fields()
|
||||
# build_frontend()
|
||||
|
||||
|
||||
@ -146,6 +146,13 @@ def add_custom_fields():
|
||||
fieldtype="Link",
|
||||
options="Contact",
|
||||
insert_after="contacts"
|
||||
),
|
||||
dict(
|
||||
fieldname="tasks",
|
||||
label="Tasks",
|
||||
fieldtype="Table",
|
||||
options="Customer Task Link",
|
||||
insert_after="projects"
|
||||
)
|
||||
],
|
||||
"Lead": [
|
||||
@ -327,6 +334,13 @@ def add_custom_fields():
|
||||
fieldtype="Table",
|
||||
options="Address Company Link",
|
||||
insert_after="contacts"
|
||||
),
|
||||
dict(
|
||||
fieldname="tasks",
|
||||
label="Tasks",
|
||||
fieldtype="Table",
|
||||
options="Address Task Link",
|
||||
insert_after="projects"
|
||||
)
|
||||
],
|
||||
"Contact": [
|
||||
@ -539,6 +553,37 @@ def add_custom_fields():
|
||||
options="Customer",
|
||||
insert_after="job_address",
|
||||
description="The customer for whom the project is being executed."
|
||||
),
|
||||
dict(
|
||||
fieldname="expected_start_time",
|
||||
label="Expected Start Time",
|
||||
fieldtype="Time",
|
||||
insert_after="expected_start_date"
|
||||
),
|
||||
dict(
|
||||
fieldname="expected_end_time",
|
||||
label="Expected End Time",
|
||||
fieldtype="Time",
|
||||
insert_after="expected_end_date"
|
||||
),
|
||||
dict(
|
||||
fieldname="actual_start_time",
|
||||
label="Actual Start Time",
|
||||
fieldtype="Time",
|
||||
insert_after="actual_start_date"
|
||||
),
|
||||
dict(
|
||||
fieldname="actual_end_time",
|
||||
label="Actual End Time",
|
||||
fieldtype="Time",
|
||||
insert_after="actual_end_date"
|
||||
),
|
||||
dict(
|
||||
fieldname="is_scheduled",
|
||||
label="Is Scheduled",
|
||||
fieldtype="Check",
|
||||
default=0,
|
||||
insert_after="expected_end_time"
|
||||
)
|
||||
],
|
||||
"Project Template": [
|
||||
@ -550,6 +595,15 @@ def add_custom_fields():
|
||||
insert_after="project_type",
|
||||
description="The company associated with this project template."
|
||||
)
|
||||
],
|
||||
"Task": [
|
||||
dict(
|
||||
fieldname="project_template",
|
||||
label="Project Template",
|
||||
fieldtype="Link",
|
||||
options="Project Template",
|
||||
insert_after="project"
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@ -18,8 +18,10 @@ const FRAPPE_CREATE_ESTIMATE_TEMPLATE_METHOD = "custom_ui.api.db.estimates.creat
|
||||
const FRAPPE_GET_JOB_METHOD = "custom_ui.api.db.jobs.get_job";
|
||||
const FRAPPE_GET_JOBS_METHOD = "custom_ui.api.db.jobs.get_jobs_table_data";
|
||||
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_JOB_TASK_TABLE_DATA_METHOD = "custom_ui.api.db.jobs.get_job_task_table_data";
|
||||
const FRAPPE_GET_JOB_TASK_LIST_METHOD = "custom_ui.api.db.jobs.get_job_task_list";
|
||||
const FRAPPE_GET_INSTALL_PROJECTS_METHOD = "custom_ui.api.db.jobs.get_install_projects";
|
||||
const FRAPPE_GET_JOBS_FOR_CALENDAR_METHOD = "custom_ui.api.db.jobs.get_projects_for_calendar";
|
||||
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";
|
||||
@ -42,6 +44,7 @@ const FRAPPE_GET_ADDRESSES_METHOD = "custom_ui.api.db.addresses.get_addresses";
|
||||
const FRAPPE_UPSERT_CLIENT_METHOD = "custom_ui.api.db.clients.upsert_client";
|
||||
const FRAPPE_GET_CLIENT_STATUS_COUNTS_METHOD = "custom_ui.api.db.clients.get_client_status_counts";
|
||||
const FRAPPE_GET_CLIENT_TABLE_DATA_METHOD = "custom_ui.api.db.clients.get_clients_table_data";
|
||||
const FRAPPE_GET_CLIENT_TABLE_DATA_V2_METHOD = "custom_ui.api.db.clients.get_clients_table_data_v2";
|
||||
const FRAPPE_GET_CLIENT_METHOD = "custom_ui.api.db.clients.get_client_v2";
|
||||
const FRAPPE_GET_CLIENT_NAMES_METHOD = "custom_ui.api.db.clients.get_client_names";
|
||||
|
||||
@ -87,6 +90,17 @@ class Api {
|
||||
return await this.request(FRAPPE_GET_CLIENT_METHOD, { clientName });
|
||||
}
|
||||
|
||||
static async getPaginatedClientDetailsV2(paginationParams = {}, filters = {}, sortings = []) {
|
||||
const { page = 0, pageSize = 10 } = paginationParams;
|
||||
const result = await this.request(FRAPPE_GET_CLIENT_TABLE_DATA_V2_METHOD, {
|
||||
filters,
|
||||
sortings,
|
||||
page: page + 1,
|
||||
pageSize,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get paginated client data with filtering and sorting
|
||||
* @param {Object} paginationParams - Pagination parameters from store
|
||||
@ -138,9 +152,10 @@ class Api {
|
||||
// ON-SITE MEETING METHODS
|
||||
// ============================================================================
|
||||
|
||||
static async getUnscheduledBidMeetings() {
|
||||
static async getUnscheduledBidMeetings(company) {
|
||||
return await this.request(
|
||||
"custom_ui.api.db.bid_meetings.get_unscheduled_bid_meetings",
|
||||
{ company }
|
||||
);
|
||||
}
|
||||
|
||||
@ -148,8 +163,8 @@ class Api {
|
||||
return await this.request(FRAPPE_GET_ONSITE_MEETINGS_METHOD, { fields, filters });
|
||||
}
|
||||
|
||||
static async getWeekBidMeetings(weekStart, weekEnd) {
|
||||
return await this.request(FRAPPE_GET_WEEK_ONSITE_MEETINGS_METHOD, { weekStart, weekEnd });
|
||||
static async getWeekBidMeetings(weekStart, weekEnd, company) {
|
||||
return await this.request(FRAPPE_GET_WEEK_ONSITE_MEETINGS_METHOD, { weekStart, weekEnd, company });
|
||||
}
|
||||
|
||||
static async updateBidMeeting(name, data) {
|
||||
@ -300,6 +315,10 @@ class Api {
|
||||
return result;
|
||||
}
|
||||
|
||||
static async getJobsForCalendar(date, company = null, projectTemplates = []) {
|
||||
return await this.request(FRAPPE_GET_JOBS_FOR_CALENDAR_METHOD, { date, company, projectTemplates });
|
||||
}
|
||||
|
||||
static async getJob(jobName) {
|
||||
if (frappe.db.exists("Project", jobName)) {
|
||||
const result = await this.request(FRAPPE_GET_JOB_METHOD, { jobId: jobName })
|
||||
@ -346,7 +365,7 @@ class Api {
|
||||
|
||||
console.log("DEBUG: API - Sending job task options to backend:", options);
|
||||
|
||||
const result = await this.request(FRAPPE_GET_JOB_TASK_LIST_METHOD, { options, filters });
|
||||
const result = await this.request(FRAPPE_GET_JOB_TASK_TABLE_DATA_METHOD, { filters, sortings: sorting, page:page+1, pageSize });
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@ -3,11 +3,8 @@
|
||||
<Tabs value="0">
|
||||
<TabList>
|
||||
<Tab value="0">Bids</Tab>
|
||||
<Tab value="1">Install</Tab>
|
||||
<Tab value="1">Projects</Tab>
|
||||
<Tab value="2">Service</Tab>
|
||||
<Tab value="3">Lowe Fencing</Tab>
|
||||
<Tab value="4">Daniel's Landscaping</Tab>
|
||||
<Tab value="5">Nuco Yardcare</Tab>
|
||||
<Tab value="6">Warranties</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
@ -22,21 +19,6 @@
|
||||
<p>Service feature coming soon!</p>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel header="Lowe Fencing" value="3">
|
||||
<div class="coming-soon">
|
||||
<p>Lowe Fencing calendar coming soon!</p>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel header="Daniel's Landscaping" value="4">
|
||||
<div class="coming-soon">
|
||||
<p>Daniel's Calendar coming soon!</p>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel header="Nuco Yardcare" value="5">
|
||||
<div class="coming-soon">
|
||||
<p>Nuco calendar coming soon!</p>
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel header="Warranties" value="6">
|
||||
<div class="coming-soon">
|
||||
<p>Warranties Calendar coming soon!</p>
|
||||
@ -56,7 +38,7 @@ import TabPanel from 'primevue/tabpanel';
|
||||
import TabPanels from 'primevue/tabpanels';
|
||||
import ScheduleBid from '../calendar/bids/ScheduleBid.vue';
|
||||
import JobsCalendar from '../calendar/jobs/JobsCalendar.vue';
|
||||
import InstallsCalendar from '../calendar/jobs/InstallsCalendar.vue';
|
||||
import InstallsCalendar from './jobs/ProjectsCalendar.vue';
|
||||
import { useNotificationStore } from '../../stores/notifications-primevue';
|
||||
|
||||
const notifications = useNotificationStore();
|
||||
@ -65,6 +47,24 @@ const notifications = useNotificationStore();
|
||||
<style scoped>
|
||||
.calendar-navigation {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.calendar-navigation :deep(.p-tabs) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.calendar-navigation :deep(.p-tabpanels) {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.calendar-navigation :deep(.p-tabpanel) {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.coming-soon {
|
||||
|
||||
@ -226,12 +226,14 @@ import BidMeetingModal from "../../modals/BidMeetingModal.vue";
|
||||
import MeetingDetailsModal from "../../modals/MeetingDetailsModal.vue";
|
||||
import { useLoadingStore } from "../../../stores/loading";
|
||||
import { useNotificationStore } from "../../../stores/notifications-primevue";
|
||||
import { useCompanyStore } from "../../../stores/company";
|
||||
import Api from "../../../api";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const loadingStore = useLoadingStore();
|
||||
const notificationStore = useNotificationStore();
|
||||
const companyStore = useCompanyStore();
|
||||
|
||||
// Query parameters
|
||||
const isNewMode = computed(() => route.query.new === "true");
|
||||
@ -816,7 +818,7 @@ const handleDropToUnscheduled = async (event) => {
|
||||
const loadUnscheduledMeetings = async () => {
|
||||
loadingStore.setLoading(true);
|
||||
try {
|
||||
const result = await Api.getUnscheduledBidMeetings();
|
||||
const result = await Api.getUnscheduledBidMeetings(companyStore.currentCompany);
|
||||
// Ensure we always have an array
|
||||
unscheduledMeetings.value = Array.isArray(result) ? result : [];
|
||||
console.log("Loaded unscheduled meetings:", unscheduledMeetings.value);
|
||||
@ -865,7 +867,7 @@ const loadWeekMeetings = async () => {
|
||||
|
||||
// Try to get meetings from API
|
||||
try {
|
||||
const apiResult = await Api.getWeekBidMeetings(weekStartStr, weekEndStr);
|
||||
const apiResult = await Api.getWeekBidMeetings(weekStartStr, weekEndStr, companyStore.currentCompany);
|
||||
if (Array.isArray(apiResult)) {
|
||||
// Transform the API data to match what the calendar expects
|
||||
meetings.value = apiResult
|
||||
@ -1085,6 +1087,15 @@ watch(currentWeekStart, () => {
|
||||
loadWeekMeetings();
|
||||
});
|
||||
|
||||
// Watch for company changes
|
||||
watch(
|
||||
() => companyStore.currentCompany,
|
||||
async () => {
|
||||
await loadWeekMeetings();
|
||||
await loadUnscheduledMeetings();
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => route.query.new,
|
||||
(newVal) => {
|
||||
@ -1098,9 +1109,10 @@ watch(
|
||||
<style scoped>
|
||||
.schedule-bid-container {
|
||||
padding: 20px;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.header {
|
||||
@ -1110,6 +1122,7 @@ watch(
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
@ -1150,9 +1163,9 @@ watch(
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 150px);
|
||||
overflow: hidden;
|
||||
transition: width 0.3s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
@ -1182,11 +1195,13 @@ watch(
|
||||
.sidebar-header h4 {
|
||||
font-size: 1.1em;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.unscheduled-meetings-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="calendar-container">
|
||||
<div class="calendar-header">
|
||||
<h2>Daily Schedule - Installs</h2>
|
||||
<h2>Daily Schedule - {{ companyStore.currentCompany }}</h2>
|
||||
<div class="header-controls">
|
||||
<v-btn
|
||||
@click="previousDay"
|
||||
@ -26,6 +26,67 @@
|
||||
<v-btn @click="goToToday" variant="outlined" size="small" class="ml-4"
|
||||
>Today</v-btn
|
||||
>
|
||||
<v-menu
|
||||
v-model="showTemplateMenu"
|
||||
:close-on-content-click="false"
|
||||
location="bottom"
|
||||
>
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn
|
||||
v-bind="props"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
class="ml-4"
|
||||
>
|
||||
<v-icon left size="small">mdi-file-document-multiple</v-icon>
|
||||
Project Template ({{ selectedProjectTemplates.length }})
|
||||
<v-icon right size="small">mdi-chevron-down</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card min-width="300" max-width="400">
|
||||
<v-card-title class="text-subtitle-1 py-2">
|
||||
Select Project Templates
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
<v-card-text class="pa-2">
|
||||
<v-list density="compact">
|
||||
<v-list-item @click="toggleAllTemplates">
|
||||
<template v-slot:prepend>
|
||||
<v-checkbox-btn
|
||||
:model-value="selectedProjectTemplates.length === projectTemplates.length"
|
||||
:indeterminate="selectedProjectTemplates.length > 0 && selectedProjectTemplates.length < projectTemplates.length"
|
||||
></v-checkbox-btn>
|
||||
</template>
|
||||
<v-list-item-title>Select All</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list-item
|
||||
v-for="template in projectTemplates"
|
||||
:key="template.name"
|
||||
@click="toggleTemplate(template.name)"
|
||||
>
|
||||
<template v-slot:prepend>
|
||||
<v-checkbox-btn
|
||||
:model-value="selectedProjectTemplates.includes(template.name)"
|
||||
></v-checkbox-btn>
|
||||
</template>
|
||||
<v-list-item-title>{{ template.name }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="flat"
|
||||
size="small"
|
||||
@click="applyTemplateFilter"
|
||||
>
|
||||
Apply
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-menu>
|
||||
<v-menu
|
||||
v-model="showForemenMenu"
|
||||
:close-on-content-click="false"
|
||||
@ -303,14 +364,41 @@
|
||||
import { ref, onMounted, computed, watch } from "vue";
|
||||
import Api from "../../../api";
|
||||
import { useNotificationStore } from "../../../stores/notifications-primevue";
|
||||
import { useCompanyStore } from "../../../stores/company";
|
||||
|
||||
const notifications = useNotificationStore();
|
||||
|
||||
const companyStore = useCompanyStore()
|
||||
|
||||
// Reactive data
|
||||
const services = ref([]);
|
||||
const currentDate = ref(new Date().toISOString().split("T")[0]);
|
||||
const eventDialog = ref(false);
|
||||
const selectedEvent = ref(null);
|
||||
const projectTemplates = ref([]);
|
||||
|
||||
// Helper function to get crew name from foreman ID
|
||||
const getCrewNameFromForemanId = (foremanId) => {
|
||||
if (!foremanId) return null;
|
||||
// If it's already a crew name format, return it
|
||||
if (foremanId.startsWith('Crew ')) return foremanId;
|
||||
// For now, we'll need to map the foreman employee ID to crew names
|
||||
// This could be enhanced with an API call to get foreman details
|
||||
return foremanId; // Return as-is for now
|
||||
};
|
||||
|
||||
// Helper function to calculate duration in minutes from start and end times
|
||||
const calculateDuration = (startTime, endTime) => {
|
||||
if (!startTime || !endTime) return 120; // Default 2 hours
|
||||
|
||||
const [startHours, startMinutes] = startTime.split(':').map(Number);
|
||||
const [endHours, endMinutes] = endTime.split(':').map(Number);
|
||||
|
||||
const startTotalMinutes = startHours * 60 + startMinutes;
|
||||
const endTotalMinutes = endHours * 60 + endMinutes;
|
||||
|
||||
return endTotalMinutes - startTotalMinutes;
|
||||
};
|
||||
|
||||
// Drag and drop state
|
||||
const draggedService = ref(null);
|
||||
@ -340,14 +428,10 @@ const showForemenMenu = ref(false);
|
||||
const showDatePicker = ref(false);
|
||||
const selectedDate = ref(null);
|
||||
|
||||
// Computed properties
|
||||
const scheduledServices = computed(() =>
|
||||
services.value.filter((service) => service.status === "scheduled" && service.foreman),
|
||||
);
|
||||
// Project template filter
|
||||
const selectedProjectTemplates = ref([]);
|
||||
const showTemplateMenu = ref(false);
|
||||
|
||||
const unscheduledServices = computed(() =>
|
||||
services.value.filter((service) => service.status === "unscheduled" || !service.foreman),
|
||||
);
|
||||
|
||||
// Daily calendar computed properties
|
||||
const dayDisplayText = computed(() => {
|
||||
@ -394,6 +478,15 @@ const timeSlots = computed(() => {
|
||||
return slots;
|
||||
});
|
||||
|
||||
// Computed properties
|
||||
const scheduledServices = computed(() =>
|
||||
services.value.filter((service) => service.status === "scheduled" && service.foreman),
|
||||
);
|
||||
|
||||
const unscheduledServices = computed(() =>
|
||||
services.value.filter((service) => service.status === "unscheduled" || !service.foreman),
|
||||
);
|
||||
|
||||
// Methods
|
||||
const getUnscheduledCount = () => unscheduledServices.value.length;
|
||||
|
||||
@ -551,6 +644,31 @@ const toggleForeman = (foremanId) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Project template selection methods
|
||||
const toggleAllTemplates = () => {
|
||||
if (selectedProjectTemplates.value.length === projectTemplates.value.length) {
|
||||
// Deselect all
|
||||
selectedProjectTemplates.value = [];
|
||||
} else {
|
||||
// Select all
|
||||
selectedProjectTemplates.value = projectTemplates.value.map(t => t.name);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleTemplate = (templateName) => {
|
||||
const index = selectedProjectTemplates.value.indexOf(templateName);
|
||||
if (index > -1) {
|
||||
selectedProjectTemplates.value.splice(index, 1);
|
||||
} else {
|
||||
selectedProjectTemplates.value.push(templateName);
|
||||
}
|
||||
};
|
||||
|
||||
const applyTemplateFilter = async () => {
|
||||
showTemplateMenu.value = false;
|
||||
await fetchProjects(currentDate.value);
|
||||
};
|
||||
|
||||
// Date picker methods
|
||||
const onDateSelected = (date) => {
|
||||
if (date) {
|
||||
@ -742,10 +860,12 @@ const handleDrop = async (event, foremanId, time) => {
|
||||
try {
|
||||
await Api.upsertJob({
|
||||
id: draggedService.value.id,
|
||||
scheduledDate: currentDate.value,
|
||||
foreman: foreman.name
|
||||
expectedStartDate: currentDate.value,
|
||||
expectedStartTime: time,
|
||||
customForeman: foreman.name // This should ideally be the foreman employee ID
|
||||
});
|
||||
notifications.addSuccess("Job scheduled successfully");
|
||||
notifications.addWarning("This feature is currently still in development. This job has not actually been scheduled yet.");
|
||||
} catch (error) {
|
||||
console.error("Error scheduling job:", error);
|
||||
notifications.addError("Failed to schedule job");
|
||||
@ -793,8 +913,9 @@ const handleUnscheduledDrop = async (event) => {
|
||||
try {
|
||||
await Api.upsertJob({
|
||||
id: draggedService.value.id,
|
||||
scheduledDate: null,
|
||||
foreman: null
|
||||
expectedStartDate: null,
|
||||
expectedStartTime: null,
|
||||
customForeman: null
|
||||
});
|
||||
notifications.addSuccess("Job unscheduled successfully");
|
||||
} catch (error) {
|
||||
@ -812,31 +933,116 @@ const handleUnscheduledDrop = async (event) => {
|
||||
|
||||
const fetchProjects = async (date) => {
|
||||
try {
|
||||
const data = await Api.getInstallProjects(date, date);
|
||||
services.value = data;
|
||||
console.log("Loaded install projects:", data);
|
||||
const data = await Api.getJobsForCalendar(date, companyStore.currentCompany, selectedProjectTemplates.value);
|
||||
|
||||
// Transform the API response into the format the component expects
|
||||
const transformedServices = [];
|
||||
|
||||
// Process scheduled projects
|
||||
if (data.projects && Array.isArray(data.projects)) {
|
||||
data.projects.forEach(project => {
|
||||
const crewName = getCrewNameFromForemanId(project.customForeman);
|
||||
const duration = calculateDuration(project.expectedStartTime, project.expectedEndTime);
|
||||
|
||||
transformedServices.push({
|
||||
id: project.name,
|
||||
title: project.projectName || project.jobAddress || 'Unnamed Project',
|
||||
serviceType: project.projectName || project.jobAddress || 'Install Project',
|
||||
customer: project.customer || 'Unknown Customer',
|
||||
address: project.jobAddress || project.customInstallationAddress || '',
|
||||
scheduledDate: project.expectedStartDate || date,
|
||||
scheduledTime: project.expectedStartTime || '08:00',
|
||||
duration: duration,
|
||||
foreman: crewName,
|
||||
priority: (project.priority || 'Medium').toLowerCase(),
|
||||
estimatedCost: project.totalSalesAmount || project.estimatedCosting || 0,
|
||||
notes: project.notes || '',
|
||||
status: 'scheduled'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Process unscheduled projects
|
||||
if (data.unscheduledProjects && Array.isArray(data.unscheduledProjects)) {
|
||||
data.unscheduledProjects.forEach(project => {
|
||||
const duration = calculateDuration(project.expectedStartTime, project.expectedEndTime);
|
||||
|
||||
transformedServices.push({
|
||||
id: project.name,
|
||||
title: project.projectName || project.jobAddress || 'Unnamed Project',
|
||||
serviceType: project.projectName || project.jobAddress || 'Install Project',
|
||||
customer: project.customer || 'Unknown Customer',
|
||||
address: project.jobAddress || project.customInstallationAddress || '',
|
||||
scheduledDate: null,
|
||||
scheduledTime: null,
|
||||
duration: duration,
|
||||
foreman: null,
|
||||
priority: (project.priority || 'Medium').toLowerCase(),
|
||||
estimatedCost: project.totalSalesAmount || project.estimatedCosting || 0,
|
||||
notes: project.notes || '',
|
||||
status: 'unscheduled'
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
services.value = transformedServices;
|
||||
console.log("Loaded install projects:", transformedServices);
|
||||
} catch (error) {
|
||||
console.error("Error loading install projects:", error);
|
||||
notifications.addError("Failed to load install projects");
|
||||
}
|
||||
};
|
||||
|
||||
const fetchForemen = async () => {
|
||||
// try {
|
||||
// const data = await Api.getForemen(companyStore.currentCompany);
|
||||
// foremen.value = data;
|
||||
// console.log("Loaded foremen:", data);
|
||||
// } catch (error) {
|
||||
// console.error("Error loading foremen:", error);
|
||||
// notifications.addError("Failed to load foremen");
|
||||
// }
|
||||
};
|
||||
|
||||
const fetchProjectTemplates = async () => {
|
||||
try {
|
||||
const data = await Api.getJobTemplates(companyStore.currentCompany);
|
||||
projectTemplates.value = data;
|
||||
// Select all templates by default
|
||||
selectedProjectTemplates.value = data.map(t => t.name);
|
||||
console.log("Loaded project templates:", data);
|
||||
}
|
||||
catch (error) {
|
||||
console.error("Error loading project templates:", error);
|
||||
notifications.addError("Failed to load project templates");
|
||||
}
|
||||
}
|
||||
|
||||
watch(currentDate, async (newDate) => {
|
||||
await fetchProjects(newDate);
|
||||
});
|
||||
|
||||
watch(companyStore, async (newCompany) => {
|
||||
await fetchForemen();
|
||||
await fetchProjectTemplates();
|
||||
await fetchProjects(currentDate.value);
|
||||
}, { deep: true });
|
||||
|
||||
// Lifecycle
|
||||
onMounted(async () => {
|
||||
await fetchProjects(currentDate.value);
|
||||
await fetchForemen();
|
||||
await fetchProjectTemplates();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.calendar-container {
|
||||
padding: 20px;
|
||||
height: 100vh;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.calendar-header {
|
||||
@ -846,6 +1052,7 @@ onMounted(async () => {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid var(--surface-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
@ -1071,6 +1278,8 @@ onMounted(async () => {
|
||||
padding-left: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.unscheduled-header {
|
||||
@ -1078,6 +1287,7 @@ onMounted(async () => {
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.unscheduled-header h4 {
|
||||
@ -1088,7 +1298,7 @@ onMounted(async () => {
|
||||
.unscheduled-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 200px);
|
||||
overflow-x: hidden;
|
||||
transition: all 0.3s ease;
|
||||
border-radius: 8px;
|
||||
}
|
||||
@ -27,7 +27,7 @@
|
||||
</template>
|
||||
|
||||
<!-- SNW Installation Status -->
|
||||
<div v-if="!isNew && !editMode" class="install-status-section">
|
||||
<div v-if="!isNew && !editMode && isSNWClient" class="install-status-section">
|
||||
<InstallStatus
|
||||
:onsite-meeting-status="snwInstallData.onsiteMeetingStatus"
|
||||
:estimate-sent-status="snwInstallData.estimateSentStatus"
|
||||
@ -438,6 +438,13 @@ const fullAddress = computed(() => {
|
||||
return DataUtils.calculateFullAddress(selectedAddressData.value);
|
||||
});
|
||||
|
||||
// Check if client is associated with Sprinklers Northwest
|
||||
const isSNWClient = computed(() => {
|
||||
// if (!props.clientData?.companies || !Array.isArray(props.clientData.companies)) return false;
|
||||
// return props.clientData.companies.some((c) => c.company === "Sprinklers Northwest");
|
||||
return companyStore.currentCompany === "Sprinklers Northwest";
|
||||
});
|
||||
|
||||
// Computed data for SNW Install status
|
||||
const snwInstallData = computed(() => {
|
||||
if (!selectedAddressData.value) {
|
||||
|
||||
@ -783,7 +783,9 @@ const hasExactlyOneRowSelected = computed(() => {
|
||||
onMounted(() => {
|
||||
const currentFilters = filtersStore.getTableFilters(props.tableName);
|
||||
filterableColumns.value.forEach((col) => {
|
||||
pendingFilters.value[col.fieldName] = currentFilters[col.fieldName]?.value || "";
|
||||
// Use defaultValue from column definition if provided, otherwise use stored filter value
|
||||
const storedValue = currentFilters[col.fieldName]?.value || "";
|
||||
pendingFilters.value[col.fieldName] = col.defaultValue || storedValue;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="showOverlay"
|
||||
class="fixed inset-0 bg-black bg-opacity-30 flex items-center justify-center z-[9999] transition-opacity duration-200"
|
||||
:class="{ 'opacity-100': showOverlay, 'opacity-0 pointer-events-none': !showOverlay }"
|
||||
class="global-loading-overlay"
|
||||
>
|
||||
<div class="bg-white rounded-lg p-6 shadow-xl max-w-sm w-full mx-4 text-center">
|
||||
<div class="mb-4">
|
||||
<i class="pi pi-spin pi-spinner text-4xl text-blue-500"></i>
|
||||
<div class="loading-content">
|
||||
<div class="spinner-container">
|
||||
<i class="pi pi-spin pi-spinner"></i>
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-800 mb-2">Loading</h3>
|
||||
<p class="text-gray-600">{{ loadingMessage }}</p>
|
||||
<h3 class="loading-title">Loading</h3>
|
||||
<p class="loading-message">{{ loadingMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -46,15 +45,51 @@ const loadingMessage = computed(() => {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* Additional styling for better visual appearance */
|
||||
.bg-opacity-30 {
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
.global-loading-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(4px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
/* Backdrop blur effect for modern browsers */
|
||||
@supports (backdrop-filter: blur(4px)) {
|
||||
.fixed.inset-0 {
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
.loading-content {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 2rem;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
|
||||
max-width: 400px;
|
||||
width: 90%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.spinner-container {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.spinner-container i {
|
||||
font-size: 3rem;
|
||||
color: #1976d2;
|
||||
}
|
||||
|
||||
.loading-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.loading-message {
|
||||
color: #666;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -7,4 +7,7 @@ import CalendarNavigation from '@/components/calendar/CalendarNavigation.vue'
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
:deep(.calendar-navigation) {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -29,10 +29,12 @@ import { useFiltersStore } from "../../stores/filters";
|
||||
import { useModalStore } from "../../stores/modal";
|
||||
import { useRouter, useRoute } from "vue-router";
|
||||
import { useNotificationStore } from "../../stores/notifications-primevue";
|
||||
import { useCompanyStore } from "../../stores/company";
|
||||
import TodoChart from "../common/TodoChart.vue";
|
||||
import { Calendar, Community, Hammer, PathArrowSolid, Clock, Shield, ShieldSearch,
|
||||
ClipboardCheck, DoubleCheck, CreditCard, CardNoAccess, ChatBubbleQuestion, Edit,
|
||||
WateringSoil, Soil, Truck, SoilAlt } from "@iconoir/vue";
|
||||
WateringSoil, Soil, Truck, SoilAlt,
|
||||
Filter} from "@iconoir/vue";
|
||||
|
||||
const notifications = useNotificationStore();
|
||||
const loadingStore = useLoadingStore();
|
||||
@ -41,6 +43,7 @@ const filtersStore = useFiltersStore();
|
||||
const modalStore = useModalStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const companyStore = useCompanyStore();
|
||||
|
||||
const tableData = ref([]);
|
||||
const totalRecords = ref(0);
|
||||
@ -50,10 +53,26 @@ const currentWeekParams = ref({});
|
||||
const chartLoading = ref(true); // Start with loading state
|
||||
|
||||
const lookup = route.query.lookup;
|
||||
const lastLazyLoadEvent = ref(null);
|
||||
|
||||
// Watch for company changes to reload data
|
||||
watch(
|
||||
() => companyStore.currentCompany,
|
||||
async () => {
|
||||
console.log("Company changed, reloading client data...");
|
||||
if (lastLazyLoadEvent.value) {
|
||||
await handleLazyLoad(lastLazyLoadEvent.value);
|
||||
}
|
||||
// Also refresh status counts
|
||||
await refreshStatusCounts();
|
||||
}
|
||||
);
|
||||
|
||||
// Computed property to get current filters for the chart
|
||||
const currentFilters = computed(() => {
|
||||
return filtersStore.getTableFilters("clients");
|
||||
filters = { ...filtersStore.getTableFilters("clients"),
|
||||
company: { value: companyStore.currentCompany, matchMode: FilterMatchMode.CONTAINS}
|
||||
};
|
||||
});
|
||||
|
||||
// Handle week change from chart
|
||||
@ -220,6 +239,7 @@ const tableActions = [
|
||||
// Handle lazy loading events from DataTable
|
||||
const handleLazyLoad = async (event) => {
|
||||
console.log("Clients page - handling lazy load:", event);
|
||||
lastLazyLoadEvent.value = event;
|
||||
|
||||
try {
|
||||
isLoading.value = true;
|
||||
@ -263,8 +283,9 @@ const handleLazyLoad = async (event) => {
|
||||
filters,
|
||||
sortingArray,
|
||||
});
|
||||
filters["company"] = { value: companyStore.currentCompany, matchMode: FilterMatchMode.CONTAINS};
|
||||
|
||||
const result = await Api.getPaginatedClientDetails(
|
||||
const result = await Api.getPaginatedClientDetailsV2(
|
||||
paginationParams,
|
||||
filters,
|
||||
sortingArray,
|
||||
|
||||
@ -66,7 +66,7 @@ const notifications = useNotificationStore();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const jobIdQuery = computed(() => route.query.jobId || "");
|
||||
const jobIdQuery = computed(() => route.query.name || "");
|
||||
const isNew = computed(() => route.query.new === "true");
|
||||
|
||||
const tableData = ref([]);
|
||||
|
||||
@ -257,7 +257,7 @@ const handleLazyLoad = async (event) => {
|
||||
|
||||
const handleRowClick = (event) => {
|
||||
const rowData = event.data;
|
||||
router.push(`/job?jobId=${rowData.name}`);
|
||||
router.push(`/job?name=${rowData.name}`);
|
||||
}
|
||||
|
||||
// Load initial data
|
||||
|
||||
@ -61,7 +61,7 @@ const currentFilters = computed(() => {
|
||||
|
||||
const columns = [
|
||||
{ label: "Task", fieldName: "subject", type: "text", sortable: true, filterable: true,
|
||||
filterInputID: "subjectFilterId" },
|
||||
filterInputID: "subjectFilterId", defaultValue: subject || null },
|
||||
{ label: "Job", fieldName: "project", type: "link", sortable: true,
|
||||
onLinkClick: (link, rowData) => handleProjectClick(link, rowData)
|
||||
},
|
||||
@ -220,11 +220,6 @@ watch(showCompleted, () => {
|
||||
|
||||
// Load initial data
|
||||
onMounted(async () => {
|
||||
if (subject) {
|
||||
const inputElement = document.getElementById(`filter-subject`);
|
||||
inputElement.text = subject;
|
||||
}
|
||||
notifications.addWarning("Tasks page coming soon");
|
||||
// Initialize pagination and filters
|
||||
paginationStore.initializeTablePagination("tasks", { rows: 10 });
|
||||
filtersStore.initializeTableFilters("tasks", columns);
|
||||
@ -236,11 +231,8 @@ onMounted(async () => {
|
||||
const initialSorting = filtersStore.getTableSorting("tasks");
|
||||
|
||||
if (subject) {
|
||||
console.log(subject);
|
||||
console.log(initialFilters);
|
||||
console.log(initialFilters.subject);
|
||||
console.log("Setting subject filter from query param:", subject);
|
||||
initialFilters.subject.value = subject;
|
||||
//initialFilters = {...initialFilters, subject: {value: subject, match_mode: "contains"}};
|
||||
}
|
||||
|
||||
const optionsResult = await Api.getTaskStatusOptions();
|
||||
@ -255,6 +247,8 @@ onMounted(async () => {
|
||||
sortOrder: initialSorting.order || initialPagination.sortOrder,
|
||||
filters: initialFilters,
|
||||
});
|
||||
|
||||
notifications.addWarning("Tasks page coming soon");
|
||||
});
|
||||
</script>
|
||||
<style lang="css">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user