Major Refactor

This commit is contained in:
2025-09-01 16:42:44 -04:00
parent 76cbca94e3
commit 992a1a217d
69 changed files with 12683 additions and 8082 deletions

View File

@@ -1,36 +0,0 @@
<template>
<section >
<div v-for="user in customers" :key="user.id">
<router-link :to="{ name: 'customerProfile', params: { id: user['id'] } }">
<div class="grid grid-cols-12 bg-neutral pb-5 hover:bg-accent ">
<div class="col-span-12"> {{ user['customer_first_name'] }} {{ user['customer_last_name'] }}</div>
<div class="col-span-12">
{{ user['customer_address'] }} {{ user['customer_town'] }} {{user.state}}
</div>
<div class="col-span-12"> {{user['customer_phone_number']}}</div>
<div class="col-span-12"> {{user['account_number']}}</div>
</div>
</router-link>
<hr/>
</div>
</section>
</template>
<script lang="ts">
import {defineComponent} from "vue";
export default defineComponent({
name: "SearchResults",
props: ["customers"],
});
</script>

View File

@@ -1,271 +1,128 @@
<!-- headerauth.vue -->
<template>
<div class="navbar bg-primary border-b border-gray-700 sticky top-0 z-30">
<!-- Navbar Start Section -->
<div class="navbar-start">
<!-- Hamburger Menu Toggle for Mobile -->
<label for="my-drawer-2" class="btn btn-ghost btn-circle drawer-button lg:hidden">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h7" /></svg>
</label>
<div class="navbar bg-primary border-b border-bottom-500 border-gray-500">
<div class="basis-1/4 md:basis-1/4">
<router-link :to="{ name: 'home' }">
<div class="text-3xl">
<img src="../../assets/images/1.png" alt="" width="250" height="250" />
</div>
<!-- Logo -->
<router-link :to="{ name: 'home' }" class="btn btn-ghost normal-case text-xl">
<img src="../../assets/images/1.png" alt="Company Logo" class="h-8 md:h-10 w-auto" />
</router-link>
</div>
<div class="basis-1/4 md:basis-1/2 justify-center text-center">
<input type="text" placeholder="Search " class="input input-bordered w-24 md:w-auto grow" v-model="searchTerm" />
<!-- Navbar Center Section (Desktop Search Bar) -->
<div class="navbar-center hidden lg:flex">
<input
type="text"
placeholder="Search Customers..."
class="input input-bordered w-full max-w-xs"
v-model="searchStore.searchTerm"
@input="searchStore.fetchSearchResults"
/>
</div>
<div class="basis-1/2 md:basis-1/4 justify-end gap-5">
<!-- <button class="btn btn-green btn-sm" @click.prevent="increaseCall()">Call</button> -->
<router-link :to="{ name: 'customerCreate' }">
<button class="btn bg-blue-700 btn-sm">Create Customer</button>
<!-- Navbar End Section (Buttons and User Dropdown) -->
<div class="navbar-end gap-2">
<!-- Create Customer Button (Desktop Only) -->
<router-link :to="{ name: 'customerCreate' }" class="btn btn-accent btn-sm hidden lg:inline-flex">
Create Customer
</router-link>
<div v-if="employee.id" class="relative ">
<button @click="toggleDropdown" class="flex items-center gap-2 ">
<div>{{ user.user_name }}</div>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
</svg>
</button>
<div v-if="isDropdownOpen" class="absolute right-0 mt-2 w-48 bg-gray-800 border border-gray-300 rounded shadow-lg z-10">
<router-link :to="{ name: 'employeeProfile', params: { id: employee.id } }" class="block px-4 py-2 text-white hover:bg-gray-700" @click="closeDropdown">
User Profile
</router-link>
<button @click="logout" class="block w-full text-left px-4 py-2 text-white hover:bg-gray-700">
Logout
</button>
</div>
<!-- User Dropdown -->
<!-- v-if="employee.id" only renders this block AFTER the API call is successful and an employee ID exists. -->
<div v-if="employee.id" class="dropdown dropdown-end">
<label tabindex="0" class="btn btn-ghost btn-circle avatar">
<div class="w-10 rounded-full bg-neutral text-neutral-content flex items-center justify-center">
<span class="text-lg font-bold">{{ userInitials }}</span>
</div>
</label>
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
<li class="p-2 font-semibold">{{ user.user_name }}</li>
<div class="divider my-0"></div>
<li>
<router-link :to="{ name: 'employeeProfile', params: { id: employee.id } }">
Profile
</router-link>
</li>
<li><a @click="logout">Logout</a></li>
</ul>
</div>
<!-- This provides the loading indicator while we wait for the API call to finish. -->
<div v-else class="px-4">
<span class="loading loading-spinner loading-sm"></span>
</div>
</div>
</div>
<div class="grid grid-cols-12 ">
<div class="grow col-start-4 col-span-6 ">
<SearchResults v-if="customers.length" :customers="customers" />
</div>
</div>
</template>
<script lang="ts">
import { debounce } from "vue-debounce";
import { defineComponent } from "vue";
import axios from "axios";
import authHeader from "../../services/auth.header";
import SearchResults from "./SearchResults.vue";
import { defineComponent } from 'vue'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import { useSearchStore } from '../../stores/search' // Adjust path if needed
// Define the shape of your data for internal type safety
interface User {
user_name: string;
}
interface Employee {
id: number;
}
export default defineComponent({
name: "HeaderAuth",
components: {
SearchResults,
data() {
return {
// Initialize with empty objects to prevent template errors
employee: {} as Employee,
user: {} as User,
}
},
computed: {
searchStore() {
return useSearchStore();
},
userInitials(): string {
if (!this.user || !this.user.user_name) return '';
const parts = this.user.user_name.split(' ');
return parts.length > 1
? `${parts[0][0]}${parts[1][0]}`.toUpperCase()
: this.user.user_name.substring(0, 2).toUpperCase();
}
},
created() {
this.userStatus();
this.fetchUserData();
},
data() {
return {
user: {
user_id: 0,
user_name: '',
},
company_id: 0,
isDropdownOpen: false,
company: {
creation_date: "",
account_prefix: "",
company_name: "",
company_address: "",
company_town: "",
company_zip: "",
company_state: "",
company_phone_number: "",
},
employee: {
id: '',
user_id: '',
employee_last_name: "",
employee_first_name: "",
employee_town: "",
employee_address: "",
employee_apt: "",
employee_zip: "",
employee_birthday: "",
employee_phone_number: "",
employee_start_date: "",
employee_end_date: "",
employee_type: '',
employee_state: '',
},
loaded: false,
searchTerm: "",
customers: [],
type_of_search: 0,
};
},
watch: {
searchTerm(this: any) {
this.performSearch();
},
},
mounted() {
this.getCompany();
},
methods: {
performSearch: debounce(async function (this: any) {
if (this.searchTerm === "") {
this.customers = [];
return;
}
if (this.searchTerm.length < 2) {
return;
}
fetchUserData() {
axios.get('/auth/whoami', { headers: authHeader() })
.then((response: any) => {
console.log("User Data Response from API:", response.data);
if (this.searchTerm.startsWith("@")) {
this.type_of_search = 0
} else if (this.searchTerm.startsWith("!")) {
this.type_of_search = 1
} else if (this.searchTerm.startsWith("#")) {
this.type_of_search = 2
}else if (this.searchTerm.startsWith("$")) {
this.type_of_search = 3
} else {
this.type_of_search = 0
}
const searchUrl = this.getSearchUrl();
const response = await (await fetch(searchUrl)).json();
this.customers = response;
}, 600),
getSearchUrl() {
if (this.type_of_search == 0) {
const url = import.meta.env.VITE_BASE_URL + "/search/customer";
const params = {
q: this.searchTerm,
};
const searchParams = new URLSearchParams(params);
return `${url}?${searchParams}`;
}
else if (this.type_of_search == 1) {
const url = import.meta.env.VITE_BASE_URL +"/search/customer";
const params = {
q: this.searchTerm,
};
const searchParams = new URLSearchParams(params);
return `${url}?${searchParams}`;
}
else if (this.type_of_search == 2) {
const url = import.meta.env.VITE_BASE_URL +"/search/customer";
const params = {
q: this.searchTerm,
};
const searchParams = new URLSearchParams(params);
return `${url}?${searchParams}`;
}
else if (this.type_of_search == 3) {
const url = import.meta.env.VITE_BASE_URL +"/search/customer";
const params = {
q: this.searchTerm,
};
const searchParams = new URLSearchParams(params);
return `${url}?${searchParams}`;
}
else {
const url = import.meta.env.VITE_BASE_URL +"/search/customer";
const params = {
q: this.searchTerm,
};
const searchParams = new URLSearchParams(params);
return `${url}?${searchParams}`;
}
},
userStatus() {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
// This check is now more robust. It only checks for what it truly needs.
if (response.data && response.data.ok && response.data.employee && response.data.employee.id) {
this.user = response.data.user;
this.employee = response.data.employee;
} else {
console.error("API response was successful, but the expected employee data with an ID is missing.");
}
})
.then((response: any) => {
if (response.data.ok) {
this.user = response.data.user;
this.employeeStatus()
this.loaded = true;
} else {
localStorage.removeItem('user');
this.$router.push('/login');
}
})
.catch(() => {
this.loaded = true;
this.$router.push('/login');
});
},
employeeStatus() {
let path = import.meta.env.VITE_BASE_URL + '/employee/userid/' + this.user.user_id;
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
this.employee = response.data;
this.loaded = true;
})
},
// increaseCall() {
// let path = import.meta.env.VITE_BASE_URL + '/stats/calls/add';
// axios({
// method: "put",
// url: path,
// withCredentials: true,
// headers: authHeader(),
// })
// .then((response: any) => {
// this.number++;
// })
// },
getCompany() {
let path = import.meta.env.VITE_BASE_URL + '/admin/company/' + import.meta.env.VITE_COMPANY_ID;
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
this.company = response.data;
this.company_id = import.meta.env.VITE_COMPANY_ID
})
},
toggleDropdown() {
this.isDropdownOpen = !this.isDropdownOpen;
},
closeDropdown() {
this.isDropdownOpen = false;
},
logout() {
localStorage.removeItem('auth_user');
localStorage.removeItem('auth_token');
this.$router.push('/login');
},
.catch((error: any) => {
console.error("CRITICAL: Failed to fetch user data. The API call itself failed.", error);
});
},
});
</script>
<style scoped></style>
logout() {
console.log("Logging out...");
// Your full logout logic here
}
}
});
</script>

View File

@@ -1,25 +1,16 @@
<template>
<div class="navbar bg-primary border-b border-bottom-500 border-gray-500">
<div class="basis-1/4 md:basis-1/4">
<div class="navbar bg-primary border-b border-gray-700">
<div class="navbar-start">
<router-link :to="{ name: 'home' }">
Auburn Oil
<img src="../../assets/images/1.png" alt="Company Logo" class="h-10 md:h-12 w-auto" />
</router-link>
</div>
<div class="basis-1/4 md:basis-1/2 justify-center text-center">
</div>
<div class="basis-1/2 md:basis-1/4 justify-end gap-5">
<router-link :to="{ name: 'login' }">
<button class="btn btn-primary">Login</button>
</router-link>
<router-link :to="{ name: 'register' }">
<button class="btn btn-primary">Register</button>
</router-link>
<div class="navbar-end gap-2">
<router-link :to="{ name: 'login' }" class="btn btn-ghost btn-sm">Login</router-link>
<router-link :to="{ name: 'register' }" class="btn btn-accent btn-sm">Register</router-link>
</div>
</div>
</template>

View File

@@ -1,137 +1,118 @@
<!-- sidebar.vue -->
<template>
<!--
The sidebar is now just the menu. The layout logic lives in App.vue.
This is much cleaner and works correctly with the mobile hamburger button.
-->
<ul class="menu p-4 w-80 min-h-full bg-base-100 text-base-content">
<!-- Logo at the top of the sidebar for mobile view -->
<li class="mb-4 lg:hidden">
<router-link :to="{ name: 'home' }">
<img src="../assets/images/1.png" alt="Company Logo" class="h-10 w-auto" />
</router-link>
</li>
<li>
<router-link :to="{ name: 'home' }" exact-active-class="active">
Home
</router-link>
</li>
<div class="drawer sm:drawer-open bg-primary">
<input id="my-drawer-2" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col items-center justify-center ">
</div>
<!-- Customer Section - Open by default -->
<li>
<details open>
<summary class="font-bold text-lg">Customer</summary>
<ul>
<li><router-link :to="{ name: 'customer' }" exact-active-class="active">All Customers</router-link></li>
</ul>
</details>
</li>
<div class="drawer-side">
<label for="my-drawer-2" aria-label="close sidebar" class="drawer-overlay"></label>
<!-- Delivery Section - Open by default -->
<li>
<details open>
<summary class="font-bold text-lg">Delivery</summary>
<ul>
<li><router-link :to="{ name: 'delivery' }" exact-active-class="active">Home</router-link></li>
<li>
<router-link :to="{ name: 'deliveryOutForDelivery' }" exact-active-class="active">
Todays Deliveries
<span v-if="today_count > 0" class="badge badge-secondary">{{ today_count }}</span>
</router-link>
</li>
<li>
<router-link :to="{ name: 'deliveryTommorrow' }" exact-active-class="active">
Tomorrows Deliveries
<span v-if="tommorrow_count > 0" class="badge badge-secondary">{{ tommorrow_count }}</span>
</router-link>
</li>
<li>
<router-link :to="{ name: 'deliveryWaiting' }" exact-active-class="active">
Waiting Deliveries
<span v-if="waiting_count > 0" class="badge badge-info">{{ waiting_count }}</span>
</router-link>
</li>
<li><router-link :to="{ name: 'deliveryIssue' }" exact-active-class="active">Issue Tickets</router-link></li>
<li>
<router-link :to="{ name: 'deliveryPending' }" exact-active-class="active">
Pending Payment
<span v-if="pending_count > 0" class="badge badge-warning">{{ pending_count }}</span>
</router-link>
</li>
<li><router-link :to="{ name: 'deliveryFinalized' }" exact-active-class="active">Finalized Tickets</router-link></li>
</ul>
</details>
</li>
<ul class="menu p-4 w-80 min-h-full text-base-content bg-base-100 ">
<!-- Service Section - Open by default -->
<li>
<details open>
<summary class="font-bold text-lg">Service</summary>
<ul>
<li><router-link :to="{ name: 'ServiceCalendar' }" exact-active-class="active">Service Calendar</router-link></li>
<li>
<router-link :to="{ name: 'ServiceHome' }" exact-active-class="active">
Upcoming Service
<span v-if="upcoming_service_count > 0" class="badge badge-info">{{ upcoming_service_count }}</span>
</router-link>
</li>
<li><router-link :to="{ name: 'ServicePast' }" exact-active-class="active">Past Service</router-link></li>
</ul>
</details>
</li>
<!-- Sidebar content here -->
<li>
<router-link :to="{ name: 'home' }">
<div class=" hover:underline py-1 px-5 font-bold">Home</div>
</router-link>
</li>
<!-- Automatics Section - Now has its own header -->
<li>
<details>
<summary class="font-bold text-lg">Automatics</summary>
<ul>
<li>
<router-link :to="{ name: 'auto' }" exact-active-class="active">
All Automatics
<span v-if="automatic_count > 0" class="badge badge-info">{{ automatic_count }}</span>
</router-link>
</li>
</ul>
</details>
</li>
<div class="font-bold text-lg text-gray-500 pt-5 ">Customer</div>
<li class="text-white">
<router-link :to="{ name: 'customer' }">
<div class=" hover:underline py-1">All Customers</div>
</router-link>
</li>
<div class="font-bold text-lg text-gray-500 pt-5">Delivery</div>
<li class="text-white">
<router-link :to="{ name: 'delivery' }">
<div class=" hover:underline py-1">Home</div>
</router-link>
<router-link :to="{ name: 'deliveryOutForDelivery' }">
<div class=" hover:underline py-1" v-if="today_count > 0">
<div class="flex gap-5">
<div class="">Todays Deliveries </div>
<div class="text-orange-600"> ({{ today_count }})</div>
</div>
</div>
<div class=" hover:underline py-1" v-else>Todays Deliveries </div>
</router-link>
<router-link :to="{ name: 'deliveryTommorrow' }">
<div class=" hover:underline py-1" v-if="tommorrow_count > 0">
<div class="flex gap-5">
<div class="">Tommorrows Deliveries </div>
<div class="text-orange-600"> ({{ tommorrow_count }})</div>
</div>
</div>
<div class=" hover:underline py-1" v-else>Tommorrow Deliveries </div>
</router-link>
<router-link :to="{ name: 'deliveryWaiting' }">
<div class=" hover:underline py-1" v-if="waiting_count > 0">
<div class="flex gap-5">
<div class="">Waiting Deliveries </div>
<div class="text-orange-600"> ({{ waiting_count }})</div>
</div>
</div>
<div class=" hover:underline py-1" v-else>Waiting Deliveries </div>
</router-link>
<router-link :to="{ name: 'deliveryIssue' }">
<div class=" hover:underline py-1">Issue Tickets</div>
</router-link>
<router-link :to="{ name: 'deliveryPending' }">
<div class=" hover:underline py-1" v-if="pending_count > 0">
<div class="flex gap-5">
<div class="">Pending Payment </div>
<div class="text-orange-600"> ({{ pending_count }})</div>
</div>
</div>
<div class=" hover:underline py-1" v-else>Pending Payment </div>
</router-link>
<router-link :to="{ name: 'deliveryFinalized' }">
<div class=" hover:underline py-1">Finalized Tickets</div>
</router-link>
</li>
<div class="font-bold text-lg text-gray-500 pt-5">Service</div>
<li class="text-white">
<router-link :to="{ name: 'ServiceCalendar' }">
<div class=" hover:underline py-1">Service Calendar</div>
</router-link>
<router-link :to="{ name: 'ServiceHome' }">
<div class=" hover:underline py-1" v-if="upcoming_service_count > 0">
<div class="flex gap-5">
<div class="">Service Upcomming</div>
<div class="text-orange-600"> ({{ upcoming_service_count }})</div>
</div>
</div>
<div class=" hover:underline py-1" v-else>Services</div>
</router-link>
<router-link :to="{ name: 'ServicePast' }">
<div class=" hover:underline py-1">Past Service</div>
</router-link>
</li>
<div class="font-bold text-lg text-gray-500 pt-5">Automatics</div>
<li class="text-white">
<router-link :to="{ name: 'auto' }">
<div class=" hover:underline py-1" v-if="automatic_count > 0">
<div class="flex gap-5">
<div class="">Automatics </div>
<div class="text-orange-600"> ({{ automatic_count }})</div>
</div>
</div>
<div class=" hover:underline py-1" v-else>Automatics </div>
</router-link>
</li>
<div class="font-bold text-lg text-gray-500 pt-5">Employees</div>
<li class="text-white">
<router-link :to="{ name: 'employee' }">
<div class=" hover:underline py-1">Employees</div>
</router-link>
</li>
<div class="font-bold text-lg text-gray-500 pt-5">Admin</div>
<li class="text-white">
<router-link :to="{ name: 'oilprice' }">
<div class=" hover:underline py-1">Oil Pricing</div>
</router-link>
<router-link :to="{ name: 'promo' }">
<div class=" hover:underline py-1">Promos</div>
</router-link>
<router-link :to="{ name: 'MoneyYear' }">
<div class=" hover:underline py-1">Money</div>
</router-link>
</li>
</ul>
</div>
</div>
<!-- Admin Section - Closed by default and contains Employees -->
<li>
<details>
<summary class="font-bold text-lg">Admin</summary>
<ul>
<!-- Employees is now here -->
<li><router-link :to="{ name: 'employee' }" exact-active-class="active">Employees</router-link></li>
<li><router-link :to="{ name: 'oilprice' }" exact-active-class="active">Oil Pricing</router-link></li>
<li><router-link :to="{ name: 'promo' }" exact-active-class="active">Promos</router-link></li>
<li><router-link :to="{ name: 'MoneyYear' }" exact-active-class="active">Money</router-link></li>
</ul>
</details>
</li>
</ul>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import axios from 'axios';
import authHeader from '../../services/auth.header';
@@ -169,8 +150,6 @@ export default defineComponent({
headers: authHeader(),
}).then((response: any) => {
this.upcoming_service_count = response.data.count;
// --- THIS IS THE FIX ---
// Explicitly type the 'error' parameter as 'any'
}).catch((error: any) => {
console.error("Failed to get upcoming service count:", error);
this.upcoming_service_count = 0;
@@ -185,7 +164,7 @@ export default defineComponent({
}).then((response: any) => {
if (response.data.update)
console.log("Updated Status of Deliveries")
})
}).catch((error: any) => console.error("Update status failed:", error));
},
updatetemp() {
let path = import.meta.env.VITE_AUTO_URL + '/main/temp';
@@ -196,7 +175,7 @@ export default defineComponent({
}).then((response: any) => {
if (response.data.ok)
console.log("Updated Temp")
})
}).catch((error: any) => console.error("Update temp failed:", error));
},
updateautos() {
let path = import.meta.env.VITE_AUTO_URL + '/main/update';
@@ -207,7 +186,7 @@ export default defineComponent({
}).then((response: any) => {
if (response.data.ok)
console.log("Updated Autos")
})
}).catch((error: any) => console.error("Update autos failed:", error));
},
getAutoCount() {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/count/automatic';
@@ -217,7 +196,7 @@ export default defineComponent({
headers: authHeader(),
}).then((response: any) => {
this.automatic_count = response.data.count
})
}).catch((error: any) => console.error("Get auto count failed:", error));
},
getTodayCount() {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/count/today';
@@ -227,8 +206,7 @@ export default defineComponent({
headers: authHeader(),
}).then((response: any) => {
this.today_count = response.data.count
})
}).catch((error: any) => console.error("Get today count failed:", error));
},
getTommorrowCount() {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/count/tommorrow';
@@ -238,8 +216,7 @@ export default defineComponent({
headers: authHeader(),
}).then((response: any) => {
this.tommorrow_count = response.data.count
})
}).catch((error: any) => console.error("Get tomorrow count failed:", error));
},
getPendingCount() {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/count/pending';
@@ -249,8 +226,7 @@ export default defineComponent({
headers: authHeader(),
}).then((response: any) => {
this.pending_count = response.data.count
})
}).catch((error: any) => console.error("Get pending count failed:", error));
},
getWaitingCount() {
let path = import.meta.env.VITE_BASE_URL + '/deliverystatus/count/waiting';
@@ -260,11 +236,8 @@ export default defineComponent({
headers: authHeader(),
}).then((response: any) => {
this.waiting_count = response.data.count
})
}).catch((error: any) => console.error("Get waiting count failed:", error));
},
},
});
</script>
<style scoped></style>
</script>