Working log in/route guard

This commit is contained in:
2025-09-04 08:03:24 -04:00
parent 992a1a217d
commit dc1ee95827
37 changed files with 1283 additions and 1191 deletions

View File

@@ -1,4 +1,5 @@
<!-- Login.vue -->
<template>
<div class="WrapperPlain">
<div class="max-w-7xl mx-auto ">
@@ -84,6 +85,7 @@ import useValidate from "@vuelidate/core"
import { required, minLength } from "@vuelidate/validators"
import Header from "../../layouts/headers/headernoauth.vue";
import authHeader from "../../services/auth.header.ts"
import { useAuthStore } from "../../stores/auth"
export default defineComponent({
@@ -122,59 +124,87 @@ export default defineComponent({
})
.then((response:any) => {
if (response.data.ok) {
const authStore = useAuthStore();
authStore.setToken(response.data.user.token, response.data.user);
this.$router.push({ name: "home" });
}
})
.catch(() => {});
},
sendLogin(payLoad: { username: string; password: string }) {
let path = import.meta.env.VITE_BASE_URL + "/auth/login"
axios({
method: "post",
url: path,
data: payLoad,
withCredentials: true,
})
.then((response:any) => {
if (response.data.user) {
localStorage.setItem("auth_token", response.data.token);
localStorage.setItem("auth_user", response.data.user);
this.$router.push({ name: "home" });
sendLogin(payLoad: { username: string; password: string }) {
console.log("1. Attempting to send login request with payload:", payLoad);
let path = import.meta.env.VITE_BASE_URL + "/auth/login";
notify({
title: "Authorization",
text: "You have been logged in!",
type: "success",
});
axios({
method: "post",
url: path,
data: payLoad,
withCredentials: true,
})
.then((response: any) => {
console.log("2. Received response from API:", response);
console.log("3. Raw response data from API:", response.data);
}
else if (response.data.locked) {
notify({
title: "Authorization",
text: "Account has been locked for security reasons. Please unlock.",
type: "error",
});
// Let's check the condition very carefully
console.log("4. Checking condition: 'if (response.data.user)'...");
this.$router.push({ name: "lostPassword" });
}
else {
notify({
title: "Authorization",
text: "Login Failure!",
type: "error",
});
}
})
.catch(() => {
notify({
title: "Authorization",
text: "Login Failure!",
type: "error",
});
if (response.data && response.data.user) {
console.log("5. SUCCESS: Condition was true. User data found:", response.data.user);
const authStore = useAuthStore();
authStore.setToken(response.data.token, response.data.user);
console.log("6. Token and user sent to Pinia store. Redirecting to home...");
this.$router.push({ name: "home" });
notify({
title: "Authorization",
text: "You have been logged in!",
type: "success",
});
} else {
console.error("5. FAILURE: Condition was false. The 'response.data.user' object is missing or falsy.");
if (response.data.locked) {
notify({
title: "Authorization",
text: "Account has been locked for security reasons. Please unlock.",
type: "error",
});
},
this.$router.push({ name: "lostPassword" });
} else {
notify({
title: "Authorization",
text: "Login Failure! Check username or password.",
type: "error",
});
}
}
})
.catch((error: any) => {
console.error("CRITICAL FAILURE: The API request failed entirely.", error);
if (error.response) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
console.error("Error Response Data:", error.response.data);
console.error("Error Response Status:", error.response.status);
} else if (error.request) {
// The request was made but no response was received
console.error("No response received from the server. Is the backend running? Is the URL correct?", error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.error('Error setting up the request:', error.message);
}
notify({
title: "Authorization",
text: "A critical error occurred. Could not connect to the server.",
type: "error",
});
});
},
onSubmit() {
const payLoad = {
username: this.loginForm.username,

View File

@@ -1,34 +1,40 @@
import Login from '../auth/login.vue';
import Register from '../auth/register.vue';
import changePassword from '../auth/changepassword.vue';
import lostPassword from '../auth/lostpassword.vue';
const authRoutes = [
{
path: '/login',
name: 'login',
component: Login,
// This page is for logging in, so it should be publicly accessible.
meta: { requiresAuth: false }
},
{
path: '/register',
name: 'register',
component: Register,
// Anyone should be able to access the registration page.
meta: { requiresAuth: false }
},
{
path: '/lostpassword',
name: 'lostPassword',
component: lostPassword,
// The lost password page must be public.
meta: { requiresAuth: false }
},
{
path: '/changepassword',
name: 'changePassword',
component: changePassword,
// This page is a bit ambiguous. If any user (even logged out) can change
// their password via a tokenized link from an email, it should be public.
// If only a logged-in user can change their password, it should be protected.
// I will assume it's for tokenized links and make it public.
meta: { requiresAuth: false }
},
]
export default authRoutes
//sourceMappingURL=index.ts.map
export default authRoutes;

View File

@@ -40,14 +40,14 @@
<!-- Name on Card -->
<div class="form-control">
<label class="label"><span class="label-text font-bold">Name on Card</span></label>
<input v-model="CardForm.card_name" type="text" placeholder="John M. Doe" class="input input-bordered input-sm w-full" />
<span v-if="v$.CardForm.card_name.$error" class="text-red-500 text-xs mt-1">Required.</span>
<input v-model="CardForm.name_on_card" type="text" placeholder="" class="input input-bordered input-sm w-full" />
<span v-if="v$.CardForm.name_on_card.$error" class="text-red-500 text-xs mt-1">Required.</span>
</div>
<!-- Card Number -->
<div class="form-control">
<label class="label"><span class="label-text font-bold">Card Number</span></label>
<input v-model="CardForm.card_number" type="text" placeholder="4242..." class="input input-bordered input-sm w-full" />
<input v-model="CardForm.card_number" type="text" placeholder="" class="input input-bordered input-sm w-full" />
<span v-if="v$.CardForm.card_number.$error" class="text-red-500 text-xs mt-1">Required.</span>
</div>
@@ -70,7 +70,7 @@
<!-- Security Number (CVV) -->
<div class="form-control">
<label class="label"><span class="label-text font-bold">CVV</span></label>
<input v-model="CardForm.security_number" type="text" placeholder="123" class="input input-bordered input-sm w-full" />
<input v-model="CardForm.security_number" type="text" placeholder="" class="input input-bordered input-sm w-full" />
<span v-if="v$.CardForm.security_number.$error" class="text-red-500 text-xs mt-1">Required.</span>
</div>
@@ -90,7 +90,7 @@
<!-- Billing Zip Code -->
<div class="form-control">
<label class="label"><span class="label-text font-bold">Billing Zip Code</span></label>
<input v-model="CardForm.zip_code" type="text" placeholder="01234" class="input input-bordered input-sm w-full" />
<input v-model="CardForm.zip_code" type="text" placeholder="" class="input input-bordered input-sm w-full" />
</div>
<!-- Main Card Checkbox -->
@@ -134,7 +134,7 @@ export default defineComponent({
customer: {} as any,
// --- REFACTORED: Simplified, flat form object ---
CardForm: {
card_name: '',
name_on_card: '',
expiration_month: '',
expiration_year: '',
type_of_card: '',
@@ -149,7 +149,7 @@ export default defineComponent({
return {
// --- REFACTORED: Validation points to the flat form object ---
CardForm: {
card_name: { required, minLength: minLength(1) },
name_on_card: { required, minLength: minLength(1) },
expiration_month: { required },
expiration_year: { required },
security_number: { required, minLength: minLength(1) },

View File

@@ -36,8 +36,8 @@
<h3 class="text-xl font-bold mb-4">Editing Card</h3>
<div v-if="card.id" class="space-y-2">
<p><strong class="font-semibold">Card Type:</strong> {{ card.type_of_card }}</p>
<p><strong class="font-semibold">Card Number:</strong> **** **** **** {{ card.last_four_digits }}</p>
<p><strong class="font-semibold">Card ID:</strong> {{ card.id }}</p>
<p><strong class="font-semibold">Card Number:</strong> {{ card.card_number }}</p>
</div>
<div v-else class="text-gray-400">Loading card details...</div>
</div>
@@ -52,14 +52,14 @@
<!-- Name on Card -->
<div class="form-control">
<label class="label"><span class="label-text font-bold">Name on Card</span></label>
<input v-model="CardForm.name_on_card" type="text" placeholder="John M Doe" class="input input-bordered input-sm w-full" />
<input v-model="CardForm.name_on_card" type="text" placeholder="" class="input input-bordered input-sm w-full" />
<span v-if="v$.CardForm.name_on_card.$error" class="text-red-500 text-xs mt-1">Required.</span>
</div>
<!-- Card Number -->
<div class="form-control">
<label class="label"><span class="label-text font-bold">Card Number</span></label>
<input v-model="CardForm.card_number" type="text" placeholder="4242..." class="input input-bordered input-sm w-full" />
<input v-model="CardForm.card_number" type="text" placeholder="" class="input input-bordered input-sm w-full" />
<span v-if="v$.CardForm.card_number.$error" class="text-red-500 text-xs mt-1">Required.</span>
</div>
@@ -82,7 +82,7 @@
<!-- Security Number (CVV) -->
<div class="form-control">
<label class="label"><span class="label-text font-bold">CVV</span></label>
<input v-model="CardForm.security_number" type="text" placeholder="123" class="input input-bordered input-sm w-full" />
<input v-model="CardForm.security_number" type="text" placeholder="" class="input input-bordered input-sm w-full" />
<span v-if="v$.CardForm.security_number.$error" class="text-red-500 text-xs mt-1">Required.</span>
</div>
@@ -102,7 +102,7 @@
<!-- Billing Zip Code -->
<div class="form-control">
<label class="label"><span class="label-text font-bold">Billing Zip Code</span></label>
<input v-model="CardForm.zip_code" type="text" placeholder="01234" class="input input-bordered input-sm w-full" />
<input v-model="CardForm.zip_code" type="text" placeholder="" class="input input-bordered input-sm w-full" />
</div>
<!-- Main Card Checkbox -->
@@ -188,53 +188,59 @@ export default defineComponent({
axios.get(path, { headers: authHeader() })
.then((response: any) => { this.customer = response.data; });
},
getCard(card_id: any) {
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`;
axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
this.card = response.data; // Store original details for display
// Populate the flat form object for editing
this.CardForm.name_on_card = response.data.name_on_card;
this.CardForm.expiration_month = response.data.expiration_month;
this.CardForm.expiration_year = response.data.expiration_year;
this.CardForm.type_of_card = response.data.type_of_card;
this.CardForm.security_number = response.data.security_number;
this.CardForm.main_card = response.data.main_card;
this.CardForm.card_number = response.data.card_number;
this.CardForm.zip_code = response.data.zip_code;
if (response.data.user_id) {
this.getCustomer(response.data.user_id);
}
});
},
editCard(payload: any) {
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/edit/${this.$route.params.id}`;
// The backend expects 'card_name', but our form now uses 'name_on_card'.
// We must create a new payload that matches the backend's expectation.
const backendPayload = {
...payload,
card_name: payload.name_on_card,
};
delete backendPayload.name_on_card; // Clean up the object
axios.put(path, backendPayload, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
if (response.data.ok) {
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
} else {
console.error("Failed to edit card:", response.data.error);
}
});
},
onSubmit() {
this.v$.$validate();
if (!this.v$.$error) {
this.editCard(this.CardForm);
} else {
console.log("Form validation failed.");
getCard(card_id: any) {
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`;
axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
this.card = response.data; // Store original details for display
// Populate the flat form object for editing
this.CardForm.name_on_card = response.data.name_on_card;
// --- FIX IS HERE ---
// Convert the month number (e.g., 8) to a zero-padded string ("08") to match the <option> value.
this.CardForm.expiration_month = String(response.data.expiration_month).padStart(2, '0');
// Convert the year number (e.g., 2025) to a string ("2025") for consistency.
this.CardForm.expiration_year = String(response.data.expiration_year);
// --- END FIX ---
this.CardForm.type_of_card = response.data.type_of_card;
this.CardForm.security_number = response.data.security_number;
this.CardForm.main_card = response.data.main_card;
this.CardForm.card_number = response.data.card_number;
this.CardForm.zip_code = response.data.zip_code;
if (response.data.user_id) {
this.getCustomer(response.data.user_id);
}
},
});
},
editCard(payload: any) {
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/edit/${this.$route.params.id}`;
// REMOVE the payload manipulation. Send the form data directly.
// The 'payload' object (which is this.CardForm) is already in the correct format.
axios.put(path, payload, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
if (response.data.ok) {
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
} else {
// You should notify the user here as well
console.error("Failed to edit card:", response.data.error);
}
})
.catch(console.log("error"));
},
onSubmit() {
this.v$.$validate();
if (!this.v$.$error) {
this.editCard(this.CardForm); // This is correct, it sends the form object.
} else {
console.log("Form validation failed.");
}
},
},
});
</script>

View File

@@ -6,6 +6,7 @@
<ul>
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
<li>Create New Customer</li>
</ul>
</div>

View File

@@ -6,7 +6,11 @@
<ul>
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
<li>Edit Customer</li>
<li v-if="customer && customer.id">
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }">
{{ customer.customer_first_name }} {{ customer.customer_last_name }}
</router-link>
</li>
</ul>
</div>

View File

@@ -12,10 +12,17 @@
<div class="xl:col-span-8 space-y-6">
<div class="grid grid-cols-1 xl:grid-cols-12 gap-6">
<ProfileMap
class="xl:col-span-7"
:customer="customer"
/>
<ProfileMap
v-if="customer && customer.customer_latitude != null && customer.customer_longitude != null"
class="xl:col-span-7"
:customer="customer"
/>
<!-- You can add a placeholder for when the map isn't ready -->
<div v-else class="xl:col-span-7 bg-base-100 rounded-lg flex justify-center items-center">
<p class="text-gray-400">Location not available...</p>
</div>
<ProfileSummary
class="xl:col-span-5"
:customer="customer"
@@ -428,18 +435,30 @@ export default defineComponent({
editCard(card_id: any) {
this.$router.push({ name: "cardedit", params: { id: card_id } });
},
removeCard(card_id: any) {
let path = import.meta.env.VITE_BASE_URL + '/payment/card/remove/' + card_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then(() => {
this.getCreditCards(this.customer.user_id)
this.getCreditCardsCount(this.customer.user_id)
notify({ title: "Card Status", text: "Card Removed", type: "Success" });
})
},
removeCard(card_id: any) {
let path = import.meta.env.VITE_BASE_URL + '/payment/card/remove/' + card_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then(() => {
// --- EFFICIENT FIX: Manipulate the local array directly ---
// 1. Filter the 'credit_cards' array to remove the card with the matching id.
this.credit_cards = this.credit_cards.filter(card => card.id !== card_id);
// 2. Decrement the count.
this.credit_cards_count--;
// --- END EFFICIENT FIX ---
notify({ title: "Card Status", text: "Card Removed", type: "Success" });
})
.catch(() => {
notify({ title: "Error", text: "Could not remove card.", type: "error" });
});
},
deleteCall(delivery_id: any) {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({

View File

@@ -68,8 +68,8 @@
<!-- Fill Location -->
<div class="form-control md:col-span-2">
<label class="label"><span class="label-text">Fill Location Description</span></label>
<input v-model="TankForm.fill_location" type="text" placeholder="e.g., Left side of house, behind shed" class="input input-bordered input-sm w-full" />
<label class="label"><span class="label-text">Fill Location </span></label>
<input v-model="TankForm.fill_location" type="text" placeholder="0-12 only" class="input input-bordered input-sm w-full" />
</div>
</div>

View File

@@ -2,210 +2,260 @@
<div class="flex">
<div class="w-full px-10">
<!-- Main container with reduced horizontal padding -->
<div class="w-full px-4 md:px-6 py-4">
<div class="text-sm breadcrumbs">
<ul>
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
<li>Create Delivery for {{ customer.customer_first_name }}</li>
<li v-if="customer && customer.id">
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }">
{{ customer.customer_first_name }} {{ customer.customer_last_name }}
</router-link>
</li>
</ul>
</div>
<!-- TOP SECTION: Customer Info and Pricing Chart -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
<!-- Customer Info Card -->
<div class="bg-neutral rounded-lg p-5">
<div class="flex justify-between items-center mb-4">
<div>
<div class="text-xl font-bold">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</div>
<div class="text-sm text-gray-400">Account: {{ customer.account_number }}</div>
</div>
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
View Profile
</router-link>
</div>
<div>
<div>{{ customer.customer_address }}</div>
<div v-if="customer.customer_apt && customer.customer_apt !== 'None'">{{ customer.customer_apt }}</div>
<div>{{ customer.customer_town }}, {{ customerStateName }} {{ customer.customer_zip }}</div>
<div class="text-sm text-gray-400 mt-1">{{ customerHomeTypeName }}</div>
<div class="mt-2">{{ customer.customer_phone_number }}</div>
</div>
</div>
<!--
NEW LAYOUT: A single 2-column grid for the whole page.
Gaps and spacing are reduced for a more compact feel.
-->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mt-4">
<!-- Pricing Chart Card -->
<div class="bg-neutral rounded-lg p-5">
<h3 class="text-xl font-bold mb-4">Today's Price Per Gallon</h3>
<div class="overflow-x-auto">
<table class="table table-sm w-full">
<thead>
<tr>
<th>Gallons</th>
<th>Total Price</th>
</tr>
</thead>
<tbody>
<tr v-for="tier in pricingTiers" :key="tier.gallons" class="hover">
<!-- Access properties of the 'tier' object -->
<td>{{ tier.gallons }}</td>
<td>${{ parseFloat(tier.price.toString()).toFixed(2) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- BOTTOM SECTION: Forms -->
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
<!-- Create Delivery Form -->
<div class="p-6 ">
<h2 class="text-2xl font-bold mb-4">Create Delivery Order</h2>
<form class="space-y-4" @submit.prevent="onDeliverySubmit">
<!-- Gallons & Fill -->
<div>
<label class="label"><span class="label-text font-bold">Gallons Ordered</span></label>
<input v-model="formDelivery.gallons_ordered" :disabled="formDelivery.customer_asked_for_fill"
class="input input-bordered input-sm w-full max-w-xs" type="number" placeholder="# gallons" />
<span v-if="v$.formDelivery.gallons_ordered.$error" class="text-red-500 text-xs mt-1">
Gallons are required unless "Fill" is checked.
</span>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-4">
<span class="label-text font-bold">Customer Asked for Fill</span>
<input v-model="formDelivery.customer_asked_for_fill" type="checkbox" class="checkbox checkbox-sm" />
</label>
</div>
<!-- Payment Section -->
<div class="p-4 border rounded-md space-y-3">
<label class="label-text font-bold">Payment Method</label>
<div class="flex flex-wrap gap-x-6 gap-y-2">
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Cash</span><input v-model="formDelivery.cash" type="checkbox" class="checkbox checkbox-xs" /></label></div>
<div v-if="userCards.length > 0" class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Credit</span><input v-model="formDelivery.credit" type="checkbox" class="checkbox checkbox-xs" /></label></div>
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Check</span><input v-model="formDelivery.check" type="checkbox" class="checkbox checkbox-xs" /></label></div>
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Other</span><input v-model="formDelivery.other" type="checkbox" class="checkbox checkbox-xs" /></label></div>
<!-- LEFT COLUMN: Primary Information & Actions -->
<div class="space-y-4">
<!-- Customer Info Card -->
<div class="bg-neutral rounded-lg p-5">
<div class="flex justify-between items-center mb-4">
<div>
<div class="text-xl font-bold">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</div>
<div class="text-sm text-gray-400">Account: {{ customer.account_number }}</div>
</div>
<div v-if="userCards.length > 0 && formDelivery.credit">
<label class="label"><span class="label-text">Select Card</span></label>
<select class="select select-bordered select-sm w-full max-w-xs" v-model="formDelivery.credit_card_id">
<option disabled :value="0">Select a card</option>
<option v-for="card in userCards" :key="card.id" :value="card.id">
{{ card.type_of_card }} - **** {{ card.last_four_digits }}
</option>
<router-link v-if="customer && customer.id" :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
View Profile
</router-link>
</div>
<div>
<div>{{ customer.customer_address }}</div>
<div v-if="customer.customer_apt && customer.customer_apt !== 'None'">{{ customer.customer_apt }}</div>
<div>{{ customer.customer_town }}, {{ customerStateName }} {{ customer.customer_zip }}</div>
<div class="mt-2">{{ customer.customer_phone_number }}</div>
</div>
</div>
<!-- Create Delivery Form (now in the left column) -->
<div class="bg-base-100 rounded-lg p-4">
<h2 class="text-2xl font-bold mb-4">Create Delivery Order</h2>
<form class="space-y-4" @submit.prevent="onDeliverySubmit">
<!-- Gallons & Fill -->
<div>
<label class="label"><span class="label-text font-bold">Gallons Ordered</span></label>
<input v-model="formDelivery.gallons_ordered" :disabled="formDelivery.customer_asked_for_fill"
class="input input-bordered input-sm w-full max-w-xs" type="number" placeholder="# gallons" />
<div class="flex flex-wrap gap-2 mt-2">
<button v-for="amount in quickGallonAmounts" :key="amount" @click.prevent="setGallons(amount)" class="btn btn-xs btn-outline">{{ amount }} gal</button>
</div>
<span v-if="v$.formDelivery.gallons_ordered.$error" class="text-red-500 text-xs mt-1">
Required unless "Fill" is checked.
</span>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-4">
<span class="label-text font-bold">Fill</span>
<input v-model="formDelivery.customer_asked_for_fill" type="checkbox" class="checkbox checkbox-sm" />
</label>
</div>
<!-- Payment Section -->
<div class="p-4 border rounded-md space-y-3">
<label class="label-text font-bold">Payment Method</label>
<div class="flex flex-wrap gap-x-6 gap-y-2">
<div v-if="userCards.length > 0" class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Credit</span><input v-model="formDelivery.credit" type="checkbox" class="checkbox checkbox-xs" /></label></div>
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Cash</span><input v-model="formDelivery.cash" type="checkbox" class="checkbox checkbox-xs" /></label></div>
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Check</span><input v-model="formDelivery.check" type="checkbox" class="checkbox checkbox-xs" /></label></div>
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Other</span><input v-model="formDelivery.other" type="checkbox" class="checkbox checkbox-xs" /></label></div>
</div>
<div v-if="userCards.length > 0 && formDelivery.credit">
<label class="label"><span class="label-text">Select Card</span></label>
<select class="select select-bordered select-sm w-full max-w-xs" v-model="formDelivery.credit_card_id">
<option disabled :value="0">Select a card</option>
<option v-for="card in userCards" :key="card.id" :value="card.id">
{{ card.type_of_card }} - **** {{ card.last_four_digits }}
</option>
</select>
</div>
<div v-if="userCards.length === 0" class="text-sm text-warning">No cards on file for credit payment.</div>
</div>
<!-- Date, Driver, Promo -->
<div>
<label class="label"><span class="label-text font-bold">Expected Delivery Date</span></label>
<input v-model="formDelivery.expected_delivery_date" class="input input-bordered input-sm w-full max-w-xs" type="date" />
<span v-if="v$.formDelivery.expected_delivery_date.$error" class="text-red-500 text-xs mt-1">Date is required.</span>
</div>
<div>
<label class="label"><span class="label-text font-bold">Assigned Driver</span></label>
<select class="select select-bordered select-sm w-full max-w-xs" v-model="formDelivery.driver_employee_id">
<option disabled value="">Select a driver</option>
<option v-for="driver in truckDriversList" :key="driver.id" :value="driver.id">
{{ driver.employee_first_name }} {{ driver.employee_last_name }}
</option>
</select>
</div>
<div>
<label class="label"><span class="label-text font-bold">Apply Promotion</span></label>
<select class="select select-bordered select-sm w-full max-w-xs" v-model="formDelivery.promo_id">
<option :value="0">No Promotion</option>
<option v-for="promo in promos" :key="promo.id" :value="promo.id">
{{ promo.name_of_promotion }} (${{ promo.money_off_delivery }} off)
</option>
</select>
</div>
<div v-if="userCards.length === 0" class="text-sm text-warning">No cards on file for credit payment.</div>
</div>
<!-- Date, Driver, Promo -->
<div>
<label class="label"><span class="label-text font-bold">Expected Delivery Date</span></label>
<input v-model="formDelivery.expected_delivery_date" class="input input-bordered input-sm w-full max-w-xs" type="date" />
<span v-if="v$.formDelivery.expected_delivery_date.$error" class="text-red-500 text-xs mt-1">Date is required.</span>
</div>
<div>
<label class="label"><span class="label-text font-bold">Assigned Driver</span></label>
<select class="select select-bordered select-sm w-full max-w-xs" v-model="formDelivery.driver_employee_id">
<option disabled value="">Select a driver</option>
<option v-for="driver in truckDriversList" :key="driver.id" :value="driver.id">
{{ driver.employee_first_name }} {{ driver.employee_last_name }}
</option>
</select>
</div>
<div>
<label class="label"><span class="label-text font-bold">Apply Promotion</span></label>
<select class="select select-bordered select-sm w-full max-w-xs" v-model="formDelivery.promo_id">
<option :value="0">No Promotion</option>
<option v-for="promo in promos" :key="promo.id" :value="promo.id">
{{ promo.name_of_promotion }} (${{ promo.money_off_delivery }} off)
</option>
</select>
</div>
<!-- Fees -->
<div class="p-4 border rounded-md">
<label class="label-text font-bold">Fees & Options</label>
<div class="flex flex-wrap gap-x-6 gap-y-2">
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Emergency</span><input v-model="formDelivery.emergency" type="checkbox" class="checkbox checkbox-xs" /></label></div>
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Prime</span><input v-model="formDelivery.prime" type="checkbox" class="checkbox checkbox-xs" /></label></div>
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Same Day</span><input v-model="formDelivery.same_day" type="checkbox" class="checkbox checkbox-xs" /></label></div>
</div>
</div>
<!-- Fees -->
<div class="p-4 border rounded-md">
<label class="label-text font-bold">Fees & Options</label>
<div class="flex flex-wrap gap-x-6 gap-y-2">
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Emergency</span><input v-model="formDelivery.emergency" type="checkbox" class="checkbox checkbox-xs" /></label></div>
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Prime</span><input v-model="formDelivery.prime" type="checkbox" class="checkbox checkbox-xs" /></label></div>
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Same Day</span><input v-model="formDelivery.same_day" type="checkbox" class="checkbox checkbox-xs" /></label></div>
</div>
</div>
<!-- Notes -->
<div>
<label class="label"><span class="label-text font-bold">Dispatcher Notes</span></label>
<textarea v-model="formDelivery.dispatcher_notes_taken" rows="3" class="textarea textarea-bordered w-full" placeholder="Notes for the driver..."></textarea>
</div>
<!-- Notes -->
<div>
<label class="label"><span class="label-text font-bold">Dispatcher Notes</span></label>
<textarea v-model="formDelivery.dispatcher_notes_taken" rows="3" class="textarea textarea-bordered w-full" placeholder="Notes for the driver..."></textarea>
</div>
<button type="submit" class="btn btn-primary btn-sm">Create Delivery</button>
</form>
</div>
<button type="submit" class="btn btn-primary btn-sm">Create Delivery</button>
</form>
</div>
<!-- Add Credit Card Form -->
<div class="p-6 ">
<h2 class="text-2xl font-bold mb-4">Add a Credit Card</h2>
<form class="space-y-3" @submit.prevent="onCardSubmit">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- RIGHT COLUMN: Reference Information & Secondary Actions -->
<div class="space-y-4">
<!-- Pricing Chart Card -->
<div class="bg-neutral rounded-lg p-5">
<h3 class="text-xl font-bold mb-4">Today's Price Per Gallon</h3>
<div class="overflow-x-auto">
<table class="table table-sm w-full">
<thead>
<tr>
<th>Gallons</th>
<th>Total Price</th>
</tr>
</thead>
<tbody>
<tr v-for="tier in pricingTiers" :key="tier.gallons" class="hover">
<td>{{ tier.gallons }}</td>
<td>${{ Number(tier.price).toFixed(2) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Credit Cards Display -->
<div v-if="customer && customer.id" class="bg-neutral rounded-lg p-5">
<div class="flex justify-between items-center">
<h2 class="text-xl font-bold">Credit Cards</h2>
<router-link :to="{ name: 'cardadd', params: { id: customer.id } }">
<button class="btn btn-xs btn-outline btn-success">Add New</button>
</router-link>
</div>
<div class="mt-2 text-sm" v-if="userCards.length === 0">
<p class="text-warning font-semibold">No cards on file.</p>
</div>
<div class="mt-4 space-y-3">
<div v-for="card in userCards" :key="card.id" class="p-3 rounded-lg border" :class="card.main_card ? 'bg-primary/10 border-primary' : 'bg-base-200 border-base-300'">
<div class="flex justify-between items-start">
<div>
<label class="label"><span class="label-text font-bold">Name on Card</span></label>
<input v-model="formCard.card_name" type="text" placeholder="John M. Doe" class="input input-bordered input-sm w-full" />
<span v-if="v$.formCard.card_name.$error" class="text-red-500 text-xs mt-1">Required</span>
</div>
<div>
<label class="label"><span class="label-text font-bold">Card Number</span></label>
<input v-model="formCard.card_number" type="text" placeholder="4242..." class="input input-bordered input-sm w-full" />
<span v-if="v$.formCard.card_number.$error" class="text-red-500 text-xs mt-1">Required</span>
</div>
<div>
<label class="label"><span class="label-text font-bold">Expiration</span></label>
<div class="flex gap-2">
<select v-model="formCard.expiration_month" class="select select-bordered select-sm w-full">
<option disabled value="">MM</option>
<option v-for="m in 12" :key="m" :value_count="String(m).padStart(2, '0')">{{ String(m).padStart(2, '0') }}</option>
</select>
<select v-model="formCard.expiration_year" class="select select-bordered select-sm w-full">
<option disabled value="">YYYY</option>
<option v-for="y in 10" :key="y" :value_count="(new Date().getFullYear() + y - 1)">{{ new Date().getFullYear() + y - 1 }}</option>
</select>
</div>
<span v-if="v$.formCard.expiration_month.$error || v$.formCard.expiration_year.$error" class="text-red-500 text-xs mt-1">Required</span>
</div>
<div>
<label class="label"><span class="label-text font-bold">CVC / CVV</span></label>
<input v-model="formCard.security_number" type="text" placeholder="123" class="input input-bordered input-sm w-full" />
<span v-if="v$.formCard.security_number.$error" class="text-red-500 text-xs mt-1">Required</span>
</div>
<div>
<label class="label"><span class="label-text font-bold">Card Type</span></label>
<select v-model="formCard.type_of_card" class="select select-bordered select-sm w-full">
<option disabled value="">Select Type</option>
<option>Visa</option><option>MasterCard</option><option>Discover</option><option>American Express</option>
</select>
<span v-if="v$.formCard.type_of_card.$error" class="text-red-500 text-xs mt-1">Required</span>
</div>
<div>
<label class="label"><span class="label-text font-bold">Billing Zip Code</span></label>
<input v-model="formCard.zip_code" type="text" placeholder="01234" class="input input-bordered input-sm w-full" />
<div class="font-bold text-sm">{{ card.name_on_card }}</div>
<div class="text-xs opacity-70">{{ card.type_of_card }}</div>
</div>
<div v-if="card.main_card" class="badge badge-primary badge-sm">Primary</div>
</div>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-4">
<span class="label-text font-bold">Set as Main Card</span>
<input v-model="formCard.main_card" type="checkbox" class="checkbox checkbox-sm" />
</label>
<div class="mt-2 text-sm font-mono tracking-wider">
<p>**** **** **** {{ card.last_four_digits }}</p>
<p>Exp: <span v-if="card.expiration_month < 10">0</span>{{ card.expiration_month }} / {{ card.expiration_year }}</p>
</div>
<div class="flex justify-end gap-2 mt-2">
<a @click.prevent="editCard(card.id)" class="link link-hover text-xs">Edit</a>
<a @click.prevent="removeCard(card.id)" class="link link-hover text-error text-xs">Remove</a>
</div>
</div>
</div>
</div>
<!-- Add Credit Card Form -->
<div class="bg-base-100 rounded-lg p-4">
<h2 class="text-xl font-bold mb-4">Quick Add Card</h2>
<form class="space-y-3" @submit.prevent="onCardSubmit">
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label class="label py-1"><span class="label-text">Name on Card</span></label>
<input v-model="formCard.card_name" type="text" class="input input-bordered input-sm w-full" />
<span v-if="v$.formCard.card_name.$error" class="text-red-500 text-xs mt-1">Required</span>
</div>
<div>
<label class="label py-1"><span class="label-text">Card Number</span></label>
<input v-model="formCard.card_number" type="text" class="input input-bordered input-sm w-full" />
<span v-if="v$.formCard.card_number.$error" class="text-red-500 text-xs mt-1">Required</span>
</div>
<div>
<label class="label py-1"><span class="label-text">Expiration</span></label>
<div class="flex gap-2">
<select v-model="formCard.expiration_month" class="select select-bordered select-sm w-full"><option disabled value="">MM</option><option v-for="m in 12" :key="m">{{ String(m).padStart(2, '0') }}</option></select>
<select v-model="formCard.expiration_year" class="select select-bordered select-sm w-full"><option disabled value="">YYYY</option><option v-for="y in 10" :key="y">{{ new Date().getFullYear() + y - 1 }}</option></select>
</div>
<span v-if="v$.formCard.expiration_month.$error || v$.formCard.expiration_year.$error" class="text-red-500 text-xs mt-1">Required</span>
</div>
<div>
<label class="label py-1"><span class="label-text">CVC</span></label>
<input v-model="formCard.security_number" type="text" class="input input-bordered input-sm w-full" />
<span v-if="v$.formCard.security_number.$error" class="text-red-500 text-xs mt-1">Required</span>
</div>
<div>
<label class="label py-1"><span class="label-text">Card Type</span></label>
<select v-model="formCard.type_of_card" class="select select-bordered select-sm w-full"><option disabled value="">Select Type</option><option>Visa</option><option>MasterCard</option><option>Discover</option><option>American Express</option></select>
<span v-if="v$.formCard.type_of_card.$error" class="text-red-500 text-xs mt-1">Required</span>
</div>
<div>
<label class="label py-1"><span class="label-text">Billing Zip</span></label>
<input v-model="formCard.zip_code" type="text" class="input input-bordered input-sm w-full" />
</div>
</div>
<button type="submit" class="btn btn-secondary btn-sm">Save Credit Card</button>
</form>
<div class="form-control">
<label class="label cursor-pointer justify-start gap-4 py-1">
<span class="label-text">Set as Main Card</span>
<input v-model="formCard.main_card" type="checkbox" class="checkbox checkbox-sm" />
</label>
</div>
<button type="submit" class="btn btn-secondary btn-sm">Save Card</button>
</form>
</div>
</div>
</div>
</div>
</div>
<div class="modal" :class="{ 'modal-open': isConfirmationModalVisible }">
<div class="modal-box">
<h3 class="font-bold text-lg">Confirm Payment Method</h3>
<p class="py-4">You have selected a non-standard payment method (Cash or Check). Please confirm you wish to proceed.</p>
<div class="modal-action">
<button @click="proceedWithSubmission" class="btn btn-primary">Confirm & Create</button>
<button @click="isConfirmationModalVisible = false" class="btn">Cancel</button>
</div>
</div>
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
@@ -235,10 +285,15 @@ interface Customer {
customer_address: string;
account_number: string;
}
// FIX: Updated UserCard interface to include all necessary display properties
interface UserCard {
id: number;
name_on_card: string;
type_of_card: string;
last_four_digits: string;
expiration_month: number;
expiration_year: number;
main_card: boolean;
}
interface Promo {
id: number;
@@ -250,12 +305,10 @@ interface Driver {
employee_first_name: string;
employee_last_name: string;
}
interface PricingTier { // <-- CHANGED: New interface for a single tier
interface PricingTier {
gallons: number | string;
price: number | string;
}
// --- Define types for your FLAT form models to match the new template ---
interface DeliveryFormData {
gallons_ordered: string;
customer_asked_for_fill: boolean;
@@ -290,12 +343,12 @@ export default defineComponent({
return {
v$: useValidate(),
user: null as any,
checked: false,
quickGallonAmounts: [100, 125, 150, 200, 220],
userCards: [] as UserCard[],
promos: [] as Promo[],
truckDriversList: [] as Driver[],
pricingTiers: [] as PricingTier[],
// --- FIX: Use flat form objects that match the template ---
pricingTiers: [] as PricingTier[],
isConfirmationModalVisible: false,
formDelivery: {
gallons_ordered: '',
customer_asked_for_fill: false,
@@ -327,7 +380,6 @@ export default defineComponent({
},
validations() {
return {
// --- FIX: Validation targets the new flat form objects ---
formDelivery: {
gallons_ordered: { required: requiredIf(function(this: any) {
return !this.formDelivery.customer_asked_for_fill;
@@ -379,13 +431,21 @@ export default defineComponent({
this.getPaymentCards(customerId);
},
methods: {
setGallons(amount: number) {
this.formDelivery.gallons_ordered = String(amount);
this.formDelivery.customer_asked_for_fill = false;
},
getPricingTiers() {
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
.then((response: SimpleResponse<PricingTier[]>) => {
this.pricingTiers = response.data;
.then((response: SimpleResponse<{ [key: string]: string }>) => {
const tiersObject = response.data;
this.pricingTiers = Object.entries(tiersObject).map(([gallons, price]) => ({
gallons: parseInt(gallons, 10),
price: price,
}));
})
.catch((error: unknown) => {
.catch(() => {
notify({ title: "Pricing Error", text: "Could not retrieve today's pricing.", type: "error" });
});
},
@@ -395,7 +455,7 @@ export default defineComponent({
.then((response: SimpleResponse<Customer>) => {
this.customer = response.data;
})
.catch((error: unknown) => {
.catch(() => {
notify({ title: "Error", text: "Could not find customer", type: "error" });
});
},
@@ -405,7 +465,7 @@ export default defineComponent({
.then((response: SimpleResponse<UserCard[]>) => {
this.userCards = response.data;
})
.catch((error: unknown) => { /* empty */ });
.catch(() => { /* empty */ });
},
getPromos() {
let path = import.meta.env.VITE_BASE_URL + "/promo/all";
@@ -413,7 +473,7 @@ export default defineComponent({
.then((response: SimpleResponse<Promo[]>) => {
this.promos = response.data;
})
.catch((error: unknown) => { /* empty */ });
.catch(() => { /* empty */ });
},
getDriversList() {
let path = import.meta.env.VITE_BASE_URL + "/employee/drivers";
@@ -421,11 +481,45 @@ export default defineComponent({
.then((response: SimpleResponse<Driver[]>) => {
this.truckDriversList = response.data;
})
.catch((error: unknown) => { /* empty */ });
.catch(() => { /* empty */ });
},
// --- FIX: New method to navigate to the card edit page ---
editCard(card_id: number) {
this.$router.push({ name: "cardedit", params: { id: card_id } });
},
// --- FIX: New method to handle removing a card ---
removeCard(card_id: number) {
if (window.confirm("Are you sure you want to remove this card?")) {
let path = `${import.meta.env.VITE_BASE_URL}/payment/card/remove/${card_id}`;
axios.delete(path, { headers: authHeader() })
.then(() => {
notify({ title: "Card Removed", type: "success" });
// Refresh the card list after deletion
this.getPaymentCards(this.customer.id);
})
.catch(() => {
notify({ title: "Error", text: "Could not remove card.", type: "error" });
});
}
},
// --- FIX: Renamed to match template's @submit event ---
onDeliverySubmit() {
this.v$.formDelivery.$validate();
if (this.v$.formDelivery.$error) {
notify({ title: "Validation Error", text: "Please fill out all required fields.", type: "error" });
return;
}
if (this.formDelivery.cash || this.formDelivery.check) {
this.isConfirmationModalVisible = true;
} else {
this.proceedWithSubmission();
}
},
proceedWithSubmission() {
this.isConfirmationModalVisible = false;
let payload = {
gallons_ordered: this.formDelivery.gallons_ordered,
customer_asked_for_fill: this.formDelivery.customer_asked_for_fill,
@@ -443,30 +537,32 @@ export default defineComponent({
driver_employee_id: this.formDelivery.driver_employee_id,
};
let pass = 0;
if (payload.driver_employee_id === '') {
notify({ title: "Error", text: "Please assign a driver.", type: "error" });
pass += 1;
return;
}
if (!payload.cash && !payload.credit && !payload.check && !payload.other) {
notify({ title: "Error", text: "Please select a payment method.", type: "error" });
pass += 1;
}
if (pass === 0) {
let path = import.meta.env.VITE_BASE_URL + "/delivery/create/" + this.customer.id;
axios({ method: "post", url: path, data: payload, withCredentials: true, headers: authHeader() })
.then((response: SimpleResponse<{ ok: boolean; delivery_id: number; error?: string }>) => {
if (response.data.ok) {
this.$router.push({ name: "payOil", params: { id: response.data.delivery_id } });
} else {
this.$router.push("/");
}
});
return;
}
let path = `${import.meta.env.VITE_BASE_URL}/delivery/create/${this.customer.id}`;
axios({ method: "post", url: path, data: payload, withCredentials: true, headers: authHeader() })
.then((response: SimpleResponse<{ ok: boolean; delivery_id: number }>) => {
if (response.data.ok) {
this.$router.push({ name: "payOil", params: { id: response.data.delivery_id } });
} else {
this.$router.push("/");
}
});
},
// --- FIX: Renamed to match template's @submit event ---
onCardSubmit() {
this.v$.formCard.$validate();
if (this.v$.formCard.$error) {
notify({ title: "Validation Error", text: "Please fill out all card fields.", type: "error" });
return;
}
let payload = {
card_name: this.formCard.card_name,
card_number: this.formCard.card_number,
@@ -478,13 +574,12 @@ export default defineComponent({
zip_code: this.formCard.zip_code,
};
let path = import.meta.env.VITE_BASE_URL + "/payment/card/create/" + this.customer.id;
let path = `${import.meta.env.VITE_BASE_URL}/payment/card/create/${this.customer.id}`;
axios({ method: "post", url: path, data: payload, withCredentials: true, headers: authHeader() })
.then((response: SimpleResponse<{ ok: boolean; error?: string }>) => {
.then((response: SimpleResponse<{ ok: boolean }>) => {
if (response.data.ok) {
notify({ type: 'success', title: 'Card Saved!' });
this.getPaymentCards(this.$route.params.id);
// Optional: Reset form after successful submission
Object.assign(this.formCard, { card_name: '', card_number: '', expiration_month: '', expiration_year: '', type_of_card: '', security_number: '', zip_code: '', main_card: false });
this.v$.formCard.$reset();
} else {
@@ -494,7 +589,4 @@ export default defineComponent({
},
},
})
</script>

View File

@@ -23,7 +23,11 @@
<div class="text-xl font-bold">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</div>
<div class="text-sm text-gray-400">Account: {{ customer.account_number }}</div>
</div>
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
<!--
FIX #1: Add a v-if guard.
This prevents the link from rendering until `customer.user_id` has a valid, non-zero value.
-->
<router-link v-if="customer " :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
View Profile
</router-link>
</div>
@@ -75,6 +79,7 @@
<span v-else-if="deliveryOrder.delivery_status == 4">Partial Delivery</span>
<span v-else-if="deliveryOrder.delivery_status == 5">Misdelivery</span>
<span v-else-if="deliveryOrder.delivery_status == 6">Unknown</span>
<span v-else-if="deliveryOrder.delivery_status == 9">Pending</span>
<span v-else-if="deliveryOrder.delivery_status == 10">Finalized</span>
</div>
</div>
@@ -144,28 +149,50 @@
</div>
</div>
<!-- Right Column: Payment, Notes, Actions -->
<div class="space-y-4">
<!-- Payment -->
<div class="p-4 border rounded-md">
<label class="label-text font-bold">Payment Method</label>
<div class="mt-1">
<div class="text-lg">
<span v-if="deliveryOrder.payment_type == 0">Cash</span>
<span v-else-if="deliveryOrder.payment_type == 1">Credit Card</span>
<span v-else-if="deliveryOrder.payment_type == 2">Credit Card & Cash</span>
<span v-else-if="deliveryOrder.payment_type == 3">Check</span>
<span v-else-if="deliveryOrder.payment_type == 4">Other</span>
<span v-else>Not Specified</span>
</div>
<div v-if="userCardfound && [1, 2, 3].includes(deliveryOrder.payment_type)" class="bg-base-100 p-3 rounded-md mt-2 text-sm">
<div class="font-mono">{{ userCard.type_of_card }}</div>
<div class="font-mono">{{ userCard.card_number }}</div>
<div>{{ userCard.name_on_card }}</div>
<div>Expires: {{ userCard.expiration_month }}/{{ userCard.expiration_year }}</div>
</div>
</div>
</div>
<div class="space-y-4">
<!--
START: Replaced Payment Section
-->
<div class="p-4 border rounded-md">
<label class="label-text font-bold">Payment Method</label>
<div class="mt-1">
<div class="text-lg">
<span v-if="deliveryOrder.payment_type == 0">Cash</span>
<span v-else-if="deliveryOrder.payment_type == 1">Credit Card</span>
<span v-else-if="deliveryOrder.payment_type == 2">Credit Card & Cash</span>
<span v-else-if="deliveryOrder.payment_type == 3">Check</span>
<span v-else-if="deliveryOrder.payment_type == 4">Other</span>
<span v-else>Not Specified</span>
</div>
<!--
This is the new, styled card display.
It uses the same logic but applies the better CSS classes.
-->
<div v-if="userCardfound && [1, 2, 3].includes(deliveryOrder.payment_type)"
class="p-4 rounded-lg border mt-2"
:class="userCard.main_card ? 'bg-primary/10 border-primary' : 'bg-base-200 border-base-300'">
<div class="flex justify-between items-start">
<div>
<div class="font-bold">{{ userCard.name_on_card }}</div>
<div class="text-xs opacity-70">{{ userCard.type_of_card }}</div>
</div>
<div v-if="userCard.main_card" class="badge badge-primary badge-sm">Primary</div>
</div>
<div class="mt-3 text-sm font-mono tracking-wider">
<!-- Using last_four_digits is more secure and looks cleaner -->
<p>**** **** **** {{ userCard.last_four_digits }}</p>
<p>
Exp:
<!-- Adds a leading zero for single-digit months -->
<span v-if="Number(userCard.expiration_month) < 10">0</span>{{ userCard.expiration_month }} / {{ userCard.expiration_year }}
</p>
</div>
</div>
</div>
</div>
<!-- Notes & Options -->
<div class="p-4 border rounded-md">
@@ -180,8 +207,11 @@
</div>
</div>
<!-- Actions -->
<div class="flex flex-wrap gap-2 pt-4">
<!--
FIX #2: Add a v-if guard to the container.
This prevents the links from rendering until `deliveryOrder.id` is available.
-->
<div v-if="deliveryOrder && deliveryOrder.id" class="flex flex-wrap gap-2 pt-4">
<router-link :to="{ name: 'deliveryEdit', params: { id: deliveryOrder.id } }">
<button class="btn btn-secondary btn-sm">Edit Delivery</button>
</router-link>
@@ -196,7 +226,6 @@
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
@@ -221,7 +250,7 @@ export default defineComponent({
return {
v$: useValidate(),
user: {
id: 0
user_id: 0
},
priceprime: 0,
pricesameday: 0,
@@ -232,19 +261,18 @@ export default defineComponent({
deliveryNotesDriver: [],
userCardfound: false,
userCard: {
date_added: '',
user_id: '',
card_number: '',
last_four_digits: '',
name_on_card: '',
expiration_month: '',
expiration_year: '',
type_of_card: '',
security_number: '',
accepted_or_declined: '',
main_card: '',
},
date_added: '',
user_id: 0, // Should be a number
card_number: '',
last_four_digits: '',
name_on_card: '',
expiration_month: 0, // Initialize as a number
expiration_year: 0, // Initialize as a number
type_of_card: '',
security_number: '',
accepted_or_declined: null, // null is better for optional values
main_card: false, // Should be a boolean
},
customer: {
account_number: '',
id: 0,
@@ -358,7 +386,7 @@ export default defineComponent({
text: "deleted delivery",
type: "success",
});
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
this.$router.push({ name: "customerProfile", params: { id: this.customer.user_id } });
} else {
notify({
title: "Failure",
@@ -379,7 +407,7 @@ export default defineComponent({
.then((response: any) => {
if (response.data.ok) {
this.user = response.data.user;
this.user.id = response.data.user_id;
}
})
},
@@ -445,34 +473,40 @@ export default defineComponent({
}
},
getOilOrder(delivery_id: any) {
let path = import.meta.env.VITE_BASE_URL + "/delivery/order/" + delivery_id;
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data) {
this.deliveryOrder = response.data
this.getCustomer(this.deliveryOrder.customer_id)
if (this.deliveryOrder.payment_type == 1) {
this.getPaymentCard(this.deliveryOrder.payment_card_id);
}
if (this.deliveryOrder.payment_type == 2) {
this.getPaymentCard(this.deliveryOrder.payment_card_id);
}
if (this.deliveryOrder.payment_type == 3) {
this.getPaymentCard(this.deliveryOrder.payment_card_id);
}
if (this.deliveryOrder.promo_id != null) {
this.getPromo(this.deliveryOrder.promo_id);
}
getOilOrder(delivery_id: any) {
if (!delivery_id) { // Add a guard to prevent calls with an undefined ID
console.error("getOilOrder called with no ID.");
return;
}
let path = import.meta.env.VITE_BASE_URL + "/delivery/" + delivery_id;
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
// FIX: Check for the 'ok' flag and access the nested 'delivery' object
if (response.data && response.data.ok) {
this.deliveryOrder = response.data.delivery; // <-- THIS IS THE CRITICAL CHANGE
}
})
},
// Now that this.deliveryOrder is the correct object, the rest of the logic will work.
this.getCustomer(this.deliveryOrder.customer_id);
if ([1, 2, 3].includes(this.deliveryOrder.payment_type)) {
this.getPaymentCard(this.deliveryOrder.payment_card_id);
}
if (this.deliveryOrder.promo_id != null) {
this.getPromo(this.deliveryOrder.promo_id);
}
} else {
console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
notify({ title: "Error", text: "Could not load delivery details.", type: "error" });
}
})
.catch( console.log("")
);
},
getOilOrderMoney(delivery_id: any) {
let path = import.meta.env.VITE_MONEY_URL + "/delivery/order/money/" + delivery_id;
axios({

View File

@@ -15,7 +15,7 @@
<!-- Header: Title and Count -->
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
<h2 class="text-lg font-bold">Archived Cancelled Deliveries</h2>
<div class="badge badge-ghost">{{ recordsLength }} items Found</div>
<!-- <div class="badge badge-ghost">{{ recordsLength }} items Found</div> -->
</div>
<div class="divider"></div>

View File

@@ -15,7 +15,7 @@
<!-- Header: Title and Count -->
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
<h2 class="text-lg font-bold">Deliveries Awaiting Finalization</h2>
<div class="badge badge-ghost">{{ recordsLength }} items Found</div>
<!-- <div class="badge badge-ghost">{{ recordsLength }} items Found</div> -->
</div>
<div class="divider"></div>

View File

@@ -14,7 +14,7 @@
<!-- Header: Title and Count -->
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
<h2 class="text-lg font-bold">Completed and Finalized Deliveries</h2>
<div class="badge badge-ghost">{{ recordsLength }} items Found</div>
<!-- <div class="badge badge-ghost">{{ recordsLength }} items Found</div> -->
</div>
<div class="divider"></div>

View File

@@ -14,7 +14,7 @@
<!-- Header: Title and Count -->
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
<h2 class="text-lg font-bold">Deliveries Requiring Attention</h2>
<div class="badge badge-ghost">{{ recordsLength }} items Found</div>
<!-- <div class="badge badge-ghost">{{ recordsLength }} items Found</div> -->
</div>
<div class="divider"></div>

View File

@@ -13,7 +13,7 @@
<!-- Header: Title and Count -->
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
<h2 class="text-lg font-bold">Deliveries Awaiting Payment</h2>
<div class="badge badge-ghost">{{ recordsLength }} items Found</div>
<!-- <div class="badge badge-ghost">{{ recordsLength }} items Found</div> -->
</div>
<div class="divider"></div>

View File

@@ -17,7 +17,7 @@
<h2 class="text-lg font-bold">Todays Deliveries</h2>
<div class="form-control">
<label class="label pt-1 pb-0">
<span class="label-text-alt">{{ recordsLength }} deliveries found</span>
<!-- <span class="label-text-alt">{{ recordsLength }} deliveries found</span> -->
</label>
</div>
</div>

View File

@@ -13,7 +13,7 @@
<!-- Header: Title and Count (No Search Input) -->
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
<h2 class="text-lg font-bold">Deliveries Scheduled</h2>
<div class="badge badge-ghost">{{ recordsLength }} deliveries found</div>
<!-- <div class="badge badge-ghost">{{ recordsLength }} deliveries found</div> -->
</div>
<div class="divider"></div>

View File

@@ -13,7 +13,7 @@
<!-- Header: Title and Count -->
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
<h2 class="text-lg font-bold">Deliveries Awaiting Dispatch</h2>
<div class="badge badge-ghost">{{ recordsLength }} deliveries found</div>
<!-- <div class="badge badge-ghost">{{ recordsLength }} deliveries found</div> -->
</div>
<div class="divider"></div>

View File

@@ -1,302 +1,157 @@
<template>
<div class="flex">
<div class=" w-full px-10">
<div class="w-full px-4 md:px-10 py-4">
<!-- Breadcrumbs & Title -->
<div class="text-sm breadcrumbs">
<ul>
<li>
<router-link :to="{ name: 'home' }">
Home
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
<!-- Add a link to the customer's profile if the data is available -->
<li v-if="customer && customer.id">
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }">
{{ customer.customer_first_name }} {{ customer.customer_last_name }}
</router-link>
</li>
<li>
</li>
<li>Confirm Payment</li>
</ul>
</div>
<h1 class="text-3xl font-bold mt-4 border-b border-gray-600 pb-2">
Confirm Delivery #{{ delivery.id }}
</h1>
<div class="grid grid-cols-1 rounded-md p-6 mb-5">
<div class=" col-span-12 text-2xl">
Confirm Payment Oil Delivery {{ delivery.id }}
<!-- Main Content Grid -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 my-6">
<!-- LEFT COLUMN: Customer and Delivery Details -->
<div class="space-y-6">
<!-- Customer Info Card -->
<div class="bg-neutral rounded-lg p-5">
<div class="flex justify-between items-center mb-4">
<div>
<div class="text-xl font-bold">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</div>
<div class="text-sm text-gray-400">Account: {{ customer.account_number }}</div>
</div>
<router-link v-if="customer && customer.id" :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
View Profile
</router-link>
</div>
<div class="space-y-1">
<div>{{ customer.customer_address }}</div>
<div v-if="customer.customer_apt && customer.customer_apt !== 'None'">Apt: {{ customer.customer_apt }}</div>
<div>{{ customer.customer_town }}, {{ customer.customer_state === 0 ? 'MA' : 'RI' }} {{ customer.customer_zip }}</div>
<div class="mt-2">{{ customer.customer_phone_number }}</div>
</div>
</div>
<!-- Delivery Details Card -->
<div class="bg-neutral rounded-lg p-5">
<h3 class="text-xl font-bold mb-4">Delivery Details</h3>
<div class="space-y-3">
<div>
<div class="font-bold text-sm">Current Status</div>
<div class="badge" :class="{ 'badge-info': delivery.delivery_status === 0, 'badge-success': delivery.delivery_status === 1, 'badge-warning': delivery.delivery_status > 1 }">
<span v-if="delivery.delivery_status == 0">Waiting</span>
<span v-else-if="delivery.delivery_status == 1">Delivered</span>
<span v-else-if="delivery.delivery_status == 2">Out for Delivery</span>
<span v-else-if="delivery.delivery_status == 3">Cancelled</span>
<span v-else-if="delivery.delivery_status == 4">Partial Delivery</span>
<span v-else-if="delivery.delivery_status == 5">Issue</span>
<span v-else>Pending</span>
</div>
</div>
<div>
<div class="font-bold text-sm">Scheduled Date</div>
<div>{{ delivery.expected_delivery_date }}</div>
</div>
<div>
<div class="font-bold text-sm">Gallons Ordered</div>
<div>
<span v-if="delivery.customer_asked_for_fill == 1" class="badge badge-info">FILL (250 gal estimate)</span>
<span v-else>{{ delivery.gallons_ordered }} Gallons</span>
</div>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-12 mb-5 gap-5">
<div class="col-span-12 xl:col-span-6 ">
<div class="col-span-12 font-bold flex pb-5 text-lg">
{{ customer.account_number }}
</div>
<div class="col-span-12 font-bold flex">
{{ customer.customer_first_name }}
{{ customer.customer_last_name }}
</div>
<div class="col-span-12 font-bold flex">
{{ customer.customer_address }}
<div v-if="customer.customer_apt != 'None'">
{{ customer.customer_apt }}
</div>
</div>
<div class="col-span-12 font-bold flex">
<div class="pr-2">
{{ customer.customer_town }},
</div>
<div class="pr-2">
<div v-if="customer.customer_state == 0">Massachusetts</div>
<div v-else-if="customer.customer_state == 1">Rhode Island</div>
<div v-else-if="customer.customer_state == 2">New Hampshire</div>
<div v-else-if="customer.customer_state == 3">Maine</div>
<div v-else-if="customer.customer_state == 4">Vermont</div>
<div v-else-if="customer.customer_state == 5">Maine</div>
<div v-else-if="customer.customer_state == 6">New York</div>
<div v-else>Unknown state</div>
</div>
<div class="pr-2">
{{ customer.customer_zip }}
</div>
</div>
<div class="col-span-12 font-bold flex" v-if="customer.customer_apt !== 'None'">
{{ customer.customer_apt }}
</div>
<div class="col-span-12 font-bold flex">
<div v-if="customer.customer_home_type == 0">Residential</div>
<div v-else-if="customer.customer_home_type == 1">apartment</div>
<div v-else-if="customer.customer_home_type == 2">condo</div>
<div v-else-if="customer.customer_home_type == 3">commercial</div>
<div v-else-if="customer.customer_home_type == 4">business</div>
<div v-else-if="customer.customer_home_type == 5">construction</div>
<div v-else-if="customer.customer_home_type == 6">container</div>
</div>
<div class="col-span-12 font-bold flex">
{{ customer.customer_phone_number }}
</div>
<!-- RIGHT COLUMN: Payment and Pricing Details -->
<div class="space-y-6">
<div class="col-span-12 ">
<div class="grid grid-cols-12 mb-5">
<div class="col-span-12 pt-10 font-bold">Delivery Status</div>
<div class="col-span-12 text-gray-500">
<div v-if="delivery.delivery_status == 0"> Waiting</div>
<div v-else-if="delivery.delivery_status == 1"> delivered</div>
<div v-else-if="delivery.delivery_status == 2"> Out for Delivery</div>
<div v-else-if="delivery.delivery_status == 3">Cancelled</div>
<div v-else-if="delivery.delivery_status == 4"> Partial Delivery</div>
<div v-else-if="delivery.delivery_status == 5">Issue</div>
<div v-else></div>
<!-- Payment & Pricing Card -->
<div class="bg-neutral rounded-lg p-5">
<h3 class="text-xl font-bold mb-4">Payment & Pricing</h3>
<div class="space-y-4">
<!-- Payment Method -->
<div>
<div class="font-bold text-sm">Payment Method</div>
<div class="text-lg">
<span v-if="delivery.payment_type == 0">Cash</span>
<span v-else-if="delivery.payment_type == 1">Credit Card</span>
<span v-else-if="delivery.payment_type == 2">Credit Card / Cash</span>
<span v-else-if="delivery.payment_type == 3">Check</span>
<span v-else-if="delivery.payment_type == 4">Other</span>
</div>
<div class="col-span-12 pt-3 font-bold">
Expected Delivery:
</div>
<div class="col-span-12 text-gray-500">
{{ delivery.expected_delivery_date }}
</div>
</div>
</div>
</div>
<div class="col-span-12 xl:col-span-6">
<div class="grid grid-cols-12 mb-5 ">
<div v-for="card in credit_cards" class="col-span-12">
<div class="col-span-12 ">
<div v-if="card.main_card" class="basis-1/3 p-2">
<div class="bg-secondary rounded-md border-2 ">
<div class="flex p-3">
{{ card.type_of_card }}
</div>
<div class="flex p-1 pl-4">
{{ card.name_on_card }}
</div>
<div class="flex p-1 pl-4">
{{ card.card_number }}
</div>
<div class="flex p-1 pl-4">
{{ card.expiration_month }}/ {{ card.expiration_year }}
</div>
<div class="flex p-1 pl-4">
{{ card.security_number }}
</div>
</div>
</div>
<div v-else class="basis-1/3 p-2">
<div class=" rounded-md border-2 ">
<div class="flex p-3">
{{ card.type_of_card }}
</div>
<div class="flex p-1 pl-4">
{{ card.name_on_card }}
</div>
<div class="flex p-1 pl-4">
{{ card.card_number }}
</div>
<div class="flex p-1 pl-4">
{{ card.expiration_month }}/ {{ card.expiration_year }}
</div>
<div class="flex p-1 pl-4">
{{ card.security_number }}
</div>
<!-- Show the main card if payment is by credit -->
<div v-if="delivery.payment_type == 1 || delivery.payment_type == 3" class="mt-2">
<div v-for="card in credit_cards" :key="card.id">
<div v-if="card.main_card" class="bg-base-100 p-3 rounded-md text-sm">
<div class="font-mono font-semibold">{{ card.type_of_card }} ending in {{ card.last_four_digits }}</div>
<div>{{ card.name_on_card }}</div>
<div>Expires: {{ card.expiration_month }}/{{ card.expiration_year }}</div>
</div>
</div>
</div>
</div>
</div>
<div class="grid grid-cols-12 mb-5">
<div class="col-span-12 pt-3 font-bold">
Payment Type:
</div>
<div class="col-span-12 text-gray-500" v-if="delivery.payment_type == 0">
Cash
</div>
<div class="col-span-12 text-gray-500" v-if="delivery.payment_type == 1">
Credit Card
</div>
<div class="col-span-12 text-gray-500" v-if="delivery.payment_type == 2">
Credit Card / Cash
</div>
<div class="col-span-12 text-gray-500" v-if="delivery.payment_type == 3">
Check with CC Hold
</div>
<div class="col-span-12 text-gray-500" v-if="delivery.payment_type == 4">
Other (Rent, etc):
</div>
<div v-if="promo_active" class="col-span-12">
<div class="col-span-12 pt-3 font-bold">
Promo
<!-- Pricing Breakdown -->
<div class="space-y-2 pt-2">
<div class="flex justify-between text-sm">
<span>Price per Gallon</span>
<span>${{ delivery.customer_price }}</span>
</div>
<div v-if="delivery.prime == 1" class="flex justify-between text-sm">
<span>Prime Fee</span>
<span>+ ${{ pricing.price_prime }}</span>
</div>
<div v-if="delivery.emergency == 1" class="flex justify-between text-sm">
<span>Emergency Fee</span>
<span>+ ${{ pricing.price_emergency }}</span>
</div>
<div v-if="delivery.same_day == 1" class="flex justify-between text-sm">
<span>Same Day Fee</span>
<span>+ ${{ pricing.price_same_day }}</span>
</div>
<div v-if="promo_active" class="flex justify-between text-sm text-success">
<span>Promo: {{ promo.name_of_promotion }}</span>
<span>- ${{ discount }}</span>
</div>
<div class="col-span-12 text-gray-500">
{{ promo.name_of_promotion }}
</div>
<div class="col-span-12 pt-3 font-bold">
Promo Discount:
</div>
<div class="col-span-12 text-gray-500">
{{ promo.money_off_delivery }} off a gallon
</div>
<div class="col-span-12 text-gray-500">
{{ discount }} off total delivery
</div>
<div class="col-span-12 text-gray-500">
{{ total_amount_after_discount }} total price after discount
</div>
<div class="col-span-12 pt-3 font-bold">
Price / Gallon:
</div>
<div class="col-span-12 text-gray-500">
{{ delivery.customer_price }} ({{ delivery.customer_price - promo.money_off_delivery}})
</div>
</div>
<div class="divider my-1"></div>
<div class="col-span-12 pt-3 font-bold">
Gallons Ordered:
</div>
<div class="col-span-12 text-gray-500">
<div v-if="delivery.customer_asked_for_fill == 1"> FILL (250)</div>
<div v-else> Gallons Ordered: {{ delivery.gallons_ordered }}</div>
</div>
<div class="col-span-12 py-3" v-if="delivery.prime == 1">
Prime Fee: {{ pricing.price_prime }}
</div>
<div class="col-span-12 py-3" v-if="delivery.emergency == 1">
Emergency Fee: {{ pricing.price_emergency }}
</div>
<div class="col-span-12 py-3" v-if="delivery.same_day == 1">
Same Day: {{ pricing.price_same_day }}
</div>
<div class="col-span-12 py-3" v-if="delivery.promo_id != null">
<div class="col-span-12 " v-if="delivery.payment_type == 0">
<div class="font-bold py-5">
CASH Total:
</div>
<div class="col-span-12 text-gray-500">
${{ total_amount_after_discount }}
</div>
</div>
<div class="col-span-12 font-bold py-5 text-accent" v-if="delivery.payment_type == 1">
<div class="">
Pre Charge Credit Card Total: ${{ total_amount_after_discount }}
</div>
</div>
<div class="col-span-12 font-bold py-5 text-accent" v-if="delivery.payment_type == 2">
<div class="">
Pre Charge Credit Card Total: ${{ total_amount_after_discount }}
</div>
</div>
<div class="col-span-12 font-bold py-5 text-accent" v-if="delivery.payment_type == 3">
<div class="">
Check - Pre Charge Credit Card Total: ${{ total_amount_after_discount }}
</div>
</div>
</div>
<div class="col-span-12 py-3" v-else>
<div class="col-span-12 " v-if="delivery.payment_type == 0">
<div class="font-bold py-5">
CASH Total:
</div>
<div class="col-span-12 text-gray-500">
${{ total_amount }}
</div>
</div>
<div class="col-span-12 font-bold py-5 text-accent" v-if="delivery.payment_type == 1">
<div class="">
Pre Charge Credit Card Total: ${{ total_amount }}
</div>
</div>
<div class="col-span-12 font-bold py-5 text-accent" v-if="delivery.payment_type == 2">
<div class="">
Pre Charge Credit Card Total: ${{ total_amount }}
</div>
</div>
<div class="col-span-12 font-bold py-5 text-accent" v-if="delivery.payment_type == 3">
<div class="">
Check - Pre Charge Credit Card Total: ${{ total_amount }}
<div class="flex justify-between items-center">
<span class="text-lg font-bold">Total to be Charged</span>
<span class="text-2xl font-bold text-accent">
${{ promo_active ? total_amount_after_discount : total_amount }}
</span>
</div>
</div>
</div>
</div>
<div class="flex justify-between">
<div class="" v-if="delivery.payment_type == 0">
<button class="btn bg-green-800 btn-sm" @click="checkoutOilUpdatePayment(0)">
Confirm Payment
</button>
</div>
<div class="" v-if="delivery.payment_type == 1">
<button class="btn bg-green-800 btn-sm" @click="checkoutOilUpdatePayment(1)">
Confirm Payment
</button>
</div>
<div class="" v-if="delivery.payment_type == 2">
<button class="btn bg-green-800 btn-sm" @click="checkoutOilUpdatePayment(2)">
Confirm Payment
</button>
</div>
<div class="" v-if="delivery.payment_type == 3">
<button class="btn bg-green-800 btn-sm" @click="checkoutOilUpdatePayment(3)">
Confirm Payment
<!-- Actions Card -->
<div class="bg-neutral rounded-lg p-5">
<div class="flex flex-wrap gap-4 justify-between items-center">
<!-- A single confirm button is cleaner -->
<button class="btn btn-primary" @click="checkoutOilUpdatePayment(delivery.payment_type)">
Confirm & Process Payment
</button>
</div>
<div class="" v-if="delivery.payment_type == 4">
<button class="btn bg-green-800 btn-sm" @click="checkoutOilUpdatePayment(3)">
Confirm Payment
</button>
</div>
</div>
<div class="col-span-12 py-10">
<router-link :to="{ name: 'deliveryEdit', params: { id: delivery.id } }">
<button class="btn btn-sm btn-secondary">Edit Delivery</button>
</router-link>
<router-link v-if="delivery && delivery.id" :to="{ name: 'deliveryEdit', params: { id: delivery.id } }">
<button class="btn btn-sm btn-ghost">Edit Delivery</button>
</router-link>
</div>
</div>
</div>
</div>
</div>
@@ -304,7 +159,6 @@
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'

View File

@@ -48,7 +48,7 @@
</thead>
<tbody>
<!-- Removed @click from tr to avoid conflicting actions -->
<tr v-for="service in services" :key="service.id" class="hover">
<tr v-for="service in services" :key="service.id" class=" hover:bg-blue-600">
<td class="align-top">
<div>{{ formatDate(service.scheduled_date) }}</div>
<div class="text-xs opacity-70">{{ formatTime(service.scheduled_date) }}</div>
@@ -56,7 +56,10 @@
<td class="align-top">{{ service.customer_name }}</td>
<td class="align-top">{{ service.customer_address }}, {{ service.customer_town }}</td>
<td class="align-top">
<span class="font-medium" :style="{ color: getServiceTypeColor(service.type_service_call) }">
<span
class="badge badge-sm text-white"
:style="{ 'background-color': getServiceTypeColor(service.type_service_call), 'border-color': getServiceTypeColor(service.type_service_call) }"
>
{{ getServiceTypeName(service.type_service_call) }}
</span>
</td>

View File

@@ -1,3 +1,4 @@
<template>
<div class="flex">
<div class="w-full px-4 md:px-10 py-4">
@@ -46,21 +47,30 @@
</tr>
</thead>
<tbody>
<!-- Removed @click from tr to avoid conflicting with "Read more" -->
<tr v-for="service in services" :key="service.id" class="hover">
<tr v-for="service in services" :key="service.id" class="hover:bg-blue-600">
<td class="align-top">
<div>{{ formatDate(service.scheduled_date) }}</div>
<div class="text-xs opacity-70">{{ formatTime(service.scheduled_date) }}</div>
</td>
<td class="align-top">{{ service.customer_name }}</td>
<td class="align-top">{{ service.customer_address }}, {{ service.customer_town }}</td>
<!--
FIX IS HERE: Replaced the colored text with a styled badge.
- `badge-sm`: Makes the pill small and compact.
- `text-white`: Ensures text is readable against the colored background.
- The background color is set dynamically using your existing `getServiceTypeColor` method.
-->
<td class="align-top">
<span class="font-medium" :style="{ color: getServiceTypeColor(service.type_service_call) }">
<span
class="badge badge-sm text-white"
:style="{ 'background-color': getServiceTypeColor(service.type_service_call), 'border-color': getServiceTypeColor(service.type_service_call) }"
>
{{ getServiceTypeName(service.type_service_call) }}
</span>
</td>
<td class="whitespace-normal text-sm align-top">
<!-- TRUNCATION LOGIC FOR DESKTOP -->
<div v-if="!isLongDescription(service.description) || isExpanded(service.id)">
{{ service.description }}
<a v-if="isLongDescription(service.description)" @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Show less</a>
@@ -72,7 +82,6 @@
</td>
<td class="text-right font-mono align-top">{{ formatCurrency(service.service_cost) }}</td>
<td class="text-right align-top">
<!-- Moved @click handler to the button for explicit action -->
<button @click="openEditModal(service)" class="btn btn-sm btn-primary">View</button>
</td>
</tr>
@@ -82,14 +91,14 @@
<!-- MOBILE VIEW: Cards (Revamped) -->
<div class="xl:hidden space-y-4">
<!-- Removed @click from card div -->
<div v-for="service in services" :key="service.id" class="card bg-base-100 shadow-md">
<div v-for="service in services" :key="service.id" class="card bg-base-100 shadow-md ">
<div class="card-body p-4">
<div class="flex justify-between items-start">
<div>
<h2 class="card-title text-base">{{ service.customer_name }}</h2>
<p class="text-xs text-gray-400">{{ service.customer_address }}, {{ service.customer_town }}</p>
</div>
<!-- Mobile view already uses a badge, which is great! No changes needed here. -->
<div class="badge badge-outline text-right" :style="{ 'border-color': getServiceTypeColor(service.type_service_call), color: getServiceTypeColor(service.type_service_call) }">
{{ getServiceTypeName(service.type_service_call) }}
</div>
@@ -101,7 +110,6 @@
<p><strong class="font-semibold">Cost:</strong> <span class="font-mono">{{ formatCurrency(service.service_cost) }}</span></p>
</div>
<!-- TRUNCATION LOGIC FOR MOBILE -->
<div v-if="service.description" class="text-sm mt-2 p-2 bg-base-200 rounded-md prose max-w-none">
<div v-if="!isLongDescription(service.description) || isExpanded(service.id)">
{{ service.description }}
@@ -114,7 +122,6 @@
</div>
<div class="card-actions justify-end mt-2">
<!-- Moved @click handler to the button -->
<button @click="openEditModal(service)" class="btn btn-sm btn-primary">View</button>
</div>
</div>
@@ -135,6 +142,7 @@
@delete-service="handleDeleteService"
/>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
@@ -297,7 +305,7 @@ export default defineComponent({
0: 'Tune-up',
1: 'No Heat',
2: 'Fix',
3: 'Tank Install',
3: 'Tank_Install',
4: 'Other',
};
return typeMap[typeId] || 'Unknown Service';