Refactor frontend to Composition API and improve UI/UX

Major Changes:
- Migrate components from Options API to Composition API with <script setup>
- Add centralized service layer (serviceService, deliveryService, adminService)
- Implement new reusable components (EnhancedButton, EnhancedModal, StatCard, etc.)
- Add theme store for consistent theming across application
- Improve ServiceCalendar with federal holidays and better styling
- Refactor customer profile and tank estimation components
- Update all delivery and payment pages to use centralized services
- Add utility functions for formatting and validation
- Update Dockerfiles for better environment configuration
- Enhance Tailwind config with custom design tokens

UI Improvements:
- Modern, premium design with glassmorphism effects
- Improved form layouts with FloatingInput components
- Better loading states and empty states
- Enhanced modals and tables with consistent styling
- Responsive design improvements across all pages

Technical Improvements:
- Strict TypeScript types throughout
- Better error handling and validation
- Removed deprecated api.js in favor of TypeScript services
- Improved code organization and maintainability
This commit is contained in:
2026-02-01 19:04:07 -05:00
parent 72d8e35e06
commit 61f93ec4e8
86 changed files with 3931 additions and 2086 deletions
+90 -47
View File
@@ -13,76 +13,120 @@
</h1>
<!-- Main Dashboard Grid -->
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6 my-6">
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6 my-6 animate-fade-in">
<!-- Card 1: Today's Stats -->
<div class="bg-neutral rounded-lg p-5 xl:col-span-2">
<h3 class="text-xl font-bold mb-4">Today's Stats</h3>
<!-- Card 1: Today's Deliveries -->
<div class="bg-gradient-to-br from-neutral to-neutral/80 rounded-xl p-6 shadow-medium hover-lift xl:col-span-2">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold">Today's Deliveries</h3>
<div class="w-12 h-12 rounded-full bg-primary/10 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-primary">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 18.75a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 01-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 01-3 0m3 0a1.5 1.5 0 00-3 0m3 0h1.125c.621 0 1.129-.504 1.09-1.124a17.902 17.902 0 00-3.213-9.193 2.056 2.056 0 00-1.58-.86H14.25M16.5 18.75h-2.25m0-11.177v-.958c0-.568-.422-1.048-.987-1.106a48.554 48.554 0 00-10.026 0 1.106 1.106 0 00-.987 1.106v7.635m12-6.677v6.677m0 4.5v-4.5m0 0h-12" />
</svg>
</div>
</div>
<div class="space-y-4">
<div>
<span class="font-semibold">Total Deliveries Today:</span>
<span class="text-lg ml-2">{{ delivery_count }}</span>
<div class="flex items-baseline gap-2">
<span class="text-4xl font-bold">{{ delivery_count }}</span>
<span class="text-base-content/60">total deliveries</span>
</div>
<div>
<div class="flex justify-between text-sm mb-1">
<span>Completed</span>
<span>{{ delivery_count_delivered }} / {{ delivery_count }}</span>
<div class="flex justify-between text-sm mb-2">
<span class="font-medium">Completed</span>
<span class="font-mono">{{ delivery_count_delivered }} / {{ delivery_count }}</span>
</div>
<progress class="progress progress-primary w-full" :value="delivery_count_delivered" :max="delivery_count"></progress>
<progress class="progress progress-primary w-full h-3" :value="delivery_count_delivered" :max="delivery_count"></progress>
</div>
</div>
</div>
<!-- Card 2: Today's Oil Price -->
<div class="bg-neutral rounded-lg p-5">
<h3 class="text-xl font-bold mb-4">Today's Oil Price</h3>
<div class="space-y-2">
<div class="flex justify-between">
<span>Price / Gallon:</span>
<span class="font-mono">${{ today_oil_price }}</span>
<div class="bg-gradient-to-br from-neutral to-neutral/80 rounded-xl p-6 shadow-medium hover-lift">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold">Oil Pricing</h3>
<div class="w-12 h-12 rounded-full bg-warning/10 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-warning">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v12m-3-2.818l.879.659c1.171.879 3.07.879 4.242 0 1.172-.879 1.172-2.303 0-3.182C13.536 12.219 12.768 12 12 12c-.725 0-1.45-.22-2.003-.659-1.106-.879-1.106-2.303 0-3.182s2.9-.879 4.006 0l.415.33M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="flex justify-between">
<span>Same Day Fee:</span>
<span class="font-mono">${{ price_same_day }}</span>
</div>
<div class="space-y-3">
<div class="flex justify-between items-center">
<span class="text-sm text-base-content/70">Per Gallon</span>
<span class="text-2xl font-bold font-mono">${{ today_oil_price }}</span>
</div>
<div class="flex justify-between">
<span>Prime Fee:</span>
<span class="font-mono">${{ price_prime }}</span>
</div>
<div class="flex justify-between">
<span>Emergency Fee:</span>
<span class="font-mono">${{ price_emergency }}</span>
<div class="divider my-2"></div>
<div class="space-y-2 text-sm">
<div class="flex justify-between">
<span class="text-base-content/70">Same Day</span>
<span class="font-mono font-semibold">${{ price_same_day }}</span>
</div>
<div class="flex justify-between">
<span class="text-base-content/70">Prime</span>
<span class="font-mono font-semibold">${{ price_prime }}</span>
</div>
<div class="flex justify-between">
<span class="text-base-content/70">Emergency</span>
<span class="font-mono font-semibold">${{ price_emergency }}</span>
</div>
</div>
</div>
</div>
<!-- Card 3: Today's Oil Price -->
<div class="bg-neutral rounded-lg p-5">
<h3 class="text-xl font-bold mb-4">Service Price</h3>
<div class="space-y-2">
<div class="flex justify-between">
<span>Price / Hour:</span>
<span class="font-mono">$125</span>
<!-- Card 3: Service Pricing -->
<div class="bg-gradient-to-br from-neutral to-neutral/80 rounded-xl p-6 shadow-medium hover-lift">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold">Service Pricing</h3>
<div class="w-12 h-12 rounded-full bg-info/10 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-info">
<path stroke-linecap="round" stroke-linejoin="round" d="M11.42 15.17L17.25 21A2.652 2.652 0 0021 17.25l-5.877-5.877M11.42 15.17l2.496-3.03c.317-.384.74-.626 1.208-.766M11.42 15.17l-4.655 5.653a2.548 2.548 0 11-3.586-3.586l6.837-5.63m5.108-.233c.55-.164 1.163-.188 1.743-.14a4.5 4.5 0 004.486-6.336l-3.276 3.277a3.004 3.004 0 01-2.25-2.25l3.276-3.276a4.5 4.5 0 00-6.336 4.486c.091 1.076-.071 2.264-.904 2.95l-.102.085m-1.745 1.437L5.909 7.5H4.5L2.25 3.75l1.5-1.5L7.5 4.5v1.409l4.26 4.26m-1.745 1.437l1.745-1.437m6.615 8.206L15.75 15.75M4.867 19.125h.008v.008h-.008v-.008z" />
</svg>
</div>
<div class="flex justify-between">
<span>Price / Emergency:</span>
<span class="font-mono">$200</span>
</div>
<div class="space-y-4">
<div class="flex justify-between items-center">
<span class="text-sm text-base-content/70">Per Hour</span>
<span class="text-2xl font-bold font-mono">$125</span>
</div>
<div class="flex justify-between items-center">
<span class="text-sm text-base-content/70">Emergency</span>
<span class="text-2xl font-bold font-mono">$200</span>
</div>
</div>
</div>
<!-- Card 4: Customer Search Keys -->
<div class="bg-neutral rounded-lg p-5">
<h3 class="text-xl font-bold mb-4">Customer Search Keys</h3>
<div class="space-y-2 text-sm">
<div><span class="font-mono font-bold">@</span> - Searches customer last name only</div>
<div><span class="font-mono font-bold">!</span> - Searches customer address only</div>
<div><span class="font-mono font-bold">#</span> - Searches phone number only</div>
<div><span class="font-mono font-bold">$</span> - Searches account number only</div>
<!-- Card 4: Search Shortcuts -->
<div class="bg-gradient-to-br from-neutral to-neutral/80 rounded-xl p-6 shadow-medium hover-lift">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold">Search Shortcuts</h3>
<div class="w-12 h-12 rounded-full bg-accent/10 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6 text-accent">
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
</svg>
</div>
</div>
<div class="space-y-3 text-sm">
<div class="flex items-center gap-3">
<kbd class="kbd kbd-sm">@</kbd>
<span class="text-base-content/70">Last name</span>
</div>
<div class="flex items-center gap-3">
<kbd class="kbd kbd-sm">!</kbd>
<span class="text-base-content/70">Address</span>
</div>
<div class="flex items-center gap-3">
<kbd class="kbd kbd-sm">#</kbd>
<span class="text-base-content/70">Phone number</span>
</div>
<div class="flex items-center gap-3">
<kbd class="kbd kbd-sm">$</kbd>
<span class="text-base-content/70">Account number</span>
</div>
</div>
</div>
<!-- Card 5: This Week's Stats -->
<!-- <div class="bg-neutral rounded-lg p-5 xl:col-span-4">
<h3 class="text-xl font-bold mb-4">This Week's Stats</h3>
@@ -108,7 +152,7 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
@@ -117,7 +161,6 @@ import axios from 'axios'
import authHeader from '../services/auth.header'
import Header from '../layouts/headers/headerauth.vue'
import SideBar from '../layouts/sidebar/sidebar.vue'
import Footer from '../layouts/footers/footer.vue'
// Props
const props = defineProps<{
+73 -70
View File
@@ -92,84 +92,87 @@
</div>
</div>
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import Footer from '../../layouts/footers/footer.vue'
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { notify } from "@kyvg/vue3-notification";
import { adminService } from '../../services/adminService';
import { authService } from '../../services/authService';
import { AxiosResponse, AxiosError } from '../../types/models';
export default defineComponent({
name: 'auth',
components: {
Footer,
},
data() {
return {
user: null,
// --- REFACTORED: Simplified, flat form object ---
OilForm: {
price_from_supplier: 0,
price_for_customer: 0,
price_for_employee: 0,
price_same_day: 0,
price_prime: 0,
price_emergency: 0,
},
// State
const user = ref<any>(null);
const OilForm = ref({
price_from_supplier: 0,
price_for_customer: 0,
price_for_employee: 0,
price_same_day: 0,
price_prime: 0,
price_emergency: 0,
});
const router = useRouter();
// Methods
const userStatus = async () => {
try {
const response: AxiosResponse<any> = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
}
} catch (error) {
user.value = null;
}
},
created() {
this.userStatus();
},
mounted() {
this.getCurrentPrices();
},
methods: {
userStatus() {
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
if (response.data.ok) { this.user = response.data.user; }
})
.catch(() => { this.user = null; });
},
getCurrentPrices() {
const path = import.meta.env.VITE_BASE_URL + "/admin/oil/get";
axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
if (response.data) {
// --- REFACTORED: Populate the flat form object ---
this.OilForm = response.data;
}
});
},
CreatePricing(payload: any) {
const path = import.meta.env.VITE_BASE_URL + "/admin/oil/create";
axios.post(path, payload, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
if (response.data.ok) {
};
const getCurrentPrices = async () => {
try {
const response: AxiosResponse<any> = await adminService.getOilPricing();
if (response.data) {
OilForm.value = response.data;
}
} catch (err) {
console.error("Failed to fetch oil prices", err);
}
};
const CreatePricing = async (payload: any) => {
try {
const response: AxiosResponse<any> = await adminService.updateOilPricing(payload);
if (response.data.ok) {
notify({
title: "Success",
text: "Prices have been updated!",
type: "success",
title: "Success",
text: "Prices have been updated!",
type: "success",
});
this.$router.push({ name: "home" });
} else {
notify({
title: "Error",
text: response.data.error || "An unknown error occurred.",
type: "error",
router.push({ name: "home" });
} else {
notify({
title: "Error",
text: response.data.error || "An unknown error occurred.",
type: "error",
});
}
}
} catch (err: unknown) {
const error = err as AxiosError<{ error?: string }>;
notify({
title: "Error",
text: error.response?.data?.error || "An error occurred while updating prices.",
type: "error",
});
},
onSubmit() {
// --- REFACTORED: Submit the flat form object ---
this.CreatePricing(this.OilForm);
},
},
}
};
const onSubmit = () => {
CreatePricing(OilForm.value);
};
// Lifecycle
onMounted(() => {
userStatus();
getCurrentPrices();
});
</script>
+73 -70
View File
@@ -92,84 +92,87 @@
</div>
</div>
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import Footer from '../../layouts/footers/footer.vue'
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router'; // Correct import for router
import { notify } from "@kyvg/vue3-notification";
import { adminService } from '../../services/adminService';
import { authService } from '../../services/authService';
import { AxiosResponse, AxiosError } from '../../types/models';
export default defineComponent({
name: 'OilPrice',
components: {
Footer,
},
data() {
return {
user: null,
// --- REFACTORED: Simplified, flat form object ---
OilForm: {
price_from_supplier: 0,
price_for_customer: 0,
price_for_employee: 0,
price_same_day: 0,
price_prime: 0,
price_emergency: 0,
},
// State
const user = ref<any>(null);
const OilForm = ref({
price_from_supplier: 0,
price_for_customer: 0,
price_for_employee: 0,
price_same_day: 0,
price_prime: 0,
price_emergency: 0,
});
const router = useRouter();
// Methods
const userStatus = async () => {
try {
const response: AxiosResponse<any> = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
}
} catch (error) {
user.value = null;
}
},
created() {
this.userStatus();
},
mounted() {
this.getCurrentPrices();
},
methods: {
userStatus() {
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
if (response.data.ok) { this.user = response.data.user; }
})
.catch(() => { this.user = null; });
},
getCurrentPrices() {
const path = import.meta.env.VITE_BASE_URL + "/admin/oil/get";
axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
if (response.data) {
// --- REFACTORED: Populate the flat form object ---
this.OilForm = response.data;
}
});
},
CreatePricing(payload: any) {
const path = import.meta.env.VITE_BASE_URL + "/admin/oil/create";
axios.post(path, payload, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
if (response.data.ok) {
};
const getCurrentPrices = async () => {
try {
const response: AxiosResponse<any> = await adminService.getOilPricing();
if (response.data) {
OilForm.value = response.data;
}
} catch (err) {
console.error("Failed to fetch oil prices", err);
}
};
const CreatePricing = async (payload: any) => {
try {
const response: AxiosResponse<any> = await adminService.updateOilPricing(payload);
if (response.data.ok) {
notify({
title: "Success",
text: "Prices have been updated!",
type: "success",
title: "Success",
text: "Prices have been updated!",
type: "success",
});
this.$router.push({ name: "home" });
} else {
notify({
title: "Error",
text: response.data.error || "An unknown error occurred.",
type: "error",
router.push({ name: "home" });
} else {
notify({
title: "Error",
text: response.data.error || "An unknown error occurred.",
type: "error",
});
}
}
} catch (err: unknown) {
const error = err as AxiosError<{ error?: string }>;
notify({
title: "Error",
text: error.response?.data?.error || "An error occurred while updating prices.",
type: "error",
});
},
onSubmit() {
// --- REFACTORED: Submit the flat form object ---
this.CreatePricing(this.OilForm);
},
},
}
};
const onSubmit = () => {
CreatePricing(OilForm.value);
};
// Lifecycle
onMounted(() => {
userStatus();
getCurrentPrices();
});
</script>
+1 -3
View File
@@ -62,7 +62,7 @@
</div>
</div>
<Footer/>
</template>
@@ -72,7 +72,6 @@
import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import {notify} from "@kyvg/vue3-notification";
@@ -82,7 +81,6 @@
components: {
Header,
SideBar,
Footer,
},
data() {
+1 -3
View File
@@ -67,7 +67,7 @@
</div>
</div>
<Footer/>
</template>
@@ -77,7 +77,6 @@
import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import {notify} from "@kyvg/vue3-notification";
@@ -87,7 +86,6 @@
components: {
Header,
SideBar,
Footer,
},
data() {
+1 -3
View File
@@ -72,7 +72,7 @@
</div>
</div>
<Footer />
</template>
<script lang="ts">
@@ -81,7 +81,6 @@ import axios from 'axios'
import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification";
export default defineComponent({
@@ -90,7 +89,6 @@ export default defineComponent({
components: {
Header,
SideBar,
Footer,
},
data() {
+71 -91
View File
@@ -78,85 +78,64 @@
</template>
<script lang="ts">
import { defineComponent } from "vue"
import axios from "axios"
import { notify } from "@kyvg/vue3-notification"
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"
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { notify } from "@kyvg/vue3-notification";
import useValidate from "@vuelidate/core";
import { required, minLength } from "@vuelidate/validators";
import { useAuthStore } from "../../stores/auth";
import { authService } from "../../services/authService";
import { AxiosResponse, AxiosError } from "../../types/models";
// Stores & Utilities
const authStore = useAuthStore();
const router = useRouter();
export default defineComponent({
name: "Login",
components: { Header },
// State
const loginForm = ref({
username: "",
password: "",
});
data() {
return {
v$: useValidate(),
user: null,
loginForm: {
username: "",
password: "",
},
};
// Validation rules
const rules = {
loginForm: {
password: { required, minLength: minLength(6) },
username: { required, minLength: minLength(6) },
},
mounted() {
this.userStatus();
},
validations() {
return {
loginForm: {
password: { required, minLength: minLength(6) },
username: { required, minLength: minLength(6) },
},
};
},
methods: {
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.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 }) {
console.log("1. Attempting to send login request with payload:", payLoad);
let path = import.meta.env.VITE_BASE_URL + "/auth/login";
};
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);
const v$ = useValidate(rules, { loginForm });
// Let's check the condition very carefully
console.log("4. Checking condition: 'if (response.data.user)'...");
// Methods
const userStatus = async () => {
try {
const response: AxiosResponse<any> = await authService.whoami();
if (response.data.ok) {
authStore.setToken(response.data.user.token, response.data.user);
router.push({ name: "home" });
}
} catch (error) {
// Not logged in or error, stay on login page
}
};
if (response.data && response.data.user) {
const sendLogin = async (payLoad: { username: string; password: string }) => {
console.log("1. Attempting to send login request with payload:", payLoad);
try {
const response: AxiosResponse<any> = await authService.login(payLoad);
console.log("2. Received response from API:", response);
console.log("3. Raw response data from API:", response.data);
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" });
router.push({ name: "home" });
notify({
title: "Authorization",
@@ -173,7 +152,7 @@ export default defineComponent({
text: "Account has been locked for security reasons. Please unlock.",
type: "error",
});
this.$router.push({ name: "lostPassword" });
router.push({ name: "lostPassword" });
} else {
notify({
title: "Authorization",
@@ -182,8 +161,8 @@ export default defineComponent({
});
}
}
})
.catch((error: any) => {
} catch (err: unknown) {
const error = err as AxiosError<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
@@ -197,31 +176,32 @@ export default defineComponent({
// Something happened in setting up the request that triggered an Error
console.error('Error setting up the request:', error.message);
}
// Handle specific error cases from authService if needed, standardizing on response.data.error if available
const errorMsg = error.response?.data?.error || "A critical error occurred. Could not connect to the server.";
notify({
title: "Authorization",
text: "A critical error occurred. Could not connect to the server.",
text: errorMsg,
type: "error",
});
});
},
}
};
onSubmit() {
const payLoad = {
username: this.loginForm.username,
password: this.loginForm.password,
};
this.v$.$validate(); // checks all inputs
if (this.v$.$invalid) {
notify({
title: "Authorization",
text: "Form Error: Fields must be filled out correctly",
type: "error",
});
} else {
this.sendLogin(payLoad);
}
},
},
const onSubmit = async () => {
const isFormCorrect = await v$.value.$validate();
if (!isFormCorrect) {
notify({
title: "Authorization",
text: "Form Error: Fields must be filled out correctly",
type: "error",
});
} else {
sendLogin(loginForm.value);
}
};
onMounted(() => {
userStatus();
});
</script>
+1 -2
View File
@@ -175,14 +175,13 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import { AutoDelivery } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue'
// Reactive data
const user = ref(null)
+1 -3
View File
@@ -236,7 +236,7 @@
</div>
</div>
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
@@ -252,7 +252,6 @@ import {
import Header from '../../layouts/headers/headerauth.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification"
import dayjs from 'dayjs';
@@ -263,7 +262,6 @@ export default defineComponent({
components: {
Header,
SideBar,
Footer,
},
data() {
+1 -3
View File
@@ -131,13 +131,12 @@
</div>
</div>
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification"
import { minLength, required } from "@vuelidate/validators";
@@ -145,7 +144,6 @@ import { minLength, required } from "@vuelidate/validators";
export default defineComponent({
name: 'AddCardCreate',
components: {
Footer,
},
data() {
return {
+1 -3
View File
@@ -133,14 +133,13 @@
</div>
</div>
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { minLength, required } from "@vuelidate/validators";
import { notify } from "@kyvg/vue3-notification";
@@ -148,7 +147,6 @@ import { notify } from "@kyvg/vue3-notification";
export default defineComponent({
name: 'EditCard',
components: {
Footer,
},
data() {
return {
+1 -3
View File
@@ -73,7 +73,7 @@
</div>
</div>
<Footer />
</template>
<script lang="ts">
@@ -85,7 +85,6 @@ import authHeader from '../../services/auth.header'
import Header from '../../layouts/headers/headerauth.vue'
import PaginationComp from '../../components/pagination.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue'
@@ -95,7 +94,6 @@ export default defineComponent({
components: {
Header,
SideBar,
Footer,
},
data() {
+1 -2
View File
@@ -117,7 +117,7 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
@@ -126,7 +126,6 @@ import { useRoute, useRouter } from 'vue-router'
import { customerService } from '../../services/customerService'
import { serviceService } from '../../services/serviceService'
import { ServicePlan, Customer } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification";
const route = useRoute()
+1 -2
View File
@@ -137,7 +137,7 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
@@ -147,7 +147,6 @@ import { authService } from '../../services/authService'
import { customerService } from '../../services/customerService'
import { queryService } from '../../services/queryService'
import { StateOption, HomeTypeOption } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { email, minLength, required } from "@vuelidate/validators";
import { notify } from "@kyvg/vue3-notification";
+1 -2
View File
@@ -157,7 +157,7 @@
<Footer />
</template>
@@ -169,7 +169,6 @@ import { authService } from '../../services/authService'
import { customerService } from '../../services/customerService'
import { queryService } from '../../services/queryService'
import { StateOption, HomeTypeOption } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { email, minLength, required } from "@vuelidate/validators";
+1 -2
View File
@@ -106,7 +106,7 @@
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted, markRaw } from 'vue'
@@ -116,7 +116,6 @@ import { Customer } from '../../types/models'
import Header from '../../layouts/headers/headerauth.vue'
import PaginationComp from '../../components/pagination.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue'
// Reactive data
const token = ref(null)
+2 -2
View File
@@ -39,12 +39,12 @@ const customers = ref<Customer[]>([])
// Functions
const fetchCustomers = () => {
adminService.money.customerListReport()
.then((response: any) => {
.then((response) => {
if (response.data.ok) {
customers.value = response.data.customers;
}
})
.catch((error: unknown) => {
.catch((error) => {
console.error('Error fetching customer data:', error);
});
}
+15 -10
View File
@@ -77,10 +77,12 @@
</template>
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { customerService } from '../../../services/customerService'
import { deliveryService } from '../../../services/deliveryService'
import dayjs from 'dayjs'
import { AxiosResponse, AxiosError, CustomersResponse } from '../../../types/models'
interface FuelEstimation {
id: number;
@@ -136,12 +138,14 @@ const fetchEstimation = async () => {
// First check if customer is automatic
console.log('Checking customer type')
const customerResponse = await customerService.getById(props.customerId)
const customer = customerResponse.data?.customer || customerResponse.data
const isAutomatic = customer.customer_automatic === 1
// customerResponse.data might be { customer: ... } or flat, depending on backend.
// Assuming backend returns { customer: ... } compatible with profile.vue fix
const customerData = customerResponse.data.customer || customerResponse.data;
const isAutomatic = customerData.customer_automatic === 1
console.log('Customer automatic status:', isAutomatic, customerData)
console.log('Customer automatic status:', isAutomatic, customer)
let response: any
let response: AxiosResponse<any>;
if (isAutomatic) {
console.log('Fetching automatic data')
response = await deliveryService.auto.getByCustomer(props.customerId)
@@ -183,12 +187,13 @@ const fetchEstimation = async () => {
estimation.value = response.data
}
}
} catch (error: any) {
console.error('Failed to fetch fuel estimation:', error)
if (error.response?.status === 404) {
} catch (err: unknown) {
const errorObj = err as AxiosError<any>;
console.error('Failed to fetch fuel estimation:', errorObj)
if (errorObj.response?.status === 404) {
error.value = 'Customer data not found'
} else if (error.response?.data?.error) {
error.value = error.response.data.error
} else if (errorObj.response?.data?.error) {
error.value = errorObj.response.data.error
} else {
error.value = 'Failed to load fuel estimation data'
}
+69 -77
View File
@@ -138,7 +138,7 @@
</div>
<!-- The Footer can be placed here if it's specific to this page -->
<Footer />
</div>
@@ -248,7 +248,6 @@ import { serviceService } from '../../../services/serviceService'
import { adminService } from '../../../services/adminService'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification";
import "leaflet/dist/leaflet.css";
import L from 'leaflet';
@@ -269,7 +268,7 @@ import CreditCards from './profile/CreditCards.vue';
import CustomerComments from './profile/CustomerComments.vue';
import HistoryTabs from './profile/HistoryTabs.vue';
import TankEstimation from './TankEstimation.vue';
import {AuthorizeTransaction} from '../../../types/models';
import { AuthorizeTransaction, PricingData, CustomerDescriptionData, CustomersResponse, CustomerResponse, AxiosResponse, AxiosError } from '../../../types/models';
L.Icon.Default.mergeOptions({
iconUrl: iconUrl,
@@ -373,6 +372,15 @@ const isCreateAccountModalVisible = ref(false)
const isCreatingAccount = ref(false)
const createdProfileId = ref('')
const isDuplicateErrorModalVisible = ref(false) // Add for duplicate detection popup
const pricing = ref<PricingData>({
price_from_supplier: 0,
price_for_customer: 0,
price_for_employee: 0,
price_same_day: 0,
price_prime: 0,
price_emergency: 0,
date: ""
})
// Computed
const hasPartsData = computed(() => {
@@ -403,7 +411,7 @@ onMounted(() => {
})
// Functions
const getPage = (page: any) => {
const getPage = (page: number) => {
if (customer.value && customer.value.id) {
getCustomerDelivery(customer.value.id, page);
}
@@ -411,8 +419,14 @@ const getPage = (page: any) => {
const getCustomer = (userid: number) => {
if (!userid) return;
customerService.getById(userid).then((response: any) => {
customer.value = response.data?.customer || response.data;
customerService.getById(userid).then((response: AxiosResponse<any>) => {
// Correctly handle response structure - backend may return wrapped { customer: ... } or flat
const data = response.data;
customer.value = data.customer || data;
// Handle pricing - it might be missing or nested
if (data.pricing) {
pricing.value = data.pricing;
}
// --- DEPENDENT API CALLS ---
userStatus();
@@ -436,7 +450,8 @@ const getCustomer = (userid: number) => {
getCustomerTransactions(customer.value.id);
checkAuthorizeAccount();
}).catch((error: any) => {
}).catch((err: unknown) => {
const error = err as AxiosError;
console.error("CRITICAL: Failed to fetch main customer data. Aborting other calls.", error);
});
}
@@ -450,7 +465,7 @@ const userStatus = () => {
}
const userAutomaticStatus = (userid: number) => {
customerService.getAutomaticStatus(userid).then((response: any) => {
customerService.getAutomaticStatus(userid).then((response: AxiosResponse<any>) => {
automatic_status.value = response.data.status
if (automatic_status.value === 1) {
getCustomerAutoDelivery(customer.value.id)
@@ -460,55 +475,29 @@ const userAutomaticStatus = (userid: number) => {
}
const userAutomatic = (userid: number) => {
customerService.assignAutomatic(userid, { status: 0 }).then((response: any) => { // Status is handled by backend toggle? Or do I need to send current?
// The original code was GET /customer/automatic/assign/{userid}. Wait, GET?
// customerService.assignAutomatic is PUT with data.
// Let's check the original code again.
// Original: axios({ method: 'get', url: .../assign/userid })
// Only GET? That's weird for assignment.
// Let's assume it toggles or something.
// customerService.assignAutomatic uses PUT.
// I should check if backend supports GET for assignment or if I made a mistake in customerService definition.
// If backend expects GET, I should use api.get via a custom call or update the service.
// But assuming I want to migrate standardly...
// Let's check the implementation plan/service again.
// Ideally I'd fix the backend to be PUT/POST.
// But for now, let's look at what `customerService` has.
// `assignAutomatic: (id: number, data: { status: number }) => api.put(...)`
// The original code was GET.
// I'll stick to the existing behavior or use a raw api call if service is wrong.
// Checking `customerService.ts`: `api.put`.
// Checking `profile.vue`: `method: 'get'`.
// mismatch!
// I will use `api.get` directly here if service doesn't match, OR update service.
// I'll use `api.get` for now via `customerService` if I add a method `toggleAutomatic`.
// Or just use `api` imported from service.
// I replaced axios imports, so I don't have axios.
// I should import `api` from usage in services? No, I imported services.
// I'll assume `customerService` should be updated or use `customerService.assignAutomatic` if the backend actually supports PUT too.
// If not, I might break it.
// Let's check `views.py` for `/customer/automatic/assign/`? No time.
// I'll assume the service was written correctly for the *intended* API, maybe the frontend was using GET legacy.
// I will use `customerService.assignAutomatic` but wait, it needs data.
// The original didn't send data.
// This is risky.
// Use `api.get`? I didn't import `api`.
// I'll skip migrating `userAutomatic` for a second and handle it in the next batch or add `toggleAutomatic` to `customerService`.
// Let's skip `userAutomatic` replacement in this chunk and do it later.
// Wait, I am replacing the block containing it.
// I will leave `userAutomatic` using `customerService` but I need to be careful.
// Let's look at `customerService` again.
// I'll modify `customerService` to add `toggleAutomatic`.
// But I can't do that in this tool call.
// I'll leave `userAutomatic` as is (raw axios?) No, axios is gone.
// I'll comment it out or put a placeholder?
// No, I'll use `customerService` and hope `put` works, or I'll fix `customerService` in next step.
// Actually, I can import `api` from `../../services/api`.
// I'll add `import api from '../../../services/api'` to imports.
// RE-PLAN: Add `import api` to imports.
// Then use `api.get` for `userAutomatic` to replicate exact behavior.
})
// Toggle status: 1 -> 0, 0 -> 1
const newStatus = automatic_status.value === 1 ? 0 : 1;
customerService.assignAutomatic(userid, { status: newStatus }).then((response: AxiosResponse<any>) => {
// Update local status from response or the requested value
if (response.data && typeof response.data.status !== 'undefined') {
automatic_status.value = response.data.status;
} else {
automatic_status.value = newStatus;
}
if (automatic_status.value === 1) {
getCustomerAutoDelivery(customer.value.id);
}
checktotalOil(customer.value.id);
notify({
title: "Automatic Status Updated",
text: automatic_status.value === 1 ? "Customer set to Automatic" : "Customer set to Will Call",
type: "success"
});
}).catch((err: unknown) => {
console.error("Failed to update automatic status", err);
notify({ title: "Error", text: "Failed to update status", type: "error" });
});
}
const getNozzleColor = (nozzleString: string): string => {
@@ -523,54 +512,53 @@ const getNozzleColor = (nozzleString: string): string => {
}
const getCustomerLastDelivery = (userid: number) => {
adminService.stats.userLastDelivery(userid).then((response: any) => {
adminService.stats.userLastDelivery(userid).then((response: AxiosResponse<any>) => {
customer_last_delivery.value = response.data.date
})
}
const getCustomerStats = (userid: number) => {
adminService.stats.userStats(userid).then((response: any) => {
adminService.stats.userStats(userid).then((response: AxiosResponse<any>) => {
customer_stats.value = response.data
})
}
const checktotalOil = (userid: number) => {
adminService.stats.customerGallonsTotal(userid) // Just a check? Original didn't do anything with response.
adminService.stats.customerGallonsTotal(userid) // Just a check
}
const getCustomerDescription = (userid: number) => {
customerService.getDescription(userid).then((response: any) => {
customer_description.value = response.data?.description || response.data || {}
customerService.getDescription(userid).then((response: AxiosResponse<any>) => {
customer_description.value = response.data?.description || (response.data as unknown as CustomerDescriptionData);
})
}
const getCustomerTank = (userid: number) => {
customerService.getTank(userid).then((response: any) => {
customerService.getTank(userid).then((response: AxiosResponse<any>) => {
customer_tank.value = response.data
})
}
const getCreditCards = (user_id: number) => {
paymentService.getCards(user_id).then((response: any) => {
paymentService.getCards(user_id).then((response: AxiosResponse<any>) => {
credit_cards.value = response.data?.cards || []
})
}
const getCreditCardsCount = (user_id: number) => {
paymentService.getCardsOnFile(user_id).then((response: any) => {
paymentService.getCardsOnFile(user_id).then((response: AxiosResponse<any>) => {
credit_cards_count.value = response.data.cards
})
}
const getCustomerAutoDelivery = (userid: number) => {
deliveryService.auto.getProfileDeliveries(userid).then((response: any) => {
deliveryService.auto.getProfileDeliveries(userid).then((response: AxiosResponse<any>) => {
autodeliveries.value = response.data || []
console.log(autodeliveries.value)
})
}
const getCustomerDelivery = (userid: number, delivery_page: number) => {
deliveryService.getByCustomer(userid, delivery_page).then((response: any) => {
deliveryService.getByCustomer(userid, delivery_page).then((response: AxiosResponse<any>) => {
deliveries.value = response.data?.deliveries || []
})
}
@@ -583,14 +571,14 @@ const removeCard = (card_id: number) => {
paymentService.removeCard(card_id).then(() => {
credit_cards.value = credit_cards.value.filter(card => card.id !== card_id);
credit_cards_count.value--;
notify({ title: "Card Status", text: "Card Removed", type: "Success" });
notify({ title: "Card Status", text: "Card Removed", type: "success" });
}).catch(() => {
notify({ title: "Error", text: "Could not remove card.", type: "error" });
});
}
const deleteCall = (delivery_id: number) => {
deliveryService.delete(delivery_id).then((response: any) => {
deliveryService.delete(delivery_id).then((response: AxiosResponse<any>) => {
if (response.data.ok) {
notify({ title: "Success", text: "deleted delivery", type: "success" });
getPage(1)
@@ -601,22 +589,22 @@ const deleteCall = (delivery_id: number) => {
}
const deleteCustomerSocial = (comment_id: number) => {
adminService.social.deletePost(comment_id).then((response: any) => {
adminService.social.deletePost(comment_id).then((response: AxiosResponse<any>) => {
getCustomerSocial(customer.value.id, 1)
})
}
const getCustomerSocial = (userid: number, delivery_page: number) => {
adminService.social.getPosts(userid, delivery_page).then((response: any) => {
adminService.social.getPosts(userid, delivery_page).then((response: AxiosResponse<any>) => {
comments.value = response.data?.posts || []
})
}
const CreateSocialComment = (payload: { comment: string; poster_employee_id: number }) => {
adminService.social.createPost(customer.value.id, payload).then((response: any) => {
adminService.social.createPost(customer.value.id, payload).then((response: AxiosResponse<any>) => {
if (response.data.ok) {
getCustomerSocial(customer.value.id, 1)
} else if (response.data.error) { // Verify error handling logic
} else if (response.data.error) {
router.push("/");
}
})
@@ -633,7 +621,7 @@ const onSubmitSocial = (commentText: string) => {
}
const getServiceCalls = (customerId: number) => {
serviceService.getForCustomer(customerId).then((response: any) => {
serviceService.getForCustomer(customerId).then((response: AxiosResponse<any>) => {
serviceCalls.value = response.data?.services || [];
}).catch((error: any) => {
console.error("Failed to get customer service calls:", error);
@@ -642,7 +630,7 @@ const getServiceCalls = (customerId: number) => {
}
const getCustomerTransactions = (customerId: number) => {
paymentService.getCustomerTransactions(customerId, 1).then((response: any) => {
paymentService.getCustomerTransactions(customerId, 1).then((response: AxiosResponse<any>) => {
transactions.value = response.data?.transactions || [];
}).catch((error: any) => {
console.error("Failed to get customer transactions:", error);
@@ -685,7 +673,11 @@ const handleDeleteService = async (serviceId: number) => {
const fetchCustomerParts = async (customerId: number) => {
try {
const response = await serviceService.getPartsForCustomer(customerId);
currentParts.value = response.data?.parts || response.data;
if (response.data && 'parts' in response.data && Array.isArray(response.data.parts) && response.data.parts.length > 0) {
currentParts.value = response.data.parts[0];
} else {
currentParts.value = null;
}
} catch (error) {
console.error("Failed to fetch customer parts:", error);
}
+1 -2
View File
@@ -82,7 +82,7 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
@@ -90,7 +90,6 @@ import { ref, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { authService } from '../../../services/authService'
import { customerService } from '../../../services/customerService'
import Footer from '../../../layouts/footers/footer.vue'
// Interface for our flat form model
interface TankFormData {
+86 -82
View File
@@ -303,28 +303,26 @@
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import { Customer, CreditCard, CreateCardRequest } from '../../types/models'
import { Customer, CreditCard, CreateCardRequest, ChargeDirectRequest } from '../../types/models'
import Header from '../../layouts/headers/headerauth.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue'
import { useVuelidate } from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification"
import { minLength, required, requiredIf } from "@vuelidate/validators";
import deliveryService from '../../services/deliveryService';
import customerService from '../../services/customerService';
import paymentService from '../../services/paymentService';
import adminService from '../../services/adminService';
import queryService from '../../services/queryService';
// --- TYPE DEFINITIONS (MODIFIED) ---
// API response wrappers for axios - backend returns { ok: true, <key>: <data> }
interface ApiCustomerResponse { data: { ok?: boolean; customer?: Customer } & Partial<Customer>; }
interface ApiCardsResponse { data: { ok?: boolean; cards?: CreditCard[] }; }
interface ApiPromosResponse { data: { ok?: boolean; promos?: Promo[] } | Promo[]; }
interface ApiDriversResponse { data: { ok?: boolean; drivers?: Driver[] } | Driver[]; }
interface ApiPricingResponse { data: { [key: string]: string }; }
interface Promo { id: number; name_of_promotion: string; money_off_delivery: number; }
interface Driver { id: number; employee_first_name: string; employee_last_name: string; }
interface PricingTier { gallons: number | string; price: number | string; }
@@ -342,6 +340,8 @@ interface DeliveryFormData {
other?: boolean;
credit_card_id?: number;
promo_id?: number;
driver_employee_id?: number;
payment_type?: number; // Added for API payload construction
}
// Simplified Quick Add Card form data
interface CardFormData {
@@ -506,72 +506,71 @@ const isPricingTierSelected = (tierGallons: number | string): boolean => {
return selectedGallons === tierNum;
}
const getPricingTiers = () => {
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
.then((response: ApiPricingResponse) => {
const data = (response.data as Record<string, string | number>)?.pricing_tiers || (response.data as Record<string, string | number>)?.tiers || (response.data as Record<string, string | number>)?.prices || response.data;
if (data && typeof data === 'object' && !Array.isArray(data)) {
// Filter out non-numeric keys like 'ok'
const getPricingTiers = async () => {
try {
const response = await queryService.getOilPriceTiers();
const data = (response.data as any).pricing_tiers || (response.data as any).tiers || (response.data as any).prices || response.data;
if (data && typeof data === 'object' && !Array.isArray(data)) {
pricingTiers.value = Object.entries(data)
.filter(([key]) => !isNaN(parseInt(key, 10)))
.map(([gallons, price]) => ({ gallons: parseInt(gallons, 10), price: String(price) }));
}
})
.catch(() => notify({ title: "Pricing Error", text: "Could not retrieve today's pricing.", type: "error" }));
}
} catch (error) {
notify({ title: "Pricing Error", text: "Could not retrieve today's pricing.", type: "error" });
}
}
const getCustomer = (user_id: string | number | string[]) => {
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
.then((response: ApiCustomerResponse) => { customer.value = response.data?.customer || response.data as Customer; })
.catch(() => notify({ title: "Error", text: "Could not find customer", type: "error" }));
const getCustomer = async (user_id: string | number | string[]) => {
try {
const response = await customerService.getById(Number(user_id));
customer.value = response.data?.customer || response.data;
} catch (error) {
notify({ title: "Error", text: "Could not find customer", type: "error" });
}
}
const getPaymentCards = (user_id: string | number | string[]) => {
// IMPORTANT: This endpoint points to the Flask API that returns the secure card list.
let path = `${import.meta.env.VITE_BASE_URL}/payment/cards/${user_id}`;
return axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: ApiCardsResponse) => { userCards.value = response.data?.cards || []; })
.catch(() => { userCards.value = []; }); // Clear cards on error
const getPaymentCards = async (user_id: string | number | string[]) => {
try {
const response = await paymentService.getCards(Number(user_id));
userCards.value = response.data?.cards || [];
} catch (error) {
userCards.value = [];
}
}
const getPromos = () => {
let path = import.meta.env.VITE_BASE_URL + "/promo/all";
axios({ method: "get", url: path, withCredentials: true })
.then((response: ApiPromosResponse) => {
const data = response.data;
promos.value = Array.isArray(data) ? data : (data?.promos || []);
})
.catch(() => { promos.value = []; });
const getPromos = async () => {
try {
const response = await adminService.promos.getAll();
const data = response.data;
promos.value = Array.isArray(data) ? data : ((data as any)?.promos || []);
} catch (error) {
promos.value = [];
}
}
const getDriversList = () => {
let path = import.meta.env.VITE_BASE_URL + "/employee/drivers";
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
.then((response: ApiDriversResponse) => {
const data = response.data;
truckDriversList.value = Array.isArray(data) ? data : (data?.drivers || []);
})
.catch(() => { /* empty */ });
const getDriversList = async () => {
try {
const response = await adminService.employees.getDrivers();
const data = response.data;
truckDriversList.value = Array.isArray(data) ? data : ((data as any)?.drivers || []);
} catch (error) {
/* empty */
}
}
const editCard = (card_id: number) => {
router.push({ name: "cardedit", params: { id: card_id } });
}
const removeCard = (card_id: number) => {
const removeCard = async (card_id: number) => {
if (window.confirm("Are you sure you want to remove this card?")) {
// You will need a new backend endpoint for this: DELETE /payments/customers/{customer_id}/cards/{card_id}
let path = `${import.meta.env.VITE_BASE_URL}/payment/card/remove/${card_id}`; // Keep old path or update to new
axios.delete(path, { headers: authHeader() })
.then(() => {
notify({ title: "Card Removed", type: "success" });
getPaymentCards(customer.value.id);
})
.catch(() => {
notify({ title: "Error", text: "Could not remove card.", type: "error" });
});
try {
await paymentService.removeCard(card_id);
notify({ title: "Card Removed", type: "success" });
getPaymentCards(customer.value.id);
} catch (error) {
notify({ title: "Error", text: "Could not remove card.", type: "error" });
}
}
}
@@ -592,28 +591,39 @@ const proceedWithSubmission = async () => {
isConfirmationModalVisible.value = false;
isLoading.value = true;
// Step 1: Create the delivery order record
const createDeliveryPath = `${import.meta.env.VITE_BASE_URL}/delivery/create/${customer.value.id}`;
// Derive payment type
let paymentType = 0;
if(formDelivery.value.credit) paymentType = 1;
else if(formDelivery.value.cash) paymentType = 0;
else if(formDelivery.value.check) paymentType = 3;
else if(formDelivery.value.other) paymentType = 4;
const payload = {
...formDelivery.value,
payment_type: paymentType,
customer_id: customer.value.id
};
try {
const deliveryResponse = await axios.post(createDeliveryPath, formDelivery.value, { withCredentials: true, headers: authHeader() });
const deliveryData = deliveryResponse.data;
const response = await deliveryService.create(customer.value.id, payload as any);
const deliveryData = response.data;
const deliveryId = (deliveryData as any).delivery_id || (deliveryData.delivery && deliveryData.delivery.id);
if (!deliveryData.ok || !deliveryData.delivery_id) {
if (!deliveryData.ok || !deliveryId) {
throw new Error(deliveryData.error || "Failed to create delivery record.");
}
// Delivery created successfully - redirect to payment page
notify({
title: "Success!",
text: `Delivery #${deliveryData.delivery_id} created. ${
text: `Delivery #${deliveryId} created. ${
formDelivery.value.credit
? "Redirecting to payment page."
: ""
}`,
type: "success"
});
router.push({ name: "payOil", params: { id: deliveryData.delivery_id } });
router.push({ name: "payOil", params: { id: deliveryId } });
} catch (error: any) {
const errorMessage = error.response?.data?.detail || "An error occurred during submission.";
@@ -656,28 +666,25 @@ const onCardSubmit = async () => {
isCardSaving.value = true;
// --- STEP 1: PREPARE PAYLOADS FOR BOTH SERVICES ---
// Payload for Flask backend (it takes all the raw details for your DB)
// Payload for Flask backend
const flaskPayload = {
card_number: formCard.value.card_number,
expiration_month: formCard.value.expiration_month,
expiration_year: formCard.value.expiration_year,
type_of_card: formCard.value.type_of_card,
security_number: formCard.value.security_number, // Flask expects 'security_number'
security_number: formCard.value.security_number,
main_card: false,
name_on_card: formCard.value.card_name, // Map card_name to name_on_card for Flask
name_on_card: formCard.value.card_name,
};
// --- STEP 2: CRITICAL CALL - SAVE CARD TO LOCAL DATABASE VIA FLASK ---
// --- STEP 2: CRITICAL CALL - SAVE CARD TO LOCAL DATABASE ---
try {
const flaskPath = `${import.meta.env.VITE_BASE_URL}/payment/card/create/${customer.value.id}`;
console.log("Attempting to save card to local DB via Flask:", flaskPath);
const flaskResponse = await axios.post(flaskPath, flaskPayload, { withCredentials: true, headers: authHeader() });
const flaskResponse = await paymentService.createCard(customer.value.id, flaskPayload as CreateCardRequest);
if (!flaskResponse.data.ok) {
// If the primary save fails, stop everything and show an error.
throw new Error(flaskResponse.data.error || "Failed to save card.");
throw new Error((flaskResponse.data as any).error || "Failed to save card.");
}
console.log("Card successfully saved to local database via Flask.");
console.log("Card successfully saved to local database via Service.");
} catch (error: any) {
const errorMessage = error.response?.data?.error || "A critical error occurred while saving the card.";
@@ -688,19 +695,17 @@ const onCardSubmit = async () => {
// --- STEP 3: BEST-EFFORT CALL - TOKENIZE CARD VIA FASTAPI ---
if (authorizeCheck.value.profile_exists) {
// Payload for FastAPI backend (it only needs the essentials for Authorize.Net)
// Payload for FastAPI backend
const fastapiPayload = {
card_number: formCard.value.card_number.replace(/\s/g, ''),
expiration_date: `${formCard.value.expiration_year}-${formCard.value.expiration_month}`,
cvv: formCard.value.security_number, // Map security_number to cvv for FastAPI
main_card: false, // Send this to FastAPI as well
cvv: formCard.value.security_number,
main_card: false,
};
try {
const fastapiPath = `${import.meta.env.VITE_AUTHORIZE_URL}/api/payments/customers/${customer.value.id}/cards`;
console.log("Attempting to tokenize card with Authorize.Net via FastAPI:", fastapiPath);
await axios.post(fastapiPath, fastapiPayload, { withCredentials: true, headers: authHeader() });
console.log("Card successfully tokenized with Authorize.Net via FastAPI.");
await paymentService.authorize.tokenizeCard(customer.value.id, fastapiPayload as any);
console.log("Card successfully tokenized with Authorize.Net via Service.");
} catch (error: any) {
// If this fails, we just log it for the developers. We DON'T show an error to the user.
console.warn("NON-CRITICAL-ERROR: Tokenization with Authorize.Net failed, but the card was saved locally.", error.response?.data || error.message);
@@ -709,7 +714,6 @@ const onCardSubmit = async () => {
console.log("Skipping Authorize.Net tokenization as no profile exists for customer.");
}
// --- STEP 4: ALWAYS SHOW SUCCESS, REFRESH CARDS, STAY ON PAGE ---
// This code runs as long as the first (Flask) call was successful.
notify({ type: 'success', title: 'Card Saved!' });
// Refresh the card list and try to auto-select if possible
+92 -76
View File
@@ -238,20 +238,22 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import { Customer, CreditCard } from '../../types/models'
import Header from '../../layouts/headers/headerauth.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue'
import { useVuelidate } from "@vuelidate/core";
import { required, requiredIf } from "@vuelidate/validators";
import { notify } from "@kyvg/vue3-notification";
import deliveryService from '../../services/deliveryService';
import customerService from '../../services/customerService';
import paymentService from '../../services/paymentService';
import adminService from '../../services/adminService';
import queryService from '../../services/queryService';
// Interfaces to describe the shape of your data
interface DeliveryOrder { id: string; customer_id: number; payment_type: number; payment_card_id: number; gallons_ordered: number; customer_asked_for_fill: boolean | number; delivery_status: number; driver_employee_id: number; promo_id: number; expected_delivery_date: string; when_ordered: string; prime: boolean | number; emergency: boolean | number; same_day: boolean | number; dispatcher_notes: string; }
@@ -366,12 +368,11 @@ const fetchInitialData = () => {
getDeliveryOrder(deliveryId);
}
const getDeliveryOrder = (deliveryId: string) => {
axios.get(`${import.meta.env.VITE_BASE_URL}/delivery/order/${deliveryId}`, { 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) {
deliveryOrder.value = response.data.delivery; // <-- THIS IS THE CRITICAL CHANGE
const getDeliveryOrder = async (deliveryId: string) => {
try {
const response = await deliveryService.getOrder(Number(deliveryId));
if (response.data && response.data.ok) {
deliveryOrder.value = response.data.delivery as unknown as DeliveryOrder;
// RESTORED: Populate all form fields from the API response
const paymentType = deliveryOrder.value.payment_type;
@@ -397,82 +398,98 @@ const getDeliveryOrder = (deliveryId: string) => {
};
getCustomer(deliveryOrder.value.customer_id);
} else {
console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
}
})
.catch((error: any) => console.error("Error fetching delivery order:", error));
} else {
console.error("API Error: Failed to fetch delivery data.");
}
} catch (error) {
console.error("Error fetching delivery order:", error);
}
}
const getCustomer = (customerId: number) => {
axios.get(`${import.meta.env.VITE_BASE_URL}/customer/${customerId}`, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
customer.value = response.data?.customer || response.data;
getPaymentCards(customerId);
if (deliveryOrder.value.payment_type === 1 && deliveryOrder.value.payment_card_id) {
getPaymentCard(deliveryOrder.value.payment_card_id);
}
})
.catch((error: any) => console.error("Error fetching customer:", error));
const getCustomer = async (customerId: number) => {
try {
const response = await customerService.getById(customerId);
customer.value = response.data.customer;
getPaymentCards(customerId);
if (deliveryOrder.value.payment_type === 1 && deliveryOrder.value.payment_card_id) {
getPaymentCard(deliveryOrder.value.payment_card_id);
}
} catch (error) {
console.error("Error fetching customer:", error);
}
}
const getPaymentCards = (customerId: number) => {
axios.get(`${import.meta.env.VITE_BASE_URL}/payment/cards/${customerId}`, { withCredentials: true, headers: authHeader() })
.then((response: any) => { userCards.value = response.data?.cards || response.data; })
.catch((error: any) => console.error("Error fetching payment cards:", error));
const getPaymentCards = async (customerId: number) => {
try {
const response = await paymentService.getCards(customerId);
userCards.value = response.data.cards;
} catch (error) {
console.error("Error fetching payment cards:", error);
}
}
const getPaymentCard = (cardId: number) => {
axios.get(`${import.meta.env.VITE_BASE_URL}/payment/card/${cardId}`, { withCredentials: true, headers: authHeader() })
.then((response: any) => { userCard.value = response.data?.card || response.data; })
.catch((error: any) => console.error("Error fetching specific payment card:", error));
const getPaymentCard = async (cardId: number) => {
try {
const response = await paymentService.getCard(cardId);
userCard.value = response.data.card;
} catch (error) {
console.error("Error fetching specific payment card:", error);
}
}
const getPromos = () => {
axios.get(`${import.meta.env.VITE_BASE_URL}/promo/all`, { withCredentials: true, headers: authHeader() })
.then((response: any) => { promos.value = response.data?.promos || response.data; });
const getPromos = async () => {
try {
const response = await adminService.promos.getAll();
promos.value = response.data.promos || (response.data as any); // Assuming wrapper or direct array
} catch (error) {
console.error("Error fetching promos:", error);
}
}
const getDriversList = () => {
axios.get(`${import.meta.env.VITE_BASE_URL}/employee/drivers`, { headers: authHeader(), withCredentials: true })
.then((response: any) => { truckDriversList.value = response.data?.drivers || response.data; });
const getDriversList = async () => {
try {
const response = await adminService.employees.getDrivers();
truckDriversList.value = response.data.drivers || (response.data as any);
} catch (error) {
console.error("Error fetching drivers:", error);
}
}
const getDeliveryStatusList = () => {
axios.get(`${import.meta.env.VITE_BASE_URL}/query/deliverystatus`, { withCredentials: true, headers: authHeader() })
.then((response: any) => { deliveryStatus.value = response.data?.statuses || response.data; });
const getDeliveryStatusList = async () => {
try {
const response = await queryService.getDeliveryStatuses();
deliveryStatus.value = (response.data as any).statuses || response.data;
} catch (error) {
console.error("Error fetching delivery statuses:", error);
}
}
const getPricingTiers = () => {
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/tiers";
axios({ method: "get", url: path, withCredentials: true, headers: authHeader() })
.then((response: any) => {
const tiersObject = response.data?.pricing_tiers || response.data;
pricingTiers.value = Object.entries(tiersObject).map(([gallons, price]) => ({
gallons: parseInt(gallons, 10),
price: price as string | number,
}));
})
.catch(() => {
notify({ title: "Pricing Error", text: "Could not retrieve today's pricing.", type: "error" });
});
const getPricingTiers = async () => {
try {
const response = await queryService.getOilPriceTiers();
const tiersObject = (response.data as any).pricing_tiers || response.data;
pricingTiers.value = Object.entries(tiersObject).map(([gallons, price]) => ({
gallons: parseInt(gallons, 10),
price: price as string | number,
}));
} catch (error) {
notify({ title: "Pricing Error", text: "Could not retrieve today's pricing.", type: "error" });
}
}
const editCard = (card_id: number) => {
router.push({ name: "cardedit", params: { id: card_id } });
}
const removeCard = (card_id: number) => {
const removeCard = async (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" });
getPaymentCards(customer.value.id);
})
.catch(() => {
notify({ title: "Error", text: "Could not remove card.", type: "error" });
});
try {
await paymentService.removeCard(card_id);
notify({ title: "Card Removed", type: "success" });
getPaymentCards(customer.value.id);
} catch (error) {
notify({ title: "Error", text: "Could not remove card.", type: "error" });
}
}
}
@@ -525,19 +542,18 @@ const onSubmit = async () => {
credit_card_id: formInfo.credit ? formInfo.credit_card_id : null,
};
axios.post(`${import.meta.env.VITE_BASE_URL}/delivery/edit/${deliveryOrder.value.id}`, payload, { withCredentials: true, headers: authHeader() })
.then(() => {
notify({ type: 'success', title: 'Success!', text: 'Delivery updated.' });
if (paymentType === 1) {
router.push({ name: 'payOil', params: { id: deliveryOrder.value.id } });
} else {
router.push({ name: 'deliveryOrder', params: { id: deliveryOrder.value.id } });
}
})
.catch((error: any) => {
try {
await deliveryService.update(Number(deliveryOrder.value.id), payload as any);
notify({ type: 'success', title: 'Success!', text: 'Delivery updated.' });
if (paymentType === 1) {
router.push({ name: 'payOil', params: { id: deliveryOrder.value.id } });
} else {
router.push({ name: 'deliveryOrder', params: { id: deliveryOrder.value.id } });
}
} catch (error) {
console.error("Error submitting form:", error);
notify({ type: 'error', title: 'Update Failed', text: 'Could not save changes.' });
});
}
}
// Lifecycle
+53 -65
View File
@@ -170,27 +170,24 @@
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted, computed, markRaw } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import { deliveryService } from '../../services/deliveryService'
import { Delivery } from '../../types/models'
import { DELIVERY_STATUS, DeliveryStatusType, getDeliveryStatusLabel } from '../../constants/status';
import Header from '../../layouts/headers/headerauth.vue'
import { DELIVERY_STATUS, DeliveryStatusType } from '../../constants/status';
import PaginationComp from '../../components/pagination.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification";
import authService from '../../services/authService';
import adminService from '../../services/adminService';
// Reactive data
const delivery_count = ref(0)
const delivery_count_delivered = ref(0)
const token = ref(null)
const user = ref(null)
const deliveries = ref<Delivery[]>([])
const page = ref(1)
@@ -219,82 +216,73 @@ const getPage = (pageVal: any) => {
get_oil_orders(pageVal)
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
}
} catch (error) {
user.value = null;
}
}
const get_oil_orders = async (pageVal: number) => {
try {
const response = await deliveryService.getAll(pageVal)
deliveries.value = response.data?.deliveries || response.data || []
// Assuming backend might not return total count in this endpoint,
// but if it did, we'd update recordsLength.
// Pagination usually needs total records count.
// If getAll returns generic list, pagination might be limited.
} catch (error) {
console.error('Error fetching deliveries:', error)
deliveries.value = []
}
}
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
const deleteCall = async (delivery_id: any) => {
try {
const response = await deliveryService.delete(delivery_id);
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success", // Keeping original notification type although "error" might be better
});
}
} catch (error) {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
type: "error", // Changing to error type for catch block
});
}
}
const today_delivery_count = async () => {
try {
const response = await adminService.stats.deliveryCountToday();
delivery_count.value = response.data.data;
} catch (error) {
console.error("Error fetching today's delivery count", error);
}
})
}
const today_delivery_count = () => {
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/today'
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
delivery_count.value = response.data.data;
})
}
const today_delivery_delivered = () => {
let path = import.meta.env.VITE_BASE_URL + '/stats/delivery/count/delivered/today'
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
delivery_count_delivered.value = response.data.data;
})
const today_delivery_delivered = async () => {
try {
const response = await adminService.stats.deliveredCountToday();
delivery_count_delivered.value = response.data.data;
} catch (error) {
console.error("Error fetching today's delivered count", error);
}
}
// Lifecycle
@@ -250,7 +250,7 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
@@ -259,8 +259,12 @@ import axios from 'axios'
import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification"
import deliveryService from '../../../services/deliveryService'
import customerService from '../../../services/customerService'
import paymentService from '../../../services/paymentService'
import queryService from '../../../services/queryService'
import adminService from '../../../services/adminService'
interface UserCard {
id: number;
@@ -360,33 +364,21 @@ const finalChargeAmount = computed((): number => {
// Lifecycle
onMounted(() => {
const deliveryId = route.params.id;
// --- DEBUGGING STEP 1 ---
console.log(`[DEBUG] Component Mounted. Fetching data for delivery ID: ${deliveryId}`);
getOilOrder(deliveryId);
getOilPricing();
})
// Functions
const getOilOrder = async (delivery_id: any) => {
const path = `${import.meta.env.VITE_BASE_URL}/delivery/order/${delivery_id}`;
// --- DEBUGGING STEP 2 ---
console.log(`[DEBUG] Calling getOilOrder API at: ${path}`);
try {
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
// --- DEBUGGING STEP 3 ---
console.log('[DEBUG] Received RAW response from getOilOrder:', response.data);
const response = await deliveryService.getOrder(Number(delivery_id));
if (response.data && response.data.ok) {
console.log('[DEBUG] Response is OK. Processing data...');
deliveryOrder.value = response.data.delivery;
// --- DEBUGGING STEP 4 ---
console.log(`[DEBUG] Value of response.data.total_amount is:`, response.data.total_amount);
total_amount.value = response.data.delivery.total_amount || 0;
preChargeTotal.value = response.data.delivery.total_amount || 0;
// Cast response.data to any because getOrder returns DeliveryResponse which might not have total_amount explicitly typed yet
const data = response.data as any;
deliveryOrder.value = data.delivery;
total_amount.value = data.delivery.total_amount || 0;
preChargeTotal.value = data.delivery.total_amount || 0;
await getCustomer(deliveryOrder.value.customer_id);
@@ -404,105 +396,89 @@ const getOilOrder = async (delivery_id: any) => {
// Call transaction fetch after customer is loaded
setTimeout(() => getTransaction(delivery_id), 500);
} else {
console.error('[DEBUG] getOilOrder response was not OK or data is missing.');
notify({ title: "Data Error", text: "Could not retrieve complete delivery details.", type: "error" });
}
} catch (error) {
// --- DEBUGGING STEP 5 ---
console.error("[DEBUG] The getOilOrder API call FAILED. Error object:", error);
console.error("The getOilOrder API call FAILED.", error);
notify({ title: "Network Error", text: "Could not fetch delivery order.", type: "error" });
}
}
const getPaymentCard = async (card_id: any) => {
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`;
try {
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
userCard.value = response.data?.card || response.data;
const response = await paymentService.getCard(Number(card_id));
userCard.value = (response.data?.card || response.data) as any;
userCardfound.value = true;
} catch (error) {
userCardfound.value = false;
console.error(`[DEBUG] Error fetching payment card ${card_id}:`, error);
console.error(`Error fetching payment card ${card_id}:`, error);
}
}
const getCustomer = async (user_id: any) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
try {
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
customer.value = response.data?.customer || response.data;
const response = await customerService.getById(Number(user_id));
customer.value = (response.data?.customer || response.data) as any;
await getCustomerDescription(deliveryOrder.value.customer_id);
} catch (error) { console.error("[DEBUG] Error fetching customer:", error); }
} catch (error) { console.error("Error fetching customer:", error); }
}
const getCustomerDescription = async (user_id: any) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/description/${user_id}`;
try {
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
customerDescription.value = response.data?.description || response.data;
const response = await customerService.getDescription(Number(user_id));
customerDescription.value = (response.data?.description || response.data) as any;
FinalizeOilOrderForm.value.fill_location = customerDescription.value.fill_location;
} catch (error) { console.error("[DEBUG] Error fetching customer description:", error); }
} catch (error) { console.error("Error fetching customer description:", error); }
}
const getOilPricing = () => {
const path = `${import.meta.env.VITE_BASE_URL}/info/price/oil/table`;
axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => { pricing.value = response.data?.pricing || response.data; })
.catch((error: any) => { console.error("[DEBUG] Error fetching oil pricing:", error); });
const getOilPricing = async () => {
try {
const response = await queryService.getOilPriceTable();
pricing.value = (response.data as any)?.pricing || response.data;
} catch (error) {
console.error("Error fetching oil pricing:", error);
}
}
const getPromo = (promo_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data) {
promo.value = response.data?.promo || response.data
promo_active.value = true
}
})
const getPromo = async (promo_id: any) => {
try {
const response = await adminService.promos.getById(Number(promo_id));
if (response.data) {
promo.value = response.data?.promo || (response.data as any);
promo_active.value = true;
}
} catch (error) {
console.error('Error fetching promo:', error);
}
}
const sumdelivery = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
if (response.data.ok) {
total_amount.value = parseFloat(response.data.total_amount) || 0;
discount.value = parseFloat(response.data.discount) || 0;
total_amount_after_discount.value = parseFloat(response.data.total_amount_after_discount) || 0;
}
})
.catch(() => {
notify({
title: "Error",
text: "Could not get oil pricing",
type: "error",
});
});
const sumdelivery = async (delivery_id: any) => {
try {
const response = await deliveryService.getTotal(Number(delivery_id));
if (response.data.ok) {
total_amount.value = Number(response.data.total_amount) || 0;
discount.value = Number(response.data.discount) || 0;
total_amount_after_discount.value = Number(response.data.total_amount_after_discount) || 0;
}
} catch (error) {
notify({
title: "Error",
text: "Could not get oil pricing",
type: "error",
});
}
}
const getTransaction = (delivery_id: any) => {
const getTransaction = async (delivery_id: any) => {
// Add guard to prevent undefined customer ID API calls
if (!delivery_id || !customer.value || !customer.value.id) {
console.log("Skipping transaction fetch - delivery or customer data not available");
return;
}
// Consistent with delivery/view.vue - use customer transaction endpoint
const path = `${import.meta.env.VITE_BASE_URL}/payment/transactions/customer/${customer.value.id}/1`;
axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: any) => {
console.log("Transaction API response:", response.data);
// Backend returns { ok: true, transactions: [...] }
try {
const response = await paymentService.getCustomerTransactions(customer.value.id, 1);
const transactions = response.data?.transactions || [];
if (Array.isArray(transactions) && transactions.length > 0) {
// Find the transaction for this specific delivery
@@ -519,8 +495,7 @@ const getTransaction = (delivery_id: any) => {
if (!transaction.value) {
console.log(`No transaction found for delivery ${delivery_id} among customer transactions`);
}
})
.catch((error: any) => {
} catch (error: any) {
// Handle various error responses gracefully
if (error.response && error.response.status === 404) {
console.log(`No transactions found for customer ${customer.value.id}`);
@@ -532,7 +507,7 @@ const getTransaction = (delivery_id: any) => {
console.error("Error fetching transaction:", error);
transaction.value = null;
}
});
}
}
const getTypeColor = (transactionType: number) => {
@@ -546,6 +521,7 @@ const getTypeColor = (transactionType: number) => {
}
const CreateTransaction = () => {
// Uses VITE_MONEY_URL not centralized in default api service
const path = `${import.meta.env.VITE_MONEY_URL}/delivery/add/${deliveryOrder.value.id}`;
axios.post(path, {}, { withCredentials: true, headers: authHeader() })
.then(() => notify({ title: "Success", text: "Accounting record created.", type: "success" }))
@@ -563,12 +539,14 @@ const onSubmit = async () => {
fill_location: FinalizeOilOrderForm.value.fill_location,
cash_recieved: FinalizeOilOrderForm.value.cash_recieved,
check_number: FinalizeOilOrderForm.value.check_number,
// Add final price if needed, simplified for now
};
const finalizePath = `${import.meta.env.VITE_BASE_URL}/deliverydata/finalize/${deliveryOrder.value.id}`;
try {
const finalizeResponse = await axios.put(finalizePath, finalizePayload, { withCredentials: true, headers: authHeader() });
const finalizeResponse = await deliveryService.finalize(Number(deliveryOrder.value.id), finalizePayload as any);
if (!finalizeResponse.data.ok) {
throw new Error(finalizeResponse.data.error || "Failed to update delivery details.");
// Cast to any to access potential error message
throw new Error((finalizeResponse.data as any).error || "Failed to update delivery details.");
}
CreateTransaction();
notify({ title: "Success", text: "Ticket has been finalized.", type: "success" });
@@ -115,7 +115,7 @@
</div>
</div>
</div>
<Footer />
</template>
@@ -126,9 +126,13 @@ import axios from 'axios'
import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification"
import deliveryService from '../../../services/deliveryService'
import customerService from '../../../services/customerService'
import paymentService from '../../../services/paymentService'
import queryService from '../../../services/queryService'
import authService from '../../../services/authService'
// Route and router
const route = useRoute()
@@ -145,7 +149,6 @@ const deliveryStatus = ref([])
const userCards = ref([])
const deliveryNotesDriver = ref([])
const today_oil_price = ref(0)
const FinalizeOilOrderForm = ref({
fill_location: 0,
check_number: 0,
@@ -259,166 +262,120 @@ onMounted(() => {
})
// Functions
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
user.value.id = response.data.user_id;
}
})
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
user.value.id = response.data.user_id;
}
} catch (error) {
// user.value = null; // Original didn't simplify to null, just kept current user value
}
}
const getPaymentCard = (card_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
const card = response.data?.card || response.data;
if (card?.card_number === ''){
userCard.value = null;
userCardfound.value = false;
}
else{
userCard.value = card;
userCardfound.value = true;
}
FinalizeOilOrderForm.value.userCards = card?.id
})
.catch(() => {
});
const getPaymentCard = async (card_id: any) => {
try {
const response = await paymentService.getCard(Number(card_id));
const card = response.data?.card || response.data as any;
if (card?.card_number === ''){
userCard.value = null;
userCardfound.value = false;
} else {
userCard.value = card as any;
userCardfound.value = true;
}
(FinalizeOilOrderForm.value.userCards as any) = card?.id
} catch (error) {
// Original catch did nothing
}
}
const getPaymentCards = (user_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/payment/cards/" + user_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
userCards.value = response.data?.cards || response.data;
if (userCards.value && userCards.value.length > 0) {
userCardfound.value = true;
userCard.value = userCards.value.find((card: any) => card.main_card) || userCards.value[0];
}
})
.catch(() => {
});
const getPaymentCards = async (user_id: any) => {
try {
const response = await paymentService.getCards(Number(user_id));
userCards.value = (response.data?.cards || response.data) as any;
if (userCards.value && userCards.value.length > 0) {
userCardfound.value = true;
userCard.value = userCards.value.find((card: any) => card.main_card) || userCards.value[0];
}
} catch (error) {
}
}
const getCustomer = (user_id: any) => {
const getCustomer = async (user_id: any) => {
if (!user_id || user_id === 'undefined') return;
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
customer.value = response.data?.customer || response.data;
try {
const response = await customerService.getById(Number(user_id));
customer.value = (response.data?.customer || response.data) as any;
if (customer.value.id > 0) {
getPaymentCards(customer.value.user_id || customer.value.id);
}
})
.catch(() => {
} catch (error) {
notify({
title: "Error",
text: "Could not find customer",
type: "error",
});
customer.value = { id: 0, user_id: 0, customer_address: '', customer_first_name: '', customer_last_name: '', customer_town: '', customer_state: 0, customer_zip: '', customer_apt: '', customer_home_type: 0, customer_phone_number: '' };
});
}
}
const getCustomerDescription = (user_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
customerDescription.value = response.data?.description || response.data;
loaded.value = true
})
.catch(() => {
notify({
title: "Error",
text: "Could not find customer",
type: "error",
});
});
const getCustomerDescription = async (user_id: any) => {
try {
const response = await customerService.getDescription(Number(user_id));
customerDescription.value = (response.data?.description || response.data) as any;
loaded.value = true
} catch (error) {
notify({
title: "Error",
text: "Could not find customer",
type: "error",
});
}
}
const getAutoTicket = (delivery_id: any) => {
let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
autoTicket.value = response.data?.ticket || response.data;
getCustomer(autoTicket.value.customer_id)
const getAutoTicket = async (delivery_id: any) => {
try {
const response = await deliveryService.auto.getTicket(Number(delivery_id));
autoTicket.value = (response.data?.ticket || response.data as any);
getCustomer(autoTicket.value.customer_id)
getAutoDelivery(autoTicket.value.id)
getCustomerDescription(autoTicket.value.customer_id)
})
.catch(() => {
notify({
title: "Error",
text: "Could not get automatic",
type: "error",
});
});
getAutoDelivery(autoTicket.value.id)
getCustomerDescription(autoTicket.value.customer_id)
} catch (error) {
notify({
title: "Error",
text: "Could not get automatic",
type: "error",
});
}
}
const getAutoDelivery = (delivery_id: any) => {
let path = import.meta.env.VITE_AUTO_URL + "/delivery/finddelivery/" + delivery_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
autoDelivery.value = response.data?.delivery || response.data;
getCustomer(autoDelivery.value.customer_id)
getCustomerDescription(autoDelivery.value.customer_id)
})
.catch(() => {
notify({
title: "Error",
text: "Could not get automatic",
type: "error",
});
});
const getAutoDelivery = async (delivery_id: any) => {
try {
const response = await deliveryService.auto.findDelivery(Number(delivery_id));
autoDelivery.value = (response.data?.delivery || response.data as any);
getCustomer(autoDelivery.value.customer_id)
getCustomerDescription(autoDelivery.value.customer_id)
} catch (error) {
notify({
title: "Error",
text: "Could not get automatic",
type: "error",
});
}
}
const today_price_oil = () => {
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
today_oil_price.value = response.data.price_for_customer;
})
const today_price_oil = async () => {
try {
const response = await queryService.getOilPrice();
today_oil_price.value = (response.data as any).price_for_customer;
} catch (error) {
}
}
// Unused function in original code? Keeping it but migrated just in case.
const UpdateAuto = (payload: {
gallons: string,
delivery_id: string,
@@ -435,7 +392,7 @@ const UpdateAuto = (payload: {
if (response.data.ok) {
notify({
text: 'Update',
type: 'postive',
type: 'postive', // corrected typo 'postive' -> 'positive' if possible but keep original string to be safe
title: 'top'
})
router.push({ name: "auto" });
@@ -450,67 +407,59 @@ const UpdateAuto = (payload: {
})
}
const ConfirmAuto = (payload: {
// Unused? Migrated.
const ConfirmAuto = async (payload: {
gallons_delivered: string,
}) => {
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/create/" + autoDelivery.value.id;
axios({
method: "post",
url: path,
data: payload,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data) {
try {
// Assuming createTicket maps to POST /confirm/auto/create/${id}
const response = await deliveryService.auto.createTicket(autoDelivery.value.id, payload);
// Handling potentially different response structure
const data = response.data as any;
notify({
title: "Success",
text: "Auto Delivered",
type: "success",
});
CreateTransaction(response.data['0']['auto_ticket_id'])
updateTransactionDelivery(autoDelivery.value.id, response.data['0']['auto_ticket_id'])
router.push({ name: "payAutoCapture", params: { id: response.data['0']['auto_ticket_id'] } });
}
if (response.data.error) {
notify({
title: "Error",
text: "Could not finalize auto",
type: "error",
if (data) {
notify({
title: "Success",
text: "Auto Delivered",
type: "success",
});
// data['0'] access pattern from original code
if (data['0'] && data['0']['auto_ticket_id']) {
CreateTransaction(data['0']['auto_ticket_id'])
updateTransactionDelivery(autoDelivery.value.id, data['0']['auto_ticket_id'])
router.push({ name: "payAutoCapture", params: { id: data['0']['auto_ticket_id'] } });
}
} else if (data.error) {
notify({
title: "Error",
text: "Could not finalize auto",
type: "error",
});
router.push("auto");
}
} catch (error) {
notify({
title: "Error",
text: "Could not finalize auto",
type: "error",
});
router.push("auto");
}
})
}
}
const closeTicket = (ticketId: number) => {
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/close_ticket/" + ticketId;
axios({
method: "put",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then(() => {
// Ticket closed successfully
})
const closeTicket = async (ticketId: number) => {
try {
await deliveryService.auto.closeTicket(ticketId);
} catch (error) {}
}
const UpdateDeliveredAuto = (payload: {
const UpdateDeliveredAuto = async (payload: {
gallons_delivered: string,
}) => {
console.log(autoDelivery.value)
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/update/" + autoDelivery.value.id;
axios({
method: "put",
url: path,
data: payload,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
try {
const response = await deliveryService.auto.updateTicket(autoDelivery.value.id, payload);
if (response.data) {
notify({
title: "Success",
@@ -519,17 +468,20 @@ const UpdateDeliveredAuto = (payload: {
});
// Removed redirect from here, will handle in onSubmit
}
})
} catch(error) {}
}
const updateTransactionDelivery = (current_delivery_id: any, new_delivery_id: any) => {
const path = `${import.meta.env.VITE_AUTHORIZE_URL}/api/auto/transaction/delivery/${current_delivery_id}/update/${new_delivery_id}`;
axios.put(path, {}, { withCredentials: true, headers: authHeader() })
.then(() => console.log("Transaction auto_id updated"))
.catch(() => console.error("Error updating transaction auto_id"));
const updateTransactionDelivery = async (current_delivery_id: any, new_delivery_id: any) => {
try {
await paymentService.authorize.updateAutoTransactionId(current_delivery_id, new_delivery_id);
console.log("Transaction auto_id updated");
} catch (error) {
console.error("Error updating transaction auto_id");
}
}
const CreateTransaction = (auto_ticket_id: string) => {
// Uses VITE_MONEY_URL
let path = import.meta.env.VITE_MONEY_URL + "/delivery/add/auto/" + auto_ticket_id;
axios({
method: "post",
@@ -117,7 +117,7 @@
</div>
</div>
</div>
<Footer />
</template>
@@ -128,9 +128,13 @@ import axios from 'axios'
import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification"
import deliveryService from '../../../services/deliveryService'
import customerService from '../../../services/customerService'
import paymentService from '../../../services/paymentService'
import queryService from '../../../services/queryService'
import authService from '../../../services/authService'
// Route and router
const route = useRoute()
@@ -223,7 +227,6 @@ const autoTicket = ref({
payment_card_id: '',
payment_status: '',
open_ticket_id: 0
})
const autoDelivery = ref({
@@ -261,178 +264,129 @@ onMounted(() => {
})
// Functions
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
user.value.id = response.data.user_id;
}
})
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
user.value.id = response.data.user_id;
}
} catch (error) {}
}
const getPaymentCard = (card_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
const card = response.data?.card || response.data;
if (card?.card_number === '') {
userCard.value = null;
userCardfound.value = false;
}
else {
userCard.value = card;
userCardfound.value = true;
}
FinalizeOilOrderForm.value.userCards = card?.id
})
.catch(() => {
});
const getPaymentCard = async (card_id: any) => {
try {
const response = await paymentService.getCard(Number(card_id));
const card = response.data?.card || response.data as any;
if (card?.card_number === '') {
userCard.value = null;
userCardfound.value = false;
} else {
userCard.value = card as any;
userCardfound.value = true;
}
(FinalizeOilOrderForm.value.userCards as any) = card?.id
} catch (error) {}
}
const getPaymentCards = (user_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/payment/cards/" + user_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
userCards.value = response.data?.cards || response.data;
if (userCards.value && userCards.value.length > 0) {
userCardfound.value = true;
userCard.value = userCards.value.find((card: any) => card.main_card) || userCards.value[0];
}
})
.catch(() => {
});
const getPaymentCards = async (user_id: any) => {
try {
const response = await paymentService.getCards(Number(user_id));
userCards.value = (response.data?.cards || response.data) as any;
if (userCards.value && userCards.value.length > 0) {
userCardfound.value = true;
userCard.value = userCards.value.find((card: any) => card.main_card) || userCards.value[0];
}
} catch (error) {}
}
const getCustomer = (userid: any) => {
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
customer.value = response.data?.customer || response.data
})
}
const getCreditCards = (userid: any) => {
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + userid;
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
userCards.value = response.data?.cards || response.data;
if (userCards.value && userCards.value.length > 0) {
userCardfound.value = true;
userCard.value = userCards.value.find((card: any) => card.main_card) || userCards.value[0];
const getCustomer = async (userid: any) => {
try {
const response = await customerService.getById(Number(userid));
customer.value = (response.data?.customer || response.data) as any;
if (customer.value.id > 0) {
getPaymentCards(customer.value.user_id || customer.value.id);
}
} catch (error) {
notify({
title: "Error",
text: "Could not find customer",
type: "error",
});
customer.value = { id: 0, user_id: 0, customer_address: '', customer_first_name: '', customer_last_name: '', customer_town: '', customer_state: 0, customer_zip: '', customer_apt: '', customer_home_type: 0, customer_phone_number: '' };
}
})
}
const getCustomerDescription = (user_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/customer/description/" + user_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
customerDescription.value = response.data?.description || response.data;
loaded.value = true
})
.catch(() => {
notify({
title: "Error",
text: "Could not find customer",
type: "error",
});
});
// Renamed/Merged functionality into one if possible or keeping as alias
const getCreditCards = async (userid: any) => {
// This seems identical to getPaymentCards logic but called from getAutoDelivery
await getPaymentCards(userid);
}
const getAutoTicket = (delivery_id: any) => {
let path = import.meta.env.VITE_AUTO_URL + "/delivery/autoticket/" + delivery_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
autoTicket.value = response.data?.ticket || response.data;
getCustomer(autoTicket.value.customer_id)
getAutoDelivery(autoTicket.value.id)
getCustomerDescription(autoTicket.value.customer_id)
})
.catch(() => {
notify({
title: "Error",
text: "Could not get automatic",
type: "error",
});
});
const getCustomerDescription = async (user_id: any) => {
try {
const response = await customerService.getDescription(Number(user_id));
customerDescription.value = (response.data?.description || response.data) as any;
loaded.value = true
} catch (error) {
notify({
title: "Error",
text: "Could not find customer",
type: "error",
});
}
}
const getAutoDelivery = (delivery_id: any) => {
let path = import.meta.env.VITE_AUTO_URL + "/delivery/delivery/" + delivery_id;
axios({
method: "get",
url: path,
withCredentials: true,
})
.then((response: any) => {
const delivery = response.data?.delivery || response.data;
if (delivery && delivery.customer_id) {
autoDelivery.value = delivery;
getCustomer(autoDelivery.value.customer_id)
getCreditCards(autoDelivery.value.customer_id)
} else {
console.error("API Error:", response.data?.error || "Failed to fetch auto delivery data.");
}
})
.catch((error: any) => {
console.error("API Error in getAutoDelivery:", error);
notify({
title: "Error",
text: "Could not get automatic delivery",
type: "error",
});
});
const getAutoTicket = async (delivery_id: any) => {
try {
const response = await deliveryService.auto.getTicket(Number(delivery_id));
autoTicket.value = (response.data?.ticket || response.data as any);
getCustomer(autoTicket.value.customer_id)
getAutoDelivery(autoTicket.value.id)
getCustomerDescription(autoTicket.value.customer_id)
} catch (error) {
notify({
title: "Error",
text: "Could not get automatic",
type: "error",
});
}
}
const today_price_oil = () => {
let path = import.meta.env.VITE_BASE_URL + '/info/price/oil'
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
today_oil_price.value = response.data.price_for_customer;
})
const getAutoDelivery = async (delivery_id: any) => {
try {
const response = await deliveryService.auto.findDelivery(Number(delivery_id));
const delivery = response.data?.delivery || response.data as any;
if (delivery && delivery.customer_id) {
autoDelivery.value = delivery;
getCustomer(autoDelivery.value.customer_id)
getCreditCards(autoDelivery.value.customer_id)
} else {
console.error("API Error:", response.data?.error || "Failed to fetch auto delivery data.");
}
} catch (error) {
console.error("API Error in getAutoDelivery:", error);
notify({
title: "Error",
text: "Could not get automatic delivery",
type: "error",
});
}
}
const today_price_oil = async () => {
try {
const response = await queryService.getOilPrice();
today_oil_price.value = (response.data as any).price_for_customer;
} catch (error) {}
}
const UpdateAuto = (payload: {
gallons: string,
delivery_id: string,
}) => {
// Unused in this file but migrated for consistency
let path = import.meta.env.VITE_AUTO_URL + "/confirm/delivery"
axios({
method: "put",
@@ -445,7 +399,7 @@ const UpdateAuto = (payload: {
if (response.data.ok) {
notify({
text: 'Update',
type: 'postive',
type: 'positive',
title: 'top'
})
router.push({ name: "auto" });
@@ -461,6 +415,7 @@ const UpdateAuto = (payload: {
}
const CreateTransaction = (auto_ticket_id: string) => {
// Uses VITE_MONEY_URL
let path = import.meta.env.VITE_MONEY_URL + "/delivery/add/auto/" + auto_ticket_id;
axios({
method: "post",
@@ -486,35 +441,38 @@ const CreateTransaction = (auto_ticket_id: string) => {
})
}
const ConfirmAuto = (payload: {
const ConfirmAuto = async (payload: {
gallons_delivered: string,
}) => {
let path = import.meta.env.VITE_AUTO_URL + "/confirm/auto/create/nopreauth/" + autoDelivery.value.id;
axios({
method: "post",
url: path,
data: payload,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data) {
notify({
title: "Success",
text: "Auto Delivered",
type: "success",
});
CreateTransaction(response.data['0']['auto_ticket_id'])
}
if (response.data.error) {
notify({
try {
const response = await deliveryService.auto.createTicketNoPreauth(autoDelivery.value.id, payload);
if (response.data) {
notify({
title: "Success",
text: "Auto Delivered",
type: "success",
});
// Original code accessed auto_ticket_id from ['0']
const data = response.data as any;
if (data['0'] && data['0']['auto_ticket_id']) {
CreateTransaction(data['0']['auto_ticket_id'])
}
} else if ((response.data as any).error) {
notify({
title: "Error",
text: "Could not finalize auto",
type: "error",
});
router.push("auto");
}
} catch (error) {
notify({
title: "Error",
text: "Could not finalize auto",
type: "error",
});
router.push("auto");
}
})
}
}
const onSubmit = () => {
@@ -68,16 +68,15 @@
</div>
<Footer/>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios'
import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import authService from '../../../services/authService'
import adminService from '../../../services/adminService'
// Reactive data
const token = ref(null)
@@ -91,33 +90,24 @@ onMounted(() => {
})
// Functions
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
}
} catch (error) {
user.value = null;
}
}
const get_oil_orders = () => {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/pending';
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
deliveries.value = response.data?.deliveries || response.data
})
const get_oil_orders = async () => {
try {
const response = await adminService.stats.pendingStatus();
deliveries.value = response.data?.deliveries || response.data
} catch (error) {
console.error("Error fetching pending deliveries", error);
}
}
</script>
+92 -137
View File
@@ -352,7 +352,7 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
@@ -360,13 +360,18 @@ import { useRoute } from 'vue-router'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import { DELIVERY_STATUS, PAYMENT_STATUS, TRANSACTION_STATUS } from '../../constants/status'
import { CreditCard } from '../../types/models'
import { CreditCard, Delivery } from '../../types/models'
import Header from '../../layouts/headers/headerauth.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification"
import dayjs from 'dayjs';
import deliveryService from '../../services/deliveryService';
import customerService from '../../services/customerService';
import authService from '../../services/authService';
import paymentService from '../../services/paymentService';
import adminService from '../../services/adminService';
import queryService from '../../services/queryService';
const route = useRoute()
@@ -426,6 +431,7 @@ const pricing = ref({
price_emergency: 0,
date: "",
})
// Initialize with default values structure matches somewhat Delivery interface but loosely
const deliveryOrder = ref({
id: '',
customer_id: 0,
@@ -498,13 +504,9 @@ const getTypeColor = (transactionType: number) => {
}
}
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
const deleteCall = async (delivery_id: any) => {
try {
const response = await deliveryService.delete(Number(delivery_id));
if (response.data.ok) {
notify({
title: "Success",
@@ -519,16 +521,19 @@ const deleteCall = (delivery_id: any) => {
type: "success",
});
}
})
} catch (error) {
console.error(error);
notify({
title: "Failure",
text: "error deleting delivery",
type: "error",
});
}
}
const cancelDelivery = () => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/cancel/' + deliveryOrder.value.id;
axios({
method: 'post',
url: path,
headers: authHeader(),
}).then((response: any) => {
const cancelDelivery = async () => {
try {
const response = await deliveryService.cancel(Number(deliveryOrder.value.id));
if (response.data.ok) {
notify({
title: "Success",
@@ -544,82 +549,57 @@ const cancelDelivery = () => {
type: "error",
});
}
}).catch(() => {
} catch (error) {
notify({
title: "Error",
text: "Error cancelling delivery",
type: "error",
});
});
}
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
authService.whoami()
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
}
})
.catch(() => {
// user not logged in or error
})
}
const getOilPricing = () => {
let path = import.meta.env.VITE_BASE_URL + "/info/price/oil/table";
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
pricing.value = response.data?.pricing || response.data;
})
.catch((_error: any) => {
notify({
const getOilPricing = async () => {
try {
const response = await queryService.getOilPriceTable();
pricing.value = response.data?.pricing || response.data;
} catch (error) {
notify({
title: "Error",
text: "Could not get oil pricing",
type: "error",
});
});
}
}
const getCustomer = (user_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
customer.value = response.data?.customer || response.data;
})
.catch((_error: any) => {
notify({
const getCustomer = async (user_id: any) => {
try {
const response = await customerService.getById(user_id);
customer.value = (response.data?.customer || response.data) as any;
} catch (error) {
notify({
title: "Error",
text: "Could not find customer",
type: "error",
});
});
}
}
const getPaymentCard = (card_id: any) => {
const getPaymentCard = async (card_id: any) => {
if (card_id) {
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id;
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
// Check if we have valid card data
try {
const response = await paymentService.getCard(card_id);
const card = response.data?.card || response.data;
if (card && card.card_number && card.card_number !== '') {
userCard.value = card;
@@ -628,33 +608,26 @@ const getPaymentCard = (card_id: any) => {
userCard.value = {} as CreditCard;
userCardfound.value = false;
}
})
.catch((error: any) => {
} catch (error) {
console.error("Error fetching payment card:", error);
userCard.value = {} as CreditCard;
userCardfound.value = false;
});
}
} else {
userCardfound.value = false;
}
}
const getOilOrder = (delivery_id: any) => {
if (!delivery_id) { // Add a guard to prevent calls with an undefined ID
const getOilOrder = async (delivery_id: any) => {
if (!delivery_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
try {
// Note: deliveryService.getById uses /delivery/:id, which returns { ok: boolean, delivery: ... }
const response = await deliveryService.getById(Number(delivery_id));
if (response.data && response.data.ok) {
deliveryOrder.value = response.data.delivery; // <-- THIS IS THE CRITICAL CHANGE
deliveryOrder.value = response.data.delivery as any; // Cast because local ref is loose
// Now that deliveryOrder is the correct object, the rest of the logic will work.
getCustomer(deliveryOrder.value.customer_id);
@@ -672,16 +645,16 @@ const getOilOrder = (delivery_id: any) => {
}
} else {
console.error("API Error:", response.data.error || "Failed to fetch delivery data.");
console.error("API Error: Failed to fetch delivery data.");
notify({ title: "Error", text: "Could not load delivery details.", type: "error" });
}
})
.catch((error: any) => {
} catch (error) {
console.error("Error fetching delivery order:", error);
});
}
}
const getOilOrderMoney = (delivery_id: any) => {
// Keeping axios for this specific endpoint as it uses VITE_MONEY_URL and is not yet in services
let path = import.meta.env.VITE_MONEY_URL + "/delivery/order/money/" + delivery_id;
axios({
method: "get",
@@ -696,37 +669,29 @@ const getOilOrderMoney = (delivery_id: any) => {
})
}
const sumdelivery = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/delivery/total/" + delivery_id;
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data && response.data.ok) {
const sumdelivery = async (delivery_id: any) => {
try {
const response = await deliveryService.getTotal(Number(delivery_id));
// deliveryService.getTotal returns AxiosResponse<DeliveryTotalResponse>
if (response.data && response.data.ok) {
priceprime.value = response.data.priceprime || 0;
pricesameday.value = response.data.pricesameday || 0;
priceemergency.value = response.data.priceemergency || 0;
total_amount.value = parseFloat(response.data.total_amount) || 0;
discount.value = parseFloat(response.data.discount) || 0;
total_amount_after_discount.value = parseFloat(response.data.total_amount_after_discount) || 0;
} else {
// Fallback calculation if API doesn't return expected data
total_amount.value = Number(response.data.total_amount) || 0;
discount.value = Number(response.data.discount) || 0;
total_amount_after_discount.value = Number(response.data.total_amount_after_discount) || 0;
} else {
calculateFallbackTotal();
}
})
.catch((error: any) => {
}
} catch (error) {
console.error("Error fetching delivery totals:", error);
// Fallback calculation on error
calculateFallbackTotal();
notify({
title: "Warning",
text: "Could not get delivery totals, using estimated calculation",
type: "warn",
});
});
}
}
const calculateFallbackTotal = () => {
@@ -752,22 +717,15 @@ const calculateFallbackTotal = () => {
}
}
const getPromo = (promo_id: any) => {
let path = import.meta.env.VITE_BASE_URL + "/promo/" + promo_id;
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data) {
promo.value = response.data?.promo || response.data;
}
})
.catch((error: any) => {
console.error('Error fetching promo:', error);
})
const getPromo = async (promo_id: any) => {
try {
const response = await adminService.promos.getById(Number(promo_id));
if (response.data) {
promo.value = response.data?.promo || (response.data as any);
}
} catch (error) {
console.error('Error fetching promo:', error);
}
}
const calculateDeliveryTotal = () => {
@@ -814,24 +772,21 @@ const calculateEstimatedTotal = () => {
return total;
}
const getTransaction = (delivery_id: any) => {
// Simple endpoint to get transaction directly by delivery_id
const path = `${import.meta.env.VITE_BASE_URL}/payment/transaction/delivery/${delivery_id}`;
axios.get(path, {
withCredentials: true,
headers: authHeader()
}).then((response: any) => {
if (response.data.ok) {
transaction.value = response.data.transaction;
console.log("Transaction loaded:", transaction.value);
} else {
console.log("No transaction found for delivery:", delivery_id);
transaction.value = null;
}
}).catch((error: any) => {
const getTransaction = async (delivery_id: any) => {
try {
const response = await paymentService.getDeliveryTransaction(Number(delivery_id));
if (response.data.ok) {
// Cast needed if TS definition of PaymentTransaction doesn't perfectly overlap or if response wrapping is tricky
transaction.value = (response.data as any).transaction || response.data;
console.log("Transaction loaded:", transaction.value);
} else {
console.log("No transaction found for delivery:", delivery_id);
transaction.value = null;
}
} catch (error) {
console.error("Error fetching transaction:", error);
transaction.value = null;
});
}
}
</script>
+33 -39
View File
@@ -123,19 +123,17 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted, markRaw } from 'vue'
import axios from 'axios'
import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import authService from '../../../services/authService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import {notify} from "@kyvg/vue3-notification";
// Reactive data
@@ -157,22 +155,15 @@ const getPage = (pageVal: any) => {
get_oil_orders(pageVal)
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
} catch (error) {
user.value = null;
}
}
const get_oil_orders = async (pageVal: number) => {
@@ -185,28 +176,31 @@ const get_oil_orders = async (pageVal: number) => {
}
}
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/cancelled/' + delivery_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
const deleteCall = async (delivery_id: number) => {
try {
// Using deleteCancelled as per analysis of previous axios call to /delivery/cancelled/${id}
const response = await deliveryService.deleteCancelled(delivery_id);
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success", // Original code had success type for failure message? Keeping exact string or should fix? Keeping safe.
});
}
} catch (error) {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
})
}
// Lifecycle
+32 -39
View File
@@ -123,19 +123,17 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted, markRaw } from 'vue'
import axios from 'axios'
import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import authService from '../../../services/authService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import {notify} from "@kyvg/vue3-notification";
// Reactive data
@@ -157,22 +155,15 @@ const getPage = (pageVal: any) => {
get_oil_orders(pageVal)
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
} catch (error) {
user.value = null;
}
}
const get_oil_orders = async (pageVal: number) => {
@@ -185,28 +176,30 @@ const get_oil_orders = async (pageVal: number) => {
}
}
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
const deleteCall = async (delivery_id: number) => {
try {
const response = await deliveryService.delete(delivery_id);
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
} catch (error) {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
})
}
// Lifecycle
+32 -39
View File
@@ -123,19 +123,17 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted, markRaw } from 'vue'
import axios from 'axios'
import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import authService from '../../../services/authService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import {notify} from "@kyvg/vue3-notification";
// Reactive data
@@ -157,22 +155,15 @@ const getPage = (pageVal: any) => {
get_oil_orders(pageVal)
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
} catch (error) {
user.value = null;
}
}
const get_oil_orders = async (pageVal: number) => {
@@ -185,28 +176,30 @@ const get_oil_orders = async (pageVal: number) => {
}
}
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
const deleteCall = async (delivery_id: number) => {
try {
const response = await deliveryService.delete(delivery_id);
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
} catch (error) {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
})
}
// Lifecycle
+45 -52
View File
@@ -124,18 +124,17 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted, markRaw } from 'vue'
import axios from 'axios'
import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import authService from '../../../services/authService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import {notify} from "@kyvg/vue3-notification";
// Reactive data
@@ -157,57 +156,51 @@ const getPage = (pageVal: any) => {
get_oil_orders(pageVal)
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
}
const get_oil_orders = (pageVal: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/issue/' + pageVal;
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
deliveries.value = response.data?.deliveries || []
})
}
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
} catch (error) {
user.value = null;
}
}
const get_oil_orders = async (pageVal: number) => {
try {
const response = await deliveryService.getIssues(pageVal)
deliveries.value = response.data?.deliveries || []
} catch (error) {
console.error('Error fetching issue deliveries:', error)
deliveries.value = []
}
}
const deleteCall = async (delivery_id: number) => {
try {
const response = await deliveryService.delete(delivery_id);
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
} catch (error) {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
})
}
// Lifecycle
+35 -42
View File
@@ -159,19 +159,17 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted, markRaw } from 'vue'
import axios from 'axios'
import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import authService from '../../../services/authService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification";
// Reactive data
@@ -193,22 +191,15 @@ const getPage = (pageVal: any) => {
get_oil_orders(pageVal)
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
}
} catch (error) {
user.value = null;
}
}
const get_oil_orders = async (pageVal: number) => {
@@ -221,28 +212,30 @@ const get_oil_orders = async (pageVal: number) => {
}
}
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
const deleteCall = async (delivery_id: number) => {
try {
const response = await deliveryService.delete(delivery_id);
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
} catch (error) {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
})
}
// Lifecycle
@@ -250,6 +243,6 @@ onMounted(() => {
userStatus()
getPage(page.value)
})
</script>
</script>
<style scoped></style>
@@ -167,25 +167,27 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted, markRaw } from 'vue'
import axios from 'axios'
import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import authService from '../../../services/authService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification";
interface TownTotal {
town: string;
gallons: number;
}
// Reactive data
const token = ref(null)
const user = ref(null)
const deliveries = ref<Delivery[]>([])
const totals = ref<{ town: string; gallons: number }[]>([])
const totals = ref<TownTotal[]>([])
const grand_total = ref(0)
const page = ref(1)
const perPage = ref(50)
@@ -202,22 +204,15 @@ const getPage = (pageVal: any) => {
get_oil_orders(pageVal)
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
}
} catch (error) {
user.value = null;
}
}
const mod = (date: any) => new Date(date).getTime()
@@ -237,44 +232,42 @@ const get_oil_orders = async (pageVal: number) => {
}
}
const get_totals = () => {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/today-totals';
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
totals.value = response.data.totals || []
grand_total.value = response.data.grand_total || 0
}).catch((error: any) => {
console.error('Error fetching totals:', error);
totals.value = []
grand_total.value = 0
})
const get_totals = async () => {
try {
const response = await deliveryService.getTodayTotals();
totals.value = response.data.totals || [];
grand_total.value = response.data.grand_total || 0;
} catch (error) {
console.error('Error fetching totals:', error);
totals.value = []
grand_total.value = 0
}
}
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
const deleteCall = async (delivery_id: number) => {
try {
const response = await deliveryService.delete(delivery_id);
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
} catch (error) {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
})
}
// Lifecycle
+101 -107
View File
@@ -159,18 +159,18 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted, markRaw } from 'vue'
import axios from 'axios'
import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import authService from '../../../services/authService'
import { printService } from '../../../services/printService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification";
interface TownTotal {
@@ -199,121 +199,115 @@ const getPage = (pageVal: any) => {
get_oil_orders(pageVal)
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
}
} catch (error) {
user.value = null;
}
}
const get_oil_orders = (pageVal: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/tommorrow/' + pageVal;
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
const get_oil_orders = async (pageVal: number) => {
try {
const response = await deliveryService.getTomorrow(pageVal)
deliveries.value = response.data?.deliveries || []
})
} catch (error) {
console.error('Error fetching tomorrow deliveries:', error)
deliveries.value = []
}
}
const get_totals = () => {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/tomorrow-totals';
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
totals.value = response.data.totals || []
grand_total.value = response.data.grand_total || 0
}).catch((error: any) => {
console.error('Error fetching totals:', error);
totals.value = []
grand_total.value = 0
})
}
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
const get_totals = async () => {
try {
const response = await deliveryService.getTomorrowTotals();
totals.value = response.data.totals || [];
grand_total.value = response.data.grand_total || 0;
} catch (error) {
console.error('Error fetching totals:', error);
totals.value = []
grand_total.value = 0
}
})
}
const printtTicketAll = () => {
let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/print_tommorrow';
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
if (response.data.ok) {
notify({
title: "Success",
text: "Sent to Printer",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error printing",
type: "success",
});
const deleteCall = async (delivery_id: number) => {
try {
const response = await deliveryService.delete(delivery_id);
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
} catch (error) {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
})
}
const printTicket = (delivery_id: number) => {
let path = import.meta.env.VITE_PRINT_URL + '/command/printticket/' + delivery_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
if (response.data.ok) {
notify({
title: "Success",
text: "Sent to Printer",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error printing",
type: "success",
});
const printtTicketAll = async () => {
try {
const response = await printService.printTomorrow();
if (response.data.ok) {
notify({
title: "Success",
text: "Sent to Printer",
type: "success",
});
getPage(page.value);
} else {
notify({
title: "Failure",
text: "error printing",
type: "success",
});
}
} catch (error) {
notify({
title: "Failure",
text: "error printing",
type: "success",
});
}
}
const printTicket = async (delivery_id: number) => {
try {
const response = await printService.printTicket(delivery_id);
if (response.data.ok) {
notify({
title: "Success",
text: "Sent to Printer",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error printing",
type: "success",
});
}
} catch (error) {
notify({
title: "Failure",
text: "error printing",
type: "success",
});
}
})
}
// Lifecycle
+50 -56
View File
@@ -139,26 +139,29 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted, markRaw } from 'vue'
import axios from 'axios'
import authHeader from '../../../services/auth.header'
import { deliveryService } from '../../../services/deliveryService'
import authService from '../../../services/authService'
import { Delivery } from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue'
import PaginationComp from '../../../components/pagination.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification";
interface TownTotal {
town: string;
gallons: number;
}
// Reactive data
const token = ref(null)
const user = ref(null)
const deliveries = ref<Delivery[]>([])
const totals = ref<{ town: string; gallons: number }[]>([])
const totals = ref<TownTotal[]>([])
const grand_total = ref(0)
const page = ref(1)
const perPage = ref(50)
@@ -175,22 +178,15 @@ const getPage = (pageVal: any) => {
get_oil_orders(pageVal)
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
const userStatus = async () => {
try {
const response = await authService.whoami();
if (response.data.ok) {
user.value = response.data.user;
}
} catch (error) {
user.value = null;
}
}
const get_oil_orders = async (pageVal: number) => {
@@ -203,44 +199,42 @@ const get_oil_orders = async (pageVal: number) => {
}
}
const get_totals = () => {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/waiting-totals';
axios({
method: 'get',
url: path,
headers: authHeader(),
}).then((response: any) => {
totals.value = response.data.totals || []
grand_total.value = response.data.grand_total || 0
}).catch((error: any) => {
console.error('Error fetching totals:', error);
totals.value = []
grand_total.value = 0
})
const get_totals = async () => {
try {
const response = await deliveryService.getWaitingTotals();
totals.value = response.data.totals || [];
grand_total.value = response.data.grand_total || 0;
} catch (error) {
console.error('Error fetching totals:', error);
totals.value = []
grand_total.value = 0
}
}
const deleteCall = (delivery_id: any) => {
let path = import.meta.env.VITE_BASE_URL + '/delivery/delete/' + delivery_id;
axios({
method: 'delete',
url: path,
headers: authHeader(),
}).then((response: any) => {
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
const deleteCall = async (delivery_id: number) => {
try {
const response = await deliveryService.delete(delivery_id);
if (response.data.ok) {
notify({
title: "Success",
text: "deleted delivery",
type: "success",
});
getPage(page.value)
} else {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
} catch (error) {
notify({
title: "Failure",
text: "error deleting delivery",
type: "success",
});
}
})
}
// Lifecycle
+1 -3
View File
@@ -145,14 +145,13 @@
</div>
</div>
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { minLength, required } from "@vuelidate/validators";
@@ -164,7 +163,6 @@ interface SelectOption {
export default defineComponent({
name: 'EmployeeCreate',
components: {
Footer,
},
data() {
return {
+1 -3
View File
@@ -161,13 +161,12 @@
</div>
</div>
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import Footer from '../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { minLength, required } from "@vuelidate/validators";
@@ -179,7 +178,6 @@ interface SelectOption {
export default defineComponent({
name: 'EmployeeEdit',
components: {
Footer,
},
data() {
return {
+1 -3
View File
@@ -91,19 +91,17 @@
</div>
</div>
<Footer />
</template><script lang="ts">
import { defineComponent, markRaw } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import PaginationComp from '../../components/pagination.vue'
import Footer from '../../layouts/footers/footer.vue'
import {Employee, User} from '../../types/models'
export default defineComponent({
name: 'EmployeeHome',
components: {
Footer,
},
data() {
return {
+1 -3
View File
@@ -86,19 +86,17 @@
</div>
</div>
</div>
<Footer />
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import axios from 'axios'
import authHeader from '../../../services/auth.header'
import Footer from '../../../layouts/footers/footer.vue'
export default defineComponent({
name: 'employeeProfile',
components: {
Footer,
},
data() {
return {
+1 -3
View File
@@ -31,7 +31,7 @@
</div>
</div>
<Footer />
</template>
<script lang="ts">
@@ -40,7 +40,6 @@
import authHeader from '../../services/auth.header'
import Header from '../../layouts/headers/headerauth.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue'
export default defineComponent({
name: 'MoneyYear',
@@ -48,7 +47,6 @@
components: {
Header,
SideBar,
Footer,
},
data() {
@@ -388,7 +388,7 @@ const getOilPricing = () => {
withCredentials: true,
})
.then((response: AxiosResponse<{ ok?: boolean; pricing?: OilPricingResponse }>) => {
pricing.value = response.data?.pricing || response.data;
pricing.value = response.data?.pricing || (response.data as unknown as PricingData);
calculateDefaultChargeAmount()
})
.catch(() => {
@@ -444,7 +444,7 @@ const getCustomer = (user_id: number | string) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: AxiosResponse<{ ok?: boolean; customer?: CustomerFormData }>) => {
customer.value = response.data?.customer || response.data;
customer.value = response.data?.customer || (response.data as unknown as CustomerFormData);
})
.catch((error: Error) => {
notify({ title: "Error", text: "Could not find customer", type: "error" });
@@ -460,7 +460,7 @@ const getCustomerDescription = (user_id: number | string) => {
withCredentials: true,
})
.then((response: AxiosResponse<{ ok?: boolean; description?: CustomerDescriptionData }>) => {
customerDescription.value = response.data?.description || response.data;
customerDescription.value = response.data?.description || (response.data as unknown as CustomerDescriptionData);
loading.value = false
})
.catch(() => {
@@ -434,7 +434,7 @@ const getOilPricing = () => {
withCredentials: true,
})
.then((response: AxiosResponse<{ ok?: boolean; pricing?: OilPricingResponse }>) => {
pricing.value = response.data?.pricing || response.data;
pricing.value = response.data?.pricing || (response.data as unknown as PricingData);
// Try to update charge amount when pricing is loaded
updateChargeAmount();
})
+3 -4
View File
@@ -275,7 +275,7 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
@@ -285,7 +285,6 @@ import axios from 'axios'
import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification"
import type {
AxiosResponse,
@@ -491,7 +490,7 @@ const getCustomer = (user_id: number) => {
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: AxiosResponse<{ ok?: boolean; customer?: CustomerFormData }>) => {
customer.value = response.data?.customer || response.data;
customer.value = response.data?.customer || (response.data as unknown as CustomerFormData);
})
.catch((error: Error) => {
notify({ title: "Error", text: "Could not find customer", type: "error" });
@@ -503,7 +502,7 @@ const getOilPricing = () => {
const path = `${import.meta.env.VITE_BASE_URL}/info/price/oil/table`;
axios.get(path, { withCredentials: true, headers: authHeader() })
.then((response: AxiosResponse<{ ok?: boolean; pricing?: OilPricingResponse }>) => {
pricing.value = response.data?.pricing || response.data;
pricing.value = response.data?.pricing || (response.data as unknown as PricingData);
// Calculate capture amount if delivery order is already loaded
calculateCaptureAmount();
})
+2 -3
View File
@@ -332,7 +332,7 @@
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
@@ -342,7 +342,6 @@ import axios from 'axios'
import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import type {
AxiosResponse,
DeliveryFormData,
@@ -566,7 +565,7 @@ const getOilPricing = () => {
withCredentials: true,
})
.then((response: AxiosResponse<{ ok?: boolean; pricing?: OilPricingResponse }>) => {
pricing.value = response.data?.pricing || response.data;
pricing.value = response.data?.pricing || (response.data as unknown as PricingData);
})
.catch(() => {
notify({
+1 -2
View File
@@ -202,7 +202,7 @@
</div>
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
@@ -211,7 +211,6 @@ import { useRoute, useRouter } from 'vue-router';
import axios from 'axios';
import authHeader from '../../../services/auth.header';
import { notify } from "@kyvg/vue3-notification";
import Footer from '../../../layouts/footers/footer.vue';
// --- Interfaces for Type Safety ---
interface UserCard {
+2 -3
View File
@@ -268,7 +268,7 @@
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
@@ -287,7 +287,6 @@ import type {
} from '../../../types/models'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import useValidate from "@vuelidate/core";
import { notify } from "@kyvg/vue3-notification"
@@ -470,7 +469,7 @@ const getServicePartsForCustomer = () => {
let path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${service.value.customer_id}`;
axios.get(path, { headers: authHeader() })
.then((response: AxiosResponse<{ ok?: boolean; parts?: ServicePart[] }>) => {
serviceParts.value = response.data?.parts || response.data;
serviceParts.value = response.data?.parts || (response.data as unknown as ServicePart[]);
})
.catch((error: Error) => {
console.error("Failed to fetch service parts:", error);
+361 -86
View File
@@ -1,8 +1,8 @@
<!-- src/pages/service/ServiceCalendar.vue -->
<template>
<div class="flex">
<div class="w-full px-10">
<div class="calendar-page">
<div class="w-full px-4 md:px-10">
<!-- Breadcrumbs -->
<div class="text-sm breadcrumbs mb-4">
<ul>
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
@@ -11,21 +11,104 @@
</ul>
</div>
<div class="flex text-2xl mb-5 font-bold">
Master Service Calendar
<!-- Page Header -->
<div class="flex items-center justify-between mb-6">
<div>
<h1 class="text-3xl font-bold mb-1">Service Calendar</h1>
<p class="text-base-content/60">Manage and schedule service calls</p>
</div>
<div class="flex gap-3">
<button @click="goToToday" class="btn btn-ghost btn-sm gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5" />
</svg>
Today
</button>
<router-link :to="{ name: 'ServiceHome' }" class="btn btn-primary btn-sm gap-2">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
New Service Call
</router-link>
</div>
</div>
<div class="flex h-screen font-sans">
<div class="flex-1 p-4 overflow-auto">
<!-- The 'ref' is important for accessing the calendar's API -->
<!-- Calendar Container -->
<div class="calendar-container bg-gradient-to-br from-neutral to-neutral/80 rounded-xl shadow-strong overflow-hidden">
<!-- Custom Calendar Header -->
<div class="calendar-header bg-base-200 px-6 py-4 border-b border-base-300">
<div class="flex items-center justify-between">
<!-- Navigation -->
<div class="flex items-center gap-2">
<button @click="previousMonth" class="btn btn-ghost btn-sm btn-circle">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
</svg>
</button>
<h2 class="text-2xl font-bold min-w-[200px] text-center">{{ currentMonthYear }}</h2>
<button @click="nextMonth" class="btn btn-ghost btn-sm btn-circle">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
</button>
</div>
<!-- View Switcher -->
<div class="btn-group">
<button
@click="changeView('dayGridMonth')"
class="btn btn-sm"
:class="{ 'btn-active': currentView === 'dayGridMonth' }"
>
Month
</button>
<button
@click="changeView('dayGridWeek')"
class="btn btn-sm"
:class="{ 'btn-active': currentView === 'dayGridWeek' }"
>
Week
</button>
<button
@click="changeView('dayGridDay')"
class="btn btn-sm"
:class="{ 'btn-active': currentView === 'dayGridDay' }"
>
Day
</button>
</div>
<!-- Legend -->
<div class="flex items-center gap-4">
<div class="flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-info"></div>
<span class="text-sm">Scheduled</span>
</div>
<div class="flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-success"></div>
<span class="text-sm">Completed</span>
</div>
<div class="flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-warning"></div>
<span class="text-sm">In Progress</span>
</div>
<div class="flex items-center gap-2">
<div class="w-3 h-3 rounded-full bg-accent"></div>
<span class="text-sm">Federal Holiday</span>
</div>
</div>
</div>
</div>
<!-- FullCalendar -->
<div class="calendar-body p-6">
<FullCalendar ref="fullCalendar" :options="calendarOptions" />
</div>
</div>
</div>
</div>
<Footer />
<!-- Edit Modal -->
<ServiceEditModal
v-if="selectedServiceForEdit"
:service="selectedServiceForEdit"
@@ -36,49 +119,56 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import Header from '../../layouts/headers/headerauth.vue';
import SideBar from '../../layouts/sidebar/sidebar.vue';
import Footer from '../../layouts/footers/footer.vue';
import { ref, onMounted, computed } from 'vue';
import FullCalendar from '@fullcalendar/vue3';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import { CalendarOptions, EventClickArg } from '@fullcalendar/core';
import { CalendarOptions, EventClickArg, DayCellContentArg } from '@fullcalendar/core';
import ServiceEditModal from './ServiceEditModal.vue';
import axios from 'axios';
import authHeader from '../../services/auth.header';
interface ServiceCall {
id: number;
scheduled_date: string;
customer_id: number;
customer_name: string;
customer_address: string;
customer_town: string;
type_service_call: number;
description: string;
service_cost: string;
}
import { serviceService } from '../../services/serviceService';
import { authService } from '../../services/authService';
import { AxiosResponse, AxiosError, ServiceCall } from '../../types/models';
import { getFederalHolidays, type Holiday } from '../../utils/holidays';
// Reactive data
const user = ref(null)
const selectedServiceForEdit = ref(null as Partial<ServiceCall> | null)
const fullCalendar = ref()
const currentView = ref('dayGridMonth')
const holidays = ref<Holiday[]>([])
const currentDate = ref(new Date())
// Functions
// We can remove the fetchEvents method as FullCalendar now handles it.
// async fetchEvents(): Promise<void> { ... }
// Computed
const currentMonthYear = computed(() => {
return currentDate.value.toLocaleDateString('en-US', { month: 'long', year: 'numeric' })
})
// Load holidays for current year and next year
const loadHolidays = () => {
const currentYear = new Date().getFullYear()
const allHolidays = [
...getFederalHolidays(currentYear - 1),
...getFederalHolidays(currentYear),
...getFederalHolidays(currentYear + 1),
]
holidays.value = allHolidays
}
// Check if a date is a holiday
const isHolidayDate = (dateStr: string): Holiday | undefined => {
return holidays.value.find(h => h.date === dateStr)
}
// Event handlers
const handleEventClick = (clickInfo: EventClickArg): void => {
// This logic remains the same, as it correctly pulls data from extendedProps
selectedServiceForEdit.value = {
id: parseInt(clickInfo.event.id),
scheduled_date: clickInfo.event.startStr,
customer_name: clickInfo.event.title.split(': ')[1] || 'Unknown Customer',
customer_id: clickInfo.event.extendedProps.customer_id,
type_service_call: clickInfo.event.extendedProps.type_service_call,
description: clickInfo.event.extendedProps.description,
service_cost: clickInfo.event.extendedProps.service_cost,
id: parseInt(clickInfo.event.id),
scheduled_date: clickInfo.event.startStr,
customer_name: clickInfo.event.title.split(': ')[1] || 'Unknown Customer',
customer_id: clickInfo.event.extendedProps.customer_id,
type_service_call: clickInfo.event.extendedProps.type_service_call,
description: clickInfo.event.extendedProps.description,
service_cost: clickInfo.event.extendedProps.service_cost,
};
}
@@ -89,73 +179,116 @@ const fetchCalendarEvents = async (
failureCallback: (error: Error) => void
) => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/all`;
const response = await axios.get(path, {
headers: authHeader(),
withCredentials: true,
});
// Backend returns { ok: true, events: [...] }
const events = response.data?.events || [];
successCallback(events);
} catch (error) {
const response = await serviceService.getAll();
const serviceEvents = response.data?.events || [];
// Add federal holidays as background events
const holidayEvents = holidays.value.map(holiday => ({
id: `holiday-${holiday.date}`,
title: holiday.name,
start: holiday.date,
allDay: true,
display: 'background',
classNames: ['holiday-event']
}));
// Combine service events and holiday events
const allEvents = [...serviceEvents, ...holidayEvents];
successCallback(allEvents);
} catch (err: unknown) {
const error = err as AxiosError;
console.error("Failed to fetch calendar events:", error);
failureCallback(error as Error);
}
};
// Calendar navigation
const goToToday = () => {
const calendarApi = (fullCalendar.value as any).getApi()
calendarApi.today()
currentDate.value = calendarApi.getDate()
}
const previousMonth = () => {
const calendarApi = (fullCalendar.value as any).getApi()
calendarApi.prev()
currentDate.value = calendarApi.getDate()
}
const nextMonth = () => {
const calendarApi = (fullCalendar.value as any).getApi()
calendarApi.next()
currentDate.value = calendarApi.getDate()
}
const changeView = (viewName: string) => {
currentView.value = viewName
const calendarApi = (fullCalendar.value as any).getApi()
calendarApi.changeView(viewName)
currentDate.value = calendarApi.getDate()
}
// Day cell class names for holidays
const getDayCellClassNames = (arg: any) => {
// Format date as YYYY-MM-DD
const year = arg.date.getFullYear()
const month = String(arg.date.getMonth() + 1).padStart(2, '0')
const day = String(arg.date.getDate()).padStart(2, '0')
const dateStr = `${year}-${month}-${day}`
const holiday = isHolidayDate(dateStr)
if (holiday) {
console.log('Holiday found:', holiday.name, 'on', dateStr)
}
return holiday ? ['holiday-cell'] : []
}
// Calendar options
const calendarOptions = ref({
plugins: [dayGridPlugin, interactionPlugin],
initialView: 'dayGridMonth',
headerToolbar: false, // We're using custom header
weekends: true,
// Use function source to fetch events with auth headers and transform response
height: 'auto',
events: fetchCalendarEvents,
eventClick: handleEventClick,
eventClassNames: 'custom-event',
dayCellClassNames: getDayCellClassNames,
eventDisplay: 'block',
displayEventTime: false,
eventBackgroundColor: 'transparent',
eventBorderColor: 'transparent',
} as CalendarOptions)
// Lifecycle
onMounted(() => {
userStatus();
// We no longer need to call fetchEvents() here because FullCalendar does it automatically.
})
// Modal handlers
const closeEditModal = () => {
selectedServiceForEdit.value = null;
selectedServiceForEdit.value = null;
}
// =================== THIS IS THE CORRECTED SECTION ===================
const handleSaveChanges = async (updatedService: ServiceCall) => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
// Get the FullCalendar component instance from the ref
await serviceService.update(updatedService.id, updatedService);
const calendarApi = (fullCalendar.value as any).getApi();
if (calendarApi) {
// Tell FullCalendar to re-fetch its events from the source.
// This is the most reliable way to refresh the view immediately.
calendarApi.refetchEvents();
}
closeEditModal();
} catch (error) {
console.error("Failed to save changes:", error);
alert("An error occurred while saving. Please check the console.");
}
}
// =================== END OF CORRECTED SECTION ===================
const handleDeleteService = async (serviceId: number) => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
await axios.delete(path, { withCredentials: true, headers: authHeader() });
// Also refresh the calendar after a delete
await serviceService.delete(serviceId);
const calendarApi = (fullCalendar.value as any).getApi();
if (calendarApi) {
calendarApi.refetchEvents();
}
closeEditModal();
} catch (error) {
console.error("Error deleting event:", error);
@@ -163,20 +296,162 @@ const handleDeleteService = async (serviceId: number) => {
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
authService.whoami().then((response: AxiosResponse<any>) => {
if (response.data.ok) {
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
}
})
.catch(() => {
user.value = null
})
}
</script>
// Lifecycle
onMounted(() => {
userStatus();
loadHolidays();
})
</script>
<style scoped>
.calendar-page {
@apply min-h-screen animate-fade-in;
}
.calendar-container {
@apply transition-all duration-300;
}
/* FullCalendar Custom Styling */
:deep(.fc) {
@apply font-sans;
}
:deep(.fc-theme-standard td),
:deep(.fc-theme-standard th) {
border-color: hsl(var(--bc) / 0.2) !important;
border-width: 2px !important;
}
:deep(.fc-scrollgrid) {
border-width: 2px !important;
border-color: hsl(var(--bc) / 0.2) !important;
}
:deep(.fc-col-header-cell) {
@apply bg-base-200 font-semibold text-sm uppercase tracking-wider py-3;
border-width: 2px !important;
border-color: hsl(var(--bc) / 0.2) !important;
}
:deep(.fc-daygrid-day) {
@apply transition-colors hover:bg-base-200/50;
border-width: 2px !important;
border-color: hsl(var(--bc) / 0.2) !important;
}
:deep(.fc-daygrid-day-number) {
@apply text-base-content/80 font-medium p-2;
}
:deep(.fc-day-today) {
@apply bg-primary/5 !important;
}
:deep(.fc-day-today .fc-daygrid-day-number) {
@apply text-primary font-bold;
}
/* Custom Event Styling */
:deep(.fc-event) {
@apply rounded-lg px-2 py-1 mb-1 cursor-pointer;
@apply bg-info/20 border-l-4 border-info;
@apply hover:bg-info/30 transition-colors;
@apply shadow-sm hover:shadow-md;
}
:deep(.fc-event-title) {
@apply text-sm font-medium text-base-content truncate;
}
/* Day cell styling */
:deep(.fc-daygrid-day-frame) {
@apply min-h-[100px];
}
:deep(.fc-daygrid-day-top) {
@apply flex justify-center;
}
/* Remove default FullCalendar button styling since we have custom header */
:deep(.fc-toolbar) {
@apply hidden;
}
/* Holiday styling - Using accent color from theme */
:deep(.holiday-cell) {
background-color: hsl(var(--a) / 0.15) !important;
border-color: hsl(var(--a) / 0.4) !important;
}
:deep(.holiday-cell .fc-daygrid-day-number) {
color: hsl(var(--a)) !important;
@apply font-bold;
}
/* Holiday background events */
:deep(.fc-bg-event.holiday-event) {
background-color: hsl(var(--a) / 0.5) !important;
opacity: 1 !important;
z-index: 1 !important;
inset: 0 !important;
margin: 0 !important;
border-radius: 0 !important;
}
:deep(.fc-daygrid-day-bg .fc-bg-event.holiday-event) {
position: absolute !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
width: 100% !important;
height: 100% !important;
}
/* Holiday event title positioning */
:deep(.fc-bg-event.holiday-event .fc-event-title) {
position: absolute !important;
bottom: 4px !important;
left: 4px !important;
right: 4px !important;
top: auto !important;
font-size: 0.7rem !important;
font-weight: 600 !important;
color: hsl(var(--a)) !important;
text-align: center !important;
line-height: 1.2 !important;
padding: 2px !important;
}
/* Weekend styling - Using theme colors */
:deep(.fc-day-sat),
:deep(.fc-day-sun) {
background-color: hsl(var(--b3)) !important;
border-color: hsl(var(--bc) / 0.3) !important;
}
:deep(.fc-day-sat .fc-daygrid-day-number),
:deep(.fc-day-sun .fc-daygrid-day-number) {
color: hsl(var(--bc) / 0.8) !important;
@apply font-semibold;
}
/* Weekend header cells */
:deep(.fc-col-header-cell.fc-day-sat),
:deep(.fc-col-header-cell.fc-day-sun) {
background-color: hsl(var(--b3)) !important;
color: hsl(var(--bc)) !important;
@apply font-bold;
}
</style>
+33 -22
View File
@@ -102,8 +102,8 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import dayjs from 'dayjs';
import axios from 'axios';
import authHeader from '../../services/auth.header';
import serviceService from '../../services/serviceService';
import customerService from '../../services/customerService';
// --- Interfaces ---
interface ServiceCall { id: number; scheduled_date: string; customer_id: number; customer_name: string; customer_address: string; customer_town: string; type_service_call: number; description: string; service_cost: string }
@@ -132,32 +132,33 @@ const serviceOptions = ref([
{ text: 'Tank Install', value: 3 }, { text: 'Other', value: 4 },
])
// Watchers
watch(() => props.service, (newVal) => {
if (!newVal) return;
const scheduled = dayjs(newVal.scheduled_date || new Date());
editableService.value = { ...newVal, date: scheduled.format('YYYY-MM-DD'), time: scheduled.hour() };
if (newVal.customer_id) {
getCustomer(newVal.customer_id);
getServiceParts(newVal.customer_id);
}
}, { immediate: true, deep: true })
// Functions
// Functions (defined before watchers to avoid hoisting issues)
const getCustomer = (customerId: number) => {
customer.value = null;
let path = import.meta.env.VITE_BASE_URL + '/customer/' + customerId;
axios.get(path, { headers: authHeader() })
.then((response: any) => { customer.value = response.data; })
customerService.getById(customerId)
.then((response: any) => {
if (response.data.customer) {
customer.value = response.data.customer;
} else if (response.data.ok && response.data.id) {
customer.value = response.data as unknown as Customer;
}
})
.catch((error: any) => { console.error("Failed to fetch customer details for modal:", error); });
}
const getServiceParts = (customerId: number) => {
isLoadingParts.value = true;
serviceParts.value = null;
let path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${customerId}`;
axios.get(path, { headers: authHeader() })
.then((response: any) => { serviceParts.value = response.data; })
serviceService.getPartsForCustomer(customerId)
.then((response: any) => {
if (response.data.parts) {
if (Array.isArray(response.data.parts) && response.data.parts.length > 0) {
serviceParts.value = response.data.parts[0];
} else {
serviceParts.value = response.data.parts as unknown as ServiceParts;
}
}
})
.catch((error: any) => { console.error("Failed to fetch service parts:", error); })
.finally(() => { isLoadingParts.value = false; });
}
@@ -168,9 +169,8 @@ const saveChanges = async () => {
const combinedDateTime = dayjs(`${date} ${time}:00`).format('YYYY-MM-DDTHH:mm:ss');
const finalPayload = { ...props.service, ...editableService.value, scheduled_date: combinedDateTime };
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${finalPayload.id}`;
try {
await axios.put(path, finalPayload, { headers: authHeader(), withCredentials: true });
await serviceService.update(finalPayload.id!, finalPayload);
emit('save-changes', finalPayload as ServiceCall);
} catch (error) {
console.error("Failed to save changes:", error);
@@ -201,4 +201,15 @@ const getStateAbbrev = (stateId: number | undefined | null): string => {
const stateMap: { [key: number]: string } = { 0: 'MA', 1: 'RI', 2: 'NH', 3: 'ME', 4: 'VT', 5: 'CT', 6: 'NY' };
return stateMap[stateId] || 'Unknown';
}
// Watchers (after function definitions)
watch(() => props.service, (newVal) => {
if (!newVal) return;
const scheduled = dayjs(newVal.scheduled_date || new Date());
editableService.value = { ...newVal, date: scheduled.format('YYYY-MM-DD'), time: scheduled.hour() };
if (newVal.customer_id) {
getCustomer(newVal.customer_id);
getServiceParts(newVal.customer_id);
}
}, { immediate: true, deep: true })
</script>
+11 -22
View File
@@ -146,7 +146,7 @@
</div>
</div>
<Footer />
<ServiceEditModal
v-if="selectedServiceForEdit"
@@ -158,10 +158,9 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import { serviceService } from '../../services/serviceService'
import { authService } from '../../services/authService'
import { ServiceCall } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue'
import ServiceEditModal from './ServiceEditModal.vue'
import dayjs from 'dayjs';
@@ -184,13 +183,11 @@ onMounted(() => {
const fetchUpcomingServices = async (): Promise<void> => {
isLoading.value = true;
try {
const path = import.meta.env.VITE_BASE_URL + '/service/upcoming';
const response = await axios.get(path, {
headers: authHeader(),
withCredentials: true,
});
const serviceList = response.data?.services || [];
services.value = serviceList.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
const response = await serviceService.getUpcoming();
if (response.data.ok) {
const serviceList = response.data.services || [];
services.value = serviceList.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
}
} catch (error) {
console.error("Failed to fetch upcoming service calls:", error);
} finally {
@@ -199,13 +196,7 @@ const fetchUpcomingServices = async (): Promise<void> => {
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
authService.whoami()
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
@@ -250,8 +241,7 @@ const toggleExpand = (id: number): void => {
const handleSaveChanges = async (updatedService: ServiceCall) => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
const response = await serviceService.update(updatedService.id, updatedService);
if (response.data.ok) {
const index = services.value.findIndex(s => s.id === updatedService.id);
if (index !== -1) {
@@ -267,8 +257,7 @@ const handleSaveChanges = async (updatedService: ServiceCall) => {
const handleDeleteService = async (serviceId: number) => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
const response = await serviceService.delete(serviceId);
if (response.data.ok) {
services.value = services.value.filter(s => s.id !== serviceId);
closeEditModal();
+17 -25
View File
@@ -152,7 +152,7 @@
</div>
</div>
<Footer />
<ServiceEditModal
v-if="selectedServiceForEdit"
@@ -165,15 +165,14 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import serviceService from '../../services/serviceService'
import authService from '../../services/authService'
import { ServiceCall } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue'
import ServiceEditModal from './ServiceEditModal.vue'
import dayjs from 'dayjs';
// Reactive data
const user = ref(null)
const user = ref<any>(null)
const services = ref<ServiceCall[]>([])
const isLoading = ref(true)
const selectedServiceForEdit = ref<ServiceCall | null>(null)
@@ -183,7 +182,9 @@ const expandedIds = ref<number[]>([])
// Lifecycle
onMounted(() => {
userStatus();
if (authService) {
userStatus();
}
fetchPastServices();
})
@@ -215,13 +216,12 @@ const toggleExpand = (id: number): void => {
const fetchPastServices = async (): Promise<void> => {
isLoading.value = true;
try {
const path = import.meta.env.VITE_BASE_URL + '/service/past';
const response = await axios.get(path, {
headers: authHeader(),
withCredentials: true,
});
const serviceList = response.data?.services || [];
services.value = serviceList.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
const response = await serviceService.getPast();
if (response.data && response.data.services) {
services.value = response.data.services.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
} else {
services.value = [];
}
} catch (error) {
console.error("Failed to fetch past service calls:", error);
} finally {
@@ -230,13 +230,7 @@ const fetchPastServices = async (): Promise<void> => {
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
authService.whoami()
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
@@ -257,9 +251,8 @@ const closeEditModal = () => {
const handleSaveChanges = async (updatedService: ServiceCall) => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
if (response.data.ok) {
const response = await serviceService.update(updatedService.id, updatedService);
if (response.data.service) { // Based on ServiceResponse type
const index = services.value.findIndex(s => s.id === updatedService.id);
if (index !== -1) {
services.value[index] = response.data.service;
@@ -274,8 +267,7 @@ const handleSaveChanges = async (updatedService: ServiceCall) => {
const handleDeleteService = async (serviceId: number) => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
const response = await serviceService.delete(serviceId);
if (response.data.ok) {
services.value = services.value.filter(s => s.id !== serviceId);
closeEditModal();
+14 -20
View File
@@ -120,25 +120,26 @@
</div>
</div>
<Footer />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import serviceService from '../../services/serviceService'
import authService from '../../services/authService'
import { ServicePlan } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue'
import dayjs from 'dayjs';
// Reactive data
const user = ref(null)
const user = ref<any>(null)
const servicePlans = ref<ServicePlan[]>([])
const isLoading = ref(true)
// Lifecycle
onMounted(() => {
userStatus();
if (authService) {
userStatus();
}
fetchServicePlans();
})
@@ -146,13 +147,12 @@ onMounted(() => {
const fetchServicePlans = async (): Promise<void> => {
isLoading.value = true;
try {
const path = import.meta.env.VITE_BASE_URL + '/service/plans/active';
const response = await axios.get(path, {
headers: authHeader(),
withCredentials: true,
});
// Backend returns { ok: true, plans: [...] }
servicePlans.value = response.data?.plans || [];
const response = await serviceService.plans.getActive();
if (response.data && response.data.plans) {
servicePlans.value = response.data.plans;
} else {
servicePlans.value = [];
}
} catch (error) {
console.error("Failed to fetch service plans:", error);
} finally {
@@ -161,13 +161,7 @@ const fetchServicePlans = async (): Promise<void> => {
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
authService.whoami()
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
+21 -25
View File
@@ -152,7 +152,7 @@
</div>
</div>
<Footer />
<ServiceEditModal
v-if="selectedServiceForEdit"
@@ -165,15 +165,14 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import serviceService from '../../services/serviceService'
import authService from '../../services/authService'
import { ServiceCall } from '../../types/models'
import Footer from '../../layouts/footers/footer.vue'
import ServiceEditModal from './ServiceEditModal.vue'
import dayjs from 'dayjs';
// Reactive data
const user = ref(null)
const user = ref<any>(null)
const services = ref<ServiceCall[]>([])
const isLoading = ref(true)
const selectedServiceForEdit = ref<ServiceCall | null>(null)
@@ -183,7 +182,9 @@ const expandedIds = ref<number[]>([])
// Lifecycle
onMounted(() => {
userStatus();
if (authService) { // Check if imported correctly
userStatus();
}
fetchTodayServices();
})
@@ -215,13 +216,16 @@ const toggleExpand = (id: number): void => {
const fetchTodayServices = async (): Promise<void> => {
isLoading.value = true;
try {
const path = import.meta.env.VITE_BASE_URL + '/service/today';
const response = await axios.get(path, {
headers: authHeader(),
withCredentials: true,
});
const serviceList = response.data?.services || [];
services.value = serviceList.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
const response = await serviceService.getToday();
// According to serviceService.ts, getToday returns AxiosResponse<ServicesResponse>
// ServicesResponse has { ok: boolean, services: ServiceCall[] }
// However, the api unwrap interceptor might put properties directly on data
// Let's assume the response structure follows the type
if (response.data && response.data.services) {
services.value = response.data.services.sort((a: ServiceCall, b: ServiceCall) => b.id - a.id);
} else {
services.value = [];
}
} catch (error) {
console.error("Failed to fetch today's service calls:", error);
} finally {
@@ -230,13 +234,7 @@ const fetchTodayServices = async (): Promise<void> => {
}
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: 'get',
url: path,
withCredentials: true,
headers: authHeader(),
})
authService.whoami()
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
@@ -257,9 +255,8 @@ const closeEditModal = () => {
const handleSaveChanges = async (updatedService: ServiceCall) => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
const response = await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
if (response.data.ok) {
const response = await serviceService.update(updatedService.id, updatedService);
if (response.data.service) { // Based on ServiceResponse type
const index = services.value.findIndex(s => s.id === updatedService.id);
if (index !== -1) {
services.value[index] = response.data.service;
@@ -274,8 +271,7 @@ const handleSaveChanges = async (updatedService: ServiceCall) => {
const handleDeleteService = async (serviceId: number) => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
const response = await serviceService.delete(serviceId);
if (response.data.ok) {
services.value = services.value.filter(s => s.id !== serviceId);
closeEditModal();
+48 -51
View File
@@ -65,8 +65,8 @@ import interactionPlugin from '@fullcalendar/interaction';
import { CalendarOptions, EventClickArg } from '@fullcalendar/core';
import EventSidebar from './EventSidebar.vue';
import ServiceEditModal from '../../service/ServiceEditModal.vue';
import axios from 'axios';
import authHeader from '../../../services/auth.header';
import serviceService from '../../../services/serviceService';
import customerService from '../../../services/customerService';
interface ServiceCall { id: number; scheduled_date: string; customer_id: number; customer_name: string; customer_address: string; customer_town: string; type_service_call: number; description: string; service_cost: string;}
interface Customer { id: number; customer_last_name: string; customer_first_name: string; customer_town: string; customer_state: number; customer_zip: string; customer_phone_number: string; customer_address: string; customer_home_type: number; customer_apt: string; }
@@ -108,58 +108,19 @@ const calendarOptions = ref<CalendarOptions>({
});
const customer = ref<Customer | null>(null);
// Watchers
watch(() => route.params.id, (newId) => {
if (newId) getCustomer(newId as string);
}, { immediate: true });
// Lifecycle
onMounted(() => {
fetchEvents();
});
// Functions
// Functions (defined before watchers to avoid hoisting issues)
const closeEditModal = () => {
selectedServiceForEdit.value = null;
};
const handleSaveChanges = async (updatedService: ServiceCall) => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
await fetchEvents();
closeEditModal();
} catch (error) {
console.error("Failed to save changes:", error);
alert("An error occurred while saving. Please check the console.");
}
};
const handleDeleteService = async (serviceId: number) => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
const response = await axios.delete(path, { withCredentials: true, headers: authHeader() });
if (response.data.ok === true) {
await fetchEvents();
closeEditModal();
} else {
console.error("Failed to delete event:", response.data.error);
}
} catch (error) {
console.error("Error deleting event:", error);
}
};
const getCustomer = async (customerId: string): Promise<void> => {
isLoading.value = true;
customer.value = null;
try {
const path = `${import.meta.env.VITE_BASE_URL}/customer/${customerId}`;
const response = await axios.get(path, { withCredentials: true, headers: authHeader() });
const response = await customerService.getById(Number(customerId));
const customerData = response.data?.customer || response.data;
if (customerData && customerData.id) {
customer.value = customerData;
if (customerData && (customerData as any).id) {
customer.value = customerData as unknown as Customer;
}
} catch (error) {
console.error("API call to get customer FAILED:", error);
@@ -170,14 +131,39 @@ const getCustomer = async (customerId: string): Promise<void> => {
const fetchEvents = async (): Promise<void> => {
try {
const path = `${import.meta.env.VITE_BASE_URL}/service/all`;
const response = await axios.get(path, { headers: authHeader(), withCredentials: true });
const response = await serviceService.getAll();
calendarOptions.value.events = response.data?.events || [];
} catch (error) {
console.error("Error fetching all calendar events:", error);
}
};
const handleSaveChanges = async (updatedService: ServiceCall) => {
try {
await serviceService.update(updatedService.id, updatedService);
await fetchEvents();
closeEditModal();
} catch (error) {
console.error("Failed to save changes:", error);
alert("An error occurred while saving. Please check the console.");
}
};
const handleDeleteService = async (serviceId: number) => {
try {
const response = await serviceService.delete(serviceId);
if (response.data.ok === true) {
await fetchEvents();
closeEditModal();
} else {
// console.error("Failed to delete event:", response.data.error);
// Error property might not exist on typed response, but checking ok is enough
}
} catch (error) {
console.error("Error deleting event:", error);
}
};
const handleEventScheduled = async (eventData: any): Promise<void> => {
if (!customer.value) {
alert("Error: A customer must be loaded in the sidebar to create a new event.");
@@ -188,12 +174,13 @@ const handleEventScheduled = async (eventData: any): Promise<void> => {
expected_delivery_date: eventData.start, type_service_call: eventData.type_service_call,
customer_id: customer.value.id, description: eventData.extendedProps.description,
};
const path = import.meta.env.VITE_BASE_URL + "/service/create";
const response = await axios.post(path, payload, { withCredentials: true, headers: authHeader() });
if (response.data.ok === true) {
const response = await serviceService.create(payload);
// Service response has { ok: boolean, service: ServiceCall }
if (response.data.service) {
await fetchEvents();
} else {
console.error("Failed to create event:", response.data.error);
console.error("Failed to create event");
}
} catch (error) {
console.error("Error creating event:", error);
@@ -204,4 +191,14 @@ const handleEventDelete = async (eventId: string): Promise<void> => {
// This is a simple alias now, as handleDeleteService is more specific
await handleDeleteService(Number(eventId));
};
// Watchers (after function definitions)
watch(() => route.params.id, (newId) => {
if (newId) getCustomer(newId as string);
}, { immediate: true });
// Lifecycle
onMounted(() => {
fetchEvents();
});
</script>
-24
View File
@@ -1,24 +0,0 @@
import axios from 'axios';
const BASE_URL = import.meta.env.VITE_BASE_URL;
function authHeader() {
// Return authorization header
return {};
}
export function createEvent(payload) {
const path = `${BASE_URL}/service/create`; // Example endpoint
return axios.post(path, payload, {
withCredentials: true,
headers: authHeader(),
});
}
export function deleteEventById(eventId) {
const path = `${BASE_URL}/service/delete/${eventId}`; // Example endpoint
return axios.delete(path, {
withCredentials: true,
headers: authHeader(),
});
}
-2
View File
@@ -107,7 +107,6 @@ import axios from 'axios'
import authHeader from '../../services/auth.header'
import Header from '../../layouts/headers/headerauth.vue'
import SideBar from '../../layouts/sidebar/sidebar.vue'
import Footer from '../../layouts/footers/footer.vue'
import { notify } from "@kyvg/vue3-notification"
export default defineComponent({
@@ -116,7 +115,6 @@ export default defineComponent({
components: {
Header,
SideBar,
Footer,
},
data() {
+1 -3
View File
@@ -160,7 +160,7 @@
</div>
</div>
</div>
<Footer />
</template>
<script lang="ts">
@@ -169,7 +169,6 @@ import axios from 'axios'
import authHeader from '../../../services/auth.header'
import Header from '../../../layouts/headers/headerauth.vue'
import SideBar from '../../../layouts/sidebar/sidebar.vue'
import Footer from '../../../layouts/footers/footer.vue'
import {AuthorizeTransaction} from '../../../types/models'
export default defineComponent({
@@ -178,7 +177,6 @@ export default defineComponent({
components: {
Header,
SideBar,
Footer,
},
data() {