lots of updates
This commit is contained in:
parent
02c48e6108
commit
8ed083fce1
14 changed files with 730 additions and 83 deletions
|
|
@ -1,6 +1,9 @@
|
|||
<template>
|
||||
<div class="estimate-page">
|
||||
<h2>{{ isNew ? 'Create Estimate' : 'View Estimate' }}</h2>
|
||||
<div v-if="!isNew && estimate" class="page-actions">
|
||||
<Button label="Duplicate" icon="pi pi-copy" @click="duplicateEstimate" />
|
||||
</div>
|
||||
|
||||
<!-- Address Section -->
|
||||
<div class="address-section">
|
||||
|
|
@ -89,11 +92,17 @@
|
|||
<Button
|
||||
label="Save Draft"
|
||||
@click="saveDraft"
|
||||
:disabled="selectedItems.length === 0"
|
||||
:disabled="selectedItems.length === 0 || estimate?.customSent === 1"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="estimate">
|
||||
<Button label="Send Estimate" @click="showConfirmationModal = true" :disabled="estimate.docstatus !== 0"/>
|
||||
<Button label="Send Estimate" @click="showConfirmationModal = true" :disabled="estimate.customSent === 1"/>
|
||||
</div>
|
||||
<div v-if="estimate && estimate.customSent === 1" class="response-status">
|
||||
<h4>Customer Response:</h4>
|
||||
<span :class="getResponseClass(estimate.customResponse)">
|
||||
{{ getResponseText(estimate.customResponse) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -131,7 +140,7 @@
|
|||
:options="{ showActions: false }"
|
||||
>
|
||||
<template #title>Add Item</template>
|
||||
<div class="modal-content">
|
||||
<div class="modal-content items-modal-content">
|
||||
<div class="search-section">
|
||||
<label for="item-search" class="field-label">Search Items</label>
|
||||
<InputText
|
||||
|
|
@ -152,7 +161,7 @@
|
|||
:tableActions="tableActions"
|
||||
selectable
|
||||
:paginator="false"
|
||||
:rows="filteredItems.length"
|
||||
:scrollHeight="'55vh'"
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
|
|
@ -223,10 +232,12 @@ const router = useRouter();
|
|||
const loadingStore = useLoadingStore();
|
||||
const notificationStore = useNotificationStore();
|
||||
|
||||
const addressQuery = route.query.address;
|
||||
const isNew = route.query.new === "true" ? true : false;
|
||||
const addressQuery = computed(() => route.query.address || "");
|
||||
const isNew = computed(() => route.query.new === "true");
|
||||
|
||||
const isSubmitting = ref(false);
|
||||
const isDuplicating = ref(false);
|
||||
const duplicatedItems = ref([]);
|
||||
|
||||
const formData = reactive({
|
||||
address: "",
|
||||
|
|
@ -252,10 +263,10 @@ const estimate = ref(null);
|
|||
|
||||
// Computed property to determine if fields are editable
|
||||
const isEditable = computed(() => {
|
||||
if (isNew) return true;
|
||||
if (isNew.value) return true;
|
||||
if (!estimate.value) return false;
|
||||
// If docstatus is 0 (draft), allow editing of contact and items
|
||||
return estimate.value.docstatus === 0;
|
||||
return estimate.value.customSent === 0;
|
||||
});
|
||||
|
||||
const itemColumns = [
|
||||
|
|
@ -370,6 +381,31 @@ const saveDraft = async () => {
|
|||
}
|
||||
};
|
||||
|
||||
const duplicateEstimate = () => {
|
||||
if (!estimate.value) return;
|
||||
|
||||
// Preserve current items/quantities for the new estimate
|
||||
duplicatedItems.value = (selectedItems.value || []).map((item) => ({ ...item }));
|
||||
isDuplicating.value = true;
|
||||
|
||||
// Navigate to new estimate mode without address/contact in query params
|
||||
router.push({ path: "/estimate", query: { new: "true" } });
|
||||
};
|
||||
|
||||
const getResponseClass = (response) => {
|
||||
if (response === "Accepted") return "response-accepted";
|
||||
if (response === "Rejected") return "response-rejected";
|
||||
if (response === "Requested help") return "response-requested-help";
|
||||
return "response-no-response";
|
||||
};
|
||||
|
||||
const getResponseText = (response) => {
|
||||
if (response === "Accepted") return "Accepted";
|
||||
if (response === "Rejected") return "Rejected";
|
||||
if (response === "Requested help") return "Requested Help";
|
||||
return "No response yet";
|
||||
};
|
||||
|
||||
const confirmAndSendEstimate = async () => {
|
||||
loadingStore.setLoading(true, "Sending estimate...");
|
||||
const updatedEstimate = await Api.sendEstimateEmail(estimate.value.name);
|
||||
|
|
@ -424,7 +460,12 @@ watch(
|
|||
async (newQuery, oldQuery) => {
|
||||
// If 'new' param or address changed, reload component state
|
||||
if (newQuery.new !== oldQuery.new || newQuery.address !== oldQuery.address) {
|
||||
// Reset all state
|
||||
const duplicating = isDuplicating.value;
|
||||
const preservedItems = duplicating
|
||||
? (duplicatedItems.value || []).map((item) => ({ ...item }))
|
||||
: [];
|
||||
|
||||
// Reset all state, but keep items if duplicating
|
||||
formData.address = "";
|
||||
formData.addressName = "";
|
||||
formData.contact = "";
|
||||
|
|
@ -433,9 +474,16 @@ watch(
|
|||
selectedContact.value = null;
|
||||
contacts.value = [];
|
||||
contactOptions.value = [];
|
||||
selectedItems.value = [];
|
||||
estimate.value = null;
|
||||
|
||||
selectedItems.value = preservedItems;
|
||||
|
||||
// Clear duplication state once applied
|
||||
if (duplicating) {
|
||||
isDuplicating.value = false;
|
||||
duplicatedItems.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Reload data based on new query params
|
||||
const newIsNew = newQuery.new === "true";
|
||||
const newAddressQuery = newQuery.address;
|
||||
|
|
@ -487,20 +535,20 @@ onMounted(async () => {
|
|||
console.error("Error loading quotation items:", error);
|
||||
}
|
||||
|
||||
if (addressQuery && isNew) {
|
||||
if (addressQuery.value && isNew.value) {
|
||||
// Creating new estimate - pre-fill address
|
||||
await selectAddress(addressQuery);
|
||||
} else if (addressQuery && !isNew) {
|
||||
await selectAddress(addressQuery.value);
|
||||
} else if (addressQuery.value && !isNew.value) {
|
||||
// Viewing existing estimate - load and populate all fields
|
||||
try {
|
||||
estimate.value = await Api.getEstimateFromAddress(addressQuery);
|
||||
estimate.value = await Api.getEstimateFromAddress(addressQuery.value);
|
||||
console.log("DEBUG: Loaded estimate:", estimate.value);
|
||||
|
||||
if (estimate.value) {
|
||||
// Set the estimate name for upserting
|
||||
formData.estimateName = estimate.value.name;
|
||||
|
||||
await selectAddress(addressQuery);
|
||||
await selectAddress(addressQuery.value);
|
||||
// Set the contact from the estimate
|
||||
formData.contact = estimate.value.partyName;
|
||||
selectedContact.value = contacts.value.find((c) => c.name === estimate.value.partyName) || null;
|
||||
|
|
@ -537,6 +585,12 @@ onMounted(async () => {
|
|||
padding: 2rem;
|
||||
}
|
||||
|
||||
.page-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.address-section,
|
||||
.contact-section {
|
||||
margin-bottom: 1.5rem;
|
||||
|
|
@ -607,6 +661,14 @@ onMounted(async () => {
|
|||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.items-modal-content {
|
||||
max-height: 80vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.search-section {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
|
@ -654,6 +716,40 @@ onMounted(async () => {
|
|||
color: #856404;
|
||||
}
|
||||
|
||||
.response-status {
|
||||
margin-top: 1rem;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
background-color: #f9f9f9;
|
||||
border: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.response-status h4 {
|
||||
margin: 0 0 0.5rem 0;
|
||||
font-size: 1.1em;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.response-accepted {
|
||||
color: #28a745;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.response-rejected {
|
||||
color: #dc3545;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.response-requested-help {
|
||||
color: #ffc107;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.response-no-response {
|
||||
color: #6c757d;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.address-search-results {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue