Added Estimate Modal and solved some merge conflicts.
This commit is contained in:
parent
942e9beeab
commit
88171caee6
@ -15,10 +15,10 @@ def get_clients(options):
|
|||||||
}
|
}
|
||||||
options = {**defaultOptions, **options}
|
options = {**defaultOptions, **options}
|
||||||
print("DEBUG: Final options:", options)
|
print("DEBUG: Final options:", options)
|
||||||
|
|
||||||
clients = []
|
clients = []
|
||||||
tableRows = []
|
tableRows = []
|
||||||
|
|
||||||
# Map frontend field names to backend field names
|
# Map frontend field names to backend field names
|
||||||
def map_field_name(frontend_field):
|
def map_field_name(frontend_field):
|
||||||
field_mapping = {
|
field_mapping = {
|
||||||
@ -26,11 +26,11 @@ def get_clients(options):
|
|||||||
"addressName": "address_title", # Legacy support
|
"addressName": "address_title", # Legacy support
|
||||||
"appointmentScheduledStatus": "address_title", # These are computed fields, sort by address_title
|
"appointmentScheduledStatus": "address_title", # These are computed fields, sort by address_title
|
||||||
"estimateSentStatus": "address_title",
|
"estimateSentStatus": "address_title",
|
||||||
"paymentReceivedStatus": "address_title",
|
"paymentReceivedStatus": "address_title",
|
||||||
"jobStatus": "address_title"
|
"jobStatus": "address_title"
|
||||||
}
|
}
|
||||||
return field_mapping.get(frontend_field, frontend_field)
|
return field_mapping.get(frontend_field, frontend_field)
|
||||||
|
|
||||||
# Process filters from PrimeVue format to Frappe format
|
# Process filters from PrimeVue format to Frappe format
|
||||||
processed_filters = {}
|
processed_filters = {}
|
||||||
if options["filters"]:
|
if options["filters"]:
|
||||||
@ -39,12 +39,12 @@ def get_clients(options):
|
|||||||
if filter_obj["value"] is not None and filter_obj["value"] != "":
|
if filter_obj["value"] is not None and filter_obj["value"] != "":
|
||||||
# Map frontend field names to backend field names
|
# Map frontend field names to backend field names
|
||||||
backend_field = map_field_name(field_name)
|
backend_field = map_field_name(field_name)
|
||||||
|
|
||||||
# Handle different match modes
|
# Handle different match modes
|
||||||
match_mode = filter_obj.get("matchMode", "contains")
|
match_mode = filter_obj.get("matchMode", "contains")
|
||||||
if isinstance(match_mode, str):
|
if isinstance(match_mode, str):
|
||||||
match_mode = match_mode.lower()
|
match_mode = match_mode.lower()
|
||||||
|
|
||||||
if match_mode in ("contains", "contains"):
|
if match_mode in ("contains", "contains"):
|
||||||
processed_filters[backend_field] = ["like", f"%{filter_obj['value']}%"]
|
processed_filters[backend_field] = ["like", f"%{filter_obj['value']}%"]
|
||||||
elif match_mode in ("startswith", "startsWith"):
|
elif match_mode in ("startswith", "startsWith"):
|
||||||
@ -56,7 +56,7 @@ def get_clients(options):
|
|||||||
else:
|
else:
|
||||||
# Default to contains
|
# Default to contains
|
||||||
processed_filters[backend_field] = ["like", f"%{filter_obj['value']}%"]
|
processed_filters[backend_field] = ["like", f"%{filter_obj['value']}%"]
|
||||||
|
|
||||||
# Process sorting
|
# Process sorting
|
||||||
order_by = None
|
order_by = None
|
||||||
if options.get("sorting") and options["sorting"]:
|
if options.get("sorting") and options["sorting"]:
|
||||||
@ -70,71 +70,71 @@ def get_clients(options):
|
|||||||
# Map frontend field to backend field
|
# Map frontend field to backend field
|
||||||
backend_sort_field = map_field_name(sort_field)
|
backend_sort_field = map_field_name(sort_field)
|
||||||
order_by = f"{backend_sort_field} {sort_direction}"
|
order_by = f"{backend_sort_field} {sort_direction}"
|
||||||
|
|
||||||
print("DEBUG: Processed filters:", processed_filters)
|
print("DEBUG: Processed filters:", processed_filters)
|
||||||
print("DEBUG: Order by:", order_by)
|
print("DEBUG: Order by:", order_by)
|
||||||
|
|
||||||
count = frappe.db.count("Address", filters=processed_filters)
|
count = frappe.db.count("Address", filters=processed_filters)
|
||||||
print("DEBUG: Total addresses count:", count)
|
print("DEBUG: Total addresses count:", count)
|
||||||
|
|
||||||
addresses = frappe.db.get_all(
|
addresses = frappe.db.get_all(
|
||||||
"Address",
|
"Address",
|
||||||
fields=options["fields"],
|
fields=options["fields"],
|
||||||
filters=processed_filters,
|
filters=processed_filters,
|
||||||
limit=options["page_size"],
|
limit=options["page_size"],
|
||||||
start=(options["page"] - 1) * options["page_size"],
|
start=(options["page"] - 1) * options["page_size"],
|
||||||
order_by=order_by
|
order_by=order_by
|
||||||
)
|
)
|
||||||
|
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
client = {}
|
client = {}
|
||||||
tableRow = {}
|
tableRow = {}
|
||||||
|
|
||||||
on_site_meetings = frappe.db.get_all(
|
on_site_meetings = frappe.db.get_all(
|
||||||
"On-Site Meeting",
|
"On-Site Meeting",
|
||||||
fields=["*"],
|
fields=["*"],
|
||||||
filters={"address": address["address_title"]}
|
filters={"address": address["address_title"]}
|
||||||
)
|
)
|
||||||
|
|
||||||
quotations = frappe.db.get_all(
|
quotations = frappe.db.get_all(
|
||||||
"Quotation",
|
"Quotation",
|
||||||
fields=["*"],
|
fields=["*"],
|
||||||
filters={"custom_installation_address": address["address_title"]}
|
filters={"custom_installation_address": address["address_title"]}
|
||||||
)
|
)
|
||||||
|
|
||||||
sales_orders = frappe.db.get_all(
|
sales_orders = frappe.db.get_all(
|
||||||
"Sales Order",
|
"Sales Order",
|
||||||
fields=["*"],
|
fields=["*"],
|
||||||
filters={"custom_installation_address": address["address_title"]}
|
filters={"custom_installation_address": address["address_title"]}
|
||||||
)
|
)
|
||||||
|
|
||||||
sales_invvoices = frappe.db.get_all(
|
sales_invvoices = frappe.db.get_all(
|
||||||
"Sales Invoice",
|
"Sales Invoice",
|
||||||
fields=["*"],
|
fields=["*"],
|
||||||
filters={"custom_installation_address": address["address_title"]}
|
filters={"custom_installation_address": address["address_title"]}
|
||||||
)
|
)
|
||||||
|
|
||||||
payment_entries = frappe.db.get_all(
|
payment_entries = frappe.db.get_all(
|
||||||
"Payment Entry",
|
"Payment Entry",
|
||||||
fields=["*"],
|
fields=["*"],
|
||||||
filters={"custom_installation_address": address["address_title"]}
|
filters={"custom_installation_address": address["address_title"]}
|
||||||
)
|
)
|
||||||
|
|
||||||
jobs = frappe.db.get_all(
|
jobs = frappe.db.get_all(
|
||||||
"Project",
|
"Project",
|
||||||
fields=["*"],
|
fields=["*"],
|
||||||
filters={
|
filters={
|
||||||
"custom_installation_address": address["address_title"],
|
"custom_installation_address": address["address_title"],
|
||||||
"project_template": "SNW Install"
|
"project_template": "SNW Install"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
tasks = frappe.db.get_all(
|
tasks = frappe.db.get_all(
|
||||||
"Task",
|
"Task",
|
||||||
fields=["*"],
|
fields=["*"],
|
||||||
filters={"project": jobs[0]["name"]}
|
filters={"project": jobs[0]["name"]}
|
||||||
) if jobs else []
|
) if jobs else []
|
||||||
|
|
||||||
tableRow["id"] = address["name"]
|
tableRow["id"] = address["name"]
|
||||||
tableRow["address_title"] = address["address_title"]
|
tableRow["address_title"] = address["address_title"]
|
||||||
tableRow["appointment_scheduled_status"] = calculate_appointment_scheduled_status(on_site_meetings[0]) if on_site_meetings else "Not Started"
|
tableRow["appointment_scheduled_status"] = calculate_appointment_scheduled_status(on_site_meetings[0]) if on_site_meetings else "Not Started"
|
||||||
@ -142,13 +142,13 @@ def get_clients(options):
|
|||||||
tableRow["payment_received_status"] = calculate_payment_recieved_status(sales_invvoices[0], payment_entries) if sales_invvoices and payment_entries else "Not Started"
|
tableRow["payment_received_status"] = calculate_payment_recieved_status(sales_invvoices[0], payment_entries) if sales_invvoices and payment_entries else "Not Started"
|
||||||
tableRow["job_status"] = calculate_job_status(jobs[0], tasks) if jobs and tasks else "Not Started"
|
tableRow["job_status"] = calculate_job_status(jobs[0], tasks) if jobs and tasks else "Not Started"
|
||||||
tableRows.append(tableRow)
|
tableRows.append(tableRow)
|
||||||
|
|
||||||
client["address"] = address
|
client["address"] = address
|
||||||
client["on_site_meetings"] = on_site_meetings
|
client["on_site_meetings"] = on_site_meetings
|
||||||
client["jobs"] = jobs
|
client["jobs"] = jobs
|
||||||
client["quotations"] = quotations
|
client["quotations"] = quotations
|
||||||
clients.append(client)
|
clients.append(client)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"total": count,
|
"total": count,
|
||||||
@ -220,10 +220,10 @@ def get_jobs(options):
|
|||||||
}
|
}
|
||||||
options = {**defaultOptions, **options}
|
options = {**defaultOptions, **options}
|
||||||
print("DEBUG: Final job options:", options)
|
print("DEBUG: Final job options:", options)
|
||||||
|
|
||||||
jobs = []
|
jobs = []
|
||||||
tableRows = []
|
tableRows = []
|
||||||
|
|
||||||
# Map frontend field names to backend field names for Project doctype
|
# Map frontend field names to backend field names for Project doctype
|
||||||
def map_job_field_name(frontend_field):
|
def map_job_field_name(frontend_field):
|
||||||
field_mapping = {
|
field_mapping = {
|
||||||
@ -234,7 +234,7 @@ def get_jobs(options):
|
|||||||
"percentComplete": "percent_complete"
|
"percentComplete": "percent_complete"
|
||||||
}
|
}
|
||||||
return field_mapping.get(frontend_field, frontend_field)
|
return field_mapping.get(frontend_field, frontend_field)
|
||||||
|
|
||||||
# Process filters from PrimeVue format to Frappe format
|
# Process filters from PrimeVue format to Frappe format
|
||||||
processed_filters = {}
|
processed_filters = {}
|
||||||
if options["filters"]:
|
if options["filters"]:
|
||||||
@ -243,12 +243,12 @@ def get_jobs(options):
|
|||||||
if filter_obj["value"] is not None and filter_obj["value"] != "":
|
if filter_obj["value"] is not None and filter_obj["value"] != "":
|
||||||
# Map frontend field names to backend field names
|
# Map frontend field names to backend field names
|
||||||
backend_field = map_job_field_name(field_name)
|
backend_field = map_job_field_name(field_name)
|
||||||
|
|
||||||
# Handle different match modes
|
# Handle different match modes
|
||||||
match_mode = filter_obj.get("matchMode", "contains")
|
match_mode = filter_obj.get("matchMode", "contains")
|
||||||
if isinstance(match_mode, str):
|
if isinstance(match_mode, str):
|
||||||
match_mode = match_mode.lower()
|
match_mode = match_mode.lower()
|
||||||
|
|
||||||
if match_mode in ("contains", "contains"):
|
if match_mode in ("contains", "contains"):
|
||||||
processed_filters[backend_field] = ["like", f"%{filter_obj['value']}%"]
|
processed_filters[backend_field] = ["like", f"%{filter_obj['value']}%"]
|
||||||
elif match_mode in ("startswith", "startsWith"):
|
elif match_mode in ("startswith", "startsWith"):
|
||||||
@ -260,7 +260,7 @@ def get_jobs(options):
|
|||||||
else:
|
else:
|
||||||
# Default to contains
|
# Default to contains
|
||||||
processed_filters[backend_field] = ["like", f"%{filter_obj['value']}%"]
|
processed_filters[backend_field] = ["like", f"%{filter_obj['value']}%"]
|
||||||
|
|
||||||
# Process sorting
|
# Process sorting
|
||||||
order_by = None
|
order_by = None
|
||||||
if options.get("sorting") and options["sorting"]:
|
if options.get("sorting") and options["sorting"]:
|
||||||
@ -274,26 +274,26 @@ def get_jobs(options):
|
|||||||
# Map frontend field to backend field
|
# Map frontend field to backend field
|
||||||
backend_sort_field = map_job_field_name(sort_field)
|
backend_sort_field = map_job_field_name(sort_field)
|
||||||
order_by = f"{backend_sort_field} {sort_direction}"
|
order_by = f"{backend_sort_field} {sort_direction}"
|
||||||
|
|
||||||
print("DEBUG: Processed job filters:", processed_filters)
|
print("DEBUG: Processed job filters:", processed_filters)
|
||||||
print("DEBUG: Job order by:", order_by)
|
print("DEBUG: Job order by:", order_by)
|
||||||
|
|
||||||
count = frappe.db.count("Project", filters=processed_filters)
|
count = frappe.db.count("Project", filters=processed_filters)
|
||||||
print("DEBUG: Total projects count:", count)
|
print("DEBUG: Total projects count:", count)
|
||||||
|
|
||||||
projects = frappe.db.get_all(
|
projects = frappe.db.get_all(
|
||||||
"Project",
|
"Project",
|
||||||
fields=options["fields"],
|
fields=options["fields"],
|
||||||
filters=processed_filters,
|
filters=processed_filters,
|
||||||
limit=options["page_size"],
|
limit=options["page_size"],
|
||||||
start=(options["page"] - 1) * options["page_size"],
|
start=(options["page"] - 1) * options["page_size"],
|
||||||
order_by=order_by
|
order_by=order_by
|
||||||
)
|
)
|
||||||
|
|
||||||
for project in projects:
|
for project in projects:
|
||||||
job = {}
|
job = {}
|
||||||
tableRow = {}
|
tableRow = {}
|
||||||
|
|
||||||
tableRow["id"] = project["name"]
|
tableRow["id"] = project["name"]
|
||||||
tableRow["name"] = project["name"]
|
tableRow["name"] = project["name"]
|
||||||
tableRow["customInstallationAddress"] = project.get("custom_installation_address", "")
|
tableRow["customInstallationAddress"] = project.get("custom_installation_address", "")
|
||||||
@ -301,10 +301,10 @@ def get_jobs(options):
|
|||||||
tableRow["status"] = project.get("status", "")
|
tableRow["status"] = project.get("status", "")
|
||||||
tableRow["percentComplete"] = project.get("percent_complete", 0)
|
tableRow["percentComplete"] = project.get("percent_complete", 0)
|
||||||
tableRows.append(tableRow)
|
tableRows.append(tableRow)
|
||||||
|
|
||||||
job["project"] = project
|
job["project"] = project
|
||||||
jobs.append(job)
|
jobs.append(job)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"total": count,
|
"total": count,
|
||||||
@ -313,4 +313,8 @@ def get_jobs(options):
|
|||||||
"total_pages": (count + options["page_size"] - 1) // options["page_size"]
|
"total_pages": (count + options["page_size"] - 1) // options["page_size"]
|
||||||
},
|
},
|
||||||
"data": tableRows if options["for_table"] else jobs
|
"data": tableRows if options["for_table"] else jobs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@frappe.whitelist()
|
||||||
|
def upsert_estimate():
|
||||||
|
pass
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { IconoirProvider } from "@iconoir/vue";
|
import { IconoirProvider } from "@iconoir/vue";
|
||||||
import SideBar from "./components/SideBar.vue";
|
import SideBar from "./components/SideBar.vue";
|
||||||
import CreateClientModal from "./components/modals/CreatClientModal.vue";
|
import CreateClientModal from "./components/modals/CreateClientModal.vue";
|
||||||
|
import CreateEstimateModal from "./components/modals/CreateEstimateModal.vue";
|
||||||
import GlobalLoadingOverlay from "./components/common/GlobalLoadingOverlay.vue";
|
import GlobalLoadingOverlay from "./components/common/GlobalLoadingOverlay.vue";
|
||||||
import ScrollPanel from "primevue/scrollpanel";
|
import ScrollPanel from "primevue/scrollpanel";
|
||||||
</script>
|
</script>
|
||||||
@ -26,6 +27,7 @@ import ScrollPanel from "primevue/scrollpanel";
|
|||||||
|
|
||||||
<!-- Global Modals -->
|
<!-- Global Modals -->
|
||||||
<CreateClientModal />
|
<CreateClientModal />
|
||||||
|
<CreateEstimateModal />
|
||||||
|
|
||||||
<!-- Global Loading Overlay -->
|
<!-- Global Loading Overlay -->
|
||||||
<GlobalLoadingOverlay />
|
<GlobalLoadingOverlay />
|
||||||
|
|||||||
@ -233,8 +233,9 @@ class Api {
|
|||||||
customer: timesheet.customer,
|
customer: timesheet.customer,
|
||||||
totalHours: timesheet.total_hours,
|
totalHours: timesheet.total_hours,
|
||||||
status: timesheet.status,
|
status: timesheet.status,
|
||||||
totalPayFormatted: timesheet.total_costing_amount,
|
totalPay: timesheet.total_costing_amount
|
||||||
};
|
}
|
||||||
|
console.log("Timesheet Row: ", tableRow);
|
||||||
data.push(tableRow);
|
data.push(tableRow);
|
||||||
}
|
}
|
||||||
console.log("DEBUG: API - getTimesheetData result: ", data);
|
console.log("DEBUG: API - getTimesheetData result: ", data);
|
||||||
@ -311,7 +312,7 @@ class Api {
|
|||||||
* @param {Object} filters
|
* @param {Object} filters
|
||||||
* @returns {Promise<Object[]>}
|
* @returns {Promise<Object[]>}
|
||||||
*/
|
*/
|
||||||
static async getDocsList(doctype, fields = [], filters = {}, page = 0, pageLength = 0) {
|
static async getDocsList(doctype, fields = [], filters = {}, page = 0, start=0, pageLength = 0) {
|
||||||
const docs = await frappe.db.get_list(doctype, {
|
const docs = await frappe.db.get_list(doctype, {
|
||||||
fields,
|
fields,
|
||||||
filters,
|
filters,
|
||||||
@ -361,6 +362,12 @@ class Api {
|
|||||||
return customerNames;
|
return customerNames;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getCompanyNames() {
|
||||||
|
const companies = await this.getDocsList("Company", ["name"]);
|
||||||
|
console.log("DEBUG: API - Fetched Company Names: ", companyNames);
|
||||||
|
return companyNames;
|
||||||
|
}
|
||||||
|
|
||||||
// Create methods
|
// Create methods
|
||||||
|
|
||||||
static async createClient(clientData) {
|
static async createClient(clientData) {
|
||||||
|
|||||||
@ -46,7 +46,8 @@ const createButtons = ref([
|
|||||||
{
|
{
|
||||||
label: "Estimate",
|
label: "Estimate",
|
||||||
command: () => {
|
command: () => {
|
||||||
frappe.new_doc("Estimate");
|
//frappe.new_doc("Estimate");
|
||||||
|
modalStore.openModal("createEstimate");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
328
frontend/src/components/modals/CreateEstimateModal.vue
Normal file
328
frontend/src/components/modals/CreateEstimateModal.vue
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
<template>
|
||||||
|
<Modal
|
||||||
|
:visible="isVisible"
|
||||||
|
:options="modalOptions"
|
||||||
|
@update:visible="handleVisibilityChange"
|
||||||
|
@close="handleClose"
|
||||||
|
>
|
||||||
|
<template #title> Create New Estimate </template>
|
||||||
|
|
||||||
|
<!-- Status Message -->
|
||||||
|
<div v-if="statusMessage" class="status-message" :class="`status-${statusType}`">
|
||||||
|
<i :class="getStatusIcon(statusType)" class="status-icon"></i>
|
||||||
|
{{ statusMessage }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form
|
||||||
|
ref="formRef"
|
||||||
|
:fields="formFields"
|
||||||
|
:form-data="formData"
|
||||||
|
:show-cancel-button="true"
|
||||||
|
:validate-on-change="false"
|
||||||
|
:validate-on-blur="true"
|
||||||
|
:validate-on-submit="true"
|
||||||
|
:loading="isSubmitting"
|
||||||
|
:disable-on-loading="true"
|
||||||
|
submit-button-text="Create Client"
|
||||||
|
cancel-button-text="Cancel"
|
||||||
|
@submit="handleSubmit"
|
||||||
|
@cancel="handleCancel"
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, computed, watch } from "vue";
|
||||||
|
import { useModalStore } from "@/stores/modal";
|
||||||
|
import Modal from "@/components/common/Modal.vue";
|
||||||
|
import Form from "@/components/common/Form.vue";
|
||||||
|
import Api from "@/api";
|
||||||
|
import DataUtils from "../../utils";
|
||||||
|
|
||||||
|
const modalStore = useModalStore();
|
||||||
|
|
||||||
|
// Modal visibility computed property
|
||||||
|
const isVisible = computed(() => modalStore.isModalOpen("createEstimate"));
|
||||||
|
const companyNames = ref([]);
|
||||||
|
// Form reference for controlling its state
|
||||||
|
const formRef = ref(null);
|
||||||
|
// Form data
|
||||||
|
const formData = reactive({
|
||||||
|
address: "",
|
||||||
|
company: "",
|
||||||
|
date: "",
|
||||||
|
quotationTo: "",
|
||||||
|
partyName: "",
|
||||||
|
items: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Available cities for the selected zipcode
|
||||||
|
const availableCities = ref([]);
|
||||||
|
|
||||||
|
// Loading state for zipcode lookup
|
||||||
|
const isLoadingZipcode = ref(false);
|
||||||
|
|
||||||
|
// Status message for user feedback
|
||||||
|
const statusMessage = ref("");
|
||||||
|
const statusType = ref("info"); // 'info', 'warning', 'error', 'success'
|
||||||
|
|
||||||
|
// Modal configuration
|
||||||
|
const modalOptions = {
|
||||||
|
maxWidth: "600px",
|
||||||
|
persistent: false,
|
||||||
|
showActions: false,
|
||||||
|
title: "Create New Estimate",
|
||||||
|
overlayColor: "rgb(59, 130, 246)", // Blue background
|
||||||
|
overlayOpacity: 0.8,
|
||||||
|
cardClass: "create-estimate-modal",
|
||||||
|
closeOnOutsideClick: true,
|
||||||
|
closeOnEscape: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Form field definitions
|
||||||
|
const formFields = computed(() => [
|
||||||
|
{
|
||||||
|
name: "address",
|
||||||
|
label: "Client Address",
|
||||||
|
type: "text",
|
||||||
|
required: true,
|
||||||
|
placeholder: "Enter street address",
|
||||||
|
cols: 12,
|
||||||
|
md: 6,
|
||||||
|
helpText: "Enter address for this estimate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "company",
|
||||||
|
label: "Company Name",
|
||||||
|
type: "select", // Changed from 'select' to 'autocomplete'
|
||||||
|
required: true,
|
||||||
|
placeholder: "Select Company",
|
||||||
|
cols: 12,
|
||||||
|
md: 6,
|
||||||
|
options: companyNames.value, // Direct array of strings
|
||||||
|
dropdown: true,
|
||||||
|
// For string arrays, don't set optionLabel at all
|
||||||
|
helpText: "Select company associated with this estimate.",
|
||||||
|
// Let the Form component handle filtering automatically
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "date",
|
||||||
|
label: "Current Date",
|
||||||
|
type: "date",
|
||||||
|
required: true,
|
||||||
|
placeholder: "",
|
||||||
|
cols: 12,
|
||||||
|
md: 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "quotationTo",
|
||||||
|
label: "Client Type",
|
||||||
|
type: "select",
|
||||||
|
required: true,
|
||||||
|
placeholder: "Select Customer or Business",
|
||||||
|
cols: 12,
|
||||||
|
md: 6,
|
||||||
|
options: ["Customer", "Business"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "partyName",
|
||||||
|
label: "Client Name",
|
||||||
|
type: "text",
|
||||||
|
required: true,
|
||||||
|
placeholder: "",
|
||||||
|
cols: 12,
|
||||||
|
md: 4,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
// Show status message to user
|
||||||
|
function showStatusMessage(message, type = "info") {
|
||||||
|
statusMessage.value = message;
|
||||||
|
statusType.value = type;
|
||||||
|
|
||||||
|
// Auto-clear message after 5 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
statusMessage.value = "";
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get icon class for status messages
|
||||||
|
function getStatusIcon(type) {
|
||||||
|
switch (type) {
|
||||||
|
case "warning":
|
||||||
|
return "pi pi-exclamation-triangle";
|
||||||
|
case "error":
|
||||||
|
return "pi pi-times-circle";
|
||||||
|
case "success":
|
||||||
|
return "pi pi-check-circle";
|
||||||
|
default:
|
||||||
|
return "pi pi-info-circle";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submission state to prevent double submission
|
||||||
|
const isSubmitting = ref(false);
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
|
async function handleSubmit(formDataFromEvent) {
|
||||||
|
// Prevent double submission with detailed logging
|
||||||
|
if (isSubmitting.value) {
|
||||||
|
console.warn(
|
||||||
|
"CreateEstimateModal: Form submission already in progress, ignoring duplicate submission",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"CreateEstimateModal: Form submission started with data:",
|
||||||
|
formDataFromEvent || formData,
|
||||||
|
);
|
||||||
|
|
||||||
|
isSubmitting.value = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
showStatusMessage("Creating estimate...", "info");
|
||||||
|
|
||||||
|
// Use the form data from the event if provided, otherwise use reactive formData
|
||||||
|
const dataToSubmit = formDataFromEvent || formData;
|
||||||
|
|
||||||
|
console.log("CreateEstimateModal: Calling API with data:", dataToSubmit);
|
||||||
|
|
||||||
|
// Call API to create client
|
||||||
|
const response = await Api.createEstimate(dataToSubmit);
|
||||||
|
|
||||||
|
console.log("CreateEstimateModal: API response received:", response);
|
||||||
|
|
||||||
|
if (response && response.success) {
|
||||||
|
showStatusMessage("Estimate created successfully!", "success");
|
||||||
|
|
||||||
|
// Close modal after a brief delay
|
||||||
|
setTimeout(() => {
|
||||||
|
handleClose();
|
||||||
|
}, 1500);
|
||||||
|
} else {
|
||||||
|
throw new Error(response?.message || "Failed to create estimate");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("CreateEstimateModal: Error creating client:", error);
|
||||||
|
showStatusMessage(error.message || "Failed to create estimate. Please try again.", "error");
|
||||||
|
} finally {
|
||||||
|
isSubmitting.value = false;
|
||||||
|
// Also reset the Form component's internal submission state
|
||||||
|
if (formRef.value && formRef.value.stopLoading) {
|
||||||
|
formRef.value.stopLoading();
|
||||||
|
}
|
||||||
|
console.log("CreateEstimateModal: Form submission completed, isSubmitting reset to false");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle cancel action
|
||||||
|
function handleCancel() {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle modal close
|
||||||
|
function handleClose() {
|
||||||
|
modalStore.closeModal("createEstimate");
|
||||||
|
resetForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle visibility changes
|
||||||
|
function handleVisibilityChange(visible) {
|
||||||
|
if (!visible) {
|
||||||
|
handleClose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset form data
|
||||||
|
function resetForm() {
|
||||||
|
Object.keys(formData).forEach((key) => {
|
||||||
|
formData[key] = "";
|
||||||
|
});
|
||||||
|
statusMessage.value = "";
|
||||||
|
statusType.value = "info";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize modal in store when component mounts
|
||||||
|
modalStore.initializeModal("createEstimate", {
|
||||||
|
closeOnEscape: true,
|
||||||
|
closeOnOutsideClick: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(isVisible, async () => {
|
||||||
|
if (isVisible.value) {
|
||||||
|
try {
|
||||||
|
const names = await Api.getCompanyNames();
|
||||||
|
companyNames.value = names;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error loading company names:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.create-client-modal {
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom styling for the modal content */
|
||||||
|
:deep(.modal-header) {
|
||||||
|
background: linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.modal-title) {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.modal-close-btn) {
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.modal-content) {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status message styling */
|
||||||
|
.status-message {
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
border-left: 4px solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-icon {
|
||||||
|
margin-right: 8px;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-info {
|
||||||
|
background-color: #e3f2fd;
|
||||||
|
color: #1565c0;
|
||||||
|
border-left-color: #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-warning {
|
||||||
|
background-color: #fff3e0;
|
||||||
|
color: #ef6c00;
|
||||||
|
border-left-color: #ff9800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-error {
|
||||||
|
background-color: #ffebee;
|
||||||
|
color: #c62828;
|
||||||
|
border-left-color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-success {
|
||||||
|
background-color: #e8f5e8;
|
||||||
|
color: #2e7d32;
|
||||||
|
border-left-color: #4caf50;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
x
Reference in New Issue
Block a user