tons fixes

This commit is contained in:
2025-08-25 17:58:30 -04:00
parent 4bcff598e6
commit adc1606312
15 changed files with 800 additions and 237 deletions

View File

@@ -40,7 +40,7 @@
</div>
<div class="col-span-6 p-5">
<div class="grid grid-cols-12 ">
<div class="col-span-12 font-bold flex justify-evenly pb-5">
<div class="col-span-12 font-bold flex justify-evenly pb-5 flex-wrap gap-2">
<router-link :to="{ name: 'deliveryCreate', params: { id: customer.id } }"
class="btn-sm btn bg-orange-600 text-white">
@@ -53,6 +53,8 @@
Create Service Call
</router-link>
<router-link :to="{ name: 'customerEdit', params: { id: customer.id } }"
class="btn-sm btn btn-secondary">
Edit Customer
@@ -60,12 +62,12 @@
<div v-if="automatic_status === 0">
<button v-on:click="userAutomatic(customer.id)" class="btn-sm btn btn-secondary">
Become Automatic Customer
Become Auto
</button>
</div>
<div v-else>
<button v-on:click="userAutomatic(customer.id)" class="btn bg-green-600 text-black btn-sm">
Become Will Call Customer
Become Will Call
</button>
</div>
</div>
@@ -173,6 +175,7 @@
{{ customer.customer_phone_number }}
</div>
</div>
</div>
<div class="col-span-6">
@@ -290,6 +293,48 @@
</div>
</div>
<div class="col-span-12 ">
<hr class=" h-1 mx-auto my-4 bg-gray-800 border-0 rounded dark:bg-gray-400">
<div class="col-span-12 ">
<div class="grid grid-cols-12">
<div class="col-span-6 font-bold flex text-2xl">
Equipment Parts
</div>
<div class="col-span-6 font-bold flex justify-start">
<button @click="openPartsModal" class="btn-sm btn btn-secondary">Edit Parts</button>
</div>
<div v-if="currentParts" class="col-span-12 mt-4">
<div v-if="hasPartsData" class="grid grid-cols-12 gap-4">
<div v-if="currentParts.oil_filter" class="col-span-6">
<div class="font-bold">Oil Filter 1:</div>
<div>{{ currentParts.oil_filter }}</div>
</div>
<div v-if="currentParts.oil_filter_2" class="col-span-6">
<div class="font-bold">Oil Filter 2:</div>
<div>{{ currentParts.oil_filter_2 }}</div>
</div>
<div v-if="currentParts.oil_nozzle" class="col-span-6">
<div class="font-bold">Oil Nozzle 1:</div>
<div :style="{ color: getNozzleColor(currentParts.oil_nozzle), fontWeight: 'bold' }">{{ currentParts.oil_nozzle }}</div>
</div>
<div v-if="currentParts.oil_nozzle_2" class="col-span-6">
<div class="font-bold">Oil Nozzle 2:</div>
<div :style="{ color: getNozzleColor(currentParts.oil_nozzle_2), fontWeight: 'bold' }">{{ currentParts.oil_nozzle_2 }}</div>
</div>
</div>
<div v-else>
<p>No equipment parts information available.</p>
</div>
</div>
</div>
</div>
</div>
<div class="col-span-12 ">
<hr class=" h-1 mx-auto my-4 bg-gray-800 border-0 rounded dark:bg-gray-400">
</div>
@@ -311,7 +356,7 @@
{{ credit_cards_count }} credit card(s) on file.
</div>
</div>
<div v-for="card in credit_cards" class="col-span-12 ">
<div v-for="card in credit_cards" :key="card.id" class="col-span-12 ">
<div class="flex flex-row ">
<div v-if="card.main_card" class="basis-1/2 p-2 ">
<div class="bg-neutral rounded-md border-2 ">
@@ -325,18 +370,8 @@
{{ card.card_number }}
</div>
<div class="flex p-1 pl-4">
<div v-if="card.expiration_month == 1">01</div>
<div v-if="card.expiration_month == 2">02</div>
<div v-if="card.expiration_month == 3">03</div>
<div v-if="card.expiration_month == 4">04</div>
<div v-if="card.expiration_month == 5">05</div>
<div v-if="card.expiration_month == 6">06</div>
<div v-if="card.expiration_month == 7">07</div>
<div v-if="card.expiration_month == 8">08</div>
<div v-if="card.expiration_month == 9">09</div>
<div v-if="card.expiration_month == 10">10</div>
<div v-if="card.expiration_month == 11">11</div>
<div v-if="card.expiration_month == 12">12</div>
<div v-if="card.expiration_month < 10">0{{ card.expiration_month }}</div>
<div v-else>{{ card.expiration_month }}</div>
<div class=" pl-1 pr-1">/ </div>
<div class=""> {{ card.expiration_year }} </div>
</div>
@@ -369,18 +404,8 @@
{{ card.card_number }}
</div>
<div class="flex p-1 pl-4">
<div v-if="card.expiration_month == 1">01</div>
<div v-if="card.expiration_month == 2">02</div>
<div v-if="card.expiration_month == 3">03</div>
<div v-if="card.expiration_month == 4">04</div>
<div v-if="card.expiration_month == 5">05</div>
<div v-if="card.expiration_month == 6">06</div>
<div v-if="card.expiration_month == 7">07</div>
<div v-if="card.expiration_month == 8">08</div>
<div v-if="card.expiration_month == 9">09</div>
<div v-if="card.expiration_month == 10">10</div>
<div v-if="card.expiration_month == 11">11</div>
<div v-if="card.expiration_month == 12">12</div>
<div v-if="card.expiration_month < 10">0{{ card.expiration_month }}</div>
<div v-else>{{ card.expiration_month }}</div>
<div class=" pl-1 pr-1">/ </div>
<div class=""> {{ card.expiration_year }} </div>
</div>
@@ -406,9 +431,6 @@
</div>
</div>
<!-- ====================================================== -->
<!-- ============== COMMENTS SECTION (FIXED LAYOUT) ============== -->
<!-- ====================================================== -->
<div class="col-span-6 px-4">
<div class="grid grid-cols-12">
<form class="rounded-md col-span-12" enctype="multipart/form-data" @submit.prevent="onSubmitSocial">
@@ -486,9 +508,6 @@
</div>
</div>
<!-- ====================================================== -->
<!-- ============== AUTOMATIC DELIVERIES (FIXED) ============== -->
<!-- ====================================================== -->
<div class="col-span-12 p-5">
<div class="grid grid-cols-12">
<div class="col-span-12 font-bold flex text-2xl mb-5">Automatic Deliveries</div>
@@ -522,9 +541,6 @@
</div>
</div>
<!-- ====================================================== -->
<!-- ============== ORDERS TABLE (FIXED) ============== -->
<!-- ====================================================== -->
<div class="col-span-12 p-5">
<div class="grid grid-cols-12">
<div class="col-span-12 font-bold flex text-2xl mb-5">Orders</div>
@@ -605,6 +621,14 @@
@save-changes="handleSaveChanges"
@delete-service="handleDeleteService"
/>
<PartsEditModal
v-if="isPartsModalOpen && currentParts"
:customer-id="customer.id"
:existing-parts="currentParts"
@close-modal="closePartsModal"
@save-parts="handleSaveParts"
/>
</template>
<script lang="ts">
@@ -617,9 +641,18 @@ import Footer from '../../../layouts/footers/footer.vue'
import PaginationComp from "../../../components/pagination.vue";
import { notify } from "@kyvg/vue3-notification";
import "leaflet/dist/leaflet.css";
import L from 'leaflet';
import iconUrl from 'leaflet/dist/images/marker-icon.png';
import shadowUrl from 'leaflet/dist/images/marker-shadow.png';
import { LMap, LTileLayer } from "@vue-leaflet/vue-leaflet";
import dayjs from 'dayjs';
import ServiceEditModal from '../../service/ServiceEditModal.vue';
import PartsEditModal from '../service/PartsEditModal.vue';
L.Icon.Default.mergeOptions({
iconUrl: iconUrl,
shadowUrl: shadowUrl,
});
interface ServiceCall {
id: number;
@@ -631,6 +664,15 @@ interface ServiceCall {
description: string;
}
interface ServiceParts {
id?: number;
customer_id: number;
oil_filter: string;
oil_filter_2: string;
oil_nozzle: string;
oil_nozzle_2: string;
}
export default defineComponent({
name: 'CustomerProfile',
components: {
@@ -640,12 +682,13 @@ export default defineComponent({
LMap,
LTileLayer,
ServiceEditModal,
PartsEditModal,
},
data() {
return {
zoom: 14,
token: null,
user: { user_id: 0, user_name: '', confirmed: '' },
user: null as { user_id: number; user_name: string; confirmed: string; } | null,
isTrue: true,
automatic_status: 0,
automatic_response: 0,
@@ -666,26 +709,62 @@ export default defineComponent({
delivery_options: { delivery_edgeNavigation: false, delivery_format: false, delivery_template: PaginationComp },
serviceCalls: [] as ServiceCall[],
selectedServiceForEdit: null as ServiceCall | null,
isPartsModalOpen: false,
currentParts: null as ServiceParts | null,
}
},
computed: {
hasPartsData() {
if (!this.currentParts) return false;
return !!(this.currentParts.oil_filter || this.currentParts.oil_filter_2 || this.currentParts.oil_nozzle || this.currentParts.oil_nozzle_2);
}
},
created() {
this.userStatus()
this.getCustomer(this.$route.params.id);
this.getCreditCards(this.$route.params.id)
this.getCreditCardsCount(this.$route.params.id)
this.getCustomerSocial(this.$route.params.id, 1)
},
mounted() {
this.getPage(this.delivery_page)
// getPage is now called from within getCustomer, so this can be removed if it's redundant
},
watch: {
$route() {
this.getCustomer(this.$route.params.id);
'$route.params.id'(newId) {
if (newId) {
this.getCustomer(newId);
}
},
},
methods: {
getPage: function (page: any) {
this.getCustomerDelivery(this.$route.params.id, page)
if (this.customer && this.customer.id) {
this.getCustomerDelivery(this.customer.id, page);
}
},
getCustomer(userid: any) {
if (!userid) return;
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
this.customer = response.data;
// --- ALL DEPENDENT API CALLS ARE NOW CHAINED HERE ---
this.userStatus();
this.getCreditCards(this.customer.id); // Assuming this uses user_id
this.getCreditCardsCount(this.customer.id); // Assuming this uses user_id
this.getCustomerSocial(this.customer.id, 1);
this.getPage(this.delivery_page);
this.checktotalOil(this.customer.id);
this.getCustomerTank(this.customer.id);
this.userAutomaticStatus(this.customer.id);
this.getCustomerDescription(this.customer.id);
this.getCustomerStats(this.customer.id);
this.getCustomerLastDelivery(this.customer.id);
this.getServiceCalls(this.customer.id);
this.fetchCustomerParts(this.customer.id);
}).catch((error: any) => {
console.error("CRITICAL: Failed to fetch main customer data. Aborting other calls.", error);
});
},
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
@@ -694,13 +773,11 @@ export default defineComponent({
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
}).then((response: any) => {
if (response.data.ok) {
this.user = response.data.user;
}
})
.catch(() => { })
}).catch(() => { this.user = null });
},
userAutomaticStatus(userid: any) {
let path = import.meta.env.VITE_BASE_URL + '/customer/automatic/status/' + userid;
@@ -734,26 +811,17 @@ export default defineComponent({
this.$notify({ title: "Automatic Status", text: 'Customer is now Manual Customer', type: 'Warning' });
}
this.getCustomer(this.$route.params.id);
this.getCreditCards(this.$route.params.id)
this.getCreditCardsCount(this.$route.params.id)
})
},
getCustomer(userid: any) {
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
this.customer = response.data
this.checktotalOil(this.customer.id)
this.getCustomerTank(this.customer.id)
this.userAutomaticStatus(this.customer.id);
this.getCustomerDescription(this.customer.id);
this.getCustomerStats(this.customer.id);
this.getCustomerLastDelivery(this.customer.id);
this.getServiceCalls(this.customer.id);
})
getNozzleColor(nozzleString: string): string {
if (!nozzleString || typeof nozzleString !== 'string') return '';
const firstChar = nozzleString.trim().toLowerCase().charAt(0);
switch (firstChar) {
case 'a': return '#EF4444';
case 'b': return '#3B82F6';
case 'w': return '#16a34a';
default: return 'inherit';
}
},
getCustomerLastDelivery(userid: any) {
let path = import.meta.env.VITE_BASE_URL + '/stats/user/lastdelivery/' + userid;
@@ -853,8 +921,8 @@ export default defineComponent({
url: path,
headers: authHeader(),
}).then(() => {
this.getCreditCards(this.$route.params.id)
this.getCreditCardsCount(this.$route.params.id)
this.getCreditCards(this.customer.user_id)
this.getCreditCardsCount(this.customer.user_id)
notify({ title: "Card Status", text: "Card Removed", type: "Success" });
})
},
@@ -912,10 +980,15 @@ export default defineComponent({
}
})
},
onSubmitSocial() {
let payload = { comment: this.CreateSocialForm.basicInfo.comment, poster_employee_id: this.user.user_id };
this.CreateSocialComment(payload);
},
onSubmitSocial() {
if (!this.user) {
console.error("Cannot submit comment: user is not logged in.");
return; // Stop the function from proceeding
}
let payload = { comment: this.CreateSocialForm.basicInfo.comment, poster_employee_id: this.user.user_id };
this.CreateSocialComment(payload);
},
getServiceCalls(customerId: number) {
let path = `${import.meta.env.VITE_BASE_URL}/service/for-customer/${customerId}`;
axios({
@@ -946,15 +1019,54 @@ export default defineComponent({
}
},
async handleDeleteService(serviceId: number) {
if (!window.confirm("Are you sure you want to delete this service call?")) return;
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
await axios.delete(path, { headers: authHeader(), withCredentials: true });
this.getServiceCalls(this.customer.id);
this.closeEditModal();
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
if(response.data.ok) {
this.getServiceCalls(this.customer.id);
this.closeEditModal();
notify({ title: "Success", text: "Service call deleted!", type: "success" });
}
} catch (error) {
console.error("Failed to delete service call:", error);
}
},
async fetchCustomerParts(customerId: number) {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${customerId}`;
const response = await axios.get(path, { headers: authHeader() });
this.currentParts = response.data;
} catch (error) {
console.error("Failed to fetch customer parts:", error);
notify({ title: "Error", text: "Could not fetch equipment parts.", type: "error" });
}
},
openPartsModal() {
if (this.currentParts) {
this.isPartsModalOpen = true;
} else {
notify({ title: "Info", text: "Parts data still loading, please wait.", type: "info" });
}
},
closePartsModal() {
this.isPartsModalOpen = false;
},
async handleSaveParts(partsToSave: ServiceParts) {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/parts/update/${partsToSave.customer_id}`;
const response = await axios.post(path, partsToSave, { headers: authHeader() });
if(response.data.ok) {
this.currentParts = partsToSave;
notify({ title: "Success", text: "Equipment parts saved successfully!", type: "success" });
}
this.closePartsModal();
} catch (error) {
console.error("Failed to save parts:", error);
notify({ title: "Error", text: "Failed to save equipment parts.", type: "error" });
}
},
formatDate(dateString: string): string {
if (!dateString) return 'N/A';
return dayjs(dateString).format('MMMM D, YYYY');