Began implementation of the send estimate feature.

This commit is contained in:
rocketdebris 2025-12-01 16:35:21 -05:00
parent cd89156ca0
commit 0bad4dbc95
2 changed files with 376 additions and 1 deletions

View File

@ -0,0 +1,333 @@
<template>
<Modal
:visible="isVisible"
:options="modalOptions"
@update:visible="handleVisibilityChange"
@close="handleClose"
>
<template #title> Submit 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="Submit Estimate"
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("submitEstimate"));
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: "autocomplete", // 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: [
{"label": "Customer", "value": "Customer"},
{"label": "Business", "value": "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("submitEstimate");
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("submitEstimate", {
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>

View File

@ -10,6 +10,25 @@
:loading="isLoading"
@lazy-load="handleLazyLoad"
/>
<Modal
:visible="showSubmitEstimateModal"
@update:visible="showSubmitEstimateModal = $event"
@close="closeSubmitEstimateModal"
:options="{ showActions: false }"
>
<template #title>Add Item</template>
<div class="modal-content">
<DataTable
:data="filteredItems"
:columns="itemColumns"
:tableName="'estimate-items'"
:tableActions="tableActions"
selectable
:paginator="false"
:rows="filteredItems.length"
/>
</div>
</Modal>
</div>
</template>
<script setup>
@ -27,16 +46,39 @@ const filtersStore = useFiltersStore();
const tableData = ref([]);
const totalRecords = ref(0);
const isLoading = ref(false);
const showSubmitEstimateModal = ref(true);
//Junk
const filteredItems= []
// End junk
const columns = [
{ label: "Estimate ID", fieldName: "name", type: "text", sortable: true, filterable: true },
//{ label: "Address", fieldName: "customInstallationAddress", type: "text", sortable: true },
{ label: "Customer", fieldName: "customer", type: "text", sortable: true, filterable: true },
{ label: "Status", fieldName: "status", type: "status", sortable: true },
{
label: "Status",
fieldName: "status",
type: "status-button",
sortable: true,
buttonVariant: "outlined",
onStatusClick: (status, rowData) => handleEstimateClick(status, rowData),
//disableCondition: (status) => status?.toLowerCase() === "draft",
disableCondition: false
},
{ label: "Order Type", fieldName: "orderType", type: "text", sortable: true },
//{ label: "Estimate Amount", fieldName:
];
const handleEstimateClick = (status, rowData) => {
showSubmitEstimateModal.value = true;
};
const closeSubmitEstimateModal = () => {
showSubmitEstimateModal.value = false;
};
const handleLazyLoad = async (event) => {
console.log("Estimates page - handling lazy load:", event);