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,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>