Major Refactor
This commit is contained in:
7462
package-lock.json
generated
Executable file → Normal file
7462
package-lock.json
generated
Executable file → Normal file
File diff suppressed because it is too large
Load Diff
@@ -21,7 +21,7 @@
|
||||
"html-to-image": "^1.11.11",
|
||||
"html2canvas": "^1.4.1",
|
||||
"moment": "^2.30.1",
|
||||
"pinia": "^2.1.7",
|
||||
"pinia": "^2.3.1",
|
||||
"v-pagination-3": "^0.1.7",
|
||||
"vue": "^3.3.11",
|
||||
"vue-debounce": "^5.0.0",
|
||||
|
||||
55
src/App.vue
55
src/App.vue
@@ -1,21 +1,44 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<!-- App.vue -->
|
||||
<template>
|
||||
<!--
|
||||
This is the new main layout for your entire application.
|
||||
- `drawer`: The base class for the component.
|
||||
- `lg:drawer-open`: On large screens (lg), the drawer is permanently open, creating the desktop sidebar view.
|
||||
-->
|
||||
<div class="drawer lg:drawer-open">
|
||||
<!-- The hidden checkbox that the hamburger button in the header will toggle -->
|
||||
<input id="my-drawer-2" type="checkbox" class="drawer-toggle" />
|
||||
|
||||
<!-- DRAWER CONTENT: This is what's visible on the main page -->
|
||||
<div class="drawer-content flex flex-col">
|
||||
<!-- The Header now lives here, at the top of the content -->
|
||||
<HeaderAuth />
|
||||
|
||||
<html class="">
|
||||
<router-view />
|
||||
<notifications position="top center" />
|
||||
</html>
|
||||
|
||||
<!-- The Main Content Area for your 30 pages -->
|
||||
<main class="flex-1 p-4 md:p-8 bg-base-200">
|
||||
<!-- <router-view> renders the current page (e.g., deliveryEdit.vue) -->
|
||||
<router-view />
|
||||
</main>
|
||||
|
||||
<!-- Your global search results and notifications still live here -->
|
||||
<SearchResults v-if="searchStore.showResults" />
|
||||
<notifications position="top center" />
|
||||
</div>
|
||||
|
||||
<!-- DRAWER SIDE: This is the sidebar that slides out on mobile -->
|
||||
<div class="drawer-side">
|
||||
<label for="my-drawer-2" class="drawer-overlay"></label>
|
||||
<!-- The Sidebar component is now placed here -->
|
||||
<SideBar />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
<script setup lang="ts">
|
||||
import { useSearchStore } from './stores/search';
|
||||
import HeaderAuth from './layouts/headers/headerauth.vue'; // Adjust path if needed
|
||||
import SideBar from './layouts/sidebar/sidebar.vue'; // Adjust path if needed
|
||||
import SearchResults from './components/SearchResults.vue'; // Adjust path if needed
|
||||
|
||||
#myfont {
|
||||
font-family: Arial, Helvetica, sans-serif, sans-serif !important;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
const searchStore = useSearchStore();
|
||||
</script>
|
||||
53
src/components/SearchResults.vue
Executable file
53
src/components/SearchResults.vue
Executable file
@@ -0,0 +1,53 @@
|
||||
<!-- SearchResults.vue -->
|
||||
<template>
|
||||
|
||||
<div class="fixed top-16 left-1/2 -translate-x-1/2 w-full max-w-2xl px-4">
|
||||
<div class="overflow-x-auto bg-base-100 rounded-lg shadow-2xl border border-gray-700">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Address</th>
|
||||
<th>Phone</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- It now reads `searchResults` directly from the store -->
|
||||
<tr v-for="user in searchStore.searchResults" :key="user.id" class="hover cursor-pointer" @click="viewProfile(user.id)">
|
||||
<td>
|
||||
<div class="font-bold">{{ user.customer_first_name }} {{ user.customer_last_name }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ user.customer_address }}</div>
|
||||
<div class="text-sm opacity-70">{{ user.customer_town }}, {{ user.customer_state }}</div>
|
||||
</td>
|
||||
<td>{{ user.customer_phone_number }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
// 1. Import the store
|
||||
import { useSearchStore } from '../stores/search'; // Adjust path if needed
|
||||
|
||||
export default defineComponent({
|
||||
name: "SearchResults",
|
||||
setup() {
|
||||
// 2. Make the store available
|
||||
const searchStore = useSearchStore();
|
||||
return { searchStore };
|
||||
},
|
||||
methods: {
|
||||
viewProfile(customerId: number) {
|
||||
// When a user is clicked, navigate to their profile...
|
||||
this.$router.push({ name: 'customerProfile', params: { id: customerId } });
|
||||
// ...and clear the search so the overlay disappears.
|
||||
this.searchStore.clearSearch();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>c
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -1,67 +1,105 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="w-full px-4 md:px-10 ">
|
||||
<!-- Breadcrumbs & Welcome Header -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex text-2xl mb-5">
|
||||
Welcome {{ employee.employee_first_name }} {{ employee.employee_last_name }}!
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mt-4">
|
||||
Welcome, {{ employee.employee_first_name }}!
|
||||
</h1>
|
||||
|
||||
<div class="grid grid-cols-12 gap-5 ">
|
||||
<div class="col-span-12 bg-secondary ">
|
||||
<div class="grid grid-cols-12 p-5 bg-neutral m-5">
|
||||
<div class="col-span-12 font-bold text-xl">Todays stats</div>
|
||||
<div class="col-span-6 py-2"> Total Deliveries: {{ delivery_count }}</div>
|
||||
<div class="col-span-6 py-2"> Completed: {{ delivery_count_delivered }} / {{ delivery_count }}</div>
|
||||
<!-- Main Dashboard Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-6 my-6">
|
||||
|
||||
<!-- 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>
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<span class="font-semibold">Total Deliveries Today:</span>
|
||||
<span class="text-lg ml-2">{{ delivery_count }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<div class="flex justify-between text-sm mb-1">
|
||||
<span>Completed</span>
|
||||
<span>{{ delivery_count_delivered }} / {{ delivery_count }}</span>
|
||||
</div>
|
||||
<progress class="progress progress-primary w-full" :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>
|
||||
<div class="flex justify-between">
|
||||
<span>Same Day Fee:</span>
|
||||
<span class="font-mono">${{ price_same_day }}</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 2: 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>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span>Price / Emergency:</span>
|
||||
<span class="font-mono">$200</span>
|
||||
</div>
|
||||
|
||||
<!-- <div class="col-span-6 py-2"> Total Calls: {{ call_count }}</div> -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 bg-secondary">
|
||||
<div class="grid grid-cols-12 p-5 bg-neutral m-5">
|
||||
<div class="col-span-12 font-bold text-xl">Todays Oil Price</div>
|
||||
<div class="col-span-12 py-2"> Price / Gallon: ${{ today_oil_price }}</div>
|
||||
<div class="col-span-12 py-2"> Same Day: ${{ price_same_day }}</div>
|
||||
<div class="col-span-12 py-2"> Prime: ${{ price_prime }}</div>
|
||||
<div class="col-span-12 py-2"> Emergency: ${{ price_emergency }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-6 bg-secondary">
|
||||
<div class="grid grid-cols-12 p-5 bg-neutral m-5">
|
||||
<div class="col-span-12 font-bold text-xl">Quick Tips</div>
|
||||
<div class="col-span-12 py-2"> search: @ = last name search</div>
|
||||
<div class="col-span-12 py-2"> search: ! = address</div>
|
||||
<div class="col-span-12 py-2"> search: $ = account number</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 bg-secondary">
|
||||
<div class="grid grid-cols-12 p-5 bg-neutral m-5">
|
||||
<div class="col-span-12 font-bold text-xl">This Weeks Stats</div>
|
||||
<div class="col-span-12 py-2"> Total Deliveries: {{ total_deliveries }}</div>
|
||||
<div class="col-span-12 py-2"> Total Gallons : {{ total_gallons_past_week }}</div>
|
||||
<div class="col-span-12 py-2"> Total Profit: ${{ total_profit_past_week }}</div>
|
||||
|
||||
|
||||
<!-- Card 4: 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>
|
||||
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-100 w-full">
|
||||
<div class="stat">
|
||||
<div class="stat-title">Total Deliveries</div>
|
||||
<div class="stat-value">{{ total_deliveries }}</div>
|
||||
<div class="stat-desc">In the last 7 days</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">Total Gallons</div>
|
||||
<div class="stat-value">{{ total_gallons_past_week }}</div>
|
||||
<div class="stat-desc">Delivered this week</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">Total Profit</div>
|
||||
<div class="stat-value text-success">${{ total_profit_past_week }}</div>
|
||||
<div class="stat-desc">Estimated earnings</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
@@ -1,221 +1,174 @@
|
||||
<template>
|
||||
<Header/>
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar/>
|
||||
</div>
|
||||
<div class=" w-full px-10">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
Customers
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 rounded-md p-6 ">
|
||||
<div class="text-[24px]">
|
||||
Add Oil Price
|
||||
</div>
|
||||
<form class="rounded-md px-8 pt-6 pb-8 mb-4 w-full"
|
||||
enctype="multipart/form-data"
|
||||
@submit.prevent="onSubmit">
|
||||
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 gap-10">
|
||||
<label class="block text-white text-sm font-bold cursor-pointer label">Price Customer</label>
|
||||
<input v-model="CreateOilForm.basicInfo.price_for_customer"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="title" type="text" placeholder="Todays Price"/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Price Employee</label>
|
||||
<input v-model="CreateOilForm.basicInfo.price_for_employee"
|
||||
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="title" type="text" placeholder="Todays Price Employee"/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2"> Price from Supplier</label>
|
||||
<input v-model="CreateOilForm.basicInfo.price_from_supplier"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="prime" type="text" placeholder="Price Prime"/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Price same day</label>
|
||||
<input v-model="CreateOilForm.basicInfo.price_same_day"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="title" type="text" placeholder="Price Same Day"/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Price Emergency Call (After Hours)</label>
|
||||
<input v-model="CreateOilForm.basicInfo.price_emergency"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="title" type="text" placeholder="Price After hours same day call"/>
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Price Prime</label>
|
||||
<input v-model="CreateOilForm.basicInfo.price_prime"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="title" type="text" placeholder="Price Prime"/>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-span-12 md:col-span-12 flex mt-5 mb-5">
|
||||
<button
|
||||
class="btn btn-secondary btn-sm">
|
||||
Create Pricing
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Set Oil Pricing</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl font-bold mt-4">
|
||||
Set Today's Oil Pricing
|
||||
</h1>
|
||||
|
||||
<Footer/>
|
||||
</template>
|
||||
<!-- Main Form Card -->
|
||||
<div class="bg-neutral rounded-lg p-6 mt-6">
|
||||
<form @submit.prevent="onSubmit" class="space-y-6">
|
||||
|
||||
<!-- SECTION 1: Base Pricing -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">Base Pricing</h2>
|
||||
<p class="text-xs text-gray-400">Set the core price per gallon for different groups.</p>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Price from Supplier -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Price from Supplier</span></label>
|
||||
<label class="input-group input-group-sm">
|
||||
<span>$</span>
|
||||
<input v-model.number="OilForm.price_from_supplier" type="number" step="0.01" placeholder="3.50" class="input input-bordered input-sm w-full" />
|
||||
</label>
|
||||
</div>
|
||||
<!-- Price for Customer -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Price for Customer</span></label>
|
||||
<label class="input-group input-group-sm">
|
||||
<span>$</span>
|
||||
<input v-model.number="OilForm.price_for_customer" type="number" step="0.01" placeholder="4.50" class="input input-bordered input-sm w-full" />
|
||||
</label>
|
||||
</div>
|
||||
<!-- Price for Employee -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Price for Employee</span></label>
|
||||
<label class="input-group input-group-sm">
|
||||
<span>$</span>
|
||||
<input v-model.number="OilForm.price_for_employee" type="number" step="0.01" placeholder="4.00" class="input input-bordered input-sm w-full" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SECTION 2: Service Fees -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">Service Fees</h2>
|
||||
<p class="text-xs text-gray-400">Set the flat fees for special delivery services.</p>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- Price Same Day -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Same Day Fee</span></label>
|
||||
<label class="input-group input-group-sm">
|
||||
<span>$</span>
|
||||
<input v-model.number="OilForm.price_same_day" type="number" step="1.00" placeholder="50.00" class="input input-bordered input-sm w-full" />
|
||||
</label>
|
||||
</div>
|
||||
<!-- Price Prime -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Prime Fee</span></label>
|
||||
<label class="input-group input-group-sm">
|
||||
<span>$</span>
|
||||
<input v-model.number="OilForm.price_prime" type="number" step="1.00" placeholder="75.00" class="input input-bordered input-sm w-full" />
|
||||
</label>
|
||||
</div>
|
||||
<!-- Price Emergency -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Emergency Fee</span></label>
|
||||
<label class="input-group input-group-sm">
|
||||
<span>$</span>
|
||||
<input v-model.number="OilForm.price_emergency" type="number" step="1.00" placeholder="150.00" class="input input-bordered input-sm w-full" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SUBMIT BUTTON -->
|
||||
<div class="pt-4">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Update Pricing</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} 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 useValidate from "@vuelidate/core";
|
||||
import {notify} from "@kyvg/vue3-notification";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'OilPrice',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: null,
|
||||
CreateOilForm: {
|
||||
basicInfo: {
|
||||
price_from_supplier: '',
|
||||
price_for_customer: '',
|
||||
price_for_employee: '',
|
||||
price_same_day: '',
|
||||
price_prime: '',
|
||||
price_emergency: '',
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
<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 { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getCurrentPrices()
|
||||
},
|
||||
methods: {
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
}
|
||||
},
|
||||
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; }
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
.catch(() => { this.user = null; });
|
||||
},
|
||||
getCurrentPrices() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/admin/oil/get";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
|
||||
this.CreateOilForm.basicInfo.price_from_supplier = response.data.price_from_supplier;
|
||||
this.CreateOilForm.basicInfo.price_for_customer = response.data.price_for_customer;
|
||||
this.CreateOilForm.basicInfo.price_for_employee = response.data.price_for_employee;
|
||||
this.CreateOilForm.basicInfo.price_same_day = response.data.price_same_day;
|
||||
this.CreateOilForm.basicInfo.price_prime = response.data.price_prime;
|
||||
this.CreateOilForm.basicInfo.price_emergency = response.data.price_emergency;
|
||||
}
|
||||
})
|
||||
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: {
|
||||
price_from_supplier: string;
|
||||
price_for_customer: string;
|
||||
price_for_employee: string;
|
||||
price_same_day: string;
|
||||
price_prime: string;
|
||||
price_emergency: string;
|
||||
|
||||
}) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/admin/oil/create";
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
notify({
|
||||
title: "update",
|
||||
text: "Prices have been updated!",
|
||||
type: "success",
|
||||
});
|
||||
this.$router.push({name: "home"});
|
||||
}
|
||||
if (response.data.error) {
|
||||
this.$router.push("/");
|
||||
}
|
||||
})
|
||||
},
|
||||
onSubmit() {
|
||||
let payload = {
|
||||
price_from_supplier: this.CreateOilForm.basicInfo.price_from_supplier,
|
||||
price_for_customer: this.CreateOilForm.basicInfo.price_for_customer,
|
||||
price_for_employee: this.CreateOilForm.basicInfo.price_for_employee,
|
||||
price_same_day: this.CreateOilForm.basicInfo.price_same_day,
|
||||
price_prime: this.CreateOilForm.basicInfo.price_prime,
|
||||
price_emergency: this.CreateOilForm.basicInfo.price_emergency,
|
||||
};
|
||||
this.CreatePricing(payload);
|
||||
},
|
||||
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) {
|
||||
notify({
|
||||
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",
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
onSubmit() {
|
||||
// --- REFACTORED: Submit the flat form object ---
|
||||
this.CreatePricing(this.OilForm);
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<Header/>
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar/>
|
||||
</div>
|
||||
|
||||
<div class=" w-full px-10">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<Header/>
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar/>
|
||||
</div>
|
||||
|
||||
<div class=" w-full px-10">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs pb-10">
|
||||
<ul>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
<template>
|
||||
<Header />
|
||||
<div class="WrapperPlain">
|
||||
<div class="container max-w-3xl mx-auto text-white">
|
||||
<div class="mt-5 mb-5 px-10 ">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
<template>
|
||||
<Header />
|
||||
<div class="WrapperPlain">
|
||||
<div class="max-w-7xl mx-auto ">
|
||||
<div class="mx-auto max-w-lg flex items-center justify-center mt-4">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
<template>
|
||||
<Header />
|
||||
<div class="WrapperPlain">
|
||||
<div class="container max-w-3xl mx-auto text-white">
|
||||
<div class="mt-5 mb-5">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
|
||||
<template>
|
||||
<Header />
|
||||
<div class="WrapperPlain">
|
||||
<div class="container mx-auto max-w-lg text-white">
|
||||
<div class="mx-auto flex items-center justify-center ">
|
||||
|
||||
@@ -1,252 +1,251 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'delivery' }">
|
||||
Delivery
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Automatic Deliveries</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="flex start text-2xl mb-10">Automatics </div>
|
||||
<div class="mb-10">
|
||||
<div class="">Home Factor</div>
|
||||
<div class="pl-10">1.50 = large oil usage</div>
|
||||
<div class="pl-10"> 1.00 = medium oil usage</div>
|
||||
<div class="pl-10">0.50 = small oil usage</div>
|
||||
<!-- <form class="col-span-12 rounded-md px-8 pt-6 pb-8 mb-4 w-full" enctype="multipart/form-data"
|
||||
@submit.prevent="get_auto_assignment">
|
||||
<div class="flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Delivery Driver </label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" aria-label="Default select example"
|
||||
id="customer_state" v-model="CreateOilOrderForm.basicInfo.driver_driver">
|
||||
<option class="text-white" v-for="(driver, index) in truckDriversList" :key="index" :value="driver['id']">
|
||||
{{ driver['employee_first_name'] }} {{ driver['employee_last_name'] }}
|
||||
</option>
|
||||
</select>
|
||||
<h1 class="text-3xl font-bold mt-4">Automatic Deliveries</h1>
|
||||
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Customers on Automatic Delivery</h2>
|
||||
<div class="badge badge-ghost">{{ deliveries.length }} customers found</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- Data Display -->
|
||||
<div>
|
||||
<!-- DESKTOP VIEW: Sortable Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<!-- SORTABLE HEADERS -->
|
||||
<th @click="sortBy('tank_level_percent')" class="cursor-pointer hover:text-white">Tank Level</th>
|
||||
<th @click="sortBy('days_since_last_fill')" class="cursor-pointer hover:text-white">Days Since Fill</th>
|
||||
<th @click="sortBy('customer_full_name')" class="cursor-pointer hover:text-white">Name</th>
|
||||
<th>Address</th>
|
||||
<th @click="sortBy('house_factor')" class="cursor-pointer hover:text-white">Usage Factor</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Loop over the new 'sortedDeliveries' computed property -->
|
||||
<tr v-for="oil in sortedDeliveries" :key="oil.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>
|
||||
<div v-if="oil.last_fill === null" class="text-gray-500">New Auto</div>
|
||||
<div v-else class="flex items-center gap-3">
|
||||
<progress class="progress w-24"
|
||||
:value="oil.estimated_gallons_left"
|
||||
:max="oil.tank_size"
|
||||
:class="{
|
||||
'progress-success': getTankLevelPercentage(oil) > 60,
|
||||
'progress-warning': getTankLevelPercentage(oil) >= 25 && getTankLevelPercentage(oil) <= 60,
|
||||
'progress-error': getTankLevelPercentage(oil) < 25
|
||||
}"
|
||||
></progress>
|
||||
<span class="font-mono text-xs">{{ oil.estimated_gallons_left }} / {{ oil.tank_size }} gal</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ oil.days_since_last_fill }} days</td>
|
||||
<td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil.customer_id } }" class="link link-hover">
|
||||
{{ oil.customer_full_name }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>{{ oil.customer_address }}, {{ oil.customer_town }}</td>
|
||||
<td>{{ oil.house_factor }}</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<router-link :to="{ name: 'customerEdit', params: { id: oil.customer_id } }" class="btn btn-sm btn-secondary">Edit Customer</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicketAuto', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Finalize</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'TicketAuto', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-success btn-sm">
|
||||
Print Ticket
|
||||
</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-sm">
|
||||
Send for delivery
|
||||
</button>
|
||||
</form> -->
|
||||
</div>
|
||||
<div class="overflow-x-auto bg-neutral">
|
||||
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account #</th>
|
||||
<th>Gallons Left</th>
|
||||
<th>Last Fill</th>
|
||||
<th>Days</th>
|
||||
<th>Name</th>
|
||||
<th>Address</th>
|
||||
<th>Town</th>
|
||||
<th>Home Factor</th>
|
||||
<th>Tank Size</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
<tr v-for="oil in deliveries" :key="oil['id']">
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil['customer_id'] } }">
|
||||
<td>
|
||||
{{ oil['customer_id'] }}
|
||||
</td>
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="oil in sortedDeliveries" :key="oil.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ oil.customer_full_name }}</h2>
|
||||
<p class="text-xs text-gray-400">{{ oil.customer_address }}, {{ oil.customer_town }}</p>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<div class="font-bold">{{ oil.days_since_last_fill }}</div>
|
||||
<div class="text-xs text-gray-400">days ago</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<label class="label p-0 mb-1"><span class="label-text">Tank Level</span></label>
|
||||
<div v-if="oil.last_fill === null" class="text-gray-500 text-sm">New Auto Customer</div>
|
||||
<div v-else>
|
||||
<progress class="progress w-full"
|
||||
:value="oil.estimated_gallons_left"
|
||||
:max="oil.tank_size"
|
||||
:class="{
|
||||
'progress-success': getTankLevelPercentage(oil) > 60,
|
||||
'progress-warning': getTankLevelPercentage(oil) >= 25 && getTankLevelPercentage(oil) <= 60,
|
||||
'progress-error': getTankLevelPercentage(oil) < 25
|
||||
}"
|
||||
></progress>
|
||||
<div class="text-xs text-gray-400 text-right">{{ oil.estimated_gallons_left }} / {{ oil.tank_size }} gal estimated</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-4">
|
||||
<router-link :to="{ name: 'customerEdit', params: { id: oil.customer_id } }" class="btn btn-sm btn-secondary">Edit Customer</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicketAuto', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Finalize</button>
|
||||
</router-link>
|
||||
<td>
|
||||
|
||||
<div class="" v-if="oil['last_fill'] === null"></div>
|
||||
<div class="" v-else> {{ oil['estimated_gallons_left'] }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="" v-if="oil['last_fill'] === null">New Auto</div>
|
||||
<div class="" v-else> {{ oil['last_fill'] }}</div>
|
||||
</td>
|
||||
<td>{{ oil['days_since_last_fill'] }}</td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil['customer_id'] } }">
|
||||
<td>{{ oil['customer_full_name'] }}</td>
|
||||
<router-link :to="{ name: 'TicketAuto', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-success btn-sm">
|
||||
Print Ticket
|
||||
</button>
|
||||
</router-link>
|
||||
<td>{{ oil['customer_address'] }}</td>
|
||||
<td>{{ oil['customer_town'] }}</td>
|
||||
|
||||
<td>{{ oil['house_factor'] }}</td>
|
||||
<td>{{ oil['tank_size'] }}</td>
|
||||
<td class="flex gap-5">
|
||||
<router-link :to="{ name: 'customerEdit', params: { id: oil['customer_id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">
|
||||
Edit Customer
|
||||
</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicketAuto', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Finalize</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'TicketAuto', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-success btn-sm">
|
||||
Print Ticket
|
||||
</button>
|
||||
</router-link>
|
||||
</td>
|
||||
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } 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 { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
// Define a type for the delivery object for better code quality
|
||||
interface AutoDelivery {
|
||||
id: number;
|
||||
customer_id: number;
|
||||
last_fill: string | null;
|
||||
estimated_gallons_left: number;
|
||||
days_since_last_fill: number;
|
||||
customer_full_name: string;
|
||||
customer_address: string;
|
||||
customer_town: string;
|
||||
house_factor: number;
|
||||
tank_size: number;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AutomaticHome',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [],
|
||||
checkedMaterials: [],
|
||||
truckDriversList: [],
|
||||
CreateOilOrderForm: {
|
||||
basicInfo: {
|
||||
driver_driver: '',
|
||||
},
|
||||
},
|
||||
deliveries: [] as AutoDelivery[],
|
||||
// --- NEW: Data properties for sorting ---
|
||||
sortKey: 'estimated_gallons_left' as keyof AutoDelivery | 'tank_level_percent',
|
||||
sortAsc: true,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// --- NEW: Computed property to handle sorting ---
|
||||
sortedDeliveries(): AutoDelivery[] {
|
||||
// Create a copy to avoid mutating the original array
|
||||
const sorted = [...this.deliveries];
|
||||
|
||||
created() {
|
||||
sorted.sort((a, b) => {
|
||||
let valA: any;
|
||||
let valB: any;
|
||||
|
||||
this.userStatus()
|
||||
this.getDriversList()
|
||||
this.get_oil_orders()
|
||||
// Special case for our calculated percentage
|
||||
if (this.sortKey === 'tank_level_percent') {
|
||||
valA = this.getTankLevelPercentage(a);
|
||||
valB = this.getTankLevelPercentage(b);
|
||||
} else {
|
||||
valA = a[this.sortKey as keyof AutoDelivery];
|
||||
valB = b[this.sortKey as keyof AutoDelivery];
|
||||
}
|
||||
|
||||
// Handle nulls or different types if necessary
|
||||
if (valA === null) return 1;
|
||||
if (valB === null) return -1;
|
||||
|
||||
// Comparison logic
|
||||
if (valA < valB) {
|
||||
return this.sortAsc ? -1 : 1;
|
||||
}
|
||||
if (valA > valB) {
|
||||
return this.sortAsc ? 1 : -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return sorted;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
this.get_oil_orders()
|
||||
|
||||
created() {
|
||||
this.userStatus();
|
||||
this.get_oil_orders();
|
||||
},
|
||||
methods: {
|
||||
// --- NEW: Method to handle sorting ---
|
||||
sortBy(key: keyof AutoDelivery | 'tank_level_percent') {
|
||||
if (this.sortKey === key) {
|
||||
// If clicking the same key, reverse the direction
|
||||
this.sortAsc = !this.sortAsc;
|
||||
} else {
|
||||
// If clicking a new key, set it and default to ascending
|
||||
this.sortKey = key;
|
||||
this.sortAsc = true;
|
||||
}
|
||||
},
|
||||
// --- NEW: Helper method for percentage calculation ---
|
||||
getTankLevelPercentage(oil: AutoDelivery): number {
|
||||
if (!oil.tank_size || oil.tank_size === 0 || oil.last_fill === null) {
|
||||
return 0; // Return 0 if tank size is invalid or it's a new customer
|
||||
}
|
||||
return (oil.estimated_gallons_left / oil.tank_size) * 100;
|
||||
},
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
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
|
||||
})
|
||||
},
|
||||
get_oil_orders() {
|
||||
|
||||
let path = import.meta.env.VITE_AUTO_URL + '/delivery/all/customers';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.deliveries = response.data
|
||||
})
|
||||
},
|
||||
|
||||
getDriversList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/employee/drivers";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.truckDriversList = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
get_auto_assignment() {
|
||||
let path = import.meta.env.VITE_AUTO_URL + '/delivery/create';
|
||||
|
||||
const selectedValues: any[] = [];
|
||||
for (const id in this.checkedMaterials) {
|
||||
if (this.checkedMaterials[id]) {
|
||||
selectedValues.push(this.checkedMaterials[id]);
|
||||
}
|
||||
}
|
||||
let payload = {
|
||||
driver_employee_id: this.CreateOilOrderForm.basicInfo.driver_driver,
|
||||
values: selectedValues
|
||||
}
|
||||
axios({
|
||||
method: 'post',
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
|
||||
if (response.data.count > 0) {
|
||||
notify({
|
||||
title: "Success",
|
||||
text: "Added automatics to delivery",
|
||||
type: "success",
|
||||
});
|
||||
|
||||
} else {
|
||||
notify({
|
||||
title: "Failure",
|
||||
text: "Incorrect driver or no auto selection.",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
|
||||
this.get_oil_orders()
|
||||
})
|
||||
get_oil_orders() {
|
||||
const path = import.meta.env.VITE_AUTO_URL + '/delivery/all/customers';
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
this.deliveries = response.data;
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Failed to fetch automatic deliveries:", error);
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
</script>
|
||||
@@ -1,217 +1,122 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
Customers
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
|
||||
<li v-if="customer.id"><router-link :to="{ name: 'customerProfile', params: { id: customer.id } }">Profile</router-link></li>
|
||||
<li>Add Credit Card</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid grid-cols-1 rounded-md p-6 ">
|
||||
|
||||
|
||||
<div class="grid grid-cols-12">
|
||||
<div class="col-span-12 text-center mb-10 text-2xl">Add a Credit Card</div>
|
||||
|
||||
<div class="col-span-6">
|
||||
<form class="rounded-md px-8 pt-6 pb-8 mb-4 w-full" enctype="multipart/form-data"
|
||||
@submit.prevent="onSubmit">
|
||||
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 gap-10">
|
||||
<label class="block text-white text-sm font-bold cursor-pointer label">Main Card</label>
|
||||
<input v-model="CreateCardForm.basicInfo.main_card" class="checkbox" id="fill" type="checkbox" />
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Name on Card</label>
|
||||
<input v-model="CreateCardForm.basicInfo.card_name"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text"
|
||||
placeholder="Name on Card" />
|
||||
<span v-if="v$.CreateCardForm.basicInfo.card_name.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCardForm.basicInfo.card_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Card Number</label>
|
||||
<input v-model="CreateCardForm.basicInfo.card_number"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text"
|
||||
placeholder="Card Number" />
|
||||
<span v-if="v$.CreateCardForm.basicInfo.card_number.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCardForm.basicInfo.card_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Expiration Month</label>
|
||||
<select v-model="CreateCardForm.basicInfo.expiration_month"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="Month">
|
||||
<option>01</option>
|
||||
<option>02</option>
|
||||
<option>03</option>
|
||||
<option>04</option>
|
||||
<option>05</option>
|
||||
<option>06</option>
|
||||
<option>07</option>
|
||||
<option>08</option>
|
||||
<option>09</option>
|
||||
<option>10</option>
|
||||
<option>11</option>
|
||||
<option>12</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Expiration Year</label>
|
||||
<select v-model="CreateCardForm.basicInfo.expiration_year"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="Month">
|
||||
<option>2025</option>
|
||||
<option>2026</option>
|
||||
<option>2027</option>
|
||||
<option>2028</option>
|
||||
<option>2029</option>
|
||||
<option>2030</option>
|
||||
<option>2031</option>
|
||||
<option>2032</option>
|
||||
|
||||
</select>
|
||||
|
||||
<span v-if="v$.CreateCardForm.basicInfo.expiration_year.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCardForm.basicInfo.expiration_year.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Type of Card</label>
|
||||
<select v-model="CreateCardForm.basicInfo.type_of_card"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="Month">
|
||||
<option>Visa</option>
|
||||
<option>MasterCard</option>
|
||||
<option>Discover</option>
|
||||
<option>American Express</option>
|
||||
</select>
|
||||
|
||||
<span v-if="v$.CreateCardForm.basicInfo.type_of_card.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCardForm.basicInfo.type_of_card.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Security Number</label>
|
||||
<input v-model="CreateCardForm.basicInfo.security_number"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text"
|
||||
placeholder="Back of card" />
|
||||
<span v-if="v$.CreateCardForm.basicInfo.security_number.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCardForm.basicInfo.security_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Zip Code</label>
|
||||
<input v-model="CreateCardForm.basicInfo.zip_code"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text"
|
||||
placeholder="Zip Code" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="col-span-12 md:col-span-12 flex mt-5 mb-5">
|
||||
<button class="btn btn-sm btn-secondary">
|
||||
Save Credit Card
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<!-- TOP SECTION: Customer Info -->
|
||||
<div class="my-6">
|
||||
<div class="bg-neutral rounded-lg p-5">
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center mb-4">
|
||||
<div>
|
||||
<div class="text-xl font-bold">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</div>
|
||||
<div class="text-sm text-gray-400">Account: {{ customer.account_number }}</div>
|
||||
</div>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm mt-2 sm:mt-0">
|
||||
View Profile
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-span-6">
|
||||
<div class="col-span-12 font-bold flex pb-5 ">{{ customer.account_number }}</div>
|
||||
<div class="col-span-12 font-bold flex">
|
||||
{{ customer.customer_first_name }}
|
||||
{{ customer.customer_last_name }}
|
||||
</div>
|
||||
<div class="col-span-12 font-bold flex">
|
||||
|
||||
{{ customer.customer_address }}
|
||||
<div v-if="customer.customer_apt != 'None'">
|
||||
{{ customer.customer_apt }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-span-12 font-bold flex">
|
||||
<div class="pr-2">
|
||||
{{ customer.customer_town }},
|
||||
</div>
|
||||
<div class="pr-2">
|
||||
|
||||
<div v-if="customer.customer_state == 0">Massachusetts</div>
|
||||
<div v-else-if="customer.customer_state == 1">Rhode Island</div>
|
||||
<div v-else-if="customer.customer_state == 2">New Hampshire</div>
|
||||
<div v-else-if="customer.customer_state == 3">Maine</div>
|
||||
<div v-else-if="customer.customer_state == 4">Vermont</div>
|
||||
<div v-else-if="customer.customer_state == 5">Maine</div>
|
||||
<div v-else-if="customer.customer_state == 6">New York</div>
|
||||
<div v-else>Unknown state</div>
|
||||
</div>
|
||||
<div class="pr-2">
|
||||
{{ customer.customer_zip }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 font-bold flex" v-if="customer.customer_apt !== 'None'">
|
||||
{{ customer.customer_apt }}
|
||||
</div>
|
||||
<div class="col-span-12 font-bold flex">
|
||||
<div v-if="customer.customer_home_type == 0">Residential</div>
|
||||
<div v-else-if="customer.customer_home_type == 1">apartment</div>
|
||||
<div v-else-if="customer.customer_home_type == 2">condo</div>
|
||||
<div v-else-if="customer.customer_home_type == 3">commercial</div>
|
||||
<div v-else-if="customer.customer_home_type == 4">business</div>
|
||||
<div v-else-if="customer.customer_home_type == 5">construction</div>
|
||||
<div v-else-if="customer.customer_home_type == 6">container</div>
|
||||
</div>
|
||||
<div class="col-span-12 font-bold flex">
|
||||
{{ customer.customer_phone_number }}
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ customer.customer_address }}</div>
|
||||
<div v-if="customer.customer_apt && customer.customer_apt !== 'None'">{{ customer.customer_apt }}</div>
|
||||
<div>{{ customer.customer_town }}, {{ customer.customer_state }} {{ customer.customer_zip }}</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BOTTOM SECTION: Add Card Form -->
|
||||
<div class="bg-neutral rounded-lg p-6">
|
||||
<h2 class="text-2xl font-bold mb-4">Add a New Credit Card</h2>
|
||||
<form @submit.prevent="onSubmit" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
||||
<!-- Name on Card -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">Name on Card</span></label>
|
||||
<input v-model="CardForm.card_name" type="text" placeholder="John M. Doe" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CardForm.card_name.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
|
||||
<!-- Card Number -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">Card Number</span></label>
|
||||
<input v-model="CardForm.card_number" type="text" placeholder="4242..." class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CardForm.card_number.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
|
||||
<!-- Expiration -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">Expiration</span></label>
|
||||
<div class="flex gap-2">
|
||||
<select v-model="CardForm.expiration_month" class="select select-bordered select-sm w-full">
|
||||
<option disabled value="">MM</option>
|
||||
<option v-for="m in 12" :key="m" :value="String(m).padStart(2, '0')">{{ String(m).padStart(2, '0') }}</option>
|
||||
</select>
|
||||
<select v-model="CardForm.expiration_year" class="select select-bordered select-sm w-full">
|
||||
<option disabled value="">YYYY</option>
|
||||
<option v-for="y in 10" :key="y" :value="new Date().getFullYear() + y - 1">{{ new Date().getFullYear() + y - 1 }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<span v-if="v$.CardForm.expiration_month.$error || v$.CardForm.expiration_year.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
|
||||
<!-- Security Number (CVV) -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">CVV</span></label>
|
||||
<input v-model="CardForm.security_number" type="text" placeholder="123" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CardForm.security_number.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
|
||||
<!-- Card Type -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">Card Type</span></label>
|
||||
<select v-model="CardForm.type_of_card" class="select select-bordered select-sm w-full">
|
||||
<option disabled value="">Select Type</option>
|
||||
<option>Visa</option>
|
||||
<option>MasterCard</option>
|
||||
<option>Discover</option>
|
||||
<option>American Express</option>
|
||||
</select>
|
||||
<span v-if="v$.CardForm.type_of_card.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
|
||||
<!-- Billing Zip Code -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">Billing Zip Code</span></label>
|
||||
<input v-model="CardForm.zip_code" type="text" placeholder="01234" class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
|
||||
<!-- Main Card Checkbox -->
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label cursor-pointer justify-start gap-4">
|
||||
<span class="label-text font-bold">Set as Main Card for this customer</span>
|
||||
<input v-model="CardForm.main_card" type="checkbox" class="checkbox checkbox-sm" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SUBMIT BUTTON -->
|
||||
<div class="pt-4">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Save Credit Card</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } 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 useValidate from "@vuelidate/core";
|
||||
import { notify } from "@kyvg/vue3-notification"
|
||||
@@ -219,151 +124,80 @@ import { minLength, required } from "@vuelidate/validators";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'AddCardCreate',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: null,
|
||||
customer: {
|
||||
id: 0,
|
||||
user_id: 0,
|
||||
customer_first_name: '',
|
||||
customer_last_name: '',
|
||||
customer_town: '',
|
||||
customer_address: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
customer_apt: '',
|
||||
customer_home_type: 0,
|
||||
customer_phone_number: '',
|
||||
account_number: '',
|
||||
},
|
||||
CreateCardForm: {
|
||||
basicInfo: {
|
||||
card_name: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
card_number: '',
|
||||
zip_code: '',
|
||||
main_card: false,
|
||||
},
|
||||
customer: {} as any,
|
||||
// --- REFACTORED: Simplified, flat form object ---
|
||||
CardForm: {
|
||||
card_name: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
card_number: '',
|
||||
zip_code: '',
|
||||
main_card: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
validations() {
|
||||
return {
|
||||
CreateCardForm: {
|
||||
basicInfo: {
|
||||
card_name: { required, minLength: minLength(1) },
|
||||
expiration_month: { required, minLength: minLength(1) },
|
||||
expiration_year: { required, minLength: minLength(1) },
|
||||
security_number: { required, minLength: minLength(1) },
|
||||
type_of_card: { required, minLength: minLength(1) },
|
||||
card_number: { required, minLength: minLength(1) },
|
||||
},
|
||||
// --- REFACTORED: Validation points to the flat form object ---
|
||||
CardForm: {
|
||||
card_name: { required, minLength: minLength(1) },
|
||||
expiration_month: { required },
|
||||
expiration_year: { required },
|
||||
security_number: { required, minLength: minLength(1) },
|
||||
type_of_card: { required },
|
||||
card_number: { required, minLength: minLength(1) },
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getCustomer(this.$route.params.id);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getCustomer(this.$route.params.id)
|
||||
this.userStatus();
|
||||
this.getCustomer(this.$route.params.id);
|
||||
},
|
||||
methods: {
|
||||
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
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
|
||||
if (response.data.ok) { this.user = response.data.user; }
|
||||
})
|
||||
.catch(() => { this.user = null; });
|
||||
},
|
||||
getCustomer(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.customer = response.data;
|
||||
})
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${user_id}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => { this.customer = response.data; })
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
type: "error",
|
||||
});
|
||||
notify({ title: "Error", text: "Could not find customer", type: "error" });
|
||||
});
|
||||
},
|
||||
|
||||
CreateCard(payload: {
|
||||
card_name: string;
|
||||
expiration_month: string;
|
||||
expiration_year: string;
|
||||
type_of_card: string;
|
||||
security_number: string;
|
||||
zip_code: string;
|
||||
card_number: string;
|
||||
main_card: boolean;
|
||||
}) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/card/create/" + this.customer.id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
CreateCard(payload: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/create/${this.customer.id}`;
|
||||
axios.post(path, payload, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
} else {
|
||||
notify({ title: "Error", text: response.data.error || "Failed to create card.", type: "error" });
|
||||
}
|
||||
if (response.data.error) {
|
||||
this.$router.push("/");
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
onSubmit() {
|
||||
let payload = {
|
||||
card_name: this.CreateCardForm.basicInfo.card_name,
|
||||
card_number: this.CreateCardForm.basicInfo.card_number,
|
||||
expiration_month: this.CreateCardForm.basicInfo.expiration_month,
|
||||
expiration_year: this.CreateCardForm.basicInfo.expiration_year,
|
||||
type_of_card: this.CreateCardForm.basicInfo.type_of_card,
|
||||
security_number: this.CreateCardForm.basicInfo.security_number,
|
||||
main_card: this.CreateCardForm.basicInfo.main_card,
|
||||
zip_code: this.CreateCardForm.basicInfo.zip_code,
|
||||
};
|
||||
this.CreateCard(payload);
|
||||
this.v$.$validate();
|
||||
if (!this.v$.$error) {
|
||||
this.CreateCard(this.CardForm);
|
||||
} else {
|
||||
notify({ title: "Validation Error", text: "Please fill out all required fields.", type: "error" });
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
});
|
||||
</script>
|
||||
@@ -1,354 +1,240 @@
|
||||
<template>
|
||||
<Header/>
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar/>
|
||||
<div class="flex">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
|
||||
<li v-if="customer.id"><router-link :to="{ name: 'customerProfile', params: { id: customer.id } }">Profile</router-link></li>
|
||||
<li>Edit Credit Card</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- TOP SECTION: Customer and Card Info -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 my-6">
|
||||
<!-- Customer Info Card -->
|
||||
<div class="bg-neutral rounded-lg p-5">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<div class="text-xl font-bold">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</div>
|
||||
<div class="text-sm text-gray-400">Account: {{ customer.account_number }}</div>
|
||||
</div>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
|
||||
View Profile
|
||||
</router-link>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ customer.customer_address }}</div>
|
||||
<div v-if="customer.customer_apt && customer.customer_apt !== 'None'">{{ customer.customer_apt }}</div>
|
||||
<div>{{ customer.customer_town }}, {{ customer.customer_state }} {{ customer.customer_zip }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class=" w-full px-10">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
Customers
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid grid-cols-1 rounded-md p-6 ">
|
||||
<div class="text-[24px]">
|
||||
Credit Card Customer: {{ customer.customer_first_name }}
|
||||
</div>
|
||||
<div class="text-[20px]">
|
||||
Card Id: {{card.id}}
|
||||
</div>
|
||||
<form class="rounded-md px-8 pt-6 pb-8 mb-4 w-full"
|
||||
enctype="multipart/form-data"
|
||||
@submit.prevent="onSubmit">
|
||||
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 gap-10">
|
||||
<label class="block text-white text-sm font-bold cursor-pointer label">Main Card</label>
|
||||
<input v-model="CreateCardForm.basicInfo.main_card"
|
||||
class="checkbox checkbox-xs"
|
||||
id="fill"
|
||||
type="checkbox"/>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Name on Card</label>
|
||||
<input v-model="CreateCardForm.basicInfo.card_name"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="title" type="text" placeholder="Name on Card"/>
|
||||
<span v-if="v$.CreateCardForm.basicInfo.card_name.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCardForm.basicInfo.card_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Card Number</label>
|
||||
<input v-model="CreateCardForm.basicInfo.card_number"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="title" type="text" placeholder="Card Number"/>
|
||||
<span v-if="v$.CreateCardForm.basicInfo.card_number.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCardForm.basicInfo.card_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Expiration Month</label>
|
||||
<select
|
||||
v-model="CreateCardForm.basicInfo.expiration_month"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="Month"
|
||||
>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
<option>3</option>
|
||||
<option>4</option>
|
||||
<option>5</option>
|
||||
<option>6</option>
|
||||
<option>7</option>
|
||||
<option>8</option>
|
||||
<option>9</option>
|
||||
<option>10</option>
|
||||
<option>11</option>
|
||||
<option>12</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Expiration Year</label>
|
||||
<select
|
||||
v-model="CreateCardForm.basicInfo.expiration_year"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="Month"
|
||||
>
|
||||
<option>2025</option>
|
||||
<option>2026</option>
|
||||
<option>2027</option>
|
||||
<option>2028</option>
|
||||
<option>2029</option>
|
||||
<option>2030</option>
|
||||
<option>2031</option>
|
||||
<option>2032</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateCardForm.basicInfo.expiration_year.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCardForm.basicInfo.expiration_year.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Type of Card</label>
|
||||
<select
|
||||
v-model="CreateCardForm.basicInfo.type_of_card"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="Month"
|
||||
>
|
||||
<option>Visa</option>
|
||||
<option>MasterCard</option>
|
||||
<option>Discover</option>
|
||||
<option>American Express</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateCardForm.basicInfo.type_of_card.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCardForm.basicInfo.type_of_card.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Zip Code</label>
|
||||
<input v-model="CreateCardForm.basicInfo.zip_code"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text"
|
||||
placeholder="Zip Code" />
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Security Number</label>
|
||||
<input v-model="CreateCardForm.basicInfo.security_number"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="title" type="text" placeholder="Back of card"/>
|
||||
<span v-if="v$.CreateCardForm.basicInfo.security_number.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCardForm.basicInfo.security_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 md:col-span-12 flex mt-5 mb-5">
|
||||
<button class="btn">
|
||||
Edit Card
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Card Being Edited Info Card -->
|
||||
<div class="bg-neutral rounded-lg p-5">
|
||||
<h3 class="text-xl font-bold mb-4">Editing Card</h3>
|
||||
<div v-if="card.id" class="space-y-2">
|
||||
<p><strong class="font-semibold">Card Type:</strong> {{ card.type_of_card }}</p>
|
||||
<p><strong class="font-semibold">Card Number:</strong> **** **** **** {{ card.last_four_digits }}</p>
|
||||
<p><strong class="font-semibold">Card ID:</strong> {{ card.id }}</p>
|
||||
</div>
|
||||
<div v-else class="text-gray-400">Loading card details...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer/>
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} 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 useValidate from "@vuelidate/core";
|
||||
|
||||
import {minLength, required} from "@vuelidate/validators";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EditCard',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: {
|
||||
id: '',
|
||||
},
|
||||
customer: {
|
||||
id: 0,
|
||||
user_id: 0,
|
||||
customer_first_name: '',
|
||||
customer_last_name: '',
|
||||
customer_town: '',
|
||||
customer_address: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
customer_apt: '',
|
||||
customer_home_type: 0,
|
||||
customer_phone_number: '',
|
||||
account_number: '',
|
||||
<!-- BOTTOM SECTION: Edit Card Form -->
|
||||
<div class="bg-neutral rounded-lg p-6">
|
||||
<h2 class="text-2xl font-bold mb-4">Update Card Details</h2>
|
||||
<form @submit.prevent="onSubmit" class="space-y-4">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
|
||||
<!-- Name on Card -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">Name on Card</span></label>
|
||||
<input v-model="CardForm.name_on_card" type="text" placeholder="John M Doe" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CardForm.name_on_card.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
|
||||
<!-- Card Number -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">Card Number</span></label>
|
||||
<input v-model="CardForm.card_number" type="text" placeholder="4242..." class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CardForm.card_number.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
|
||||
<!-- Expiration -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">Expiration</span></label>
|
||||
<div class="flex gap-2">
|
||||
<select v-model="CardForm.expiration_month" class="select select-bordered select-sm w-full">
|
||||
<option disabled value="">MM</option>
|
||||
<option v-for="m in 12" :key="m" :value="String(m).padStart(2, '0')">{{ String(m).padStart(2, '0') }}</option>
|
||||
</select>
|
||||
<select v-model="CardForm.expiration_year" class="select select-bordered select-sm w-full">
|
||||
<option disabled value="">YYYY</option>
|
||||
<option v-for="y in 10" :key="y" :value="new Date().getFullYear() + y - 1">{{ new Date().getFullYear() + y - 1 }}</option>
|
||||
</select>
|
||||
</div>
|
||||
<span v-if="v$.CardForm.expiration_month.$error || v$.CardForm.expiration_year.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
|
||||
<!-- Security Number (CVV) -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">CVV</span></label>
|
||||
<input v-model="CardForm.security_number" type="text" placeholder="123" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CardForm.security_number.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
|
||||
<!-- Card Type -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">Card Type</span></label>
|
||||
<select v-model="CardForm.type_of_card" class="select select-bordered select-sm w-full">
|
||||
<option disabled value="">Select Type</option>
|
||||
<option>Visa</option>
|
||||
<option>MasterCard</option>
|
||||
<option>Discover</option>
|
||||
<option>American Express</option>
|
||||
</select>
|
||||
<span v-if="v$.CardForm.type_of_card.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
|
||||
<!-- Billing Zip Code -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text font-bold">Billing Zip Code</span></label>
|
||||
<input v-model="CardForm.zip_code" type="text" placeholder="01234" class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
|
||||
<!-- Main Card Checkbox -->
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label cursor-pointer justify-start gap-4">
|
||||
<span class="label-text font-bold">Set as Main Card</span>
|
||||
<input v-model="CardForm.main_card" type="checkbox" class="checkbox checkbox-sm" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SUBMIT BUTTON -->
|
||||
<div class="pt-4">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</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";
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EditCard',
|
||||
components: {
|
||||
Footer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: null as any,
|
||||
customer: {} as any,
|
||||
card: {} as any, // To store original card details for display
|
||||
// --- REFACTORED: Simplified, flat form object ---
|
||||
CardForm: {
|
||||
name_on_card: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
card_number: '',
|
||||
zip_code: '',
|
||||
main_card: false,
|
||||
},
|
||||
card: {
|
||||
id: '',
|
||||
card_name: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
main_card: '',
|
||||
zip_code: '',
|
||||
user_id: '',
|
||||
},
|
||||
|
||||
card_id: null,
|
||||
customer_id: null,
|
||||
CreateCardForm: {
|
||||
basicInfo: {
|
||||
card_name: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
card_number: '',
|
||||
zip_code: '',
|
||||
main_card: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
// --- REFACTORED: Validation points to the flat form object ---
|
||||
CardForm: {
|
||||
name_on_card: { required, minLength: minLength(1) },
|
||||
expiration_month: { required },
|
||||
expiration_year: { required },
|
||||
security_number: { required, minLength: minLength(1) },
|
||||
type_of_card: { required },
|
||||
card_number: { required, minLength: minLength(1) },
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.userStatus();
|
||||
this.getCard(this.$route.params.id);
|
||||
},
|
||||
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; });
|
||||
},
|
||||
getCustomer(userid: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${userid}`;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => { this.customer = response.data; });
|
||||
},
|
||||
getCard(card_id: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/${card_id}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
this.card = response.data; // Store original details for display
|
||||
// Populate the flat form object for editing
|
||||
this.CardForm.name_on_card = response.data.name_on_card;
|
||||
this.CardForm.expiration_month = response.data.expiration_month;
|
||||
this.CardForm.expiration_year = response.data.expiration_year;
|
||||
this.CardForm.type_of_card = response.data.type_of_card;
|
||||
this.CardForm.security_number = response.data.security_number;
|
||||
this.CardForm.main_card = response.data.main_card;
|
||||
this.CardForm.card_number = response.data.card_number;
|
||||
this.CardForm.zip_code = response.data.zip_code;
|
||||
|
||||
if (response.data.user_id) {
|
||||
this.getCustomer(response.data.user_id);
|
||||
}
|
||||
});
|
||||
},
|
||||
editCard(payload: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/payment/card/edit/${this.$route.params.id}`;
|
||||
// The backend expects 'card_name', but our form now uses 'name_on_card'.
|
||||
// We must create a new payload that matches the backend's expectation.
|
||||
const backendPayload = {
|
||||
...payload,
|
||||
card_name: payload.name_on_card,
|
||||
};
|
||||
delete backendPayload.name_on_card; // Clean up the object
|
||||
|
||||
axios.put(path, backendPayload, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
} else {
|
||||
console.error("Failed to edit card:", response.data.error);
|
||||
}
|
||||
});
|
||||
},
|
||||
onSubmit() {
|
||||
this.v$.$validate();
|
||||
if (!this.v$.$error) {
|
||||
this.editCard(this.CardForm);
|
||||
} else {
|
||||
console.log("Form validation failed.");
|
||||
}
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
CreateCardForm: {
|
||||
basicInfo: {
|
||||
card_name: {required, minLength: minLength(1)},
|
||||
expiration_month: {required, minLength: minLength(1)},
|
||||
expiration_year: {required, minLength: minLength(1)},
|
||||
security_number: {required, minLength: minLength(1)},
|
||||
type_of_card: {required, minLength: minLength(1)},
|
||||
card_number: {required, minLength: minLength(1)},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getCard(this.$route.params.id);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getCard(this.$route.params.id)
|
||||
},
|
||||
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) {
|
||||
this.user = response.data.user;
|
||||
this.user.id = response.data.user.id;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user.id = '';
|
||||
})
|
||||
},
|
||||
getCustomer(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer = response.data
|
||||
})
|
||||
},
|
||||
|
||||
getCard (card_id:any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/card/" + card_id ;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
|
||||
this.CreateCardForm.basicInfo.card_name= response.data.name_on_card;
|
||||
this.CreateCardForm.basicInfo.expiration_month= response.data.expiration_month;
|
||||
this.CreateCardForm.basicInfo.expiration_year= response.data.expiration_year;
|
||||
this.CreateCardForm.basicInfo.type_of_card= response.data.type_of_card;
|
||||
this.CreateCardForm.basicInfo.security_number= response.data.security_number;
|
||||
this.CreateCardForm.basicInfo.main_card= response.data.main_card;
|
||||
this.CreateCardForm.basicInfo.card_number= response.data.card_number;
|
||||
this.CreateCardForm.basicInfo.zip_code= response.data.zip_code;
|
||||
console.log(response.data)
|
||||
this.user.id = response.data.user_id
|
||||
this.card=response.data
|
||||
this.getCustomer(response.data.user_id)
|
||||
|
||||
})
|
||||
},
|
||||
editCard(payload: {
|
||||
|
||||
card_name: string;
|
||||
expiration_month: string;
|
||||
expiration_year: string;
|
||||
type_of_card: string;
|
||||
security_number: string;
|
||||
zip_code: string;
|
||||
main_card: boolean;
|
||||
}) {
|
||||
|
||||
let path = import.meta.env.VITE_BASE_URL + "/payment/card/edit/" + this.$route.params.id ;
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.$router.push({name: "customerProfile", params: { id: this.card.user_id }});
|
||||
}
|
||||
if (response.data.error) {
|
||||
this.$router.push("/");
|
||||
}
|
||||
})
|
||||
},
|
||||
onSubmit() {
|
||||
let payload = {
|
||||
card_name: this.CreateCardForm.basicInfo.card_name,
|
||||
expiration_month: this.CreateCardForm.basicInfo.expiration_month,
|
||||
expiration_year: this.CreateCardForm.basicInfo.expiration_year,
|
||||
type_of_card: this.CreateCardForm.basicInfo.type_of_card,
|
||||
security_number: this.CreateCardForm.basicInfo.security_number,
|
||||
card_number: this.CreateCardForm.basicInfo.card_number,
|
||||
zip_code: this.CreateCardForm.basicInfo.zip_code,
|
||||
main_card: this.CreateCardForm.basicInfo.main_card,
|
||||
};
|
||||
this.editCard(payload);
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
},
|
||||
});
|
||||
</script>
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
|
||||
@@ -1,154 +1,138 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div v-if="user">
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
<div class="flex">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
|
||||
<li>Create New Customer</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="w-full px-10">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
Customers
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grid grid-cols-12 rounded-md p-6 ">
|
||||
<div class="col-span-12 text-[24px] ">Create a customer</div>
|
||||
<form class="col-span-12 rounded-md px-8 pt-6 pb-8 mb-4 w-full" enctype="multipart/form-data"
|
||||
@submit.prevent="onSubmit">
|
||||
|
||||
<h1 class="text-3xl font-bold mt-4">
|
||||
Create New Customer
|
||||
</h1>
|
||||
|
||||
<div class="grid grid-cols-12">
|
||||
<div class="col-span-6">
|
||||
<div class="col-span-12 text-[18px] mt-5 mb-5">General Info</div>
|
||||
<div class="col-span-12 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2"> First Name</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_first_name"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text"
|
||||
placeholder="First Name" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_first_name.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_first_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-12 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2"> Last Name</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_last_name"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text"
|
||||
placeholder="Last Name" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_last_name.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_last_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-12 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Phone Number</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_phone_number"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="phone number" type="tel"
|
||||
placeholder="Phone Number" @input="acceptNumber()" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_phone_number.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_phone_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-12 mb-4">
|
||||
<div class="flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Customer Type</label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" aria-label="Default select example"
|
||||
id="customer_type" v-model="CreateCustomerForm.basicInfo.customer_home_type">
|
||||
<option class="text-white" v-for="(customer, index) in custList" :key="index"
|
||||
:value="customer['value']">
|
||||
{{ customer['text'] }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Email (Optional)</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_email"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="email" type="text" placeholder="Email" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_email.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_email.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Main Form Card -->
|
||||
<div class="bg-neutral rounded-lg p-6 mt-6">
|
||||
<form @submit.prevent="onSubmit" class="space-y-6">
|
||||
|
||||
<!-- SECTION 1: General Info -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">General Info</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- First Name -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">First Name</span></label>
|
||||
<input v-model="CreateCustomerForm.customer_first_name" type="text" placeholder="First Name" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.customer_first_name.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.customer_first_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-6">
|
||||
<div class="grid grid-cols-12">
|
||||
<div class="text-[18px] mt-5 mb-5">Customer Address</div>
|
||||
<div class="col-span-12 mb-5 md:mb-5">
|
||||
<label class="block text-white text-sm font-bold mb-2">Street Address</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_address"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="address" type="text"
|
||||
placeholder="Address" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_address.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_address.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-12 mb-5 md:mb-5">
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_apt"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="apt" type="text"
|
||||
placeholder="Apt, suite, unit, building, floor, etc" />
|
||||
</div>
|
||||
<div class="col-span-12 mb-20 md:mb-5 ">
|
||||
<label class="block text-white text-sm font-bold mb-2">Town</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_town"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="town" type="text" placeholder="Town" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_town.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_town.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class=" col-span-12 flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">State</label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" aria-label="Default select example"
|
||||
id="customer_state" v-model="CreateCustomerForm.basicInfo.customer_state">
|
||||
<option class="text-white" v-for="(state, index) in stateList" :key="index"
|
||||
:value="state['value']">
|
||||
{{ state['text'] }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_state.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_state.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-4 mb-5 md:mb-5">
|
||||
<label class="block text-white text-sm font-bold mb-2">Zip Code</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_zip" class="w-full input input-bordered input-sm "
|
||||
id="zip" type="text" placeholder="Zip" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_zip.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_zip.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Last Name -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Last Name</span></label>
|
||||
<input v-model="CreateCustomerForm.customer_last_name" type="text" placeholder="Last Name" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.customer_last_name.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.customer_last_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-span-6">
|
||||
<div class="text-[18px] mt-5 mb-5"> Description</div>
|
||||
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0">
|
||||
<textarea v-model="CreateCustomerForm.basicInfo.customer_description" rows="4"
|
||||
class="textarea block p-2.5 w-full input-bordered " id="description" type="text"
|
||||
placeholder="Description of Customer House" />
|
||||
</div>
|
||||
|
||||
<!-- Phone Number -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Phone Number</span></label>
|
||||
<input v-model="CreateCustomerForm.customer_phone_number" type="tel" placeholder="Phone Number" class="input input-bordered input-sm w-full" @input="acceptNumber()" />
|
||||
<span v-if="v$.CreateCustomerForm.customer_phone_number.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.customer_phone_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-12 flex mt-5 mb-5">
|
||||
<button class="btn btn-accent btn-sm">
|
||||
Submit Create Customer
|
||||
</button>
|
||||
<!-- Email -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Email (Optional)</span></label>
|
||||
<input v-model="CreateCustomerForm.customer_email" type="text" placeholder="Email" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.customer_email.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.customer_email.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Customer Type -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Customer Type</span></label>
|
||||
<select v-model="CreateCustomerForm.customer_home_type" class="select select-bordered select-sm w-full">
|
||||
<option disabled :value="0">Select a type</option>
|
||||
<option v-for="customer in custList" :key="customer.value" :value="customer.value">
|
||||
{{ customer.text }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateCustomerForm.customer_home_type.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SECTION 2: Address -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">Address</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Street Address -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Street Address</span></label>
|
||||
<input v-model="CreateCustomerForm.customer_address" type="text" placeholder="Street Address" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.customer_address.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.customer_address.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Apt, Suite, etc. -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Apt, Suite, etc. (Optional)</span></label>
|
||||
<input v-model="CreateCustomerForm.customer_apt" type="text" placeholder="Apt, suite, unit..." class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
<!-- Town -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Town</span></label>
|
||||
<input v-model="CreateCustomerForm.customer_town" type="text" placeholder="Town" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.customer_town.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.customer_town.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- State -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">State</span></label>
|
||||
<select v-model="CreateCustomerForm.customer_state" class="select select-bordered select-sm w-full">
|
||||
<option disabled :value="0">Select a state</option>
|
||||
<option v-for="state in stateList" :key="state.value" :value="state.value">
|
||||
{{ state.text }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateCustomerForm.customer_state.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
<!-- Zip Code -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Zip Code</span></label>
|
||||
<input v-model="CreateCustomerForm.customer_zip" type="text" placeholder="Zip Code" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.customer_zip.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.customer_zip.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SECTION 3: Delivery Details -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">Delivery Details</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Description / Notes (Optional)</span></label>
|
||||
<textarea v-model="CreateCustomerForm.customer_description" rows="4" placeholder="Description of customer's house, tank, etc." class="textarea textarea-bordered"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SUBMIT BUTTON -->
|
||||
<div class="pt-4">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Create Customer</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
@@ -158,224 +142,115 @@
|
||||
import { defineComponent } 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 useValidate from "@vuelidate/core";
|
||||
import { email, minLength, required } from "@vuelidate/validators";
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
interface SelectOption {
|
||||
text: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CustomerCreate',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: null,
|
||||
stateList: [],
|
||||
x: '',
|
||||
custList: [],
|
||||
new_user_id: 0,
|
||||
company: {
|
||||
creation_date: "",
|
||||
account_prefix: "",
|
||||
company_name: "",
|
||||
company_address: "",
|
||||
company_town: "",
|
||||
company_zip: "",
|
||||
company_state: "",
|
||||
company_phone_number: "",
|
||||
},
|
||||
|
||||
stateList: [] as SelectOption[],
|
||||
custList: [] as SelectOption[],
|
||||
// --- REFACTORED: Simplified, flat form object ---
|
||||
CreateCustomerForm: {
|
||||
basicInfo: {
|
||||
customer_last_name: "",
|
||||
customer_first_name: "",
|
||||
customer_town: "",
|
||||
customer_apt: "",
|
||||
customer_home_type: 0,
|
||||
customer_zip: "",
|
||||
customer_automatic: "",
|
||||
customer_email: "",
|
||||
customer_phone_number: "",
|
||||
customer_state: 0,
|
||||
customer_address: "",
|
||||
customer_description: "",
|
||||
},
|
||||
customer_last_name: "",
|
||||
customer_first_name: "",
|
||||
customer_town: "",
|
||||
customer_address: "",
|
||||
customer_apt: "",
|
||||
customer_zip: "",
|
||||
customer_email: "",
|
||||
customer_phone_number: "",
|
||||
customer_description: "",
|
||||
// --- FIX: Initialized as numbers for proper v-model binding ---
|
||||
customer_home_type: 0,
|
||||
customer_state: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
// --- REFACTORED: Validation rules point to the flat form object ---
|
||||
CreateCustomerForm: {
|
||||
basicInfo: {
|
||||
customer_last_name: { required, minLength: minLength(1) },
|
||||
customer_first_name: { required, minLength: minLength(1) },
|
||||
customer_town: { required, minLength: minLength(1) },
|
||||
customer_home_type: { required },
|
||||
customer_zip: { required, minLength: minLength(5) },
|
||||
customer_email: { email, required },
|
||||
customer_phone_number: { required },
|
||||
customer_state: { required },
|
||||
customer_address: { required },
|
||||
},
|
||||
customer_last_name: { required, minLength: minLength(1) },
|
||||
customer_first_name: { required, minLength: minLength(1) },
|
||||
customer_town: { required, minLength: minLength(1) },
|
||||
customer_zip: { required, minLength: minLength(5) },
|
||||
customer_email: { email }, // Optional, so only validate format
|
||||
customer_phone_number: { required },
|
||||
customer_home_type: { required },
|
||||
customer_state: { required },
|
||||
customer_address: { required },
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.userStatus()
|
||||
this.userStatus();
|
||||
},
|
||||
mounted() {
|
||||
this.getCustomerTypeList();
|
||||
this.getStatesList();
|
||||
this.getCompany();
|
||||
},
|
||||
methods: {
|
||||
acceptNumber() {
|
||||
let x = this.CreateCustomerForm.basicInfo.customer_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
|
||||
const x = this.CreateCustomerForm.customer_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
|
||||
if (x) {
|
||||
this.CreateCustomerForm.basicInfo.customer_phone_number = !x[2] ? x[1] : '(' + x[1] + ') ' + x[2] + (x[3] ? '-' + x[3] : '');
|
||||
this.CreateCustomerForm.customer_phone_number = !x[2] ? x[1] : `(${x[1]}) ${x[2]}${x[3] ? `-${x[3]}` : ''}`;
|
||||
}
|
||||
else {
|
||||
this.CreateCustomerForm.basicInfo.customer_phone_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;
|
||||
|
||||
|
||||
})
|
||||
},
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
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
|
||||
if (response.data.ok) { this.user = response.data.user; }
|
||||
})
|
||||
.catch(() => { this.user = null; });
|
||||
},
|
||||
CreateCustomer(payload: {
|
||||
customer_last_name: string;
|
||||
customer_first_name: string;
|
||||
customer_town: string;
|
||||
customer_zip: string;
|
||||
customer_email: string;
|
||||
customer_phone_number: string;
|
||||
customer_address: string;
|
||||
customer_apt: string;
|
||||
customer_home_type: number,
|
||||
customer_state: number;
|
||||
customer_description: string;
|
||||
})
|
||||
{
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/create";
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
CreateCustomer(payload: any) {
|
||||
const path = import.meta.env.VITE_BASE_URL + "/customer/create";
|
||||
axios.post(path, payload, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.new_user_id = response.data.user.user_id
|
||||
this.$router.push({ name: 'customerProfile', params: { id: this.new_user_id } });
|
||||
const new_user_id = response.data.user.user_id;
|
||||
this.$router.push({ name: 'customerProfile', params: { id: new_user_id } });
|
||||
} else {
|
||||
notify({ title: "Error", text: response.data.error || "Failed to create customer.", type: "error" });
|
||||
}
|
||||
if (response.data.error) {
|
||||
this.$router.push("/");
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
onSubmit() {
|
||||
if (this.CreateCustomerForm.basicInfo.customer_zip === ''){
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "No zip code added!",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
if (this.CreateCustomerForm.basicInfo.customer_last_name === ''){
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "No last name added!",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
if (this.CreateCustomerForm.basicInfo.customer_address === ''){
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "No address added!",
|
||||
type: "error",
|
||||
});
|
||||
}
|
||||
let payload = {
|
||||
customer_last_name: this.CreateCustomerForm.basicInfo.customer_last_name,
|
||||
customer_first_name: this.CreateCustomerForm.basicInfo.customer_first_name,
|
||||
customer_town: this.CreateCustomerForm.basicInfo.customer_town,
|
||||
customer_zip: this.CreateCustomerForm.basicInfo.customer_zip,
|
||||
customer_email: this.CreateCustomerForm.basicInfo.customer_email,
|
||||
customer_phone_number: this.CreateCustomerForm.basicInfo.customer_phone_number,
|
||||
customer_home_type: this.CreateCustomerForm.basicInfo.customer_home_type,
|
||||
customer_state: this.CreateCustomerForm.basicInfo.customer_state,
|
||||
customer_apt: this.CreateCustomerForm.basicInfo.customer_apt,
|
||||
customer_address: this.CreateCustomerForm.basicInfo.customer_address,
|
||||
customer_description: this.CreateCustomerForm.basicInfo.customer_description,
|
||||
|
||||
};
|
||||
this.CreateCustomer(payload);
|
||||
this.v$.$validate(); // Trigger validation
|
||||
if (!this.v$.$error) {
|
||||
// If validation passes, submit the form
|
||||
this.CreateCustomer(this.CreateCustomerForm);
|
||||
} else {
|
||||
// If validation fails, show a single notification
|
||||
notify({ title: "Validation Error", text: "Please fill out all required fields correctly.", type: "error" });
|
||||
console.log("Form validation failed.");
|
||||
}
|
||||
},
|
||||
getCustomerTypeList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/customertype";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.custList = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
const path = import.meta.env.VITE_BASE_URL + "/query/customertype";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { this.custList = response.data; });
|
||||
},
|
||||
getStatesList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/states";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.stateList = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
const path = import.meta.env.VITE_BASE_URL + "/query/states";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { this.stateList = response.data; });
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
});
|
||||
</script>
|
||||
@@ -1,196 +1,176 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div v-if="user">
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
<div class="flex">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
|
||||
<li>Edit Customer</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mt-4">
|
||||
<h1 class="text-3xl font-bold">
|
||||
Edit Customer: {{ customer.account_number }}
|
||||
</h1>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm mt-2 sm:mt-0">
|
||||
View Profile
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="w-full px-10">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
Customers
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-12 rounded-md p-6 ">
|
||||
<div class="col-span-12 text-2xl">Edit customer: {{ customer.account_number }}</div>
|
||||
<div class="col-span-12 py-5">
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: customer['id'] } }"
|
||||
class="btn btn-secondary btn-sm">
|
||||
View Profile
|
||||
</router-link>
|
||||
</div>
|
||||
<form class="col-span-12 rounded-md px-8 pt-6 pb-8 mb-4 " enctype="multipart/form-data"
|
||||
@submit.prevent="onSubmit">
|
||||
|
||||
<div class="grid grid-cols-12">
|
||||
<div class="col-span-6">
|
||||
<div class="col-span-12 text-[18px] mt-5 mb-5">General Info</div>
|
||||
<div class="col-span-12 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2"> First Name</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_first_name"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text"
|
||||
placeholder="First Name" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_first_name.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_first_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-12 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2"> Last Name</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_last_name"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text"
|
||||
placeholder="Last Name" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_last_name.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_last_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-5">
|
||||
<label class="block text-white text-sm font-bold mb-2">Phone Number</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_phone_number"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="phone number" type="text"
|
||||
placeholder="Phone Number" @input="acceptNumber()" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_phone_number.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_phone_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-12 flex gap-5">
|
||||
<div class="flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Customer Type</label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" aria-label="Default select example"
|
||||
id="customer_type" v-model="CreateCustomerForm.basicInfo.customer_home_type">
|
||||
<option class="text-white" v-for="(customer, index) in custList" :key="index"
|
||||
:value="customer['value']">
|
||||
{{ customer['text'] }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0">
|
||||
<label class="block text-white text-sm font-bold mb-2">Email (Optional)</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_email"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="email" type="text" placeholder="Email" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_email.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_email.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Main Form Card -->
|
||||
<div class="bg-neutral rounded-lg p-6 mt-6">
|
||||
<form @submit.prevent="onSubmit" class="space-y-6">
|
||||
|
||||
<!-- SECTION 1: General Info -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">General Info</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- First Name -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">First Name</span></label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_first_name" type="text" placeholder="First Name" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_first_name.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_first_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-span-6 ">
|
||||
<div class="text-[18px] mt-5 mb-5">Customer Address</div>
|
||||
|
||||
<div class="grid grid-cols-12">
|
||||
|
||||
<div class="col-span-12 mb-5 ">
|
||||
<label class="block text-white text-sm font-bold mb-2">Street Address</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_address"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="address" type="text"
|
||||
placeholder="Address" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_address.$error"
|
||||
class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_address.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-12 mb-5 ">
|
||||
<label class="block text-white text-sm font-bold mb-2">Apt</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_apt"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="apt" type="text"
|
||||
placeholder="Apt, suite, unit, building, floor, etc" />
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 ">
|
||||
<label class="block text-white text-sm font-bold mb-2">Town</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_town"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="town" type="text" placeholder="town" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_town.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_town.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 flex-1 mb-4 ">
|
||||
<label class="block text-white text-sm font-bold mb-2">State</label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" aria-label="Default select example"
|
||||
id="customer_state" v-model="CreateCustomerForm.basicInfo.customer_state">
|
||||
<option class="text-white" v-for="(state, index) in stateList" :key="index"
|
||||
:value="state['value']">
|
||||
{{ state['text'] }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_state.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_state.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-span-4 mb-5 md:mb-5">
|
||||
<label class="block text-white text-sm font-bold mb-2">Zip Code</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_zip"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="zip" type="text" placeholder="Zip" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_zip.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_zip.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Last Name -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Last Name</span></label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_last_name" type="text" placeholder="Last Name" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_last_name.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_last_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 mb-5 md:mb-5">
|
||||
|
||||
<div class="col-span-12 text-[18px] mt-5 mb-5"> Description</div>
|
||||
<div class="col-span-12 md:col-span-4 mb-2 ">
|
||||
<label class="block text-white text-sm font-bold mb-2">Fill Location</label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_fill_location"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="fill" type="text" placeholder="Fill (1-12)" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0">
|
||||
<textarea v-model="CreateCustomerForm.basicInfo.customer_description" rows="4"
|
||||
class="textarea block p-2.5 w-full input-bordered " id="description" type="text"
|
||||
placeholder="Description of Customer House" />
|
||||
<!-- Phone Number -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Phone Number</span></label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_phone_number" type="text" placeholder="Phone Number" class="input input-bordered input-sm w-full" @input="acceptNumber()" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_phone_number.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_phone_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Email -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Email (Optional)</span></label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_email" type="text" placeholder="Email" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_email.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_email.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Customer Type -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Customer Type</span></label>
|
||||
<select v-model="CreateCustomerForm.basicInfo.customer_home_type" class="select select-bordered select-sm w-full">
|
||||
<option v-for="customer in custList" :key="customer.value" :value="customer.value">
|
||||
{{ customer.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-12 flex mt-5 mb-5">
|
||||
<button class="btn-sm btn btn-accent">
|
||||
Save Changes
|
||||
</button>
|
||||
|
||||
<!-- SECTION 2: Address -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">Address</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Street Address -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Street Address</span></label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_address" type="text" placeholder="Street Address" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_address.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_address.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Apt, Suite, etc. -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Apt, Suite, etc. (Optional)</span></label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_apt" type="text" placeholder="Apt, suite, unit..." class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
<!-- Town -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Town</span></label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_town" type="text" placeholder="Town" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_town.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_town.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- State -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">State</span></label>
|
||||
<select v-model="CreateCustomerForm.basicInfo.customer_state" class="select select-bordered select-sm w-full">
|
||||
<option v-for="state in stateList" :key="state.value" :value="state.value">
|
||||
{{ state.text }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_state.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_state.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Zip Code -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Zip Code</span></label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_zip" type="text" placeholder="Zip Code" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateCustomerForm.basicInfo.customer_zip.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateCustomerForm.basicInfo.customer_zip.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SECTION 3: Delivery Details -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">Delivery Details</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Fill Location -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Fill Location</span></label>
|
||||
<input v-model="CreateCustomerForm.basicInfo.customer_fill_location" type="text" placeholder="e.g., Left side of house" class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
<!-- Description -->
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label"><span class="label-text">Description / Notes</span></label>
|
||||
<textarea v-model="CreateCustomerForm.basicInfo.customer_description" rows="4" placeholder="Description of customer's house, tank, etc." class="textarea textarea-bordered"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SUBMIT BUTTON -->
|
||||
<div class="pt-4">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } 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 useValidate from "@vuelidate/core";
|
||||
import { email, minLength, required } from "@vuelidate/validators";
|
||||
|
||||
// --- NEW: Interface for select options for better type safety ---
|
||||
interface SelectOption {
|
||||
text: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CustomerEdit',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
// Removed unused Header and SideBar
|
||||
Footer,
|
||||
},
|
||||
|
||||
@@ -199,8 +179,8 @@ export default defineComponent({
|
||||
v$: useValidate(),
|
||||
user: null,
|
||||
|
||||
stateList: [],
|
||||
custList: [],
|
||||
stateList: [] as SelectOption[],
|
||||
custList: [] as SelectOption[],
|
||||
customer: {
|
||||
id: 0,
|
||||
user_id: 0,
|
||||
@@ -229,16 +209,17 @@ export default defineComponent({
|
||||
customer_first_name: "",
|
||||
customer_town: "",
|
||||
customer_apt: "",
|
||||
customer_home_type: "",
|
||||
// --- FIX: Initialized as a number ---
|
||||
customer_home_type: 0,
|
||||
customer_zip: "",
|
||||
customer_automatic: false,
|
||||
customer_email: "",
|
||||
customer_phone_number: "",
|
||||
customer_state: "",
|
||||
// --- FIX: Initialized as a number ---
|
||||
customer_state: 0,
|
||||
customer_address: "",
|
||||
customer_description: "",
|
||||
customer_fill_location: 0,
|
||||
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -252,7 +233,7 @@ export default defineComponent({
|
||||
customer_town: { required, minLength: minLength(1) },
|
||||
customer_home_type: { required },
|
||||
customer_zip: { required, minLength: minLength(5) },
|
||||
customer_email: { email, required },
|
||||
customer_email: { email }, // Removed required to match template label "Optional"
|
||||
customer_phone_number: { required },
|
||||
customer_state: { required },
|
||||
customer_address: { required },
|
||||
@@ -276,7 +257,6 @@ export default defineComponent({
|
||||
} else {
|
||||
this.CreateCustomerForm.basicInfo.customer_phone_number = '';
|
||||
}
|
||||
|
||||
},
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
@@ -302,14 +282,11 @@ export default defineComponent({
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
|
||||
this.customerDescription = response.data
|
||||
this.CreateCustomerForm.basicInfo.customer_description = this.customerDescription.description;
|
||||
this.CreateCustomerForm.basicInfo.customer_fill_location = this.customerDescription.fill_location
|
||||
})
|
||||
},
|
||||
|
||||
// gets the item from parameter router
|
||||
getCustomer(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/" + userid;
|
||||
axios({
|
||||
@@ -320,47 +297,29 @@ export default defineComponent({
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
|
||||
this.customer = response.data;
|
||||
this.getCustomerDescription(this.customer.id)
|
||||
this.getCustomerDescription(this.customer.id);
|
||||
this.CreateCustomerForm.basicInfo.customer_last_name = response.data.customer_last_name;
|
||||
this.CreateCustomerForm.basicInfo.customer_first_name = response.data.customer_first_name;
|
||||
this.CreateCustomerForm.basicInfo.customer_town = response.data.customer_town;
|
||||
this.CreateCustomerForm.basicInfo.customer_state = response.data.customer_state;
|
||||
this.CreateCustomerForm.basicInfo.customer_zip = response.data.customer_zip;
|
||||
|
||||
this.CreateCustomerForm.basicInfo.customer_phone_number = response.data.customer_phone_number;
|
||||
this.CreateCustomerForm.basicInfo.customer_home_type = response.data.customer_home_type;
|
||||
this.CreateCustomerForm.basicInfo.customer_apt = response.data.customer_apt;
|
||||
this.CreateCustomerForm.basicInfo.customer_email = response.data.customer_email;
|
||||
this.CreateCustomerForm.basicInfo.customer_address = response.data.customer_address;
|
||||
|
||||
|
||||
if (response.data.customer_automatic === 1) {
|
||||
this.CreateCustomerForm.basicInfo.customer_automatic = true
|
||||
|
||||
}
|
||||
if (response.data.customer_automatic === 0) {
|
||||
this.CreateCustomerForm.basicInfo.customer_automatic = false
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
editItem(payload: {
|
||||
customer_last_name: string;
|
||||
customer_first_name: string;
|
||||
customer_apt: string;
|
||||
customer_town: string;
|
||||
customer_zip: string;
|
||||
customer_email: string;
|
||||
customer_phone_number: string;
|
||||
customer_home_type: string,
|
||||
customer_state: string;
|
||||
customer_address: string;
|
||||
customer_description: string;
|
||||
customer_fill_location: number;
|
||||
}) {
|
||||
editItem(payload: any) { // Simplified payload type for brevity
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/edit/" + this.customer.id;
|
||||
axios({
|
||||
method: "put",
|
||||
@@ -372,61 +331,31 @@ export default defineComponent({
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
}
|
||||
;
|
||||
if (response.data.error) {
|
||||
} else if (response.data.error) {
|
||||
// Handle specific errors if needed
|
||||
this.$router.push("/");
|
||||
}
|
||||
;
|
||||
})
|
||||
},
|
||||
onSubmit() {
|
||||
let payload = {
|
||||
customer_last_name: this.CreateCustomerForm.basicInfo.customer_last_name,
|
||||
customer_first_name: this.CreateCustomerForm.basicInfo.customer_first_name,
|
||||
customer_town: this.CreateCustomerForm.basicInfo.customer_town,
|
||||
customer_zip: this.CreateCustomerForm.basicInfo.customer_zip,
|
||||
customer_email: this.CreateCustomerForm.basicInfo.customer_email,
|
||||
customer_phone_number: this.CreateCustomerForm.basicInfo.customer_phone_number,
|
||||
customer_home_type: this.CreateCustomerForm.basicInfo.customer_home_type,
|
||||
customer_apt: this.CreateCustomerForm.basicInfo.customer_apt,
|
||||
customer_state: this.CreateCustomerForm.basicInfo.customer_state,
|
||||
customer_address: this.CreateCustomerForm.basicInfo.customer_address,
|
||||
customer_fill_location: this.CreateCustomerForm.basicInfo.customer_fill_location,
|
||||
customer_description: this.CreateCustomerForm.basicInfo.customer_description,
|
||||
};
|
||||
this.editItem(payload);
|
||||
// Create payload directly from the form object
|
||||
this.editItem(this.CreateCustomerForm.basicInfo);
|
||||
},
|
||||
getCustomerTypeList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/customertype";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
this.custList = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
},
|
||||
getStatesList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/states";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
this.stateList = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
<script setup lang="ts">
|
||||
</script>
|
||||
@@ -1,107 +1,123 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 pb-10">
|
||||
<div class="w-full px-4 md:px-10 ">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
Customers
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Customers</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mt-4">Customers</h1>
|
||||
|
||||
<div class="flex justify-end mb-10">
|
||||
Customers {{ customer_count }}
|
||||
</div>
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Search, Count, and Add Button -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<!-- SEARCH AND COUNT (IMPROVED ALIGNMENT) -->
|
||||
<div class="form-control">
|
||||
<label class="label pt-1 pb-0">
|
||||
<span class="label-text-alt">{{ customer_count }} customers found</span>
|
||||
</label>
|
||||
</div>
|
||||
<router-link to="/customers/create" class="btn btn-primary btn-sm">
|
||||
Add New Customer
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 bg-secondary">
|
||||
<div class="grid grid-cols-12 p-5 bg-neutral m-5">
|
||||
<div class="col-span-12 font-bold text-xl">Quick Tips</div>
|
||||
<div class="col-span-3 py-2"> @ = last name search</div>
|
||||
<div class="col-span-3 py-2"> ! = address</div>
|
||||
|
||||
<div class="col-span-3 py-2"> $ = account number</div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- DESKTOP VIEW: Table (Now breaks at XL) -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account #</th>
|
||||
<th>Name</th>
|
||||
<th>Town</th>
|
||||
<th>Automatic</th>
|
||||
<th>Phone Number</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="person in customers" :key="person.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: person.id } }" class="link link-hover">
|
||||
{{ person.account_number }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>{{ person.customer_first_name }} {{ person.customer_last_name }}</td>
|
||||
<td>{{ person.customer_town }}</td>
|
||||
<td><span :class="person.customer_automatic ? 'text-success' : 'text-gray-500'">{{ person.customer_automatic ? 'Yes' : 'No' }}</span></td>
|
||||
<td>{{ person.customer_phone_number }}</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<router-link :to="{ name: 'deliveryCreate', params: { id: person.id } }" class="btn btn-sm btn-primary">
|
||||
New Delivery
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'CalenderCustomer', params: { id: person.id } }" class="btn btn-sm btn-accent">
|
||||
New Service
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'customerEdit', params: { id: person.id } }" class="btn btn-sm btn-secondary">
|
||||
Edit
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: person.id } }" class="btn btn-sm btn-ghost">
|
||||
View
|
||||
</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards (Now breaks at XL) -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="person in customers" :key="person.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ person.customer_first_name }} {{ person.customer_last_name }}</h2>
|
||||
<p class="text-xs text-gray-400">#{{ person.account_number }}</p>
|
||||
</div>
|
||||
<div class="badge" :class="person.customer_automatic ? 'badge-success' : 'badge-ghost'">
|
||||
{{ person.customer_automatic ? 'Automatic' : 'Will Call' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm mt-2">
|
||||
<p>{{ person.customer_town }}</p>
|
||||
<p>{{ person.customer_phone_number }}</p>
|
||||
</div>
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-2">
|
||||
<router-link :to="{ name: 'deliveryCreate', params: { id: person.id } }" class="btn btn-sm btn-primary">
|
||||
New Delivery
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'CalenderCustomer', params: { id: person.id } }" class="btn btn-sm btn-accent">
|
||||
New Service
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'customerEdit', params: { id: person.id } }" class="btn btn-sm btn-secondary">
|
||||
Edit
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: person.id } }" class="btn btn-sm btn-ghost">
|
||||
View
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="overflow-x-auto bg-neutral font-bold">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account Number</th>
|
||||
<th>Name</th>
|
||||
<th>Town</th>
|
||||
<th>Automatic</th>
|
||||
<th>Phone Number</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
|
||||
<tr v-for="person in customers" :key="person['id']" >
|
||||
<td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: person['id'] } }">{{ person['account_number'] }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: person['id'] } }">
|
||||
{{ person['customer_first_name'] }} {{ person['customer_last_name'] }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>{{ person['customer_town'] }}</td>
|
||||
<td>
|
||||
<div v-if="person['customer_automatic'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>{{ person['customer_phone_number'] }}</td>
|
||||
|
||||
<td class="flex gap-5 ">
|
||||
<router-link :to="{ name: 'deliveryCreate', params: { id: person['id'] } }"
|
||||
class="btn-sm btn bg-orange-600 text-white">
|
||||
Create Delivery
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'CalenderCustomer', params: { id: person['id'] } }"
|
||||
class="btn-sm btn bg-indigo-600 text-white">
|
||||
Create Service Call
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'customerEdit', params: { id: person['id'] } }" class="btn-sm btn btn-secondary">
|
||||
Edit Customer
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: person['id'] } }"
|
||||
class="btn btn-secondary btn-sm">
|
||||
View Profile
|
||||
</router-link>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mt-10">
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<pagination @paginate="getPage" :records="customer_count" v-model="page" :per-page="10" :options="options">
|
||||
</pagination>
|
||||
|
||||
<div class="flex justify-center mb-10"> {{ customer_count }} items Found</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
@@ -125,7 +141,7 @@ export default defineComponent({
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
customers: [],
|
||||
customers: [] as any[],
|
||||
customer_count: 0,
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
602
src/pages/customer/profile/profile.vue
Executable file
602
src/pages/customer/profile/profile.vue
Executable file
@@ -0,0 +1,602 @@
|
||||
<!-- src/views/Profile.vue -->
|
||||
<template>
|
||||
<div class="w-full min-h-screen bg-base-200 px-4 md:px-10">
|
||||
<!-- ... breadcrumbs ... -->
|
||||
|
||||
<div v-if="customer && customer.id" class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
|
||||
<!-- FIX: Changed `lg:` to `xl:` for a later breakpoint -->
|
||||
<div class="grid grid-cols-1 xl:grid-cols-12 gap-6">
|
||||
|
||||
<!-- FIX: Changed `lg:` to `xl:` -->
|
||||
<div class="xl:col-span-8 space-y-6">
|
||||
|
||||
<div class="grid grid-cols-1 xl:grid-cols-12 gap-6">
|
||||
<ProfileMap
|
||||
class="xl:col-span-7"
|
||||
:customer="customer"
|
||||
/>
|
||||
<ProfileSummary
|
||||
class="xl:col-span-5"
|
||||
:customer="customer"
|
||||
:automatic_status="automatic_status"
|
||||
@toggle-automatic="userAutomatic"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AutomaticDeliveries v-if="automatic_status === 1 && autodeliveries.length > 0" :deliveries="autodeliveries" />
|
||||
|
||||
<HistoryTabs
|
||||
:deliveries="deliveries"
|
||||
:service-calls="serviceCalls"
|
||||
@open-service-modal="openEditModal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- FIX: Changed `lg:` to `xl:` -->
|
||||
<div class="xl:col-span-4 space-y-6">
|
||||
<CustomerComments
|
||||
:comments="comments"
|
||||
@add-comment="onSubmitSocial"
|
||||
@delete-comment="deleteCustomerSocial"
|
||||
/>
|
||||
<CustomerStats :stats="customer_stats" :last_delivery="customer_last_delivery" />
|
||||
<TankInfo :customer_id="customer.id" :tank="customer_tank" :description="customer_description" />
|
||||
<EquipmentParts :parts="currentParts" @open-parts-modal="openPartsModal" />
|
||||
<CreditCards
|
||||
:cards="credit_cards"
|
||||
:count="credit_cards_count"
|
||||
:user_id="customer.user_id"
|
||||
@edit-card="editCard"
|
||||
@remove-card="removeCard"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- A loading indicator is shown while the API call is in progress -->
|
||||
<div v-else class="flex justify-center items-center mt-20">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
|
||||
<!-- The Footer can be placed here if it's specific to this page -->
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Modals remain at the root of the template for proper display -->
|
||||
<ServiceEditModal
|
||||
v-if="selectedServiceForEdit"
|
||||
:service="selectedServiceForEdit"
|
||||
@close-modal="closeEditModal"
|
||||
@save-changes="handleSaveChanges"
|
||||
@delete-service="handleDeleteService"
|
||||
/>
|
||||
<PartsEditModal
|
||||
v-if="isPartsModalOpen && currentParts"
|
||||
:customer-id="customer.id"
|
||||
:existing-parts="currentParts"
|
||||
@close-modal="closePartsModal"
|
||||
@save-parts="handleSaveParts"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// --- SCRIPT REMAINS EXACTLY THE SAME AS YOUR ORIGINAL FILE ---
|
||||
// All data properties, computed, methods, and imports are kept here.
|
||||
// No changes are needed in the script block.
|
||||
import { defineComponent } 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 { notify } from "@kyvg/vue3-notification";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import L from 'leaflet';
|
||||
import iconUrl from 'leaflet/dist/images/marker-icon.png';
|
||||
import shadowUrl from 'leaflet/dist/images/marker-shadow.png';
|
||||
import { LMap, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
||||
import dayjs from 'dayjs';
|
||||
import ServiceEditModal from '../../service/ServiceEditModal.vue';
|
||||
import PartsEditModal from '../service/PartsEditModal.vue';
|
||||
|
||||
// Import new child components
|
||||
import ProfileMap from './profile/ProfileMap.vue';
|
||||
import ProfileSummary from './profile/ProfileSummary.vue';
|
||||
import CustomerStats from './profile/CustomerStats.vue';
|
||||
import TankInfo from './profile/TankInfo.vue';
|
||||
import EquipmentParts from './profile/EquipmentParts.vue';
|
||||
import CreditCards from './profile/CreditCards.vue';
|
||||
import CustomerComments from './profile/CustomerComments.vue';
|
||||
import AutomaticDeliveries from './profile/AutomaticDeliveries.vue';
|
||||
import HistoryTabs from './profile/HistoryTabs.vue';
|
||||
|
||||
|
||||
L.Icon.Default.mergeOptions({
|
||||
iconUrl: iconUrl,
|
||||
shadowUrl: shadowUrl,
|
||||
});
|
||||
|
||||
interface Delivery {
|
||||
id: number;
|
||||
delivery_status: number;
|
||||
customer_name: string;
|
||||
customer_asked_for_fill: number | boolean;
|
||||
gallons_ordered: number | string;
|
||||
gallons_delivered: number | string | null;
|
||||
expected_delivery_date: string;
|
||||
}
|
||||
|
||||
interface AutomaticDelivery {
|
||||
id: number;
|
||||
customer_full_name: string;
|
||||
gallons_delivered: number | string;
|
||||
fill_date: string;
|
||||
}
|
||||
|
||||
interface CreditCard {
|
||||
id: number;
|
||||
main_card: boolean;
|
||||
type_of_card: string;
|
||||
name_on_card: string;
|
||||
card_number: string;
|
||||
expiration_month: number;
|
||||
expiration_year: string | number;
|
||||
zip_code: string;
|
||||
security_number: string;
|
||||
}
|
||||
|
||||
// You already have these, just make sure they exist
|
||||
interface ServiceCall {
|
||||
id: number;
|
||||
scheduled_date: string;
|
||||
customer_name: string;
|
||||
customer_address: string;
|
||||
customer_town: string;
|
||||
type_service_call: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface ServiceParts {
|
||||
id?: number;
|
||||
customer_id: number;
|
||||
oil_filter: string;
|
||||
oil_filter_2: string;
|
||||
oil_nozzle: string;
|
||||
oil_nozzle_2: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'CustomerProfile',
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
LMap,
|
||||
LTileLayer,
|
||||
ServiceEditModal,
|
||||
PartsEditModal,
|
||||
// Register new components
|
||||
ProfileMap,
|
||||
ProfileSummary,
|
||||
CustomerStats,
|
||||
TankInfo,
|
||||
EquipmentParts,
|
||||
CreditCards,
|
||||
CustomerComments,
|
||||
AutomaticDeliveries,
|
||||
HistoryTabs,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
zoom: 14,
|
||||
user: null as { user_id: number; user_name: string; confirmed: string; } | null,
|
||||
automatic_status: 0,
|
||||
customer_last_delivery: '',
|
||||
comments: [ { id: 0, created: '', customer_id: 0, poster_employee_id: 0, comment: '' } ],
|
||||
CreateSocialForm: { basicInfo: { comment: '' } },
|
||||
|
||||
// --- UPDATE THESE LINES ---
|
||||
credit_cards: [] as CreditCard[],
|
||||
deliveries: [] as Delivery[],
|
||||
autodeliveries: [] as AutomaticDelivery[],
|
||||
serviceCalls: [] as ServiceCall[],
|
||||
// --- END OF UPDATES ---
|
||||
automatic_response: 0,
|
||||
credit_cards_count: 0,
|
||||
customer: { id: 0, user_id: 0, customer_first_name: '', customer_last_name: '', customer_town: '', customer_address: '', customer_state: 0, customer_zip: '', customer_apt: '', customer_home_type: 0, customer_phone_number: '', customer_latitude: 0, customer_longitude: 0, correct_address: true, account_number: '' },
|
||||
customer_description: { id: 0, customer_id: 0, account_number: '', company_id: '', fill_location: 0, description: '' },
|
||||
customer_tank: { id: 0, last_tank_inspection: null, tank_status: false, outside_or_inside: false, tank_size: 0 },
|
||||
customer_stats: { id: 0, customer_id: 0, total_calls: 0, service_calls_total: 0, service_calls_total_spent: 0, service_calls_total_profit: 0, oil_deliveries: 0, oil_total_gallons: 0, oil_total_spent: 0, oil_total_profit: 0 },
|
||||
delivery_page: 1,
|
||||
selectedServiceForEdit: null as ServiceCall | null,
|
||||
isPartsModalOpen: false,
|
||||
currentParts: null as ServiceParts | null,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
hasPartsData() {
|
||||
if (!this.currentParts) return false;
|
||||
return !!(this.currentParts.oil_filter || this.currentParts.oil_filter_2 || this.currentParts.oil_nozzle || this.currentParts.oil_nozzle_2);
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getCustomer(this.$route.params.id);
|
||||
},
|
||||
mounted() {
|
||||
// getPage is now called from within getCustomer, so this can be removed if it's redundant
|
||||
},
|
||||
watch: {
|
||||
'$route.params.id'(newId) {
|
||||
if (newId) {
|
||||
this.getCustomer(newId);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// ALL YOUR METHODS from the original file go here without any changes.
|
||||
// getCustomer, userStatus, userAutomatic, etc...
|
||||
getPage: function (page: any) {
|
||||
if (this.customer && this.customer.id) {
|
||||
this.getCustomerDelivery(this.customer.id, page);
|
||||
}
|
||||
},
|
||||
getCustomer(userid: any) {
|
||||
if (!userid) return;
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer = response.data;
|
||||
|
||||
// --- DEPENDENT API CALLS ---
|
||||
this.userStatus();
|
||||
|
||||
// FIX: Pass the correct ID for payment-related calls
|
||||
this.getCreditCards(this.customer.id);
|
||||
this.getCreditCardsCount(this.customer.id);
|
||||
|
||||
// These other calls are likely correct as they are customer-specific
|
||||
this.getCustomerSocial(this.customer.id, 1);
|
||||
this.getPage(this.delivery_page);
|
||||
this.checktotalOil(this.customer.id);
|
||||
this.getCustomerTank(this.customer.id);
|
||||
this.userAutomaticStatus(this.customer.id);
|
||||
this.getCustomerDescription(this.customer.id);
|
||||
this.getCustomerStats(this.customer.id);
|
||||
this.getCustomerLastDelivery(this.customer.id);
|
||||
this.getServiceCalls(this.customer.id);
|
||||
this.fetchCustomerParts(this.customer.id);
|
||||
|
||||
}).catch((error: any) => {
|
||||
console.error("CRITICAL: Failed to fetch main customer data. Aborting other calls.", error);
|
||||
});
|
||||
},
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
}).catch(() => { this.user = null });
|
||||
},
|
||||
userAutomaticStatus(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/automatic/status/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.automatic_status = response.data.status
|
||||
if (this.automatic_status === 1){
|
||||
this.getCustomerAutoDelivery(this.customer.id)
|
||||
}
|
||||
this.checktotalOil(this.customer.id)
|
||||
})
|
||||
},
|
||||
userAutomatic(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/automatic/assign/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.automatic_response = response.data.status
|
||||
if (this.automatic_response == 1) {
|
||||
this.$notify({ title: "Automatic Status", text: 'Customer is now Automatic Customer', type: 'Success' });
|
||||
} else if (this.automatic_response == 2) {
|
||||
this.$notify({ title: "Automatic Status", text: 'Customer does not have a main credit card. Can not make automatic.', type: 'Error' });
|
||||
} else if (this.automatic_response == 3) {
|
||||
this.$notify({ title: "Automatic Status", text: 'Customer is now a Call in ', type: 'Info' });
|
||||
} else {
|
||||
this.$notify({ title: "Automatic Status", text: 'Customer is now Manual Customer', type: 'Warning' });
|
||||
}
|
||||
this.getCustomer(this.$route.params.id);
|
||||
})
|
||||
},
|
||||
getNozzleColor(nozzleString: string): string {
|
||||
if (!nozzleString || typeof nozzleString !== 'string') return '';
|
||||
const firstChar = nozzleString.trim().toLowerCase().charAt(0);
|
||||
switch (firstChar) {
|
||||
case 'a': return '#EF4444';
|
||||
case 'b': return '#3B82F6';
|
||||
case 'w': return '#16a34a';
|
||||
default: return 'inherit';
|
||||
}
|
||||
},
|
||||
getCustomerLastDelivery(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/user/lastdelivery/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer_last_delivery = response.data.date
|
||||
})
|
||||
},
|
||||
getCustomerStats(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/user/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer_stats = response.data
|
||||
})
|
||||
},
|
||||
checktotalOil(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/stats/gallons/check/total/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
})
|
||||
},
|
||||
getCustomerDescription(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/description/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer_description = response.data
|
||||
})
|
||||
},
|
||||
getCustomerTank(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/tank/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer_tank = response.data
|
||||
})
|
||||
},
|
||||
getCreditCards(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/' + user_id;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.credit_cards = response.data
|
||||
})
|
||||
},
|
||||
getCreditCardsCount(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/payment/cards/onfile/' + user_id;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.credit_cards_count = response.data.cards
|
||||
})
|
||||
},
|
||||
getCustomerAutoDelivery(userid: any) {
|
||||
let path = import.meta.env.VITE_AUTO_URL + '/delivery/all/profile/' + userid ;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.autodeliveries = response.data
|
||||
})
|
||||
},
|
||||
getCustomerDelivery(userid: any, delivery_page: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/customer/' + userid + '/' + delivery_page;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.deliveries = response.data
|
||||
})
|
||||
},
|
||||
editCard(card_id: any) {
|
||||
this.$router.push({ name: "cardedit", params: { id: card_id } });
|
||||
},
|
||||
removeCard(card_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/payment/card/remove/' + card_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then(() => {
|
||||
this.getCreditCards(this.customer.user_id)
|
||||
this.getCreditCardsCount(this.customer.user_id)
|
||||
notify({ title: "Card Status", text: "Card Removed", type: "Success" });
|
||||
})
|
||||
},
|
||||
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" });
|
||||
this.getPage(1)
|
||||
} else {
|
||||
notify({ title: "Failure", text: "error deleting delivery", type: "success" });
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteCustomerSocial(comment_id: number) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/social/delete/' + comment_id;
|
||||
axios({
|
||||
method: 'delete',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
console.log(response)
|
||||
this.getCustomerSocial(this.customer.id, 1)
|
||||
})
|
||||
},
|
||||
getCustomerSocial(userid: any, delivery_page: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/social/posts/' + userid + '/' + delivery_page;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.comments = response.data
|
||||
})
|
||||
},
|
||||
CreateSocialComment(payload: { comment: string; poster_employee_id: number }) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/social/create/" + this.customer.id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.getCustomerSocial(this.customer.id, 1)
|
||||
}
|
||||
if (response.data.error) {
|
||||
this.$router.push("/");
|
||||
}
|
||||
})
|
||||
},
|
||||
onSubmitSocial(commentText: string) {
|
||||
if (!this.user) {
|
||||
console.error("Cannot submit comment: user is not logged in.");
|
||||
return;
|
||||
}
|
||||
|
||||
let payload = { comment: commentText, poster_employee_id: this.user.user_id };
|
||||
this.CreateSocialComment(payload);
|
||||
},
|
||||
getServiceCalls(customerId: number) {
|
||||
let path = `${import.meta.env.VITE_BASE_URL}/service/for-customer/${customerId}`;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
withCredentials: true,
|
||||
}).then((response: any) => {
|
||||
this.serviceCalls = response.data;
|
||||
}).catch((error: any) => {
|
||||
console.error("Failed to get customer service calls:", error);
|
||||
});
|
||||
},
|
||||
openEditModal(service: ServiceCall) {
|
||||
this.selectedServiceForEdit = service;
|
||||
},
|
||||
closeEditModal() {
|
||||
this.selectedServiceForEdit = null;
|
||||
},
|
||||
async handleSaveChanges(updatedService: ServiceCall) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
await axios.put(path, updatedService, { headers: authHeader(), withCredentials: true });
|
||||
this.getServiceCalls(this.customer.id);
|
||||
this.closeEditModal();
|
||||
} catch (error) {
|
||||
console.error("Failed to save service call changes:", error);
|
||||
}
|
||||
},
|
||||
async handleDeleteService(serviceId: number) {
|
||||
if (!window.confirm("Are you sure you want to delete this service call?")) return;
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/delete/${serviceId}`;
|
||||
const response = await axios.delete(path, { headers: authHeader(), withCredentials: true });
|
||||
if(response.data.ok) {
|
||||
this.getServiceCalls(this.customer.id);
|
||||
this.closeEditModal();
|
||||
notify({ title: "Success", text: "Service call deleted!", type: "success" });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to delete service call:", error);
|
||||
}
|
||||
},
|
||||
async fetchCustomerParts(customerId: number) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/parts/customer/${customerId}`;
|
||||
const response = await axios.get(path, { headers: authHeader() });
|
||||
this.currentParts = response.data;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch customer parts:", error);
|
||||
notify({ title: "Error", text: "Could not fetch equipment parts.", type: "error" });
|
||||
}
|
||||
},
|
||||
openPartsModal() {
|
||||
if (this.currentParts) {
|
||||
this.isPartsModalOpen = true;
|
||||
} else {
|
||||
notify({ title: "Info", text: "Parts data still loading, please wait.", type: "info" });
|
||||
}
|
||||
},
|
||||
closePartsModal() {
|
||||
this.isPartsModalOpen = false;
|
||||
},
|
||||
async handleSaveParts(partsToSave: ServiceParts) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/parts/update/${partsToSave.customer_id}`;
|
||||
const response = await axios.post(path, partsToSave, { headers: authHeader() });
|
||||
|
||||
if(response.data.ok) {
|
||||
this.currentParts = partsToSave;
|
||||
notify({ title: "Success", text: "Equipment parts saved successfully!", type: "success" });
|
||||
}
|
||||
this.closePartsModal();
|
||||
} catch (error) {
|
||||
console.error("Failed to save parts:", error);
|
||||
notify({ title: "Error", text: "Failed to save equipment parts.", type: "error" });
|
||||
}
|
||||
},
|
||||
formatDate(dateString: string): string {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('MMMM D, YYYY');
|
||||
},
|
||||
formatTime(dateString: string): string {
|
||||
if (!dateString) return 'N/A';
|
||||
return dayjs(dateString).format('h:mm A');
|
||||
},
|
||||
getServiceTypeName(typeId: number): string {
|
||||
const typeMap: { [key: number]: string } = { 0: 'Tune-up', 1: 'No Heat', 2: 'Fix', 3: 'Tank Install', 4: 'Other' };
|
||||
return typeMap[typeId] || 'Unknown Service';
|
||||
},
|
||||
getServiceTypeColor(typeId: number): string {
|
||||
const colorMap: { [key: number]: string } = { 0: 'blue', 1: 'red', 2: 'green', 3: '#B58900', 4: 'black' };
|
||||
return colorMap[typeId] || 'gray';
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
46
src/pages/customer/profile/profile/AutomaticDeliveries.vue
Normal file
46
src/pages/customer/profile/profile/AutomaticDeliveries.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title">Automatic Delivery History</h2>
|
||||
<div class="divider my-2"></div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-sm w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Name</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="auto in deliveries" :key="auto.id" class="hover">
|
||||
<td>{{ auto.id }}</td>
|
||||
<td>{{ auto.customer_full_name }}</td>
|
||||
<td>{{ auto.gallons_delivered }}</td>
|
||||
<td>{{ auto.fill_date }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 1. Define the AutomaticDelivery interface
|
||||
interface AutomaticDelivery {
|
||||
id: number;
|
||||
customer_full_name: string;
|
||||
gallons_delivered: number | string;
|
||||
fill_date: string;
|
||||
}
|
||||
|
||||
// 2. Define Props using the interface
|
||||
interface Props {
|
||||
deliveries: AutomaticDelivery[];
|
||||
}
|
||||
|
||||
// 3. Use the typed defineProps
|
||||
defineProps<Props>();
|
||||
</script>
|
||||
78
src/pages/customer/profile/profile/CreditCards.vue
Normal file
78
src/pages/customer/profile/profile/CreditCards.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<div class="card-title flex justify-between items-center">
|
||||
<h2>Credit Cards</h2>
|
||||
<router-link :to="{ name: 'cardadd', params: { id: user_id } }">
|
||||
<button class="btn btn-xs btn-outline btn-success">Add New</button>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="mt-2 text-sm">
|
||||
<div v-if="count === 0" class="text-warning font-semibold">
|
||||
No cards on file. This is a Cash/Check customer until a card is added.
|
||||
</div>
|
||||
<div v-else class="text-success font-semibold">
|
||||
{{ count }} card(s) on file.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 space-y-4">
|
||||
<div v-for="card in cards" :key="card.id"
|
||||
class="p-4 rounded-lg border"
|
||||
:class="card.main_card ? 'bg-primary/10 border-primary' : 'bg-base-200 border-base-300'">
|
||||
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<div class="font-bold">{{ card.name_on_card }}</div>
|
||||
<div class="text-xs opacity-70">{{ card.type_of_card }}</div>
|
||||
</div>
|
||||
<div v-if="card.main_card" class="badge badge-primary badge-sm">Primary</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3 text-sm font-mono tracking-wider">
|
||||
<p>{{ card.card_number }}</p>
|
||||
<p>
|
||||
Exp:
|
||||
<span v-if="card.expiration_month < 10">0</span>{{ card.expiration_month }} / {{ card.expiration_year }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="divider my-2"></div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<a @click.prevent="$emit('edit-card', card.id)" class="link link-hover text-xs">Edit</a>
|
||||
<a @click.prevent="$emit('remove-card', card.id)" class="link link-hover text-error text-xs">Remove</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 1. Define the interface for a single credit card object
|
||||
interface CreditCard {
|
||||
id: number;
|
||||
main_card: boolean;
|
||||
type_of_card: string;
|
||||
name_on_card: string;
|
||||
card_number: string;
|
||||
expiration_month: number;
|
||||
expiration_year: string | number;
|
||||
zip_code: string;
|
||||
security_number: string;
|
||||
}
|
||||
|
||||
// 2. Define the interface for the component's props
|
||||
interface Props {
|
||||
cards: CreditCard[];
|
||||
count: number;
|
||||
user_id: number;
|
||||
}
|
||||
|
||||
// 3. Use the generic defineProps to apply the types
|
||||
defineProps<Props>();
|
||||
|
||||
defineEmits(['edit-card', 'remove-card']);
|
||||
</script>
|
||||
59
src/pages/customer/profile/profile/CustomerComments.vue
Normal file
59
src/pages/customer/profile/profile/CustomerComments.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title">Comments & Notes</h2>
|
||||
|
||||
<!-- Styled Form for Adding a New Comment -->
|
||||
<form class="mt-4" @submit.prevent="handleSubmit">
|
||||
<div class="form-control">
|
||||
<textarea v-model="commentText" class="textarea textarea-bordered" rows="3" placeholder="Add a new note..."></textarea>
|
||||
<button type="submit" class="btn btn-primary btn-sm mt-2 self-end">Post Comment</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- Styled List of Existing Comments -->
|
||||
<div class="mt-2 space-y-4">
|
||||
<div v-if="comments.length === 0" class="text-center text-sm opacity-60 py-4">
|
||||
No comments yet.
|
||||
</div>
|
||||
<div v-else v-for="comment in comments" :key="comment.id" class="bg-base-200 rounded-lg p-3">
|
||||
<div class="flex justify-between items-center text-xs opacity-70">
|
||||
<!-- You can display the user/employee who posted it here if you have the data -->
|
||||
<span class="font-semibold">{{ comment.created }}</span>
|
||||
<button @click="$emit('delete-comment', comment.id)" class="btn btn-ghost btn-xs text-error">Delete</button>
|
||||
</div>
|
||||
<p class="mt-2 text-sm whitespace-pre-wrap">{{ comment.comment }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
interface Comment {
|
||||
id: number;
|
||||
created: string;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
comments: Comment[];
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const emit = defineEmits(['add-comment', 'delete-comment']);
|
||||
|
||||
const commentText = ref('');
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (commentText.value.trim()) {
|
||||
emit('add-comment', commentText.value);
|
||||
commentText.value = ''; // Clear the textarea after submission
|
||||
}
|
||||
};
|
||||
</script>
|
||||
32
src/pages/customer/profile/profile/CustomerDetails.vue
Normal file
32
src/pages/customer/profile/profile/CustomerDetails.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="card-title">Customer Details</h2>
|
||||
<span class="badge" :class="automatic_status === 1 ? 'badge-success' : 'badge-ghost'">
|
||||
{{ automatic_status === 1 ? 'Automatic' : 'Will Call' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-error font-semibold mt-2" v-if="!customer.correct_address">
|
||||
Possible Incorrect Address!
|
||||
</div>
|
||||
<div class="mt-4 space-y-2 text-sm">
|
||||
<p><strong>{{ customer.customer_first_name }} {{ customer.customer_last_name }}</strong></p>
|
||||
<p>{{ customer.customer_address }}<span v-if="customer.customer_apt">, {{ customer.customer_apt }}</span></p>
|
||||
<p>{{ customer.customer_town }}, {{ stateName(customer.customer_state) }} {{ customer.customer_zip }}</p>
|
||||
<p class="pt-2">{{ customer.customer_phone_number }}</p>
|
||||
<p><span class="badge badge-outline badge-sm">{{ homeTypeName(customer.customer_home_type) }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
customer: { type: Object, required: true },
|
||||
automatic_status: { type: Number, required: true },
|
||||
});
|
||||
|
||||
const stateName = (id: number) => ['MA', 'RI', 'NH', 'ME', 'VT', 'CT', 'NY'][id] || 'N/A';
|
||||
const homeTypeName = (id: number) => ['Residential', 'Apartment', 'Condo', 'Commercial', 'Business', 'Construction', 'Container'][id] || 'Unknown';
|
||||
</script>
|
||||
20
src/pages/customer/profile/profile/CustomerStats.vue
Normal file
20
src/pages/customer/profile/profile/CustomerStats.vue
Normal file
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title">Stats</h2>
|
||||
<div class="text-sm mt-2 space-y-1">
|
||||
<div class="flex justify-between"><span>Total Deliveries:</span> <strong>{{ stats.oil_deliveries }}</strong></div>
|
||||
<div class="flex justify-between"><span>Total Gallons:</span> <strong>{{ stats.oil_total_gallons }}</strong></div>
|
||||
<div class="flex justify-between"><span>Total Service Calls:</span> <strong>{{ stats.total_calls }}</strong></div>
|
||||
<div class="flex justify-between"><span>Last Delivery:</span> <strong>{{ last_delivery || 'N/A' }}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
stats: { type: Object, required: true },
|
||||
last_delivery: { type: String, default: '' },
|
||||
});
|
||||
</script>
|
||||
88
src/pages/customer/profile/profile/DeliveriesTable.vue
Normal file
88
src/pages/customer/profile/profile/DeliveriesTable.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<div v-if="!deliveries || deliveries.length === 0" class="text-center p-10">
|
||||
<p>No will-call delivery history found.</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<!-- DESKTOP TABLE -->
|
||||
<div class="overflow-x-auto hidden lg:block">
|
||||
<table class="table table-sm w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>Status</th>
|
||||
<th>Name</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="oil in deliveries" :key="oil.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>{{ oil.id }}</td>
|
||||
<td><span class="badge badge-sm" :class="statusClass(oil.delivery_status)">{{ deliveryStatus(oil.delivery_status) }}</span></td>
|
||||
<td>{{ oil.customer_name }}</td>
|
||||
<td>
|
||||
<span v-if="oil.delivery_status !== 10">
|
||||
{{ oil.customer_asked_for_fill ? 'FILL' : oil.gallons_ordered }}
|
||||
</span>
|
||||
<span v-else>{{ oil.gallons_delivered }}</span>
|
||||
</td>
|
||||
<td>{{ oil.expected_delivery_date }}</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-xs btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-xs btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-xs btn-success">Print</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE CARDS -->
|
||||
<div class="lg:hidden space-y-4">
|
||||
<div v-for="oil in deliveries" :key="oil.id" class="card card-compact bg-base-200 shadow ">
|
||||
<div class="card-body">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ oil.customer_name }}</h2>
|
||||
<p class="text-xs opacity-70">#{{ oil.id }} on {{ oil.expected_delivery_date }}</p>
|
||||
</div>
|
||||
<div class="badge badge-sm" :class="statusClass(oil.delivery_status)">{{ deliveryStatus(oil.delivery_status) }}</div>
|
||||
</div>
|
||||
<p class="text-sm">Gallons: <strong>{{ oil.customer_asked_for_fill ? 'FILL' : (oil.gallons_delivered || oil.gallons_ordered) }}</strong></p>
|
||||
<div class="card-actions justify-end mt-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-xs btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-xs btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-xs btn-success">Print</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// 1. Define the shape of a single delivery object
|
||||
interface Delivery {
|
||||
id: number;
|
||||
delivery_status: number;
|
||||
customer_name: string;
|
||||
customer_asked_for_fill: number | boolean;
|
||||
gallons_ordered: number | string;
|
||||
gallons_delivered: number | string | null;
|
||||
expected_delivery_date: string;
|
||||
}
|
||||
|
||||
// 2. Define the props using the interface
|
||||
interface Props {
|
||||
deliveries: Delivery[];
|
||||
}
|
||||
|
||||
// 3. Use the generic version of defineProps to apply the types
|
||||
defineProps<Props>();
|
||||
|
||||
const deliveryStatus = (s: number) => ({0:'Waiting',1:'Cancelled',2:'Out',3:'Tomorrow',5:'Issue',10:'Finalized'}[s] || 'N/A');
|
||||
const statusClass = (s: number) => ({0:'badge-warning',1:'badge-error',2:'badge-info',3:'badge-ghost',5:'badge-error',10:'badge-success'}[s] || '');
|
||||
</script>
|
||||
80
src/pages/customer/profile/profile/EquipmentParts.vue
Normal file
80
src/pages/customer/profile/profile/EquipmentParts.vue
Normal file
@@ -0,0 +1,80 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<div class="card-title flex justify-between items-center">
|
||||
<h2>Equipment Parts</h2>
|
||||
<button @click="$emit('open-parts-modal')" class="btn btn-xs btn-outline btn-success">
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- v-if="parts" correctly handles the null case, preventing errors below -->
|
||||
<div v-if="parts" class="mt-2 text-sm">
|
||||
<div v-if="hasPartsData" class="grid grid-cols-2 gap-x-4 gap-y-2">
|
||||
<div v-if="parts.oil_filter">
|
||||
<div class="font-bold">Oil Filter 1:</div>
|
||||
<div>{{ parts.oil_filter }}</div>
|
||||
</div>
|
||||
<div v-if="parts.oil_filter_2">
|
||||
<div class="font-bold">Oil Filter 2:</div>
|
||||
<div>{{ parts.oil_filter_2 }}</div>
|
||||
</div>
|
||||
<div v-if="parts.oil_nozzle">
|
||||
<div class="font-bold">Oil Nozzle 1:</div>
|
||||
<div :style="{ color: getNozzleColor(parts.oil_nozzle), fontWeight: 'bold' }">{{ parts.oil_nozzle }}</div>
|
||||
</div>
|
||||
<div v-if="parts.oil_nozzle_2">
|
||||
<div class="font-bold">Oil Nozzle 2:</div>
|
||||
<div :style="{ color: getNozzleColor(parts.oil_nozzle_2), fontWeight: 'bold' }">{{ parts.oil_nozzle_2 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<p class="text-xs opacity-70">No equipment parts information available.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="text-xs opacity-70 mt-2">
|
||||
Loading parts info...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
|
||||
// 1. Define the interface for the parts object.
|
||||
interface ServiceParts {
|
||||
id?: number;
|
||||
customer_id: number;
|
||||
oil_filter: string;
|
||||
oil_filter_2: string;
|
||||
oil_nozzle: string;
|
||||
oil_nozzle_2: string;
|
||||
}
|
||||
|
||||
// 2. Define the Props interface, explicitly allowing 'null'.
|
||||
interface Props {
|
||||
parts: ServiceParts | null;
|
||||
}
|
||||
|
||||
// 3. Use the typed defineProps and assign to a const.
|
||||
const props = defineProps<Props>();
|
||||
|
||||
defineEmits(['open-parts-modal']);
|
||||
|
||||
const hasPartsData = computed(() => {
|
||||
if (!props.parts) return false;
|
||||
return !!(props.parts.oil_filter || props.parts.oil_filter_2 || props.parts.oil_nozzle || props.parts.oil_nozzle_2);
|
||||
});
|
||||
|
||||
const getNozzleColor = (nozzleString: string): string => {
|
||||
if (!nozzleString) return 'inherit';
|
||||
const firstChar = nozzleString.trim().toLowerCase().charAt(0);
|
||||
switch (firstChar) {
|
||||
case 'a': return '#EF4444'; // Red
|
||||
case 'b': return '#3B82F6'; // Blue
|
||||
case 'w': return '#16a34a'; // Green
|
||||
default: return 'inherit';
|
||||
}
|
||||
};
|
||||
</script>
|
||||
54
src/pages/customer/profile/profile/HistoryTabs.vue
Normal file
54
src/pages/customer/profile/profile/HistoryTabs.vue
Normal file
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div role="tablist" class="tabs tabs-lifted">
|
||||
<a role="tab" class="tab [--tab-bg:oklch(var(--b1))] text-base-content" :class="{ 'tab-active': activeTab === 'deliveries' }" @click="activeTab = 'deliveries'">
|
||||
Will-Call Deliveries
|
||||
</a>
|
||||
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-4" v-show="activeTab === 'deliveries'">
|
||||
<DeliveriesTable :deliveries="deliveries" />
|
||||
</div>
|
||||
|
||||
<a role="tab" class="tab [--tab-bg:oklch(var(--b1))] text-base-content" :class="{ 'tab-active': activeTab === 'service' }" @click="activeTab = 'service'">
|
||||
Service History
|
||||
</a>
|
||||
<div role="tabpanel" class="tab-content bg-base-100 border-base-300 rounded-box p-4" v-show="activeTab === 'service'">
|
||||
<ServiceCallsTable :service-calls="serviceCalls" @open-service-modal="(service) => $emit('openServiceModal', service)" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import DeliveriesTable from './DeliveriesTable.vue';
|
||||
import ServiceCallsTable from './ServiceCallsTable.vue';
|
||||
|
||||
// 1. Define the interfaces for the data this component receives and passes down.
|
||||
// These should match the interfaces in the child components.
|
||||
interface Delivery {
|
||||
id: number;
|
||||
delivery_status: number;
|
||||
customer_name: string;
|
||||
customer_asked_for_fill: number | boolean;
|
||||
gallons_ordered: number | string;
|
||||
gallons_delivered: number | string | null;
|
||||
expected_delivery_date: string;
|
||||
}
|
||||
|
||||
interface ServiceCall {
|
||||
id: number;
|
||||
scheduled_date: string;
|
||||
type_service_call: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// 2. Define the Props interface
|
||||
interface Props {
|
||||
deliveries: Delivery[];
|
||||
serviceCalls: ServiceCall[];
|
||||
}
|
||||
|
||||
// 3. Use the typed defineProps
|
||||
defineProps<Props>();
|
||||
|
||||
defineEmits(['openServiceModal']);
|
||||
const activeTab = ref('deliveries');
|
||||
</script>
|
||||
37
src/pages/customer/profile/profile/ProfileHeader.vue
Normal file
37
src/pages/customer/profile/profile/ProfileHeader.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-2">
|
||||
<h2 class="card-title text-2xl">{{ customer.account_number }}</h2>
|
||||
<div class="flex flex-wrap gap-2 justify-start sm:justify-end">
|
||||
<router-link :to="{ name: 'deliveryCreate', params: { id: customer.id } }" class="btn btn-sm btn-primary">New Delivery</router-link>
|
||||
<router-link :to="{ name: 'CalenderCustomer', params: { id: customer.id } }" class="btn btn-sm btn-info">New Service</router-link>
|
||||
<router-link :to="{ name: 'customerEdit', params: { id: customer.id } }" class="btn btn-sm btn-secondary">Edit Customer</router-link>
|
||||
<button @click="$emit('toggleAutomatic', customer.id)" class="btn btn-sm"
|
||||
:class="automatic_status === 1 ? 'btn-success' : 'btn-warning'">
|
||||
{{ automatic_status === 1 ? 'Set to Will Call' : 'Set to Automatic' }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider my-2"></div>
|
||||
<div class="rounded-lg overflow-hidden" style="height:400px; width:100%">
|
||||
<l-map ref="map" v-model:zoom="zoom" :center="[customer.customer_latitude, customer.customer_longitude]">
|
||||
<l-tile-layer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" layer-type="base" name="OpenStreetMap"></l-tile-layer>
|
||||
</l-map>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { LMap, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
||||
|
||||
defineProps({
|
||||
customer: { type: Object, required: true },
|
||||
automatic_status: { type: Number, required: true },
|
||||
});
|
||||
defineEmits(['toggleAutomatic']);
|
||||
const zoom = ref(14);
|
||||
</script>
|
||||
32
src/pages/customer/profile/profile/ProfileMap.vue
Normal file
32
src/pages/customer/profile/profile/ProfileMap.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-xl h-full">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title text-2xl mb-4">{{ customer.account_number }}</h2>
|
||||
<div class="rounded-lg overflow-hidden h-full min-h-[400px]">
|
||||
<l-map ref="map" v-model:zoom="zoom" :center="[customer.customer_latitude, customer.customer_longitude]">
|
||||
<l-tile-layer url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" layer-type="base" name="OpenStreetMap"></l-tile-layer>
|
||||
</l-map>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import "leaflet/dist/leaflet.css";
|
||||
import { LMap, LTileLayer } from "@vue-leaflet/vue-leaflet";
|
||||
|
||||
interface Customer {
|
||||
account_number: string;
|
||||
customer_latitude: number;
|
||||
customer_longitude: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
customer: Customer;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
const zoom = ref(14);
|
||||
</script>
|
||||
63
src/pages/customer/profile/profile/ProfileSummary.vue
Normal file
63
src/pages/customer/profile/profile/ProfileSummary.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-xl h-full">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<router-link :to="{ name: 'deliveryCreate', params: { id: customer.id } }" class="btn btn-sm btn-primary">New Delivery</router-link>
|
||||
<router-link :to="{ name: 'CalenderCustomer', params: { id: customer.id } }" class="btn btn-sm btn-info">New Service</router-link>
|
||||
<router-link :to="{ name: 'customerEdit', params: { id: customer.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<button @click="$emit('toggleAutomatic', customer.id)" class="btn btn-sm" :class="automatic_status === 1 ? 'btn-success' : 'btn-warning'">
|
||||
{{ automatic_status === 1 ? 'Set to Will Call' : 'Set to Automatic' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="divider my-4"></div>
|
||||
|
||||
<!-- Customer Details -->
|
||||
<div>
|
||||
<div class="flex justify-between items-center">
|
||||
<h2 class="card-title text-lg">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</h2>
|
||||
<span class="badge" :class="automatic_status === 1 ? 'badge-success' : 'badge-ghost'">
|
||||
{{ automatic_status === 1 ? 'Automatic' : 'Will Call' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="text-error font-semibold mt-2" v-if="!customer.correct_address">
|
||||
Possible Incorrect Address!
|
||||
</div>
|
||||
<div class="mt-4 space-y-2 text-sm">
|
||||
<p>{{ customer.customer_address }}<span v-if="customer.customer_apt">, {{ customer.customer_apt }}</span></p>
|
||||
<p>{{ customer.customer_town }}, {{ stateName(customer.customer_state) }} {{ customer.customer_zip }}</p>
|
||||
<p class="pt-2">{{ customer.customer_phone_number }}</p>
|
||||
<p><span class="badge badge-outline badge-sm">{{ homeTypeName(customer.customer_home_type) }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Customer {
|
||||
id: number;
|
||||
customer_first_name: string;
|
||||
customer_last_name: string;
|
||||
correct_address: boolean;
|
||||
customer_address: string;
|
||||
customer_apt: string | null;
|
||||
customer_town: string;
|
||||
customer_state: number;
|
||||
customer_zip: string;
|
||||
customer_phone_number: string;
|
||||
customer_home_type: number;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
customer: Customer;
|
||||
automatic_status: number;
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
defineEmits(['toggleAutomatic']);
|
||||
|
||||
const stateName = (id: number) => ['MA', 'RI', 'NH', 'ME', 'VT', 'CT', 'NY'][id] || 'N/A';
|
||||
const homeTypeName = (id: number) => ['Residential', 'Apartment', 'Condo', 'Commercial', 'Business', 'Construction', 'Container'][id] || 'Unknown';
|
||||
</script>
|
||||
116
src/pages/customer/profile/profile/ServiceCallsTable.vue
Normal file
116
src/pages/customer/profile/profile/ServiceCallsTable.vue
Normal file
@@ -0,0 +1,116 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-if="!serviceCalls || serviceCalls.length === 0" class="text-center p-10">
|
||||
<p>No service call history found for this customer.</p>
|
||||
</div>
|
||||
<div v-else class="overflow-x-auto">
|
||||
<table class="table table-sm w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date</th>
|
||||
<th>Type</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Note: The @click handler is removed from the <tr> since the row itself is no longer the primary action -->
|
||||
<tr v-for="service in serviceCalls" :key="service.id" class="hover">
|
||||
<td class="align-top">
|
||||
<div>{{ formatDate(service.scheduled_date) }}</div>
|
||||
<div class="text-xs opacity-70">{{ formatTime(service.scheduled_date) }}</div>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
<span class="font-medium" :style="{ color: getServiceTypeColor(service.type_service_call) }">
|
||||
{{ getServiceTypeName(service.type_service_call) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="whitespace-normal text-sm align-top">
|
||||
<!-- If the text is short OR this row is expanded, show the full text -->
|
||||
<div v-if="!isLongDescription(service.description) || isExpanded(service.id)">
|
||||
{{ service.description }}
|
||||
<a v-if="isLongDescription(service.description)"
|
||||
@click.prevent="toggleExpand(service.id)"
|
||||
href="#"
|
||||
class="link link-info link-hover text-xs ml-1 whitespace-nowrap">
|
||||
Show less
|
||||
</a>
|
||||
</div>
|
||||
<!-- Otherwise, show the truncated text -->
|
||||
<div v-else>
|
||||
{{ truncateDescription(service.description) }}
|
||||
<a @click.prevent="toggleExpand(service.id)"
|
||||
href="#"
|
||||
class="link link-info link-hover text-xs ml-1 whitespace-nowrap">
|
||||
Read more
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
interface ServiceCall {
|
||||
id: number;
|
||||
scheduled_date: string;
|
||||
type_service_call: number;
|
||||
description: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
serviceCalls: ServiceCall[];
|
||||
}
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
// --- NEW LOGIC FOR TEXT TRUNCATION ---
|
||||
|
||||
// 1. Define the word limit as a constant for easy changes
|
||||
const WORD_LIMIT = 50;
|
||||
|
||||
// 2. Create a reactive array to store the IDs of expanded rows
|
||||
const expandedIds = ref<number[]>([]);
|
||||
|
||||
// 3. Helper function to check if a description is long
|
||||
const isLongDescription = (text: string): boolean => {
|
||||
if (!text) return false;
|
||||
return text.split(/\s+/).length > WORD_LIMIT;
|
||||
};
|
||||
|
||||
// 4. Helper function to truncate the description
|
||||
const truncateDescription = (text: string): string => {
|
||||
if (!isLongDescription(text)) return text;
|
||||
const words = text.split(/\s+/);
|
||||
return words.slice(0, WORD_LIMIT).join(' ') + '...';
|
||||
};
|
||||
|
||||
// 5. Helper function to check if a specific row is expanded
|
||||
const isExpanded = (id: number): boolean => {
|
||||
return expandedIds.value.includes(id);
|
||||
};
|
||||
|
||||
// 6. Function to add/remove an ID from the expanded list
|
||||
const toggleExpand = (id: number): void => {
|
||||
const index = expandedIds.value.indexOf(id);
|
||||
if (index === -1) {
|
||||
// If not found, add it to expand
|
||||
expandedIds.value.push(id);
|
||||
} else {
|
||||
// If found, remove it to collapse
|
||||
expandedIds.value.splice(index, 1);
|
||||
}
|
||||
};
|
||||
|
||||
// --- EXISTING HELPER FUNCTIONS ---
|
||||
|
||||
const formatDate = (dateString: string) => dayjs(dateString).format('MMMM D, YYYY');
|
||||
const formatTime = (dateString: string) => dayjs(dateString).format('h:mm A');
|
||||
const getServiceTypeName = (typeId: number) => ({ 0: 'Tune-up', 1: 'No Heat', 2: 'Fix', 3: 'Tank Install', 4: 'Other' }[typeId] || 'Unknown');
|
||||
const getServiceTypeColor = (typeId: number) => ({ 0: 'blue', 1: 'red', 2: 'green', 3: '#B58900', 4: 'black' }[typeId] || 'gray');
|
||||
</script>
|
||||
32
src/pages/customer/profile/profile/TankInfo.vue
Normal file
32
src/pages/customer/profile/profile/TankInfo.vue
Normal file
@@ -0,0 +1,32 @@
|
||||
<template>
|
||||
<div class="card bg-base-100 shadow-xl">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<div class="card-title flex justify-between items-center">
|
||||
<h2>Tank Info</h2>
|
||||
<router-link :to="{ name: 'TankEdit', params: { id: customer_id } }" class="btn btn-xs btn-outline btn-success">
|
||||
Edit
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="text-sm mt-2 space-y-1">
|
||||
<div class="flex justify-between">
|
||||
<span>Status:</span>
|
||||
<span class="badge" :class="tank.tank_status ? 'badge-success' : 'badge-error'">
|
||||
{{ tank.tank_status ? 'Inspected' : 'Needs Inspection' }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between"><span>Last Inspection:</span> <strong>{{ tank.last_tank_inspection || 'N/A' }}</strong></div>
|
||||
<div class="flex justify-between"><span>Location:</span> <strong class="badge" :class="tank.outside_or_inside ? '' : 'badge-warning'">{{ tank.outside_or_inside ? 'Inside' : 'Outside' }}</strong></div>
|
||||
<div class="flex justify-between"><span>Size:</span> <strong>{{ tank.tank_size }} Gallons</strong></div>
|
||||
<div class="flex justify-between"><span>Fill Location:</span> <strong>{{ description.fill_location }}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
customer_id: { type: Number, required: true },
|
||||
tank: { type: Object, required: true },
|
||||
description: { type: Object, required: true }
|
||||
});
|
||||
</script>
|
||||
@@ -3,7 +3,7 @@
|
||||
import CustomerHome from '../customer/home.vue';
|
||||
import CustomerCreate from '../customer/create.vue';
|
||||
import CustomerEdit from "../customer/edit.vue";
|
||||
import CustomerProfile from "./profile/home.vue"
|
||||
import CustomerProfile from "./profile/profile.vue"
|
||||
import TankEdit from "./tank/edit.vue"
|
||||
|
||||
|
||||
|
||||
@@ -1,280 +1,186 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
Customers
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="flex">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
|
||||
<li>Edit Tank Details</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mt-4">
|
||||
<h1 v-if="customer.id" class="text-3xl font-bold">
|
||||
Tank for: {{ customer.customer_first_name }} {{ customer.customer_last_name }}
|
||||
</h1>
|
||||
<router-link v-if="customer.id" :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm mt-2 sm:mt-0">
|
||||
Back to Profile
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- Main Form Card -->
|
||||
<div class="bg-neutral rounded-lg p-6 mt-6">
|
||||
<form @submit.prevent="onSubmit" class="space-y-6">
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-x-6 gap-y-4">
|
||||
|
||||
<!-- Inspection Date -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Last Inspection Date</span></label>
|
||||
<input v-model="TankForm.last_tank_inspection" type="date" class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid grid-cols-1 rounded-md p-6 ">
|
||||
<div class="text-[24px]">
|
||||
Customer: {{ customer.customer_first_name }} {{ customer.customer_last_name }} | {{ customer.account_number }}
|
||||
</div>
|
||||
|
||||
<form class="rounded-md px-8 pt-6 pb-8 mb-4 w-full" enctype="multipart/form-data"
|
||||
@submit.prevent="onSubmit">
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Inspection Date </label>
|
||||
<input v-model="CreateTankForm.basicInfo.last_tank_inspection"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="date"
|
||||
min="2023-01-01" max="2030-01-01" />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Good Or bad Tank</label>
|
||||
<label>
|
||||
<input type="radio" name="goodtank1" value="true" class="radio"
|
||||
v-model="CreateTankForm.basicInfo.tank_status" />
|
||||
Good
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="goodtank2" value="false" class="radio"
|
||||
v-model="CreateTankForm.basicInfo.tank_status" />
|
||||
Bad
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Tank Size</label>
|
||||
<input v-model="CreateTankForm.basicInfo.tank_size"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text"
|
||||
placeholder="Gallon size of tank" />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Inside or Outside</label>
|
||||
<label>
|
||||
<input type="radio" name="insideoutside1" value="true" class="radio"
|
||||
v-model="CreateTankForm.basicInfo.outside_or_inside" />
|
||||
Inside
|
||||
</label>
|
||||
<label>
|
||||
|
||||
<input type="radio" name="insideoutside2" value="false" class="radio"
|
||||
v-model="CreateTankForm.basicInfo.outside_or_inside" />
|
||||
Outside
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Fill Location</label>
|
||||
<input v-model="CreateTankForm.basicInfo.fill_location"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text"
|
||||
placeholder="Fill Location" />
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-span-12 md:col-span-12 flex mt-5 mb-5">
|
||||
<button class="btn btn-accent btn-sm">
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<!-- Tank Size -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Tank Size (Gallons)</span></label>
|
||||
<input v-model="TankForm.tank_size" type="number" placeholder="e.g., 275" class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tank Status -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Tank Status</span></label>
|
||||
<div class="flex items-center gap-6 bg-base-100 p-2 rounded-lg">
|
||||
<label class="label cursor-pointer gap-2">
|
||||
<span class="label-text">Good</span>
|
||||
<input type="radio" v-model="TankForm.tank_status" :value="true" class="radio radio-primary" />
|
||||
</label>
|
||||
<label class="label cursor-pointer gap-2">
|
||||
<span class="label-text">Bad</span>
|
||||
<input type="radio" v-model="TankForm.tank_status" :value="false" class="radio radio-primary" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tank Location -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Tank Location</span></label>
|
||||
<div class="flex items-center gap-6 bg-base-100 p-2 rounded-lg">
|
||||
<label class="label cursor-pointer gap-2">
|
||||
<span class="label-text">Inside</span>
|
||||
<input type="radio" v-model="TankForm.outside_or_inside" :value="true" class="radio radio-primary" />
|
||||
</label>
|
||||
<label class="label cursor-pointer gap-2">
|
||||
<span class="label-text">Outside</span>
|
||||
<input type="radio" v-model="TankForm.outside_or_inside" :value="false" class="radio radio-primary" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Fill Location -->
|
||||
<div class="form-control md:col-span-2">
|
||||
<label class="label"><span class="label-text">Fill Location Description</span></label>
|
||||
<input v-model="TankForm.fill_location" type="text" placeholder="e.g., Left side of house, behind shed" class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SUBMIT BUTTON -->
|
||||
<div class="pt-4">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } 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'
|
||||
|
||||
// Interface for our flat form model
|
||||
interface TankFormData {
|
||||
last_tank_inspection: string | null;
|
||||
tank_status: boolean;
|
||||
outside_or_inside: boolean;
|
||||
tank_size: number;
|
||||
fill_location: string;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'TankEdit',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
user: {
|
||||
id: '',
|
||||
},
|
||||
customer: {
|
||||
id: 0,
|
||||
user_id: 0,
|
||||
customer_first_name: '',
|
||||
customer_last_name: '',
|
||||
customer_town: '',
|
||||
customer_address: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
customer_apt: '',
|
||||
customer_home_type: 0,
|
||||
customer_phone_number: '',
|
||||
account_number: '',
|
||||
},
|
||||
tank: {
|
||||
customer_id: 0,
|
||||
last_tank_inspection: true,
|
||||
tank_status: true,
|
||||
outside_or_inside: true,
|
||||
tank_size: 0,
|
||||
|
||||
},
|
||||
|
||||
CreateTankForm: {
|
||||
basicInfo: {
|
||||
last_tank_inspection: null,
|
||||
tank_status: true,
|
||||
outside_or_inside: true,
|
||||
tank_size: 0,
|
||||
fill_location: 0,
|
||||
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getCustomer(this.$route.params.id);
|
||||
this.getCustomerDescription(this.$route.params.id);
|
||||
this.getTank(this.$route.params.id);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.getCustomer(this.$route.params.id);
|
||||
this.getCustomerDescription(this.$route.params.id);
|
||||
this.getTank(this.$route.params.id);
|
||||
},
|
||||
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) {
|
||||
this.user = response.data.user;
|
||||
this.user.id = response.data.user.id;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user.id = '';
|
||||
})
|
||||
},
|
||||
getCustomer(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.customer = response.data
|
||||
})
|
||||
},
|
||||
getCustomerDescription(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/customer/description/' + userid;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
|
||||
this.CreateTankForm.basicInfo.fill_location = response.data.fill_location;
|
||||
name: 'TankEdit',
|
||||
components: {
|
||||
Footer,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
user: null as any,
|
||||
customer: {} as any,
|
||||
// --- REFACTORED: Simplified, flat form object ---
|
||||
TankForm: {
|
||||
last_tank_inspection: null,
|
||||
tank_status: true,
|
||||
outside_or_inside: true,
|
||||
tank_size: 0,
|
||||
fill_location: '',
|
||||
} as TankFormData,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.userStatus();
|
||||
const customerId = this.$route.params.id;
|
||||
this.getCustomer(customerId);
|
||||
this.getCustomerDescription(customerId);
|
||||
this.getTank(customerId);
|
||||
},
|
||||
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;
|
||||
}
|
||||
})
|
||||
},
|
||||
getTank(customer_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/tank/" + customer_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.tank = response.data
|
||||
|
||||
this.CreateTankForm.basicInfo.last_tank_inspection = response.data.last_tank_inspection;
|
||||
this.CreateTankForm.basicInfo.tank_status = response.data.tank_status;
|
||||
this.CreateTankForm.basicInfo.outside_or_inside = response.data.outside_or_inside;
|
||||
this.CreateTankForm.basicInfo.tank_size = response.data.tank_size;
|
||||
|
||||
console.log(this.CreateTankForm.basicInfo.outside_or_inside)
|
||||
console.log(this.CreateTankForm.basicInfo.tank_status)
|
||||
})
|
||||
},
|
||||
editTank(payload: {
|
||||
|
||||
last_tank_inspection: any;
|
||||
tank_status: boolean;
|
||||
outside_or_inside: boolean;
|
||||
tank_size: number;
|
||||
fill_location: number;
|
||||
}) {
|
||||
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/edit/tank/" + this.$route.params.id;
|
||||
axios({
|
||||
method: "put",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.tank.customer_id } });
|
||||
}
|
||||
if (response.data.error) {
|
||||
this.$router.push("/");
|
||||
}
|
||||
})
|
||||
},
|
||||
onSubmit() {
|
||||
console.log(this.CreateTankForm.basicInfo.outside_or_inside)
|
||||
console.log(this.CreateTankForm.basicInfo.tank_status)
|
||||
let payload = {
|
||||
last_tank_inspection: this.CreateTankForm.basicInfo.last_tank_inspection,
|
||||
tank_status: this.CreateTankForm.basicInfo.tank_status,
|
||||
outside_or_inside: this.CreateTankForm.basicInfo.outside_or_inside,
|
||||
tank_size: this.CreateTankForm.basicInfo.tank_size,
|
||||
fill_location: this.CreateTankForm.basicInfo.fill_location,
|
||||
};
|
||||
this.editTank(payload);
|
||||
},
|
||||
.catch(() => { this.user = null; });
|
||||
},
|
||||
getCustomer(userid: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/${userid}`;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
this.customer = response.data;
|
||||
});
|
||||
},
|
||||
getCustomerDescription(userid: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/description/${userid}`;
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
// Only update fill_location if the response has it
|
||||
if (response.data && response.data.fill_location) {
|
||||
this.TankForm.fill_location = response.data.fill_location;
|
||||
}
|
||||
});
|
||||
},
|
||||
getTank(customer_id: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/tank/${customer_id}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
// Update the form model with data from the tank endpoint
|
||||
this.TankForm.last_tank_inspection = response.data.last_tank_inspection;
|
||||
this.TankForm.tank_status = response.data.tank_status;
|
||||
this.TankForm.outside_or_inside = response.data.outside_or_inside;
|
||||
this.TankForm.tank_size = response.data.tank_size;
|
||||
}
|
||||
});
|
||||
},
|
||||
editTank(payload: TankFormData) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/customer/edit/tank/${this.$route.params.id}`;
|
||||
axios.put(path, payload, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.$router.push({ name: "customerProfile", params: { id: this.customer.id } });
|
||||
} else {
|
||||
console.error("Failed to edit tank:", response.data.error);
|
||||
}
|
||||
});
|
||||
},
|
||||
onSubmit() {
|
||||
// The payload is simply the entire form object now
|
||||
this.editTank(this.TankForm);
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,290 +1,153 @@
|
||||
<template>
|
||||
<Header />
|
||||
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10">
|
||||
<!-- Main Content -->
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs Navigation -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Edit Oil Delivery #{{ deliveryOrder.id }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid grid-cols-2 rounded-md p-6 ">
|
||||
<div class="col-span-1">
|
||||
<div class="text-2xl border-b border-gray-500 mb-10">
|
||||
Edit Oil Delivery
|
||||
</div>
|
||||
<div class=" rounded-md">
|
||||
<div class="flex text-2xl">
|
||||
Delivery id: {{ deliveryOrder.id }}
|
||||
</div>
|
||||
<div class="grid grid-cols-12">
|
||||
<div class="col-span-12 py-5">
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: customer['id'] } }"
|
||||
class="btn btn-secondary btn-sm">
|
||||
View Profile
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="col-span-6 p-5">
|
||||
<div class="grid grid-cols-12">
|
||||
<div class="col-span-12 flex">
|
||||
{{ customer.customer_first_name }}
|
||||
{{ customer.customer_last_name }}
|
||||
</div>
|
||||
<div class="col-span-12 flex">{{customer.customer_address}}</div>
|
||||
<div class="col-span-12 flex">
|
||||
<div class="pr-2">
|
||||
{{ customer.customer_town }},
|
||||
</div>
|
||||
<div class="pr-2">
|
||||
<div v-if="customer.customer_state == 0">Massachusetts</div>
|
||||
<div v-else-if="customer.customer_state == 1">Rhode Island</div>
|
||||
<div v-else-if="customer.customer_state == 2">New Hampshire</div>
|
||||
<div v-else-if="customer.customer_state == 3">Maine</div>
|
||||
<div v-else-if="customer.customer_state == 4">Vermont</div>
|
||||
<div v-else-if="customer.customer_state == 5">Maine</div>
|
||||
<div v-else-if="customer.customer_state == 6">New York</div>
|
||||
<div v-else>Unknown state</div>
|
||||
</div>
|
||||
<div class="pr-2">
|
||||
{{ customer.customer_zip }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 flex" v-if="customer.customer_apt !== 'None'">
|
||||
{{ customer.customer_apt }}
|
||||
</div>
|
||||
<div class="col-span-12 flex">
|
||||
<div v-if="customer.customer_home_type == 0">Residential</div>
|
||||
<div v-else-if="customer.customer_home_type == 1">apartment</div>
|
||||
<div v-else-if="customer.customer_home_type == 2">condo</div>
|
||||
<div v-else-if="customer.customer_home_type == 3">commercial</div>
|
||||
<div v-else-if="customer.customer_home_type == 4">business</div>
|
||||
<div v-else-if="customer.customer_home_type == 5">construction</div>
|
||||
<div v-else-if="customer.customer_home_type == 6">container</div>
|
||||
</div>
|
||||
<div class="col-span-12 flex">
|
||||
{{ customer.customer_phone_number }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- TOP SECTION: Customer and Payment Info -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 my-6">
|
||||
<!-- Customer Info Card -->
|
||||
<div v-if="customer.id" class="bg-neutral rounded-lg p-5">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<div class="text-xl font-bold">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</div>
|
||||
<div class="text-sm text-gray-400">Account: {{ customer.account_number }}</div>
|
||||
</div>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
|
||||
View Profile
|
||||
</router-link>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ customer.customer_address }}</div>
|
||||
<div v-if="customer.customer_apt && customer.customer_apt !== 'None'">{{ customer.customer_apt }}</div>
|
||||
<div>{{ customer.customer_town }}, {{ stateName }} {{ customer.customer_zip }}</div>
|
||||
<div class="mt-2">{{ customer.customer_phone_number }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="grid grid-cols-1 " v-if="deliveryOrder.payment_type == 1">
|
||||
|
||||
<div class=" p-2 ">
|
||||
<div class="rounded-md border-2 bg-neutral">
|
||||
<div class="flex p-3">
|
||||
{{ userCard.type_of_card }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.name_on_card }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.card_number }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.expiration_month }}/ {{ userCard.expiration_year }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.security_number }}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class=" rounded-md mx-5 my-5">
|
||||
<div class="flex">
|
||||
Order Date:
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
{{ deliveryOrder.when_ordered }}
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
Expected Delivery Date:
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
{{ deliveryOrder.expected_delivery_date }}
|
||||
</div>
|
||||
|
||||
<div class="flex">
|
||||
Price per gallon:
|
||||
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
{{ deliveryOrder.customer_price }}
|
||||
<!-- Payment Card on File Card -->
|
||||
<div v-if="deliveryOrder.payment_type === 1 && userCard.id" class="bg-neutral rounded-lg p-5">
|
||||
<h3 class="text-xl font-bold mb-4">Card on File</h3>
|
||||
<div class="space-y-1">
|
||||
<p><span class="font-semibold">Card Type:</span> {{ userCard.type_of_card }}</p>
|
||||
<p><span class="font-semibold">Card Number:</span> {{ userCard.card_number }}</p>
|
||||
<p><span class="font-semibold">Name:</span> {{ userCard.name_on_card }}</p>
|
||||
<p><span class="font-semibold">Expires:</span> {{ userCard.expiration_month }}/{{ userCard.expiration_year }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-1">
|
||||
|
||||
|
||||
<form class="rounded-md px-8 pb-8 mb-4 w-full" enctype="multipart/form-data" @submit.prevent="onSubmit">
|
||||
|
||||
<div class="col-span-12 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Gallons Ordered</label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.gallons_ordered"
|
||||
:disabled="CreateOilOrderForm.basicInfo.customer_asked_for_fill"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text" placeholder="# gallons" />
|
||||
<span v-if="v$.CreateOilOrderForm.basicInfo.gallons_ordered.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateOilOrderForm.basicInfo.gallons_ordered.$errors[0].$message }}
|
||||
</span>
|
||||
<!-- BOTTOM SECTION: Edit Form -->
|
||||
<div class="bg-neutral rounded-lg p-6">
|
||||
<h2 class="text-2xl font-bold mb-4">Update Delivery Details</h2>
|
||||
<form @submit.prevent="onSubmit" class="space-y-4">
|
||||
<!-- Gallons Ordered & Fill Checkbox -->
|
||||
<div class="flex items-end gap-4">
|
||||
<div class="flex-grow">
|
||||
<label class="label"><span class="label-text font-bold">Gallons Ordered</span></label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.gallons_ordered" :disabled="CreateOilOrderForm.basicInfo.customer_asked_for_fill"
|
||||
class="input input-bordered input-sm w-full max-w-xs" type="number" placeholder="# gallons" />
|
||||
<span v-if="v$.CreateOilOrderForm.basicInfo.gallons_ordered.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateOilOrderForm.basicInfo.gallons_ordered.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-control pb-1">
|
||||
<label class="label cursor-pointer justify-start gap-4">
|
||||
<span class="label-text font-bold">Fill</span>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.customer_asked_for_fill" type="checkbox" class="checkbox checkbox-sm" />
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 gap-10">
|
||||
<label class="block text-white text-sm font-bold cursor-pointer label">Fill </label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.customer_asked_for_fill" class="checkbox checkbox-xs" id="fill"
|
||||
type="checkbox" />
|
||||
<!-- Date Fields -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="label"><span class="label-text font-bold">Order Created Date</span></label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.created_delivery_date" type="date" class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="label"><span class="label-text font-bold">Expected Delivery Date</span></label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.expected_delivery_date" type="date" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateOilOrderForm.basicInfo.expected_delivery_date.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateOilOrderForm.basicInfo.expected_delivery_date.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 gap-10">
|
||||
<label class="block text-white text-sm font-bold cursor-pointer label">Cash</label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.cash" class="checkbox checkbox-xs" id="cash" type="checkbox" />
|
||||
</div>
|
||||
|
||||
<div v-if="userCards.length > 0">
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 gap-10">
|
||||
<label class="block text-white text-sm font-bold cursor-pointer label">Credit </label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.card" class="checkbox checkbox-xs" id="Credit"
|
||||
type="checkbox" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="userCards.length > 0">
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 gap-10">
|
||||
<label class="block text-white text-sm font-bold cursor-pointer label">Check</label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.check" class="checkbox checkbox-xs" id="check"
|
||||
type="checkbox" />
|
||||
</div>
|
||||
<!-- Fees & Options -->
|
||||
<div class="p-4 border rounded-md">
|
||||
<label class="label-text font-bold">Fees & Options</label>
|
||||
<div class="flex flex-wrap gap-x-6 gap-y-2 mt-2">
|
||||
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Prime</span><input v-model="CreateOilOrderForm.basicInfo.prime" type="checkbox" class="checkbox checkbox-xs" /></label></div>
|
||||
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Emergency</span><input v-model="CreateOilOrderForm.basicInfo.emergency" type="checkbox" class="checkbox checkbox-xs" /></label></div>
|
||||
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Same Day</span><input v-model="CreateOilOrderForm.basicInfo.same_day" type="checkbox" class="checkbox checkbox-xs" /></label></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="userCards.length > 0">
|
||||
<div class="flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Customer Cards Payment</label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" aria-label="Default select example"
|
||||
id="userCards" v-model="CreateOilOrderForm.basicInfo.userCards">
|
||||
|
||||
<option class="text-white" v-for="(card, index) in userCards" :key="index" :value="card['id']">
|
||||
{{ card['type_of_card'] }} {{ card['card_number'] }}
|
||||
<!-- Payment Type Radio Group -->
|
||||
<div class="p-4 border rounded-md space-y-3">
|
||||
<label class="label-text font-bold">Payment Method</label>
|
||||
<div class="flex flex-wrap gap-x-6 gap-y-2">
|
||||
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Cash</span><input type="radio" v-model="CreateOilOrderForm.basicInfo.payment_type" :value="0" class="radio radio-xs" /></label></div>
|
||||
<div v-if="userCards.length > 0" class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Card</span><input type="radio" v-model="CreateOilOrderForm.basicInfo.payment_type" :value="1" class="radio radio-xs" /></label></div>
|
||||
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Check</span><input type="radio" v-model="CreateOilOrderForm.basicInfo.payment_type" :value="3" class="radio radio-xs" /></label></div>
|
||||
<div class="form-control"><label class="label cursor-pointer gap-2"><span class="label-text">Other</span><input type="radio" v-model="CreateOilOrderForm.basicInfo.payment_type" :value="4" class="radio radio-xs" /></label></div>
|
||||
</div>
|
||||
<!-- Customer Card Selection -->
|
||||
<div v-if="userCards.length > 0 && CreateOilOrderForm.basicInfo.payment_type === 1">
|
||||
<label class="label"><span class="label-text">Select Card</span></label>
|
||||
<select v-model="CreateOilOrderForm.basicInfo.credit_card_id" class="select select-bordered select-sm w-full max-w-xs">
|
||||
<option disabled :value="0">Select a card</option>
|
||||
<option v-for="card in userCards" :key="card.id" :value="card.id">
|
||||
{{ card.type_of_card }} {{ card.card_number }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 py-5">
|
||||
No Cards on File!
|
||||
<router-link :to="{ name: 'cardadd', params: { id: customer.user_id } }">
|
||||
<button class="btn btn-sm bg-blue-700 text-white">Add CreditCard</button>
|
||||
</router-link>
|
||||
|
||||
<!-- Delivery Status & Driver -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="label"><span class="label-text font-bold">Delivery Status</span></label>
|
||||
<select v-model="CreateOilOrderForm.basicInfo.delivery_status" class="select select-bordered select-sm w-full">
|
||||
<option v-for="status in deliveryStatus" :key="status.value" :value="status.value">
|
||||
{{ status.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="label"><span class="label-text font-bold">Assigned Driver</span></label>
|
||||
<select v-model="CreateOilOrderForm.basicInfo.driver_employee_id" class="select select-bordered select-sm w-full">
|
||||
<option disabled :value="0">Select a driver</option>
|
||||
<option v-for="driver in truckDriversList" :key="driver.id" :value="driver.id">
|
||||
{{ driver.employee_first_name }} {{ driver.employee_last_name }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Delivery Status</label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" aria-label="Default select example"
|
||||
id="delivery_status" v-model="CreateOilOrderForm.basicInfo.delivery_status">
|
||||
<option class="text-white" v-for="(delivery, index) in deliveryStatus" :key="index"
|
||||
:value="delivery['value']">
|
||||
{{ delivery['text'] }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateOilOrderForm.basicInfo.delivery_status.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateCustomerForm.basicInfo.delivery_status.$errors[0].$message }}
|
||||
</span>
|
||||
<!-- Dispatcher Notes -->
|
||||
<div>
|
||||
<label class="label"><span class="label-text font-bold">Dispatcher Notes</span></label>
|
||||
<textarea v-model="CreateOilOrderForm.basicInfo.dispatcher_notes_taken" rows="3" class="textarea textarea-bordered w-full"></textarea>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Delivery Created </label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.created_delivery_date"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="date" min="2023-01-01"
|
||||
max="2030-01-01" />
|
||||
|
||||
</div>
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Expected </label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.expected_delivery_date"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="date" min="2023-01-01"
|
||||
max="2030-01-01" />
|
||||
<span v-if="v$.CreateOilOrderForm.basicInfo.expected_delivery_date.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateOilOrderForm.basicInfo.expected_delivery_date.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 gap-10">
|
||||
<label class="block text-white text-sm font-bold cursor-pointer label">Prime</label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.prime" class="checkbox checkbox-xs" id="prime"
|
||||
type="checkbox" />
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 gap-10">
|
||||
<label class="block text-white text-sm font-bold cursor-pointer label">Emergency</label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.emergency" class="checkbox checkbox-xs" id="emergency"
|
||||
type="checkbox" />
|
||||
</div>
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0 gap-10">
|
||||
<label class="block text-white text-sm font-bold cursor-pointer label">Same Day </label>
|
||||
<input v-model="CreateOilOrderForm.basicInfo.same_day" class="checkbox checkbox-xs" id="same_day"
|
||||
type="checkbox" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Delivery Driver </label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" aria-label="Default select example"
|
||||
id="customer_state" v-model="CreateOilOrderForm.basicInfo.driver_driver">
|
||||
<option class="text-white" v-for="(driver, index) in truckDriversList" :key="index" :value="driver['id']">
|
||||
{{ driver['employee_first_name'] }} {{ driver['employee_last_name'] }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Select a Promo</label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" aria-label="Default select example"
|
||||
id="promos" v-model="CreateOilOrderForm.basicInfo.promo_id">
|
||||
<option class="text-white" v-for="(promo, index) in promos" :key="index" :value="promo['id']">
|
||||
{{ promo['name_of_promotion'] }} {{ promo['money_off_delivery'] }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Dispatcher Note on Ticket</label>
|
||||
<textarea v-model="CreateOilOrderForm.basicInfo.dispatcher_notes_taken" rows="4"
|
||||
class="textarea block p-2.5 w-full input-bordered " id="description" type="text" placeholder="Notes on ticket" />
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 md:col-span-12 flex mt-5 mb-5">
|
||||
<button class="btn btn-accent btn-sm">
|
||||
Save Changes
|
||||
</button>
|
||||
<!-- Submit Button -->
|
||||
<div class="pt-2">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
@@ -293,413 +156,176 @@ 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 { minLength, required } from "@vuelidate/validators";
|
||||
import { required, minLength } from "@vuelidate/validators";
|
||||
import { notify } from "@kyvg/vue3-notification";
|
||||
|
||||
// Interfaces to describe the shape of your data
|
||||
interface Customer { account_number: string; id: number; customer_first_name: string; customer_last_name: string; customer_address: string; customer_apt: string; customer_town: string; customer_state: number; customer_zip: string; customer_phone_number: string; }
|
||||
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; }
|
||||
interface UserCard { id: number; type_of_card: string; card_number: string; name_on_card: string; expiration_month: string; expiration_year: string; last_four_digits: string; }
|
||||
|
||||
const STATE_MAP: { [key: number]: string } = { 0: 'Massachusetts', 1: 'Rhode Island', 2: 'New Hampshire', 3: 'Maine', 4: 'Vermont', 5: 'Connecticut', 6: 'New York' };
|
||||
|
||||
export default defineComponent({
|
||||
name: 'deliveryEdit',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
components: { Header, SideBar, Footer },
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: null,
|
||||
deliveryStatus: [],
|
||||
truckDriversList: [],
|
||||
userCards: [],
|
||||
promos: [],
|
||||
|
||||
userCard: {
|
||||
date_added: '',
|
||||
user_id: '',
|
||||
card_number: '',
|
||||
last_four_digits: '',
|
||||
name_on_card: '',
|
||||
expiration_month: '',
|
||||
expiration_year: '',
|
||||
type_of_card: '',
|
||||
security_number: '',
|
||||
accepted_or_declined: '',
|
||||
main_card: '',
|
||||
},
|
||||
customer: {
|
||||
id: 0,
|
||||
user_id: 0,
|
||||
customer_first_name: '',
|
||||
customer_address: '',
|
||||
customer_last_name: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
customer_apt: '',
|
||||
customer_home_type: 0,
|
||||
customer_phone_number: '',
|
||||
},
|
||||
deliveryOrder: {
|
||||
id: '',
|
||||
customer_id: 0,
|
||||
customer_name: '',
|
||||
customer_address: '',
|
||||
customer_town: '',
|
||||
customer_state: 0,
|
||||
customer_zip: '',
|
||||
gallons_ordered: 0,
|
||||
customer_asked_for_fill: 0,
|
||||
gallons_delivered: '',
|
||||
customer_filled: 0,
|
||||
delivery_status: 0,
|
||||
when_ordered: '',
|
||||
when_delivered: '',
|
||||
expected_delivery_date: '',
|
||||
created_delivery_date: '',
|
||||
automatic: 0,
|
||||
oil_id: 0,
|
||||
supplier_price: '',
|
||||
customer_price: '',
|
||||
customer_temperature: '',
|
||||
dispatcher_notes: '',
|
||||
prime: 0,
|
||||
same_day: 0,
|
||||
emergency: 0,
|
||||
payment_type: 0,
|
||||
payment_card_id: 0,
|
||||
|
||||
driver_employee_id: 0,
|
||||
driver_first_name: '',
|
||||
driver_last_name: '',
|
||||
|
||||
promo_id: 0,
|
||||
promo_money_discount: '',
|
||||
|
||||
},
|
||||
deliveryStatus: [] as any[],
|
||||
truckDriversList: [] as any[],
|
||||
userCards: [] as UserCard[],
|
||||
promos: [] as any[],
|
||||
customer: {} as Customer,
|
||||
deliveryOrder: {} as DeliveryOrder,
|
||||
userCard: {} as UserCard,
|
||||
// RESTORED: Add all form fields to the data model
|
||||
CreateOilOrderForm: {
|
||||
basicInfo: {
|
||||
gallons_ordered: '',
|
||||
customer_asked_for_fill: false,
|
||||
expected_delivery_date: '',
|
||||
created_delivery_date: '',
|
||||
dispatcher_notes_taken: '',
|
||||
expected_delivery_date: '',
|
||||
prime: false,
|
||||
same_day: false,
|
||||
emergency: false,
|
||||
delivery_status: '',
|
||||
userCards: [],
|
||||
promos: [],
|
||||
credit_card_id: 0,
|
||||
same_day: false,
|
||||
delivery_status: 0,
|
||||
driver_employee_id: 0,
|
||||
dispatcher_notes_taken: '',
|
||||
promo_id: 0,
|
||||
cash: false,
|
||||
card: false,
|
||||
other: false,
|
||||
check: false,
|
||||
driver_driver: '',
|
||||
payment_type: 0,
|
||||
credit_card_id: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
validations() {
|
||||
return {
|
||||
CreateOilOrderForm: {
|
||||
basicInfo: {
|
||||
gallons_ordered: { required, minLength: minLength(1) },
|
||||
// RESTORED: Add validation for expected date
|
||||
expected_delivery_date: { required },
|
||||
delivery_status: { required },
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getPromos()
|
||||
this.userStatus()
|
||||
this.getDriversList()
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getDeliveryForm(this.$route.params.id);
|
||||
this.getDeliveryOrder(this.$route.params.id);
|
||||
|
||||
computed: {
|
||||
stateName(): string {
|
||||
if (this.customer && this.customer.customer_state !== undefined) {
|
||||
return STATE_MAP[this.customer.customer_state];
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.getDeliveryForm(this.$route.params.id)
|
||||
this.getDeliveryOrder(this.$route.params.id);
|
||||
this.getDeliveryStatusList();
|
||||
this.fetchInitialData();
|
||||
},
|
||||
|
||||
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) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
fetchInitialData() {
|
||||
const deliveryId = this.$route.params.id as string;
|
||||
this.getPromos();
|
||||
this.getDriversList();
|
||||
this.getDeliveryStatusList();
|
||||
this.getDeliveryOrder(deliveryId);
|
||||
},
|
||||
getPromos() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/promo/all";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.promos = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
},
|
||||
getDeliveryOrder(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
console.log( response.data.delivery.delivery_dispatcher_notes)
|
||||
this.CreateOilOrderForm.basicInfo.gallons_ordered = response.data.delivery.delivery_gallons_ordered;
|
||||
this.CreateOilOrderForm.basicInfo.customer_asked_for_fill = response.data.delivery.delivery_asked_for_fill;
|
||||
this.CreateOilOrderForm.basicInfo.expected_delivery_date = response.data.delivery.delivery_expected_delivery_date;
|
||||
this.CreateOilOrderForm.basicInfo.created_delivery_date = response.data.delivery.when_ordered;
|
||||
this.CreateOilOrderForm.basicInfo.dispatcher_notes_taken = response.data.delivery.dispatcher_notes_taken;
|
||||
this.CreateOilOrderForm.basicInfo.prime = response.data.delivery.delivery_prime;
|
||||
this.CreateOilOrderForm.basicInfo.emergency = response.data.delivery.delivery_emergency;
|
||||
this.CreateOilOrderForm.basicInfo.same_day = response.data.delivery.delivery_same_day;
|
||||
this.CreateOilOrderForm.basicInfo.delivery_status = response.data.delivery.delivery_status;
|
||||
|
||||
this.CreateOilOrderForm.basicInfo.driver_driver = response.data.delivery.driver_employee_id;
|
||||
if (response.data.delivery.delivery_asked_for_fill == 1) {
|
||||
this.CreateOilOrderForm.basicInfo.customer_asked_for_fill = true
|
||||
}
|
||||
if (response.data.delivery.payment_type == 1) {
|
||||
this.CreateOilOrderForm.basicInfo.userCards = response.data.delivery.payment_card_id;
|
||||
}
|
||||
if (response.data.delivery.delivery_prime == 1) {
|
||||
this.CreateOilOrderForm.basicInfo.prime = true
|
||||
}
|
||||
if (response.data.delivery.delivery_same_day == 1) {
|
||||
this.CreateOilOrderForm.basicInfo.same_day = true
|
||||
}
|
||||
if (response.data.delivery.delivery_emergency == 1) {
|
||||
this.CreateOilOrderForm.basicInfo.emergency = true
|
||||
}
|
||||
if (response.data.delivery.payment_type == 0) {
|
||||
this.CreateOilOrderForm.basicInfo.card = false
|
||||
this.CreateOilOrderForm.basicInfo.cash = true
|
||||
this.CreateOilOrderForm.basicInfo.check = false
|
||||
}
|
||||
if (response.data.delivery.payment_type == 1) {
|
||||
this.CreateOilOrderForm.basicInfo.card = true
|
||||
this.CreateOilOrderForm.basicInfo.cash = false
|
||||
this.CreateOilOrderForm.basicInfo.check = false
|
||||
}
|
||||
if (response.data.delivery.payment_type == 2) {
|
||||
this.CreateOilOrderForm.basicInfo.card = true
|
||||
this.CreateOilOrderForm.basicInfo.cash = true
|
||||
this.CreateOilOrderForm.basicInfo.check = false
|
||||
}
|
||||
if (response.data.delivery.payment_type == 3) {
|
||||
this.CreateOilOrderForm.basicInfo.card = false
|
||||
this.CreateOilOrderForm.basicInfo.cash = false
|
||||
this.CreateOilOrderForm.basicInfo.check = true
|
||||
}
|
||||
// Other
|
||||
if (response.data.delivery.payment_type == 4) {
|
||||
this.CreateOilOrderForm.basicInfo.card = false
|
||||
this.CreateOilOrderForm.basicInfo.cash = false
|
||||
this.CreateOilOrderForm.basicInfo.check = false
|
||||
this.CreateOilOrderForm.basicInfo.other = true
|
||||
}
|
||||
if (response.data.delivery.promo_id !== 0) {
|
||||
|
||||
this.CreateOilOrderForm.basicInfo.promo_id = response.data.delivery.promo_id;
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
},
|
||||
getDeliveryForm(delivery_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/order/" + delivery_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
getDeliveryOrder(deliveryId: string) {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/delivery/order/${deliveryId}`, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
this.deliveryOrder = response.data
|
||||
this.getCustomer(this.deliveryOrder.customer_id)
|
||||
}
|
||||
this.deliveryOrder = response.data;
|
||||
|
||||
// RESTORED: Populate all form fields from the API response
|
||||
this.CreateOilOrderForm.basicInfo = {
|
||||
gallons_ordered: String(response.data.gallons_ordered),
|
||||
customer_asked_for_fill: !!response.data.customer_asked_for_fill,
|
||||
created_delivery_date: response.data.when_ordered,
|
||||
expected_delivery_date: response.data.expected_delivery_date,
|
||||
prime: !!response.data.prime,
|
||||
emergency: !!response.data.emergency,
|
||||
same_day: !!response.data.same_day,
|
||||
delivery_status: response.data.delivery_status,
|
||||
driver_employee_id: response.data.driver_employee_id || 0,
|
||||
dispatcher_notes_taken: response.data.dispatcher_notes,
|
||||
promo_id: response.data.promo_id || 0,
|
||||
payment_type: response.data.payment_type,
|
||||
credit_card_id: response.data.payment_card_id || 0,
|
||||
};
|
||||
|
||||
this.getCustomer(response.data.customer_id);
|
||||
})
|
||||
.catch((error: any) => console.error("Error fetching delivery order:", error));
|
||||
},
|
||||
getCustomer(user_id: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/customer/" + user_id;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
|
||||
getCustomer(customerId: number) {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/customer/${customerId}`, { withCredentials: true })
|
||||
.then((response: any) => {
|
||||
this.customer = response.data;
|
||||
this.getPaymentCards(this.deliveryOrder.customer_id);
|
||||
if (this.deliveryOrder.payment_type == 1) {
|
||||
this.getPaymentCard(this.deliveryOrder.payment_card_id)
|
||||
}
|
||||
if (this.deliveryOrder.payment_type == 2) {
|
||||
this.getPaymentCard(this.deliveryOrder.payment_card_id)
|
||||
this.getPaymentCards(customerId);
|
||||
if (this.deliveryOrder.payment_type === 1 && this.deliveryOrder.payment_card_id) {
|
||||
this.getPaymentCard(this.deliveryOrder.payment_card_id);
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notify({
|
||||
title: "Error",
|
||||
text: "Could not find customer",
|
||||
type: "error",
|
||||
});
|
||||
});
|
||||
.catch((error: any) => console.error("Error fetching customer:", error));
|
||||
},
|
||||
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) => {
|
||||
this.userCard = response.data;
|
||||
this.CreateOilOrderForm.basicInfo.userCards = response.data.id
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
|
||||
getPaymentCards(customerId: number) {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/payment/cards/${customerId}`, { withCredentials: true })
|
||||
.then((response: any) => { this.userCards = response.data; })
|
||||
.catch((error: any) => console.error("Error fetching payment cards:", error));
|
||||
},
|
||||
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) => {
|
||||
this.userCards = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
|
||||
getPaymentCard(cardId: number) {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/payment/card/${cardId}`, { withCredentials: true })
|
||||
.then((response: any) => { this.userCard = response.data; })
|
||||
.catch((error: any) => console.error("Error fetching specific payment card:", error));
|
||||
},
|
||||
updatestatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/delivery/updatestatus';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
if (response.data.update)
|
||||
console.log("ok")
|
||||
})
|
||||
|
||||
getPromos() {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/promo/all`, { withCredentials: true })
|
||||
.then((response: any) => { this.promos = response.data; });
|
||||
},
|
||||
getDriversList() {
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/employee/drivers`, { headers: authHeader(), withCredentials: true })
|
||||
.then((response: any) => { this.truckDriversList = response.data; });
|
||||
},
|
||||
getDeliveryStatusList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/deliverystatus";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.deliveryStatus = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
},
|
||||
|
||||
getDriversList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/employee/drivers";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.truckDriversList = response.data;
|
||||
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
},
|
||||
|
||||
editOilOrder(payload: {
|
||||
gallons_ordered: string;
|
||||
customer_asked_for_fill: boolean;
|
||||
prime: boolean;
|
||||
same_day: boolean;
|
||||
emergency: boolean;
|
||||
delivery_status: string;
|
||||
expected_delivery_date: string;
|
||||
created_delivery_date: string;
|
||||
dispatcher_notes_taken: string;
|
||||
cash: boolean;
|
||||
credit: boolean;
|
||||
check: boolean;
|
||||
credit_card_id: any;
|
||||
promo_id: any;
|
||||
driver_employee_id: string,
|
||||
}) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/delivery/edit/" + this.deliveryOrder.id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok === true) {
|
||||
this.updatestatus()
|
||||
this.$router.push({ name: "deliveryOrder", params: { id: this.deliveryOrder.id } });
|
||||
}
|
||||
if (response.data.error) {
|
||||
this.$router.push("/");
|
||||
}
|
||||
})
|
||||
axios.get(`${import.meta.env.VITE_BASE_URL}/query/deliverystatus`, { withCredentials: true })
|
||||
.then((response: any) => { this.deliveryStatus = response.data; });
|
||||
},
|
||||
|
||||
onSubmit() {
|
||||
let payload = {
|
||||
gallons_ordered: this.CreateOilOrderForm.basicInfo.gallons_ordered,
|
||||
customer_asked_for_fill: this.CreateOilOrderForm.basicInfo.customer_asked_for_fill,
|
||||
expected_delivery_date: this.CreateOilOrderForm.basicInfo.expected_delivery_date,
|
||||
created_delivery_date: this.CreateOilOrderForm.basicInfo.created_delivery_date,
|
||||
dispatcher_notes_taken: this.CreateOilOrderForm.basicInfo.dispatcher_notes_taken,
|
||||
prime: this.CreateOilOrderForm.basicInfo.prime,
|
||||
emergency: this.CreateOilOrderForm.basicInfo.emergency,
|
||||
same_day: this.CreateOilOrderForm.basicInfo.same_day,
|
||||
delivery_status: this.CreateOilOrderForm.basicInfo.delivery_status,
|
||||
driver_employee_id: this.CreateOilOrderForm.basicInfo.driver_driver,
|
||||
cash: this.CreateOilOrderForm.basicInfo.cash,
|
||||
credit: this.CreateOilOrderForm.basicInfo.card,
|
||||
check: this.CreateOilOrderForm.basicInfo.check,
|
||||
promo_id: this.CreateOilOrderForm.basicInfo.promo_id,
|
||||
credit_card_id: this.CreateOilOrderForm.basicInfo.userCards,
|
||||
};
|
||||
|
||||
this.editOilOrder(payload);
|
||||
this.v$.$validate();
|
||||
if (this.v$.$error) {
|
||||
notify({ type: 'error', title: 'Validation Error', text: 'Please check the form fields.' });
|
||||
return;
|
||||
}
|
||||
|
||||
},
|
||||
const formInfo = this.CreateOilOrderForm.basicInfo;
|
||||
// The payload now automatically includes all the restored fields
|
||||
const payload = {
|
||||
...formInfo,
|
||||
cash: formInfo.payment_type === 0,
|
||||
credit: formInfo.payment_type === 1,
|
||||
check: formInfo.payment_type === 3,
|
||||
other: formInfo.payment_type === 4,
|
||||
credit_card_id: formInfo.payment_type === 1 ? formInfo.credit_card_id : null,
|
||||
};
|
||||
|
||||
axios.post(`${import.meta.env.VITE_BASE_URL}/delivery/edit/${this.deliveryOrder.id}`, payload, { withCredentials: true, headers: authHeader() })
|
||||
.then(() => {
|
||||
notify({ type: 'success', title: 'Success!', text: 'Delivery updated.' });
|
||||
this.$router.push({ name: 'deliveryOrder', params: { id: this.deliveryOrder.id } });
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Error submitting form:", error);
|
||||
notify({ type: 'error', title: 'Update Failed', text: 'Could not save changes.' });
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
</script>
|
||||
@@ -1,150 +1,176 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Deliveries</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex text-2xl mb-5">
|
||||
Delivery Home
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-12 gap-5 ">
|
||||
<div class="col-span-12 bg-secondary ">
|
||||
<div class="grid grid-cols-12 p-5 bg-neutral m-5">
|
||||
<div class="col-span-12 font-bold text-xl">Todays stats</div>
|
||||
<div class="col-span-6 py-2"> Total Deliveries: {{ delivery_count }}</div>
|
||||
<div class="col-span-6 py-2"> Completed: {{ delivery_count_delivered }} / {{ delivery_count }}</div>
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Search and Stats -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-6 mb-4">
|
||||
<div class="form-control">
|
||||
<h2 class="text-lg font-bold">Deliveries </h2>
|
||||
</div>
|
||||
<!-- Today's Stats Card -->
|
||||
<div class="stats stats-vertical sm:stats-horizontal shadow bg-base-100 text-center text-sm">
|
||||
<div class="stat p-3">
|
||||
<div class="stat-title text-xs">Today's Deliveries</div>
|
||||
<div class="stat-value text-lg">{{ delivery_count }}</div>
|
||||
</div>
|
||||
<div class="stat p-3">
|
||||
<div class="stat-title text-xs">Completed</div>
|
||||
<div class="stat-value text-lg">{{ delivery_count_delivered }} / {{ delivery_count }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 flex start pb-10 text-2xl">Recent 100 delivieres </div>
|
||||
<div class="divider">Recent Deliveries</div>
|
||||
|
||||
|
||||
<div class="col-span-12 overflow-x-auto bg-neutral">
|
||||
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account #</th>
|
||||
<th>Delivery #</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Town</th>
|
||||
<th>Address</th>
|
||||
<th>Town / Address</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Automatic</th>
|
||||
<th>Prime</th>
|
||||
<th>Same Day</th>
|
||||
<th>Emergency</th>
|
||||
<th>Options</th>
|
||||
<th>Payment</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
<tr v-for="oil in deliveries" :key="oil['id']">
|
||||
<td>{{ oil['id'] }} </td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil['id'] } }">
|
||||
<td>
|
||||
<div class="hover:text-accent">{{ oil['customer_name'] }} </div>
|
||||
</td>
|
||||
</router-link>
|
||||
<tr v-for="oil in deliveries" :key="oil.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>{{ oil.id }}</td>
|
||||
<td>
|
||||
<div v-if="oil['delivery_status'] == 0">Waiting</div>
|
||||
<div v-else-if="oil['delivery_status'] == 1">delivered</div>
|
||||
<div v-else-if="oil['delivery_status'] == 2">Out for Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 3">Cancelled</div>
|
||||
<div v-else-if="oil['delivery_status'] == 4">Partial Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 5">Issue</div>
|
||||
<div v-else-if="oil['delivery_status'] == 10">Finalized</div>
|
||||
<div v-else></div>
|
||||
</td>
|
||||
<td>{{ oil['customer_town'] }}</td>
|
||||
<td>{{ oil['customer_address'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['customer_asked_for_fill'] == 1">Fill</div>
|
||||
<div v-else> {{ oil['gallons_ordered'] }}</div>
|
||||
</td>
|
||||
<td>{{ oil['expected_delivery_date'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['automatic'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
|
||||
<td>
|
||||
<div v-if="oil['prime'] == 0">No</div>
|
||||
<div v-else class="text-red-600">Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['same_day'] == 0">No</div>
|
||||
<div v-else class="text-red-600">Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['emergency'] == 0">No</div>
|
||||
<div v-else class="text-red-600">Yes</div>
|
||||
</td>
|
||||
|
||||
|
||||
|
||||
<td>
|
||||
|
||||
<div v-if="oil['payment_type'] == 0">Cash</div>
|
||||
<div v-else-if="oil['payment_type'] == 1">CC</div>
|
||||
<div v-else-if="oil['payment_type'] == 2">Cash/CC</div>
|
||||
<div v-else-if="oil['payment_type'] == 3">Check</div>
|
||||
<div v-else-if="oil['payment_type'] == 4">Other</div>
|
||||
<div v-else></div>
|
||||
|
||||
</td>
|
||||
<td class="flex gap-5">
|
||||
|
||||
|
||||
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">View Delivery</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Edit Delivery</button>
|
||||
</router-link>
|
||||
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil['id'] } }"
|
||||
v-if="oil['delivery_status'] != 10">
|
||||
<button class="btn btn-secondary btn-sm">Finalize</button>
|
||||
</router-link>
|
||||
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-success btn-sm">
|
||||
Print Ticket
|
||||
</button>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil.customer_id } }" class="link link-hover">
|
||||
{{ oil.customer_name }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-sm" :class="{
|
||||
'badge-warning': oil.delivery_status == 0,
|
||||
'badge-success': [1, 10].includes(oil.delivery_status),
|
||||
'badge-info': oil.delivery_status == 2,
|
||||
'badge-error': [3, 5].includes(oil.delivery_status),
|
||||
}">
|
||||
<span v-if="oil.delivery_status == 0">Waiting</span>
|
||||
<span v-else-if="oil.delivery_status == 1">Delivered</span>
|
||||
<span v-else-if="oil.delivery_status == 2">Out for Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 3">Cancelled</span>
|
||||
<span v-else-if="oil.delivery_status == 4">Partial Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 5">Issue</span>
|
||||
<span v-else-if="oil.delivery_status == 10">Finalized</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ oil.customer_town }}</div>
|
||||
<div class="text-xs opacity-70">{{ oil.customer_address }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.customer_asked_for_fill == 1" class="badge badge-info">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</td>
|
||||
<td>{{ oil.expected_delivery_date }}</td>
|
||||
<td>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span v-if="oil.prime" class="badge badge-error badge-xs">PRIME</span>
|
||||
<span v-if="oil.same_day" class="badge badge-error badge-xs">SAME DAY</span>
|
||||
<span v-if="oil.emergency" class="badge badge-error badge-xs">EMERGENCY</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.payment_type == 0">Cash</span>
|
||||
<span v-else-if="oil.payment_type == 1">CC</span>
|
||||
<span v-else-if="oil.payment_type == 2">Cash/CC</span>
|
||||
<span v-else-if="oil.payment_type == 3">Check</span>
|
||||
<span v-else-if="oil.payment_type == 4">Other</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" v-if="oil.delivery_status != 10" class="btn btn-sm btn-accent">Finalize</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="25" :options="options" class="mt-10">
|
||||
</pagination>
|
||||
<div class="flex justify-center mb-10"> {{ recordsLength }} items Found</div>
|
||||
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="oil in deliveries" :key="oil.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ oil.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">Delivery #{{ oil.id }}</p>
|
||||
</div>
|
||||
<div class="badge" :class="{
|
||||
'badge-warning': oil.delivery_status == 0,
|
||||
'badge-success': [1, 10].includes(oil.delivery_status),
|
||||
'badge-info': oil.delivery_status == 2,
|
||||
'badge-error': [3, 5].includes(oil.delivery_status),
|
||||
}">
|
||||
<span v-if="oil.delivery_status == 0">Waiting</span>
|
||||
<span v-else-if="oil.delivery_status == 1">Delivered</span>
|
||||
<span v-else-if="oil.delivery_status == 2">Out for Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 3">Cancelled</span>
|
||||
<span v-else-if="oil.delivery_status == 4">Partial Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 5">Issue</span>
|
||||
<span v-else-if="oil.delivery_status == 10">Finalized</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div v-if="oil.prime" class="badge badge-error badge-sm">PRIME</div>
|
||||
<div v-if="oil.same_day" class="badge badge-error badge-sm">SAME DAY</div>
|
||||
<div v-if="oil.emergency" class="badge badge-error badge-sm">EMERGENCY</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Address:</strong> {{ oil.customer_address }}</p>
|
||||
<p><strong class="font-semibold">Town:</strong> {{ oil.customer_town }}</p>
|
||||
<p><strong class="font-semibold">Date:</strong> {{ oil.expected_delivery_date }}</p>
|
||||
<p><strong class="font-semibold">Gallons:</strong>
|
||||
<span v-if="oil.customer_asked_for_fill" class="badge badge-info badge-xs">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" v-if="oil.delivery_status != 10" class="btn btn-sm btn-accent">Finalize</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="25" :options="options">
|
||||
</pagination>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
@@ -170,7 +196,7 @@ export default defineComponent({
|
||||
delivery_count_delivered: 0,
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [],
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<Header />
|
||||
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<ul>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
<template>
|
||||
<Header />
|
||||
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<ul>
|
||||
|
||||
@@ -1,384 +1,202 @@
|
||||
<template>
|
||||
<Header />
|
||||
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<!-- Main Content -->
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
Customers
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
|
||||
<li>View Delivery</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mt-4 border-b border-gray-600 pb-2">
|
||||
Delivery #{{ deliveryOrder.id }}
|
||||
</h1>
|
||||
|
||||
<div class="grid grid-cols-1 rounded-md pb-5">
|
||||
<div class="text-2xl border-b-2 border-gray-500 mb-10">
|
||||
View Delivery # {{ deliveryOrder.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-12">
|
||||
|
||||
<div class="col-span-6">
|
||||
<div class="col-span-12 font-bold">
|
||||
Customer
|
||||
<!-- TOP SECTION: Customer & Status Info -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 my-6">
|
||||
<!-- Customer Info Card -->
|
||||
<div class="bg-neutral rounded-lg p-5">
|
||||
<div class="flex justify-between items-center mb-4">
|
||||
<div>
|
||||
<div class="text-xl font-bold">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</div>
|
||||
<div class="text-sm text-gray-400">Account: {{ customer.account_number }}</div>
|
||||
</div>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: customer.id } }" class="btn btn-secondary btn-sm">
|
||||
View Profile
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="col-span-6 p-5 text-gray-500 text-sm">
|
||||
<div class="grid grid-cols-12">
|
||||
<div class="col-span-12 flex ">
|
||||
{{ customer.account_number }}
|
||||
</div>
|
||||
<div class="col-span-12 flex">
|
||||
{{ customer.customer_first_name }}
|
||||
{{ customer.customer_last_name }}
|
||||
</div>
|
||||
<div class="col-span-12 flex">{{customer.customer_address}}</div>
|
||||
<div class="col-span-12 flex ">
|
||||
<div class="pr-2">
|
||||
{{ customer.customer_town }},
|
||||
</div>
|
||||
<div class="pr-2">
|
||||
<div v-if="customer.customer_state == 0">Massachusetts</div>
|
||||
<div v-else-if="customer.customer_state == 1">Rhode Island</div>
|
||||
<div v-else-if="customer.customer_state == 2">New Hampshire</div>
|
||||
<div v-else-if="customer.customer_state == 3">Maine</div>
|
||||
<div v-else-if="customer.customer_state == 4">Vermont</div>
|
||||
<div v-else-if="customer.customer_state == 5">Maine</div>
|
||||
<div v-else-if="customer.customer_state == 6">New York</div>
|
||||
<div v-else>Unknown state</div>
|
||||
</div>
|
||||
<div class="pr-2">
|
||||
{{ customer.customer_zip }}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>{{ customer.customer_address }}</div>
|
||||
<div v-if="customer.customer_apt && customer.customer_apt !== 'None'">Apt: {{ customer.customer_apt }}</div>
|
||||
<div>
|
||||
{{ customer.customer_town }},
|
||||
<span v-if="customer.customer_state == 0">Massachusetts</span>
|
||||
<span v-else-if="customer.customer_state == 1">Rhode Island</span>
|
||||
<span v-else-if="customer.customer_state == 2">New Hampshire</span>
|
||||
<span v-else-if="customer.customer_state == 3">Maine</span>
|
||||
<span v-else-if="customer.customer_state == 4">Vermont</span>
|
||||
<span v-else-if="customer.customer_state == 5">Connecticut</span>
|
||||
<span v-else-if="customer.customer_state == 6">New York</span>
|
||||
<span v-else>Unknown state</span>
|
||||
{{ customer.customer_zip }}
|
||||
</div>
|
||||
<div class="text-sm text-gray-400 mt-1">
|
||||
<span v-if="customer.customer_home_type == 0">Residential</span>
|
||||
<span v-else-if="customer.customer_home_type == 1">Apartment</span>
|
||||
<span v-else-if="customer.customer_home_type == 2">Condo</span>
|
||||
<span v-else-if="customer.customer_home_type == 3">Commercial</span>
|
||||
<span v-else-if="customer.customer_home_type == 4">Business</span>
|
||||
<span v-else-if="customer.customer_home_type == 5">Construction</span>
|
||||
<span v-else-if="customer.customer_home_type == 6">Container</span>
|
||||
</div>
|
||||
<div class="mt-2">{{ customer.customer_phone_number }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 flex" v-if="customer.customer_apt !== 'None'">
|
||||
{{ customer.customer_apt }}
|
||||
</div>
|
||||
<div class="col-span-12 flex">
|
||||
<div v-if="customer.customer_home_type == 0">Residential</div>
|
||||
<div v-else-if="customer.customer_home_type == 1">apartment</div>
|
||||
<div v-else-if="customer.customer_home_type == 2">condo</div>
|
||||
<div v-else-if="customer.customer_home_type == 3">commercial</div>
|
||||
<div v-else-if="customer.customer_home_type == 4">business</div>
|
||||
<div v-else-if="customer.customer_home_type == 5">construction</div>
|
||||
<div v-else-if="customer.customer_home_type == 6">container</div>
|
||||
</div>
|
||||
<div class="col-span-12 flex">
|
||||
{{ customer.customer_phone_number }}
|
||||
<!-- Delivery Status Card -->
|
||||
<div class="bg-neutral rounded-lg p-5">
|
||||
<h3 class="text-xl font-bold mb-4">Delivery Status</h3>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<div class="font-bold text-sm">Current Status</div>
|
||||
<div class="badge badge-lg"
|
||||
:class="{
|
||||
'badge-success': [1, 10].includes(deliveryOrder.delivery_status),
|
||||
'badge-info': deliveryOrder.delivery_status == 2,
|
||||
'badge-error': deliveryOrder.delivery_status == 5,
|
||||
'badge-warning': ![1, 10, 2, 5].includes(deliveryOrder.delivery_status)
|
||||
}">
|
||||
<span v-if="deliveryOrder.delivery_status == 0">Waiting</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 1">Delivered</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 2">Out for Delivery</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 3">Tomorrow</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 4">Partial Delivery</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 5">Misdelivery</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 6">Unknown</span>
|
||||
<span v-else-if="deliveryOrder.delivery_status == 10">Finalized</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-sm">Assigned Driver</div>
|
||||
<div>{{ deliveryOrder.driver_first_name }} {{ deliveryOrder.driver_last_name }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-sm">Scheduled Date</div>
|
||||
<div>{{ deliveryOrder.expected_delivery_date }}</div>
|
||||
</div>
|
||||
<div v-if="deliveryOrder.delivery_status === 10">
|
||||
<div class="font-bold text-sm">Date Delivered</div>
|
||||
<div>{{ (deliveryOrder.when_delivered) }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="font-bold text-sm">Date Ordered</div>
|
||||
<div>{{ (deliveryOrder.when_ordered) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-6 ">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="grid grid-cols-12">
|
||||
|
||||
|
||||
|
||||
<div class="col-span-6">
|
||||
<div class="col-span-12 font-bold">
|
||||
Delivery Status
|
||||
</div>
|
||||
<div class="grid grid-cols-12 p-5">
|
||||
<div class="col-span-12 font-bold">
|
||||
Status
|
||||
</div>
|
||||
<div class="col-span-12 text-sm mb-5 text-gray-500">
|
||||
<div v-if="deliveryOrder.delivery_status == 0">waiting</div>
|
||||
<div v-else-if="deliveryOrder.delivery_status == 1">delivered</div>
|
||||
<div v-else-if="deliveryOrder.delivery_status == 2">Out for Delivery</div>
|
||||
<div v-else-if="deliveryOrder.delivery_status == 3">tommorrow</div>
|
||||
<div v-else-if="deliveryOrder.delivery_status == 4">Partial Delivery</div>
|
||||
<div v-else-if="deliveryOrder.delivery_status == 5">misdelivery</div>
|
||||
<div v-else-if="deliveryOrder.delivery_status == 6">unknown</div>
|
||||
<div v-else-if="deliveryOrder.delivery_status == 10">Finalized</div>
|
||||
<div v-else></div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-span-12 font-bold ">
|
||||
Scheduled date/time
|
||||
</div>
|
||||
<div class="col-span-12 mb-5 text-sm text-gray-500">
|
||||
{{ deliveryOrder.expected_delivery_date }}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-span-12 font-bold">
|
||||
When Called
|
||||
</div>
|
||||
<div class="col-span-12 mb-5 text-sm text-gray-500">
|
||||
{{ (deliveryOrder.when_ordered) }}
|
||||
</div>
|
||||
<div v-if="deliveryOrder.delivery_status == 10">
|
||||
<div class="col-span-12 font-bold">
|
||||
When Delivered
|
||||
</div>
|
||||
<div class="col-span-12 mb-5 text-sm text-gray-500">
|
||||
{{ (deliveryOrder.when_delivered) }}
|
||||
<!-- BOTTOM SECTION: Delivery & Financial Details -->
|
||||
<div class="bg-neutral rounded-lg p-6">
|
||||
<div class="grid grid-cols-1 xl:grid-cols-2 gap-8">
|
||||
<!-- Left Column: Pricing, Gallons, Promo -->
|
||||
<div class="space-y-4">
|
||||
<!-- Gallons -->
|
||||
<div class="p-4 border rounded-md">
|
||||
<label class="label-text font-bold">Gallons Ordered</label>
|
||||
<div class="text-lg mt-1">
|
||||
<span v-if="deliveryOrder.customer_asked_for_fill == 1" class="badge badge-lg badge-info">FILL</span>
|
||||
<span v-else>{{ deliveryOrder.gallons_ordered }} gallons</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-span-12 font-bold ">
|
||||
Driver:
|
||||
</div>
|
||||
<div class="col-span-12 text-gray-500">
|
||||
{{ deliveryOrder.driver_first_name }} {{ deliveryOrder.driver_last_name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 font-bold">
|
||||
Info
|
||||
</div>
|
||||
<div class="grid grid-cols-12 p-5">
|
||||
<div class="col-span-12 text-gray-500">
|
||||
<div v-if="deliveryOrder.prime == 1">
|
||||
Prime Required: Yes
|
||||
<!-- Pricing & Fees -->
|
||||
<div class="p-4 border rounded-md space-y-2">
|
||||
<label class="label-text font-bold">Estimated / Finalized Total</label>
|
||||
<!-- Finalized View -->
|
||||
<div v-if="deliveryOrder.delivery_status == 10" class="text-2xl font-mono">
|
||||
${{ deliveryMoney.total_amount_oil }}
|
||||
</div>
|
||||
<div v-if="deliveryOrder.prime == 0">
|
||||
Prime Required: No
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="col-span-12 text-gray-500">
|
||||
<div v-if="deliveryOrder.same_day == 1">
|
||||
Same Day: Yes
|
||||
</div>
|
||||
<div v-if="deliveryOrder.same_day == 0">
|
||||
Same Day: No
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 text-gray-500">
|
||||
<div v-if="deliveryOrder.emergency == 1">
|
||||
Emergency: Yes
|
||||
</div>
|
||||
<div v-if="deliveryOrder.emergency == 0">
|
||||
Emergency: No
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
<div class="col-span-12 font-bold">
|
||||
Dispatcher Notes
|
||||
</div>
|
||||
<div class="grid grid-cols-12 p-5">
|
||||
<div class="col-span-12 text-gray-500">
|
||||
{{ deliveryOrder.dispatcher_notes }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div class="col-span-6">
|
||||
<div class="col-span-12 font-bold">
|
||||
Gallons
|
||||
</div>
|
||||
<div class="grid grid-cols-12 p-5">
|
||||
<div class="col-span-12 text-sm text-gray-500">
|
||||
<div v-if="deliveryOrder.customer_asked_for_fill == 1">FILL</div>
|
||||
<div v-else>{{ deliveryOrder.gallons_ordered }} gallons</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="deliveryOrder.delivery_status !== 10">
|
||||
<div class="col-span-12 font-bold">
|
||||
Promo
|
||||
</div>
|
||||
<div class="grid grid-cols-12 p-5">
|
||||
<div class="col-span-12 text-sm text-gray-500">
|
||||
<!-- Estimated View -->
|
||||
<div v-else class="space-y-1">
|
||||
<div v-if="deliveryOrder.promo_id !== null">
|
||||
<div class="">{{ promo.name_of_promotion }}</div>
|
||||
<div class="">{{ promo.description }}</div>
|
||||
<div class="">{{ promo.money_off_delivery }} off a gallon</div>
|
||||
<div class="">{{ promo.text_on_ticket }}</div>
|
||||
<div>Before Discount: ${{ total_amount }}</div>
|
||||
<div>Discount: -${{ discount }}</div>
|
||||
<div class="font-bold">Subtotal: ${{ total_amount_after_discount }}</div>
|
||||
</div>
|
||||
<div v-else>No Promo Added</div>
|
||||
<div v-else class="font-bold text-lg">${{ total_amount }}</div>
|
||||
|
||||
<div v-if="deliveryOrder.prime == 1" class="text-sm text-gray-400">+ ${{ pricing.price_prime }} Prime Fee</div>
|
||||
<div v-if="deliveryOrder.emergency == 1" class="text-sm text-gray-400">+ ${{ pricing.price_emergency }} Emergency Fee</div>
|
||||
<div v-if="deliveryOrder.same_day == 1" class="text-sm text-gray-400">+ ${{ pricing.price_same_day }} Same Day Fee</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Promotion -->
|
||||
<div v-if="deliveryOrder.promo_id !== null && deliveryOrder.delivery_status !== 10" class="p-4 border rounded-md">
|
||||
<label class="label-text font-bold">Promotion Applied</label>
|
||||
<div class="mt-1">
|
||||
<div class="font-semibold">{{ promo.name_of_promotion }} (${{ promo.money_off_delivery }} off)</div>
|
||||
<div class="text-sm text-gray-400">{{ promo.description }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="deliveryOrder.delivery_status !== 10">
|
||||
<div class="col-span-12 font-bold">
|
||||
Estimated Amount
|
||||
</div>
|
||||
<div class="grid grid-cols-12 p-5">
|
||||
<div class="col-span-12 text-sm text-gray-500">
|
||||
<div class="col-span-12 text-gray-500">
|
||||
<div v-if="deliveryOrder.customer_asked_for_fill == 1"> FILL (250)</div>
|
||||
<div v-else> Gallons Ordered: {{ deliveryOrder.gallons_ordered }}</div>
|
||||
<!-- Right Column: Payment, Notes, Actions -->
|
||||
<div class="space-y-4">
|
||||
<!-- Payment -->
|
||||
<div class="p-4 border rounded-md">
|
||||
<label class="label-text font-bold">Payment Method</label>
|
||||
<div class="mt-1">
|
||||
<div class="text-lg">
|
||||
<span v-if="deliveryOrder.payment_type == 0">Cash</span>
|
||||
<span v-else-if="deliveryOrder.payment_type == 1">Credit Card</span>
|
||||
<span v-else-if="deliveryOrder.payment_type == 2">Credit Card & Cash</span>
|
||||
<span v-else-if="deliveryOrder.payment_type == 3">Check</span>
|
||||
<span v-else-if="deliveryOrder.payment_type == 4">Other</span>
|
||||
<span v-else>Not Specified</span>
|
||||
</div>
|
||||
|
||||
<div v-if="deliveryOrder.promo_id !== null">
|
||||
<div class="col-span-12 text-sm text-gray-500">
|
||||
Before Discount: {{ total_amount }}
|
||||
</div>
|
||||
<div class="col-span-12 text-sm text-gray-500">
|
||||
Discount Amount: ${{ discount }}
|
||||
</div>
|
||||
<div class="col-span-12 text-sm text-gray-500">
|
||||
Promo Amount: ${{ total_amount_after_discount }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="col-span-12 text-sm text-gray-500">
|
||||
${{ total_amount }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 py-3" v-if="deliveryOrder.prime == 1">
|
||||
Prime Fee: {{ pricing.price_prime }}
|
||||
</div>
|
||||
<div class="col-span-12 py-3" v-if="deliveryOrder.emergency == 1">
|
||||
Emergency Fee: {{ pricing.price_emergency }}
|
||||
</div>
|
||||
<div class="col-span-12 py-3" v-if="deliveryOrder.same_day == 1">
|
||||
Same Day: {{ pricing.price_same_day }}
|
||||
<div v-if="userCardfound && [1, 2, 3].includes(deliveryOrder.payment_type)" class="bg-base-100 p-3 rounded-md mt-2 text-sm">
|
||||
<div class="font-mono">{{ userCard.type_of_card }}</div>
|
||||
<div class="font-mono">{{ userCard.card_number }}</div>
|
||||
<div>{{ userCard.name_on_card }}</div>
|
||||
<div>Expires: {{ userCard.expiration_month }}/{{ userCard.expiration_year }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notes & Options -->
|
||||
<div class="p-4 border rounded-md">
|
||||
<label class="label-text font-bold">Notes & Options</label>
|
||||
<div class="mt-2 space-y-1 text-sm">
|
||||
<div><span class="font-semibold">Prime Required:</span> {{ deliveryOrder.prime ? 'Yes' : 'No' }}</div>
|
||||
<div><span class="font-semibold">Same Day:</span> {{ deliveryOrder.same_day ? 'Yes' : 'No' }}</div>
|
||||
<div><span class="font-semibold">Emergency:</span> {{ deliveryOrder.emergency ? 'Yes' : 'No' }}</div>
|
||||
</div>
|
||||
<div class="prose prose-sm mt-4 max-w-none">
|
||||
<blockquote class="text-gray-400">{{ deliveryOrder.dispatcher_notes || 'No dispatcher notes provided.' }}</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex flex-wrap gap-2 pt-4">
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: deliveryOrder.id } }">
|
||||
<button class="btn btn-secondary btn-sm">Edit Delivery</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: deliveryOrder.id } }">
|
||||
<button class="btn btn-success btn-sm">Print Ticket</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div v-if="deliveryOrder.delivery_status == 10">
|
||||
<div class="col-span-12 font-bold" v-if="deliveryOrder.delivery_status == 10">
|
||||
Finalized Amount
|
||||
</div>
|
||||
<div class="grid grid-cols-12 p-5">
|
||||
<div class="col-span-12 text-sm text-gray-500">
|
||||
<div>{{ deliveryMoney.total_amount_oil }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
<div class="col-span-12 font-bold">
|
||||
Payment
|
||||
</div>
|
||||
<div class="grid grid-cols-12 p-5">
|
||||
<div class="col-span-12 text-sm">
|
||||
<div v-if="deliveryOrder.payment_type == 0">Cash</div>
|
||||
<div v-else-if="deliveryOrder.payment_type == 1">Credit Card</div>
|
||||
<div v-else-if="deliveryOrder.payment_type == 2">Credit Card & cash</div>
|
||||
<div v-else-if="deliveryOrder.payment_type == 3">Check</div>
|
||||
<div v-else-if="deliveryOrder.payment_type == 4">Check</div>
|
||||
<div v-else>No Payment Type Added</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12" v-if="deliveryOrder.payment_type == 1">
|
||||
<div class="flex" v-if="userCardfound">
|
||||
<div class="basis-1/3 p-2">
|
||||
<div class="rounded-md border-2 bg-accent">
|
||||
<div class="flex p-3">
|
||||
{{ userCard.type_of_card }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.name_on_card }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.card_number }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.expiration_month }}/ {{ userCard.expiration_year }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12" v-if="deliveryOrder.payment_type == 2">
|
||||
<div class="flex" v-if="userCardfound">
|
||||
<div class="basis-1/3 p-2">
|
||||
<div class="bg-accent rounded-md border-2 ">
|
||||
<div class="flex p-3">
|
||||
{{ userCard.type_of_card }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.name_on_card }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.card_number }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.expiration_month }}/ {{ userCard.expiration_year }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12" v-if="deliveryOrder.payment_type == 3">
|
||||
<div class="flex" v-if="userCardfound">
|
||||
<div class="basis-1/3 p-2">{{ userCard }}
|
||||
<div class="bg-accent rounded-md border-2 ">
|
||||
<div class="flex p-3">
|
||||
{{ userCard.type_of_card }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.name_on_card }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.card_number }}
|
||||
</div>
|
||||
<div class="flex p-1 pl-4">
|
||||
{{ userCard.expiration_month }}/ {{ userCard.expiration_year }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 " v-if="deliveryOrder.id">
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: deliveryOrder['customer_id'] } }">
|
||||
<button class="btn btn-neutral btn-sm">View Customer</button>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="col-span-12 pt-5" v-if="deliveryOrder.id">
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: deliveryOrder['id'] } }">
|
||||
<button class="btn btn-sm btn-secondary">Edit</button>
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 pt-5" v-if="deliveryOrder.id">
|
||||
<router-link :to="{ name: 'Ticket', params: { id: deliveryOrder['id'] } }">
|
||||
<button class="btn btn-success btn-sm">Print Ticket</button>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
|
||||
@@ -1,104 +1,123 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs pb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Cancelled Deliveries</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mt-4">Cancelled Deliveries</h1>
|
||||
|
||||
<div class="flex start pb-10 text-2xl">Cancelled Deliveries </div>
|
||||
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Title and Count -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Archived Cancelled Deliveries</h2>
|
||||
<div class="badge badge-ghost">{{ recordsLength }} items Found</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto bg-neutral">
|
||||
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Address</th>
|
||||
<th>Town</th>
|
||||
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Automatic</th>
|
||||
<th>Prime</th>
|
||||
<th>Same Day</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
<tr v-for="oil in deliveries" :key="oil['id']"> <router-link :to="{ name: 'customerProfile', params: { id: oil['customer_id'] } }">
|
||||
<td>
|
||||
<div class="hover:text-accent">{{ oil['customer_name'] }} </div>
|
||||
</td>
|
||||
</router-link>
|
||||
<td>
|
||||
<div v-if="oil['delivery_status'] == 0">Waiting</div>
|
||||
<div v-else-if="oil['delivery_status'] == 1">cancelled</div>
|
||||
<div v-else-if="oil['delivery_status'] == 2">Out for Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 3">tommorrow</div>
|
||||
<div v-else-if="oil['delivery_status'] == 4">Partial Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 5">Issue</div>
|
||||
<div v-else-if="oil['delivery_status'] == 10" class="bg-green-600">Finalized</div>
|
||||
<div v-else></div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Delivery #</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Town / Address</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Options</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="oil in deliveries" :key="oil.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>{{ oil.id }}</td>
|
||||
<td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil.customer_id } }" class="link link-hover">
|
||||
{{ oil.customer_name }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-sm badge-error">Cancelled</span>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ oil.customer_town }}</div>
|
||||
<div class="text-xs opacity-70">{{ oil.customer_address }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.customer_asked_for_fill == 1" class="badge badge-info">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</td>
|
||||
<td>{{ oil.expected_delivery_date }}</td>
|
||||
<td>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span v-if="oil.prime" class="badge badge-error badge-xs">PRIME</span>
|
||||
<span v-if="oil.same_day" class="badge badge-error badge-xs">SAME DAY</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="oil in deliveries" :key="oil.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ oil.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">Delivery #{{ oil.id }}</p>
|
||||
</div>
|
||||
<div class="badge badge-error">
|
||||
Cancelled
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div v-if="oil.prime" class="badge badge-error badge-sm">PRIME</div>
|
||||
<div v-if="oil.same_day" class="badge badge-error badge-sm">SAME DAY</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Address:</strong> {{ oil.customer_address }}</p>
|
||||
<p><strong class="font-semibold">Town:</strong> {{ oil.customer_town }}</p>
|
||||
<p><strong class="font-semibold">Gallons:</strong>
|
||||
<span v-if="oil.customer_asked_for_fill" class="badge badge-info badge-xs">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</p>
|
||||
<p><strong class="font-semibold">Date:</strong> {{ oil.expected_delivery_date }}</p>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
<td>{{ oil['customer_address'] }}</td>
|
||||
<td>{{ oil['customer_town'] }}</td>
|
||||
|
||||
<td>
|
||||
<div v-if="oil['customer_asked_for_fill'] == 1">Fill</div>
|
||||
<div v-else> {{ oil['gallons_ordered'] }}</div>
|
||||
</td>
|
||||
<td>{{ oil['expected_delivery_date'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['automatic'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['prime'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['same_day'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
|
||||
<td class="flex gap-5">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary">View delivery</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary">Edit Delivery</button>
|
||||
</router-link>
|
||||
<!-- <button @click.prevent="deleteCall(oil['id'])" class="btn bg-red-600 text-black">DELETE</button> -->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50"
|
||||
:options="options" class="mt-10">
|
||||
</pagination>
|
||||
<div class="flex justify-center mb-10"> {{ recordsLength }} items Found</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options">
|
||||
</pagination>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer/>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -124,7 +143,7 @@ export default defineComponent({
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [],
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
|
||||
@@ -1,99 +1,123 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs pb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Delivered Deliveries</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mt-4">Delivered Deliveries</h1>
|
||||
|
||||
<div class="flex start pb-10 text-2xl">Delivered Deliveries </div>
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Title and Count -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Deliveries Awaiting Finalization</h2>
|
||||
<div class="badge badge-ghost">{{ recordsLength }} items Found</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="overflow-x-auto bg-neutral">
|
||||
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Address</th>
|
||||
<th>Town</th>
|
||||
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Automatic</th>
|
||||
<th>Prime</th>
|
||||
<th>Same Day</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
<tr v-for="oil in deliveries" :key="oil['id']">
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil['customer_id'] } }">
|
||||
<td>
|
||||
<div class="hover:text-accent">{{ oil['customer_name'] }} </div>
|
||||
</td>
|
||||
</router-link>
|
||||
<td>
|
||||
<div v-if="oil['delivery_status'] == 0">Waiting</div>
|
||||
<div v-else-if="oil['delivery_status'] == 1">cancelled</div>
|
||||
<div v-else-if="oil['delivery_status'] == 2">Out for Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 3">tommorrow</div>
|
||||
<div v-else-if="oil['delivery_status'] == 4">Partial Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 5">Issue</div>
|
||||
<div v-else-if="oil['delivery_status'] == 10" class="bg-green-600">Finalized</div>
|
||||
<div v-else></div>
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Delivery #</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Town / Address</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Options</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="oil in deliveries" :key="oil.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>{{ oil.id }}</td>
|
||||
<td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil.customer_id } }" class="link link-hover">
|
||||
{{ oil.customer_name }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-sm badge-success">Delivered</span>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ oil.customer_town }}</div>
|
||||
<div class="text-xs opacity-70">{{ oil.customer_address }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.customer_asked_for_fill == 1" class="badge badge-info">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</td>
|
||||
<td>{{ oil.expected_delivery_date }}</td>
|
||||
<td>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span v-if="oil.prime" class="badge badge-error badge-xs">PRIME</span>
|
||||
<span v-if="oil.same_day" class="badge badge-error badge-xs">SAME DAY</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" class="btn btn-sm btn-accent">
|
||||
Finalize
|
||||
</router-link>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="oil in deliveries" :key="oil.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ oil.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">Delivery #{{ oil.id }}</p>
|
||||
</div>
|
||||
<div class="badge badge-success">
|
||||
Delivered
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div v-if="oil.prime" class="badge badge-error badge-sm">PRIME</div>
|
||||
<div v-if="oil.same_day" class="badge badge-error badge-sm">SAME DAY</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Address:</strong> {{ oil.customer_address }}</p>
|
||||
<p><strong class="font-semibold">Town:</strong> {{ oil.customer_town }}</p>
|
||||
<p><strong class="font-semibold">Gallons:</strong>
|
||||
<span v-if="oil.customer_asked_for_fill" class="badge badge-info badge-xs">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</p>
|
||||
<p><strong class="font-semibold">Date:</strong> {{ oil.expected_delivery_date }}</p>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
<td>{{ oil['customer_address'] }}</td>
|
||||
<td>{{ oil['customer_town'] }}</td>
|
||||
|
||||
<td>
|
||||
<div v-if="oil['customer_asked_for_fill'] == 1">Fill</div>
|
||||
<div v-else> {{ oil['gallons_ordered'] }}</div>
|
||||
</td>
|
||||
<td>{{ oil['expected_delivery_date'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['automatic'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['prime'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['same_day'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
|
||||
<td class="flex gap-5">
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-success btn-sm">Finalize</button>
|
||||
</router-link>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-2">
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" class="btn btn-sm btn-accent">
|
||||
Finalize
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50"
|
||||
:options="options" class="mt-10">
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options">
|
||||
</pagination>
|
||||
<div class="flex justify-center mb-10"> {{ recordsLength }} items Found</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer/>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -119,7 +143,7 @@ export default defineComponent({
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [],
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
|
||||
@@ -1,111 +1,125 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs pb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Finalized Deliveries</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex start pb-10 text-2xl">Finalized Delivery </div>
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Title and Count -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Completed and Finalized Deliveries</h2>
|
||||
<div class="badge badge-ghost">{{ recordsLength }} items Found</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="overflow-x-auto bg-neutral">
|
||||
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ticket Id</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
|
||||
<th>Address</th>
|
||||
<th>Town</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Automatic</th>
|
||||
<th>Prime</th>
|
||||
<th>Same Day</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Ticket #</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Town / Address</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Options</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
<tr v-for="oil in deliveries" :key="oil['id']">
|
||||
<td>{{ oil['id'] }} </td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil['customer_id'] } }">
|
||||
<td>
|
||||
<div class="hover:text-accent">{{ oil['customer_name'] }} </div>
|
||||
<tr v-for="oil in deliveries" :key="oil.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>{{ oil.id }}</td>
|
||||
<td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil.customer_id } }" class="link link-hover">
|
||||
{{ oil.customer_name }}
|
||||
</router-link>
|
||||
</td>
|
||||
</router-link>
|
||||
<td>
|
||||
<div v-if="oil['delivery_status'] == 0">Waiting</div>
|
||||
<div v-else-if="oil['delivery_status'] == 1">cancelled</div>
|
||||
<div v-else-if="oil['delivery_status'] == 2">Out for Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 3">tommorrow</div>
|
||||
<div v-else-if="oil['delivery_status'] == 4">Partial Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 5">Issue</div>
|
||||
<div v-else-if="oil['delivery_status'] == 10" class="bg-green-600">Finalized</div>
|
||||
<div v-else></div>
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
<td>{{ oil['customer_address'] }}</td>
|
||||
<td>{{ oil['customer_town'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['customer_asked_for_fill'] == 1">Fill</div>
|
||||
<div v-else> {{ oil['gallons_ordered'] }}</div>
|
||||
</td>
|
||||
<td>{{ oil['expected_delivery_date'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['automatic'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['prime'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['same_day'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
|
||||
<td class="flex gap-5">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">View Delivery</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Edit Delivery</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-success btn-sm">
|
||||
Print Ticket
|
||||
</button>
|
||||
</router-link>
|
||||
<!-- <button @click.prevent="deleteCall(oil['id'])" class="btn bg-red-600 text-black">Delete</button> -->
|
||||
</td>
|
||||
</tr>
|
||||
<td>
|
||||
<span class="badge badge-sm badge-success">Finalized</span>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ oil.customer_town }}</div>
|
||||
<div class="text-xs opacity-70">{{ oil.customer_address }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.customer_asked_for_fill == 1" class="badge badge-info">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</td>
|
||||
<td>{{ oil.expected_delivery_date }}</td>
|
||||
<td>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span v-if="oil.prime" class="badge badge-error badge-xs">PRIME</span>
|
||||
<span v-if="oil.same_day" class="badge badge-error badge-xs">SAME DAY</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50"
|
||||
:options="options" class="mt-10">
|
||||
</pagination>
|
||||
<div class="flex justify-center mb-10"> {{ recordsLength }} items Found</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="oil in deliveries" :key="oil.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ oil.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">Ticket #{{ oil.id }}</p>
|
||||
</div>
|
||||
<div class="badge badge-success">
|
||||
Finalized
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div v-if="oil.prime" class="badge badge-error badge-sm">PRIME</div>
|
||||
<div v-if="oil.same_day" class="badge badge-error badge-sm">SAME DAY</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Address:</strong> {{ oil.customer_address }}</p>
|
||||
<p><strong class="font-semibold">Town:</strong> {{ oil.customer_town }}</p>
|
||||
<p><strong class="font-semibold">Gallons:</strong>
|
||||
<span v-if="oil.customer_asked_for_fill" class="badge badge-info badge-xs">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</p>
|
||||
<p><strong class="font-semibold">Date:</strong> {{ oil.expected_delivery_date }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options">
|
||||
</pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer/>
|
||||
</template>
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
@@ -130,7 +144,7 @@
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [],
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
|
||||
@@ -1,111 +1,124 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs pb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex start pb-10 text-2xl">Issue with Delivery </div>
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Title and Count -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Deliveries Requiring Attention</h2>
|
||||
<div class="badge badge-ghost">{{ recordsLength }} items Found</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="overflow-x-auto bg-neutral">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ticket Id</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
|
||||
<th>Address</th>
|
||||
<th>Town</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Automatic</th>
|
||||
<th>Prime</th>
|
||||
<th>Same Day</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
<tr v-for="oil in deliveries" :key="oil['id']">
|
||||
<td>{{ oil['id'] }} </td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil['customer_id'] } }">
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Ticket #</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Town / Address</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Options</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="oil in deliveries" :key="oil.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>{{ oil.id }}</td>
|
||||
<td>
|
||||
<div class="hover:text-accent">{{ oil['customer_name'] }} </div>
|
||||
</td>
|
||||
</router-link>
|
||||
<td>
|
||||
<div v-if="oil['delivery_status'] == 0">Waiting</div>
|
||||
<div v-else-if="oil['delivery_status'] == 1">cancelled</div>
|
||||
<div v-else-if="oil['delivery_status'] == 2">Out for Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 3">tommorrow</div>
|
||||
<div v-else-if="oil['delivery_status'] == 4">Partial Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 5">Issue</div>
|
||||
<div v-else-if="oil['delivery_status'] == 10" class="bg-green-600">Finalized</div>
|
||||
<div v-else></div>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil.customer_id } }" class="link link-hover">
|
||||
{{ oil.customer_name }}
|
||||
</router-link>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-sm badge-error">Issue</span>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ oil.customer_town }}</div>
|
||||
<div class="text-xs opacity-70">{{ oil.customer_address }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.customer_asked_for_fill == 1" class="badge badge-info">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</td>
|
||||
<td>{{ oil.expected_delivery_date }}</td>
|
||||
<td>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span v-if="oil.prime" class="badge badge-error badge-xs">PRIME</span>
|
||||
<span v-if="oil.same_day" class="badge badge-error badge-xs">SAME DAY</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="oil in deliveries" :key="oil.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ oil.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">Ticket #{{ oil.id }}</p>
|
||||
</div>
|
||||
<div class="badge badge-error">
|
||||
Issue
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div v-if="oil.prime" class="badge badge-error badge-sm">PRIME</div>
|
||||
<div v-if="oil.same_day" class="badge badge-error badge-sm">SAME DAY</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Address:</strong> {{ oil.customer_address }}</p>
|
||||
<p><strong class="font-semibold">Town:</strong> {{ oil.customer_town }}</p>
|
||||
<p><strong class="font-semibold">Gallons:</strong>
|
||||
<span v-if="oil.customer_asked_for_fill" class="badge badge-info badge-xs">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</p>
|
||||
<p><strong class="font-semibold">Date:</strong> {{ oil.expected_delivery_date }}</p>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td>{{ oil['customer_address'] }}</td>
|
||||
<td>{{ oil['customer_town'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['customer_asked_for_fill'] == 1">Fill</div>
|
||||
<div v-else> {{ oil['gallons_ordered'] }}</div>
|
||||
</td>
|
||||
<td>{{ oil['expected_delivery_date'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['automatic'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['prime'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['same_day'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
|
||||
<td class="flex gap-5">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary">View</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary ">Edit</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">
|
||||
Print Ticket
|
||||
</button>
|
||||
</router-link>
|
||||
|
||||
<!-- <button @click.prevent="deleteCall(oil['id'])" class="btn bg-red-600 text-black">Delete</button> -->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50"
|
||||
:options="options" class="mt-10">
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options">
|
||||
</pagination>
|
||||
<div class="flex justify-center mb-10"> {{ recordsLength }} items Found</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer/>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -131,7 +144,7 @@ export default defineComponent({
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [],
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
|
||||
@@ -1,139 +1,160 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
<div class="flex">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs pb-10">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Title and Count -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Deliveries Awaiting Payment</h2>
|
||||
<div class="badge badge-ghost">{{ recordsLength }} items Found</div>
|
||||
</div>
|
||||
|
||||
<div class="flex start pb-10 text-2xl">Pending Payment Deliveries </div>
|
||||
<div class="flex justify-end pb-5">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead class=" bg-neutral">
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Delivery #</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
|
||||
<th>Address</th>
|
||||
<th>Town</th>
|
||||
<th>Town / Address</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Auto</th>
|
||||
<th>Prime</th>
|
||||
<th>Same Day</th>
|
||||
<th>Emergency</th>
|
||||
<th>Payment</th>
|
||||
<th></th>
|
||||
<th>Options</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-neutral">
|
||||
<!-- row 1 -->
|
||||
|
||||
<tr v-for="oil in deliveries" :key="oil['id']">
|
||||
|
||||
<td>{{ oil['id'] }} </td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil['customer_id'] } }">
|
||||
<td>
|
||||
<div class="hover:text-accent">{{ oil['customer_name'] }} </div>
|
||||
</td>
|
||||
</router-link>
|
||||
<tbody>
|
||||
<tr v-for="oil in deliveries" :key="oil.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>{{ oil.id }}</td>
|
||||
<td>
|
||||
<div v-if="oil['delivery_status'] == 0">Waiting</div>
|
||||
<div v-else-if="oil['delivery_status'] == 1">cancelled</div>
|
||||
<div v-else-if="oil['delivery_status'] == 2">Out for Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 3">tommorrow</div>
|
||||
<div v-else-if="oil['delivery_status'] == 4">Partial Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 5">Issue</div>
|
||||
<div v-else-if="oil['delivery_status'] == 10" class="bg-green-600">Finalized</div>
|
||||
<div v-else></div>
|
||||
|
||||
|
||||
</td>
|
||||
|
||||
|
||||
<td>{{ oil['customer_address'] }}</td>
|
||||
<td>{{ oil['customer_town'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['customer_asked_for_fill'] == 1">Fill</div>
|
||||
<div v-else> {{ oil['gallons_ordered'] }}</div>
|
||||
</td>
|
||||
<td>{{ oil['expected_delivery_date'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['automatic'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['prime'] == 0">No</div>
|
||||
<div v-else class="text-red-600">Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['same_day'] == 0">No</div>
|
||||
<div v-else class="text-red-600">Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['emergency'] == 0">No</div>
|
||||
<div v-else class="text-red-600">Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
<div v-if="oil['payment_type'] == 0">Cash</div>
|
||||
<div v-else-if="oil['payment_type'] == 1">CC</div>
|
||||
<div v-else-if="oil['payment_type'] == 2">Cash/CC</div>
|
||||
<div v-else-if="oil['payment_type'] == 3">Check</div>
|
||||
<div v-else-if="oil['payment_type'] == 4">Other</div>
|
||||
|
||||
<div v-else></div>
|
||||
|
||||
</td>
|
||||
<td class="flex gap-2">
|
||||
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">View Delivery</button>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil.customer_id } }" class="link link-hover">
|
||||
{{ oil.customer_name }}
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Edit Delivery</button>
|
||||
</router-link>
|
||||
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Finalize</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-success btn-sm">
|
||||
Print Ticket
|
||||
</button>
|
||||
</router-link>
|
||||
<!-- <button @click.prevent="deleteCall(oil['id'])" class="btn btn-error btn-sm">
|
||||
Delete
|
||||
</button> -->
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-sm" :class="{
|
||||
'badge-warning': oil.delivery_status == 0,
|
||||
'badge-success': oil.delivery_status == 10,
|
||||
'badge-info': oil.delivery_status == 2,
|
||||
'badge-error': [1, 5].includes(oil.delivery_status),
|
||||
}">
|
||||
<span v-if="oil.delivery_status == 0">Waiting</span>
|
||||
<span v-else-if="oil.delivery_status == 1">Cancelled</span>
|
||||
<span v-else-if="oil.delivery_status == 2">Out for Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 10">Finalized</span>
|
||||
<span v-else>N/A</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ oil.customer_town }}</div>
|
||||
<div class="text-xs opacity-70">{{ oil.customer_address }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.customer_asked_for_fill == 1" class="badge badge-info">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.payment_type == 0">Cash</span>
|
||||
<span v-else-if="oil.payment_type == 1">CC</span>
|
||||
<span v-else-if="oil.payment_type == 2">Cash/CC</span>
|
||||
<span v-else-if="oil.payment_type == 3">Check</span>
|
||||
<span v-else-if="oil.payment_type == 4">Other</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span v-if="oil.prime" class="badge badge-error badge-xs">PRIME</span>
|
||||
<span v-if="oil.same_day" class="badge badge-error badge-xs">SAME DAY</span>
|
||||
<span v-if="oil.emergency" class="badge badge-error badge-xs">EMERGENCY</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" class="btn btn-sm btn-accent">Finalize</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options" class="mt-10">
|
||||
</pagination>
|
||||
<div class="flex justify-center mb-10"> {{ recordsLength }} items Found</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="oil in deliveries" :key="oil.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ oil.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">Delivery #{{ oil.id }}</p>
|
||||
</div>
|
||||
<div class="badge" :class="{
|
||||
'badge-warning': oil.delivery_status == 0,
|
||||
'badge-success': oil.delivery_status == 10,
|
||||
'badge-info': oil.delivery_status == 2,
|
||||
'badge-error': [1, 5].includes(oil.delivery_status),
|
||||
}">
|
||||
<span v-if="oil.delivery_status == 0">Waiting</span>
|
||||
<span v-else-if="oil.delivery_status == 1">Cancelled</span>
|
||||
<span v-else-if="oil.delivery_status == 2">Out for Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 10">Finalized</span>
|
||||
<span v-else>N/A</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div v-if="oil.prime" class="badge badge-error badge-sm">PRIME</div>
|
||||
<div v-if="oil.same_day" class="badge badge-error badge-sm">SAME DAY</div>
|
||||
<div v-if="oil.emergency" class="badge badge-error badge-sm">EMERGENCY</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Address:</strong> {{ oil.customer_address }}</p>
|
||||
<p><strong class="font-semibold">Town:</strong> {{ oil.customer_town }}</p>
|
||||
<p><strong class="font-semibold">Gallons:</strong>
|
||||
<span v-if="oil.customer_asked_for_fill" class="badge badge-info badge-xs">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</p>
|
||||
<p><strong class="font-semibold">Payment:</strong>
|
||||
<span v-if="oil.payment_type == 0">Cash</span>
|
||||
<span v-else-if="oil.payment_type == 1">CC</span>
|
||||
<span v-else-if="oil.payment_type == 2">Cash/CC</span>
|
||||
<span v-else-if="oil.payment_type == 3">Check</span>
|
||||
<span v-else-if="oil.payment_type == 4">Other</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" class="btn btn-sm btn-accent">Finalize</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options">
|
||||
</pagination>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
@@ -158,7 +179,7 @@
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [],
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
|
||||
@@ -1,138 +1,170 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs pb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Today's Deliveries</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="flex start pb-10 text-2xl">Todays Deliveries </div>
|
||||
<div class="flex justify-end pb-5">
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Search and Count -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Todays Deliveries</h2>
|
||||
<div class="form-control">
|
||||
<label class="label pt-1 pb-0">
|
||||
<span class="label-text-alt">{{ recordsLength }} deliveries found</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead class=" bg-neutral">
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Town</th>
|
||||
<th>Address</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Auto</th>
|
||||
<th>Prime</th>
|
||||
<th>Same Day</th>
|
||||
<th>Emergency</th>
|
||||
<th>Payment</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-neutral">
|
||||
<!-- row 1 -->
|
||||
|
||||
<tr v-for="oil in deliveries" :key="oil['id']">
|
||||
|
||||
<td>{{ oil['id'] }} </td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil['customer_id'] } }">
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Delivery #</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Town / Address</th>
|
||||
<th>Gallons</th>
|
||||
<th>Options</th>
|
||||
<th>Payment</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="oil in deliveries" :key="oil.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>{{ oil.id }}</td>
|
||||
<td>
|
||||
<div class="hover:text-accent">{{ oil['customer_name'] }} </div>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil.customer_id } }" class="link link-hover">
|
||||
{{ oil.customer_name }}
|
||||
</router-link>
|
||||
</td>
|
||||
</router-link>
|
||||
<td>
|
||||
<div v-if="oil['delivery_status'] == 0">Waiting</div>
|
||||
<div v-else-if="oil['delivery_status'] == 1">cancelled</div>
|
||||
<div v-else-if="oil['delivery_status'] == 2">Out for Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 3">tommorrow</div>
|
||||
<div v-else-if="oil['delivery_status'] == 4">Partial Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 5">Issue</div>
|
||||
<div v-else-if="oil['delivery_status'] == 10" class="bg-green-600">Finalized</div>
|
||||
<div v-else></div>
|
||||
<td>
|
||||
<span class="badge badge-sm" :class="{
|
||||
'badge-warning': oil.delivery_status == 0,
|
||||
'badge-success': oil.delivery_status == 10,
|
||||
'badge-info': oil.delivery_status == 2,
|
||||
'badge-error': [1, 5].includes(oil.delivery_status),
|
||||
}">
|
||||
<span v-if="oil.delivery_status == 0">Waiting</span>
|
||||
<span v-else-if="oil.delivery_status == 1">Cancelled</span>
|
||||
<span v-else-if="oil.delivery_status == 2">Out for Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 3">Tomorrow</span>
|
||||
<span v-else-if="oil.delivery_status == 4">Partial</span>
|
||||
<span v-else-if="oil.delivery_status == 5">Issue</span>
|
||||
<span v-else-if="oil.delivery_status == 10">Finalized</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ oil.customer_town }}</div>
|
||||
<div class="text-xs opacity-70">{{ oil.customer_address }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.customer_asked_for_fill == 1" class="badge badge-info">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span v-if="oil.prime" class="badge badge-error badge-xs">PRIME</span>
|
||||
<span v-if="oil.same_day" class="badge badge-error badge-xs">SAME DAY</span>
|
||||
<span v-if="oil.emergency" class="badge badge-error badge-xs">EMERGENCY</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.payment_type == 0">Cash</span>
|
||||
<span v-else-if="oil.payment_type == 1">CC</span>
|
||||
<span v-else-if="oil.payment_type == 2">Cash/CC</span>
|
||||
<span v-else-if="oil.payment_type == 3">Check</span>
|
||||
<span v-else-if="oil.payment_type == 4">Other</span>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" v-if="oil.delivery_status != 10" class="btn btn-sm btn-accent">Finalize</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="oil in deliveries" :key="oil.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ oil.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">Delivery #{{ oil.id }}</p>
|
||||
</div>
|
||||
<div class="badge" :class="{
|
||||
'badge-warning': oil.delivery_status == 0,
|
||||
'badge-success': oil.delivery_status == 10,
|
||||
'badge-info': oil.delivery_status == 2,
|
||||
'badge-error': [1, 5].includes(oil.delivery_status),
|
||||
}">
|
||||
<span v-if="oil.delivery_status == 0">Waiting</span>
|
||||
<span v-else-if="oil.delivery_status == 1">Cancelled</span>
|
||||
<span v-else-if="oil.delivery_status == 2">Out for Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 3">Tomorrow</span>
|
||||
<span v-else-if="oil.delivery_status == 4">Partial</span>
|
||||
<span v-else-if="oil.delivery_status == 5">Issue</span>
|
||||
<span v-else-if="oil.delivery_status == 10">Finalized</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</td>
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div v-if="oil.prime" class="badge badge-error badge-sm">PRIME</div>
|
||||
<div v-if="oil.same_day" class="badge badge-error badge-sm">SAME DAY</div>
|
||||
<div v-if="oil.emergency" class="badge badge-error badge-sm">EMERGENCY</div>
|
||||
</div>
|
||||
|
||||
<td>{{ oil['customer_town'] }}</td>
|
||||
<td>{{ oil['customer_address'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['customer_asked_for_fill'] == 1">Fill</div>
|
||||
<div v-else> {{ oil['gallons_ordered'] }}</div>
|
||||
</td>
|
||||
<td>{{ oil['expected_delivery_date'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['automatic'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['prime'] == 0">No</div>
|
||||
<div v-else class="text-red-600">Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['same_day'] == 0">No</div>
|
||||
<div v-else class="text-red-600">Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['emergency'] == 0">No</div>
|
||||
<div v-else class="text-red-600">Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
<div v-if="oil['payment_type'] == 0">Cash</div>
|
||||
<div v-else-if="oil['payment_type'] == 1">CC</div>
|
||||
<div v-else-if="oil['payment_type'] == 2">Cash/CC</div>
|
||||
<div v-else-if="oil['payment_type'] == 3">Check</div>
|
||||
<div v-else-if="oil['payment_type'] == 4">Other</div>
|
||||
|
||||
<div v-else></div>
|
||||
|
||||
</td>
|
||||
<td class="flex gap-2">
|
||||
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">View Delivery</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Edit Delivery</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Finalize</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-success btn-sm">
|
||||
Print Ticket
|
||||
</button>
|
||||
</router-link>
|
||||
<!-- <button @click.prevent="deleteCall(oil['id'])" class="btn btn-error btn-sm">
|
||||
Delete
|
||||
</button> -->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Address:</strong> {{ oil.customer_address }}</p>
|
||||
<p><strong class="font-semibold">Town:</strong> {{ oil.customer_town }}</p>
|
||||
<p><strong class="font-semibold">Gallons:</strong>
|
||||
<span v-if="oil.customer_asked_for_fill" class="badge badge-info badge-xs">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</p>
|
||||
<p><strong class="font-semibold">Payment:</strong>
|
||||
<span v-if="oil.payment_type == 0">Cash</span>
|
||||
<span v-else-if="oil.payment_type == 1">CC</span>
|
||||
<span v-else-if="oil.payment_type == 2">Cash/CC</span>
|
||||
<span v-else-if="oil.payment_type == 3">Check</span>
|
||||
<span v-else-if="oil.payment_type == 4">Other</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" v-if="oil.delivery_status != 10" class="btn btn-sm btn-accent">Finalize</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options">
|
||||
</pagination>
|
||||
</div>
|
||||
</div>
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options"
|
||||
class="mt-10">
|
||||
</pagination>
|
||||
<div class="flex justify-center mb-10"> {{ recordsLength }} items Found</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
@@ -156,7 +188,7 @@ export default defineComponent({
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [],
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
|
||||
@@ -1,115 +1,146 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex start pb-10 text-2xl">Tommorrows Deliveries </div>
|
||||
<div class="flex justify-end pb-5">
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Title and Count (No Search Input) -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Deliveries Scheduled</h2>
|
||||
<div class="badge badge-ghost">{{ recordsLength }} deliveries found</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="overflow-x-auto bg-neutral">
|
||||
<div class="flex start"> </div>
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account Id</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
|
||||
<th>Address</th>
|
||||
<th>Town</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Automatic</th>
|
||||
<th>Prime</th>
|
||||
<th>Same Day</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
<tr v-for="oil in deliveries" :key="oil['id']">
|
||||
<td>{{ oil['id'] }} </td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil['customer_id'] } }">
|
||||
|
||||
<td>
|
||||
<div class="hover:text-accent">{{ oil['customer_name'] }} </div>
|
||||
</td>
|
||||
</router-link>
|
||||
<td>
|
||||
<div v-if="oil['delivery_status'] == 0">Waiting</div>
|
||||
<div v-else-if="oil['delivery_status'] == 1">cancelled</div>
|
||||
<div v-else-if="oil['delivery_status'] == 2">Out for Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 3">tommorrow</div>
|
||||
<div v-else-if="oil['delivery_status'] == 4">Partial Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 5">Issue</div>
|
||||
<div v-else-if="oil['delivery_status'] == 10">Finalized</div>
|
||||
<div v-else></div>
|
||||
</td>
|
||||
|
||||
|
||||
<td>{{ oil['customer_address'] }}</td>
|
||||
<td>{{ oil['customer_town'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['customer_asked_for_fill'] == 1">Fill</div>
|
||||
<div v-else> {{ oil['gallons_ordered'] }}</div>
|
||||
</td>
|
||||
<td>{{ oil['expected_delivery_date'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['automatic'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['prime'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['same_day'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
|
||||
<td class="flex gap-5">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">View Delivery</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Edit Delivery</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Finalize</button>
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Delivery #</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Town / Address</th>
|
||||
<th>Gallons</th>
|
||||
<th>Options</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="oil in deliveries" :key="oil.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>{{ oil.id }}</td>
|
||||
<td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil.customer_id } }" class="link link-hover">
|
||||
{{ oil.customer_name }}
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-success btn-sm ">
|
||||
Print Ticket
|
||||
</button>
|
||||
</router-link>
|
||||
<!-- <button @click.prevent="deleteCall(oil['id'])" class="btn btn-error btn-sm">
|
||||
Delete
|
||||
</button> -->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-sm" :class="{
|
||||
'badge-warning': oil.delivery_status == 0,
|
||||
'badge-success': oil.delivery_status == 10,
|
||||
'badge-info': oil.delivery_status == 2,
|
||||
'badge-error': [1, 5].includes(oil.delivery_status),
|
||||
}">
|
||||
<span v-if="oil.delivery_status == 0">Waiting</span>
|
||||
<span v-else-if="oil.delivery_status == 1">Cancelled</span>
|
||||
<span v-else-if="oil.delivery_status == 2">Out for Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 3">Tomorrow</span>
|
||||
<span v-else-if="oil.delivery_status == 4">Partial</span>
|
||||
<span v-else-if="oil.delivery_status == 5">Issue</span>
|
||||
<span v-else-if="oil.delivery_status == 10">Finalized</span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ oil.customer_town }}</div>
|
||||
<div class="text-xs opacity-70">{{ oil.customer_address }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.customer_asked_for_fill == 1" class="badge badge-info">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span v-if="oil.prime" class="badge badge-error badge-xs">PRIME</span>
|
||||
<span v-if="oil.same_day" class="badge badge-error badge-xs">SAME DAY</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" v-if="oil.delivery_status != 10" class="btn btn-sm btn-accent">Finalize</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="oil in deliveries" :key="oil.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ oil.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">Delivery #{{ oil.id }}</p>
|
||||
</div>
|
||||
<div class="badge" :class="{
|
||||
'badge-warning': oil.delivery_status == 0,
|
||||
'badge-success': oil.delivery_status == 10,
|
||||
'badge-info': oil.delivery_status == 2,
|
||||
'badge-error': [1, 5].includes(oil.delivery_status),
|
||||
}">
|
||||
<span v-if="oil.delivery_status == 0">Waiting</span>
|
||||
<span v-else-if="oil.delivery_status == 1">Cancelled</span>
|
||||
<span v-else-if="oil.delivery_status == 2">Out for Delivery</span>
|
||||
<span v-else-if="oil.delivery_status == 3">Tomorrow</span>
|
||||
<span v-else-if="oil.delivery_status == 4">Partial</span>
|
||||
<span v-else-if="oil.delivery_status == 5">Issue</span>
|
||||
<span v-else-if="oil.delivery_status == 10">Finalized</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div v-if="oil.prime" class="badge badge-error badge-sm">PRIME</div>
|
||||
<div v-if="oil.same_day" class="badge badge-error badge-sm">SAME DAY</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Address:</strong> {{ oil.customer_address }}</p>
|
||||
<p><strong class="font-semibold">Town:</strong> {{ oil.customer_town }}</p>
|
||||
<p><strong class="font-semibold">Gallons:</strong>
|
||||
<span v-if="oil.customer_asked_for_fill" class="badge badge-info badge-xs">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</p>
|
||||
<p><strong class="font-semibold">Date:</strong> {{ oil.expected_delivery_date }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" v-if="oil.delivery_status != 10" class="btn btn-sm btn-accent">Finalize</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options" class="mt-10">
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options">
|
||||
</pagination>
|
||||
<!-- <div class="flex justify-center mb-10"> {{ recordsLength }} items Found</div> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
@@ -136,7 +167,7 @@ export default defineComponent({
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [],
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
|
||||
@@ -1,115 +1,126 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs pb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex start pb-10 text-2xl">Waiting Deliveries </div>
|
||||
<div class="flex justify-end pb-5">
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Title and Count -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Deliveries Awaiting Dispatch</h2>
|
||||
<div class="badge badge-ghost">{{ recordsLength }} deliveries found</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="overflow-x-auto bg-neutral">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Account Id</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
|
||||
<th>Address</th>
|
||||
<th>Town</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Automatic</th>
|
||||
<th>Prime</th>
|
||||
<th>Same Day</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
<tr v-for="oil in deliveries" :key="oil['id']">
|
||||
<td>{{ oil['id'] }} </td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil['customer_id'] } }">
|
||||
<td>
|
||||
<div class="hover:text-accent">{{ oil['customer_name'] }} </div>
|
||||
</td>
|
||||
</router-link>
|
||||
<td>
|
||||
<div v-if="oil['delivery_status'] == 0">Waiting</div>
|
||||
<div v-else-if="oil['delivery_status'] == 1">delivered</div>
|
||||
<div v-else-if="oil['delivery_status'] == 2">Out for Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 3">Cancelled</div>
|
||||
<div v-else-if="oil['delivery_status'] == 4">Partial Delivery</div>
|
||||
<div v-else-if="oil['delivery_status'] == 5">Issue</div>
|
||||
<div v-else-if="oil['delivery_status'] == 10">Finalized</div>
|
||||
<div v-else></div>
|
||||
</td>
|
||||
|
||||
|
||||
<td>{{ oil['customer_address'] }}</td>
|
||||
<td>{{ oil['customer_town'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['customer_asked_for_fill'] == 1">Fill</div>
|
||||
<div v-else> {{ oil['gallons_ordered'] }}</div>
|
||||
</td>
|
||||
<td>{{ oil['expected_delivery_date'] }}</td>
|
||||
<td>
|
||||
<div v-if="oil['automatic'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['prime'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
<td>
|
||||
<div v-if="oil['same_day'] == 0">No</div>
|
||||
<div v-else>Yes</div>
|
||||
</td>
|
||||
|
||||
<td class="flex gap-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">View Delivery</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Edit Delivery</button>
|
||||
</router-link>
|
||||
|
||||
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Finalize</button>
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Delivery #</th>
|
||||
<th>Name</th>
|
||||
<th>Status</th>
|
||||
<th>Town / Address</th>
|
||||
<th>Gallons</th>
|
||||
<th>Date</th>
|
||||
<th>Options</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="oil in deliveries" :key="oil.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>{{ oil.id }}</td>
|
||||
<td>
|
||||
<router-link :to="{ name: 'customerProfile', params: { id: oil.customer_id } }" class="link link-hover">
|
||||
{{ oil.customer_name }}
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil['id'] } }">
|
||||
<button class="btn btn-success btn-sm">
|
||||
Print Ticket
|
||||
</button>
|
||||
</router-link>
|
||||
<!-- <button @click.prevent="deleteCall(oil['id'])" class="btn btn-error btn-sm">
|
||||
Delete
|
||||
</button> -->
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-sm badge-warning">
|
||||
Waiting
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div>{{ oil.customer_town }}</div>
|
||||
<div class="text-xs opacity-70">{{ oil.customer_address }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<span v-if="oil.customer_asked_for_fill == 1" class="badge badge-info">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</td>
|
||||
<td>{{ oil.expected_delivery_date }}</td>
|
||||
<td>
|
||||
<div class="flex flex-col gap-1">
|
||||
<span v-if="oil.prime" class="badge badge-error badge-xs">PRIME</span>
|
||||
<span v-if="oil.same_day" class="badge badge-error badge-xs">SAME DAY</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" class="btn btn-sm btn-accent">Finalize</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="oil in deliveries" :key="oil.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ oil.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">Delivery #{{ oil.id }}</p>
|
||||
</div>
|
||||
<div class="badge badge-warning">
|
||||
Waiting
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2 mt-2">
|
||||
<div v-if="oil.prime" class="badge badge-error badge-sm">PRIME</div>
|
||||
<div v-if="oil.same_day" class="badge badge-error badge-sm">SAME DAY</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Address:</strong> {{ oil.customer_address }}</p>
|
||||
<p><strong class="font-semibold">Town:</strong> {{ oil.customer_town }}</p>
|
||||
<p><strong class="font-semibold">Gallons:</strong>
|
||||
<span v-if="oil.customer_asked_for_fill" class="badge badge-info badge-xs">FILL</span>
|
||||
<span v-else>{{ oil.gallons_ordered }}</span>
|
||||
</p>
|
||||
<p><strong class="font-semibold">Date:</strong> {{ oil.expected_delivery_date }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-2">
|
||||
<router-link :to="{ name: 'deliveryOrder', params: { id: oil.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
<router-link :to="{ name: 'deliveryEdit', params: { id: oil.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'finalizeTicket', params: { id: oil.id } }" class="btn btn-sm btn-accent">Finalize</router-link>
|
||||
<router-link :to="{ name: 'Ticket', params: { id: oil.id } }" class="btn btn-sm btn-success">Print</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options" class="mt-10">
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options">
|
||||
</pagination>
|
||||
<div class="flex justify-center mb-10"> {{ recordsLength }} items Found</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
@@ -136,7 +147,7 @@ export default defineComponent({
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
deliveries: [],
|
||||
deliveries: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
|
||||
@@ -1,233 +1,214 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div v-if="user">
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
<div class="flex">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Employees</li>
|
||||
<li>Create New Employee</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="w-full px-10">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
Customers
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 rounded-md p-6 ">
|
||||
<div class="text-[24px]">Create a new employee</div>
|
||||
<form class="rounded-md px-8 pt-6 pb-8 mb-4 w-full" enctype="multipart/form-data" @submit.prevent="onSubmit">
|
||||
|
||||
<h1 class="text-3xl font-bold mt-4">
|
||||
Create New Employee
|
||||
</h1>
|
||||
|
||||
<!-- Main Form Card -->
|
||||
<div class="bg-neutral rounded-lg p-6 mt-6">
|
||||
<form @submit.prevent="onSubmit" class="space-y-6">
|
||||
|
||||
<div class="text-[18px] mt-10 mb-10">General Info</div>
|
||||
|
||||
<!-- last name -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Last Name</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_last_name"
|
||||
class="input input-bordered w-full max-w-xs" id="title" type="text" placeholder="Last Name" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_last_name.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_last_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- first name -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">First Name</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_first_name"
|
||||
class="input input-bordered w-full max-w-xs" id="title" type="text" placeholder="First Name" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_first_name.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_first_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- employee type -->
|
||||
<div class="flex gap-5">
|
||||
<div class="flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Type of employee</label>
|
||||
<select class="select select-bordered w-full max-w-xs" aria-label="Default select example"
|
||||
id="employee_type" v-model="CreateEmployeeForm.basicInfo.employee_type">
|
||||
<option class="text-white" v-for="(employee, index) in employList" :key="index"
|
||||
:value="employee['value']">
|
||||
{{ employee['text'] }}
|
||||
<!-- SECTION 1: General Info -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">General Info</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- First Name -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">First Name</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_first_name" type="text" placeholder="First Name" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_first_name.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_first_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Last Name -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Last Name</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_last_name" type="text" placeholder="Last Name" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_last_name.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_last_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Phone Number -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Phone Number</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_phone_number" @input="acceptNumber()" type="text" placeholder="Phone Number" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_phone_number.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_phone_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Employee Type -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Employee Role</span></label>
|
||||
<select v-model="CreateEmployeeForm.employee_type" class="select select-bordered select-sm w-full">
|
||||
<option disabled :value="0">Select a role</option>
|
||||
<option v-for="employee in employList" :key="employee.value" :value="employee.value">
|
||||
{{ employee.text }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateEmployeeForm.employee_type.$error" class="text-red-500 text-xs mt-1">
|
||||
Role is required.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="text-[18px] mt-10 mb-10">Employee Address</div>
|
||||
|
||||
<!-- street address -->
|
||||
<div class="col-span-12 mb-5 md:mb-5">
|
||||
<label class="block text-white text-sm font-bold mb-2">Street Address</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_address" class="input input-bordered w-full max-w-xs"
|
||||
id="address" type="text" placeholder="Address" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_address.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_address.$errors[0].$message }}
|
||||
</span>
|
||||
<!-- SECTION 2: Address -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">Address</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Street Address -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Street Address</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_address" type="text" placeholder="Street Address" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_address.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_address.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Apt, Suite, etc. -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Apt, Suite, etc. (Optional)</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_apt" type="text" placeholder="Apt, suite, unit..." class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_apt.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
<!-- Town -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Town</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_town" type="text" placeholder="Town" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_town.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_town.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- State -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">State</span></label>
|
||||
<select v-model="CreateEmployeeForm.employee_state" class="select select-bordered select-sm w-full">
|
||||
<option disabled :value="0">Select a state</option>
|
||||
<option v-for="state in stateList" :key="state.value" :value="state.value">
|
||||
{{ state.text }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateEmployeeForm.employee_state.$error" class="text-red-500 text-xs mt-1">
|
||||
State is required.
|
||||
</span>
|
||||
</div>
|
||||
<!-- Zip Code -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Zip Code</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_zip" type="text" placeholder="Zip Code" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_zip.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_zip.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- apt -->
|
||||
<div class="col-span-12 mb-5 md:mb-5">
|
||||
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_apt" class="input input-bordered w-full max-w-xs"
|
||||
id="apt" type="text" placeholder="Apt, suite, unit, building, floor, etc" />
|
||||
<!-- SECTION 3: Employment Dates -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">Employment Dates & Details</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Birthday</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_birthday" type="date" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_birthday.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Start Date</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_start_date" type="date" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_start_date.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">End Date (Optional)</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_end_date" type="date" class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- customer_town -->
|
||||
<div class="col-span-12 md:col-span-4 mb-20 md:mb-5 ">
|
||||
<label class="block text-white text-sm font-bold mb-2">Town</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_town" class="input input-bordered w-full max-w-xs"
|
||||
id="city" type="text" placeholder="Town" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_town.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_town.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- phone number -->
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-5">
|
||||
<label class="block text-white text-sm font-bold mb-2">Phone Number</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_phone_number"
|
||||
class="input input-bordered w-full max-w-xs" id="phone number" type="text" placeholder="Phone Number"
|
||||
@input="acceptNumber()" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_phone_number.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_phone_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- state -->
|
||||
<div class="flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">State</label>
|
||||
<select class="select select-bordered w-full max-w-xs" aria-label="Default select example"
|
||||
id="employee_state" v-model="CreateEmployeeForm.basicInfo.employee_state">
|
||||
<option class="text-white" v-for="(state, index) in stateList" :key="index" :value="state['value']">
|
||||
{{ state['text'] }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_state.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_state.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- zip -->
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0">
|
||||
<label class="block text-white text-sm font-bold mb-2">Zip Code</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_zip" class="input input-bordered w-full max-w-xs"
|
||||
id="zip" type="text" placeholder="Zip" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_zip.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_zip.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Employee BirthDay</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_birthday" class="input input-bordered w-full max-w-xs"
|
||||
id="title" type="date" min="1945-01-01" max="2030-01-01" />
|
||||
|
||||
</div>
|
||||
<div class="text-[18px] mt-10 mb-10">Employee Dates</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Employee Start Date</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_start_date"
|
||||
class="input input-bordered w-full max-w-xs" id="title" type="date" min="2023-01-01" max="2030-01-01" />
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Employee End Date</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_end_date" class="input input-bordered w-full max-w-xs"
|
||||
id="title" type="date" min="2023-01-01" max="2030-01-01" />
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 md:col-span-12 flex mt-5 mb-5">
|
||||
<button class="btn">
|
||||
Create Employee
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- SUBMIT BUTTON -->
|
||||
<div class="pt-4">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Create Employee</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
import { defineComponent } 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 useValidate from "@vuelidate/core";
|
||||
import {minLength, required} from "@vuelidate/validators";
|
||||
import { minLength, required } from "@vuelidate/validators";
|
||||
|
||||
interface SelectOption {
|
||||
text: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EmployeeCreate',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: null,
|
||||
stateList: [],
|
||||
employList: [],
|
||||
employee_id: '',
|
||||
stateList: [] as SelectOption[],
|
||||
employList: [] as SelectOption[],
|
||||
// --- REFACTORED: Simplified, flat form object ---
|
||||
CreateEmployeeForm: {
|
||||
basicInfo: {
|
||||
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: "",
|
||||
|
||||
},
|
||||
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: "",
|
||||
// --- FIX: Initialized as a number for proper v-model binding ---
|
||||
employee_type: 0,
|
||||
employee_state: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
// --- REFACTORED: Validation rules point to the flat form object ---
|
||||
CreateEmployeeForm: {
|
||||
basicInfo: {
|
||||
employee_last_name: {required, minLength: minLength(1)},
|
||||
employee_first_name: {required, minLength: minLength(1)},
|
||||
employee_town: {required, minLength: minLength(1)},
|
||||
employee_type: {required},
|
||||
employee_zip: {required, minLength: minLength(5)},
|
||||
employee_state: {required},
|
||||
employee_apt: {required},
|
||||
employee_address: {required},
|
||||
employee_birthday: {required},
|
||||
employee_phone_number: {required},
|
||||
employee_start_date: {required},
|
||||
},
|
||||
employee_last_name: { required, minLength: minLength(1) },
|
||||
employee_first_name: { required, minLength: minLength(1) },
|
||||
employee_town: { required, minLength: minLength(1) },
|
||||
employee_type: { required },
|
||||
employee_zip: { required, minLength: minLength(5) },
|
||||
employee_state: { required },
|
||||
employee_apt: { required },
|
||||
employee_address: { required },
|
||||
employee_birthday: { required },
|
||||
employee_phone_number: { required },
|
||||
employee_start_date: { required },
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.userStatus()
|
||||
this.userStatus();
|
||||
},
|
||||
mounted() {
|
||||
this.getEmployeeTypeList();
|
||||
@@ -235,108 +216,50 @@ export default defineComponent({
|
||||
},
|
||||
methods: {
|
||||
acceptNumber() {
|
||||
let x = this.CreateEmployeeForm.basicInfo.employee_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
|
||||
if (x){
|
||||
this.CreateEmployeeForm.basicInfo.employee_phone_number = !x[2] ? x[1] : '(' + x[1] + ') ' + x[2] + (x[3] ? '-' + x[3] : '');
|
||||
}
|
||||
else {
|
||||
this.CreateEmployeeForm.basicInfo.employee_phone_number = ''
|
||||
}
|
||||
const x = this.CreateEmployeeForm.employee_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
|
||||
if (x) {
|
||||
this.CreateEmployeeForm.employee_phone_number = !x[2] ? x[1] : `(${x[1]}) ${x[2]}${x[3] ? `-${x[3]}` : ''}`;
|
||||
}
|
||||
},
|
||||
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) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
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; });
|
||||
},
|
||||
CreateItem(payload: {
|
||||
employee_last_name: string;
|
||||
employee_first_name: string;
|
||||
employee_town: string;
|
||||
employee_address: string;
|
||||
employee_zip: string;
|
||||
employee_apt: string,
|
||||
employee_birthday: string;
|
||||
employee_phone_number: string;
|
||||
employee_start_date: string,
|
||||
employee_end_date: string;
|
||||
employee_type: string;
|
||||
employee_state: string;
|
||||
}) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/employee/create";
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.employee_id= response.data['user_id']
|
||||
this.$router.push({name: "employeeProfile", params: { id: this.employee_id }});
|
||||
}
|
||||
if (response.data.error) {
|
||||
this.$router.push("/");
|
||||
}
|
||||
})
|
||||
CreateItem(payload: any) {
|
||||
const path = import.meta.env.VITE_BASE_URL + "/employee/create";
|
||||
axios.post(path, payload, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
const employee_id = response.data['user_id'];
|
||||
this.$router.push({ name: "employeeProfile", params: { id: employee_id } });
|
||||
} else {
|
||||
console.error("Failed to create employee:", response.data.error);
|
||||
// Optionally, show a notification to the user
|
||||
}
|
||||
});
|
||||
},
|
||||
onSubmit() {
|
||||
let payload = {
|
||||
employee_last_name: this.CreateEmployeeForm.basicInfo.employee_last_name,
|
||||
employee_first_name: this.CreateEmployeeForm.basicInfo.employee_first_name,
|
||||
employee_town: this.CreateEmployeeForm.basicInfo.employee_town,
|
||||
employee_address: this.CreateEmployeeForm.basicInfo.employee_address,
|
||||
employee_zip: this.CreateEmployeeForm.basicInfo.employee_zip,
|
||||
employee_apt: this.CreateEmployeeForm.basicInfo.employee_apt,
|
||||
employee_birthday: this.CreateEmployeeForm.basicInfo.employee_birthday,
|
||||
employee_phone_number: this.CreateEmployeeForm.basicInfo.employee_phone_number,
|
||||
employee_start_date: this.CreateEmployeeForm.basicInfo.employee_start_date,
|
||||
employee_end_date: this.CreateEmployeeForm.basicInfo.employee_end_date,
|
||||
employee_type: this.CreateEmployeeForm.basicInfo.employee_type,
|
||||
employee_state: this.CreateEmployeeForm.basicInfo.employee_state,
|
||||
};
|
||||
this.CreateItem(payload);
|
||||
this.v$.$validate(); // Trigger validation
|
||||
if (!this.v$.$error) {
|
||||
this.CreateItem(this.CreateEmployeeForm);
|
||||
} else {
|
||||
console.log("Form validation failed.");
|
||||
}
|
||||
},
|
||||
getEmployeeTypeList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/employeetype";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.employList = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
const path = import.meta.env.VITE_BASE_URL + "/query/employeetype";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { this.employList = response.data; });
|
||||
},
|
||||
getStatesList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/states";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.stateList = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
const path = import.meta.env.VITE_BASE_URL + "/query/states";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { this.stateList = response.data; });
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
</script>
|
||||
@@ -1,383 +1,275 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div v-if="user">
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
<div class="flex">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Employees</li>
|
||||
<li>Edit Employee</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="w-full px-10">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
Customers
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 rounded-md p-6 ">
|
||||
<div class="text-[24px]">Edit employee</div>
|
||||
<form class="rounded-md px-8 pt-6 pb-8 mb-4 w-full" enctype="multipart/form-data" @submit.prevent="onSubmit">
|
||||
|
||||
<div class="text-[18px] mt-10 mb-10">General Info</div>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mt-4">
|
||||
<h1 class="text-3xl font-bold">
|
||||
Edit Employee
|
||||
</h1>
|
||||
<router-link v-if="employee_id" :to="{ name: 'employeeProfile', params: { id: employee_id } }" class="btn btn-secondary btn-sm mt-2 sm:mt-0">
|
||||
Back to Profile
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<!-- first name -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">First Name</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_first_name"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text" placeholder="First Name" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_first_name.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_first_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Main Form Card -->
|
||||
<div class="bg-neutral rounded-lg p-6 mt-6">
|
||||
<form @submit.prevent="onSubmit" class="space-y-6">
|
||||
|
||||
|
||||
<!-- last name -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Last Name</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_last_name"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="text" placeholder="Last Name" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_last_name.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_last_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- employee type -->
|
||||
<div class="flex gap-5">
|
||||
<div class="flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Type of employee</label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" aria-label="Default select example"
|
||||
id="employee_type" v-model="CreateEmployeeForm.basicInfo.employee_type">
|
||||
<option class="text-white" v-for="(employee, index) in employList" :key="index"
|
||||
:value="employee['value']">
|
||||
{{ employee['text'] }}
|
||||
<!-- SECTION 1: General Info -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">General Info</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
<!-- First Name -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">First Name</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_first_name" type="text" placeholder="First Name" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_first_name.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_first_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Last Name -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Last Name</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_last_name" type="text" placeholder="Last Name" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_last_name.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_last_name.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Phone Number -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Phone Number</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_phone_number" @input="acceptNumber()" type="text" placeholder="Phone Number" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_phone_number.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_phone_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Employee Type -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Employee Role</span></label>
|
||||
<select v-model="CreateEmployeeForm.employee_type" class="select select-bordered select-sm w-full">
|
||||
<option disabled :value="0">Select a role</option>
|
||||
<option v-for="employee in employList" :key="employee.value" :value="employee.value">
|
||||
{{ employee.text }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateEmployeeForm.employee_type.$error" class="text-red-500 text-xs mt-1">
|
||||
Role is required.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="text-[18px] mt-10 mb-10">Employee Address</div>
|
||||
|
||||
<!-- street address -->
|
||||
<div class="col-span-12 mb-5 md:mb-5">
|
||||
<label class="block text-white text-sm font-bold mb-2">Street Address</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_address"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="address" type="text" placeholder="Address" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_address.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_address.$errors[0].$message }}
|
||||
</span>
|
||||
<!-- SECTION 2: Address -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">Address</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Street Address -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Street Address</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_address" type="text" placeholder="Street Address" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_address.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_address.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- Apt, Suite, etc. -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Apt, Suite, etc. (Optional)</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_apt" type="text" placeholder="Apt, suite, unit..." class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
<!-- Town -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Town</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_town" type="text" placeholder="Town" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_town.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_town.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
<!-- State -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">State</span></label>
|
||||
<select v-model="CreateEmployeeForm.employee_state" class="select select-bordered select-sm w-full">
|
||||
<option disabled :value="0">Select a state</option>
|
||||
<option v-for="state in stateList" :key="state.value" :value="state.value">
|
||||
{{ state.text }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateEmployeeForm.employee_state.$error" class="text-red-500 text-xs mt-1">
|
||||
State is required.
|
||||
</span>
|
||||
</div>
|
||||
<!-- Zip Code -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Zip Code</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_zip" type="text" placeholder="Zip Code" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_zip.$error" class="text-red-500 text-xs mt-1">
|
||||
{{ v$.CreateEmployeeForm.employee_zip.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- apt -->
|
||||
<div class="col-span-12 mb-5 md:mb-5">
|
||||
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_apt"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="apt" type="text" placeholder="Apt, suite, unit, building, floor, etc" />
|
||||
<!-- SECTION 3: Employment Dates -->
|
||||
<div>
|
||||
<h2 class="text-lg font-bold">Employment Dates & Details</h2>
|
||||
<div class="divider mt-2 mb-4"></div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Birthday</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_birthday" type="date" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_birthday.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Start Date</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_start_date" type="date" class="input input-bordered input-sm w-full" />
|
||||
<span v-if="v$.CreateEmployeeForm.employee_start_date.$error" class="text-red-500 text-xs mt-1">Required.</span>
|
||||
</div>
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">End Date (Optional)</span></label>
|
||||
<input v-model="CreateEmployeeForm.employee_end_date" type="date" class="input input-bordered input-sm w-full" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- employee_town -->
|
||||
<div class="col-span-12 md:col-span-4 mb-20 md:mb-5 ">
|
||||
<label class="block text-white text-sm font-bold mb-2">Town</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_town"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="city" type="text" placeholder="Town" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_town.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_town.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- phone number -->
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-5">
|
||||
<label class="block text-white text-sm font-bold mb-2">Phone Number</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_phone_number" @input="acceptNumber()"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="phone number" type="text" placeholder="Phone Number" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_phone_number.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_phone_number.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- state -->
|
||||
<div class="flex-1 mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">State</label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs" aria-label="Default select example"
|
||||
id="employee_state" v-model="CreateEmployeeForm.basicInfo.employee_state">
|
||||
<option class="text-white" v-for="(state, index) in stateList" :key="index" defaultValue="state_default"
|
||||
:value="state['value']">
|
||||
{{ state['text'] }}
|
||||
</option>
|
||||
</select>
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_state.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_state.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- zip -->
|
||||
<div class="col-span-12 md:col-span-4 mb-5 md:mb-0">
|
||||
<label class="block text-white text-sm font-bold mb-2">Zip Code</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_zip"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="zip" type="text" placeholder="Zip" />
|
||||
<span v-if="v$.CreateEmployeeForm.basicInfo.employee_zip.$error" class="text-red-600 text-center">
|
||||
{{ v$.CreateEmployeeForm.basicInfo.employee_zip.$errors[0].$message }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Employee BirthDay</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_birthday"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="title" type="date" min="1945-01-01" max="2030-01-01" />
|
||||
</div>
|
||||
|
||||
<div class="text-[18px] mt-10 mb-10">Employee Dates</div>
|
||||
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Employee Start Date</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_start_date"
|
||||
class="input input-bordered input-sm w-full max-w-xs" id="title" type="date" min="2023-01-01" max="2030-01-01" />
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label class="block text-white text-sm font-bold mb-2">Employee End Date</label>
|
||||
<input v-model="CreateEmployeeForm.basicInfo.employee_end_date"
|
||||
class="input input-bordered input-sm w-full max-w-xs"
|
||||
id="title" type="date" min="2023-01-01" max="2030-01-01" />
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 md:col-span-12 flex mt-5 mb-5">
|
||||
<button class="btn btn-sm btn-secondary">
|
||||
Edit Employee
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!-- SUBMIT BUTTON -->
|
||||
<div class="pt-4">
|
||||
<button type="submit" class="btn btn-primary btn-sm">Save Changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
import { defineComponent } 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 useValidate from "@vuelidate/core";
|
||||
import {minLength, required} from "@vuelidate/validators";
|
||||
import { minLength, required } from "@vuelidate/validators";
|
||||
|
||||
interface SelectOption {
|
||||
text: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EmployeeEdit',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
v$: useValidate(),
|
||||
user: null,
|
||||
stateList: [],
|
||||
employList: [],
|
||||
employee_id: '',
|
||||
state_default: '',
|
||||
stateList: [] as SelectOption[],
|
||||
employList: [] as SelectOption[],
|
||||
employee_id: this.$route.params.id as string,
|
||||
CreateEmployeeForm: {
|
||||
basicInfo: {
|
||||
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: "",
|
||||
|
||||
},
|
||||
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: 0,
|
||||
employee_state: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
CreateEmployeeForm: {
|
||||
basicInfo: {
|
||||
employee_last_name: {required, minLength: minLength(1)},
|
||||
employee_first_name: {required, minLength: minLength(1)},
|
||||
employee_town: {required, minLength: minLength(1)},
|
||||
employee_type: {required},
|
||||
employee_zip: {required, minLength: minLength(5)},
|
||||
employee_state: {required},
|
||||
employee_apt: {required},
|
||||
employee_address: {required},
|
||||
employee_birthday: {required},
|
||||
employee_phone_number: {required},
|
||||
employee_start_date: {required},
|
||||
},
|
||||
employee_last_name: { required, minLength: minLength(1) },
|
||||
employee_first_name: { required, minLength: minLength(1) },
|
||||
employee_town: { required, minLength: minLength(1) },
|
||||
employee_type: { required },
|
||||
employee_zip: { required, minLength: minLength(5) },
|
||||
employee_state: { required },
|
||||
employee_address: { required },
|
||||
employee_birthday: { required },
|
||||
employee_phone_number: { required },
|
||||
employee_start_date: { required },
|
||||
},
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.userStatus()
|
||||
this.getEmployee(this.$route.params.id)
|
||||
this.userStatus();
|
||||
this.getEmployee(this.employee_id);
|
||||
},
|
||||
mounted() {
|
||||
this.getEmployeeTypeList();
|
||||
this.getStatesList();
|
||||
this.getEmployee(this.$route.params.id)
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.getEmployee(this.$route.params.id);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// --- FIX APPLIED HERE ---
|
||||
acceptNumber() {
|
||||
let x = this.CreateEmployeeForm.basicInfo.employee_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
|
||||
if (x){
|
||||
this.CreateEmployeeForm.basicInfo.employee_phone_number = !x[2] ? x[1] : '(' + x[1] + ') ' + x[2] + (x[3] ? '-' + x[3] : '');
|
||||
}
|
||||
else {
|
||||
this.CreateEmployeeForm.basicInfo.employee_phone_number = ''
|
||||
}
|
||||
const x = this.CreateEmployeeForm.employee_phone_number.replace(/\D/g, '').match(/(\d{0,3})(\d{0,3})(\d{0,4})/);
|
||||
// This 'if' block ensures 'x' is not null before we use it.
|
||||
if (x) {
|
||||
this.CreateEmployeeForm.employee_phone_number = !x[2] ? x[1] : `(${x[1]}) ${x[2]}${x[3] ? `-${x[3]}` : ''}`;
|
||||
}
|
||||
},
|
||||
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) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.user = null
|
||||
})
|
||||
},
|
||||
EditEmployee(payload: {
|
||||
employee_last_name: string;
|
||||
employee_first_name: string;
|
||||
employee_town: string;
|
||||
employee_address: string;
|
||||
employee_zip: string;
|
||||
employee_apt: string,
|
||||
employee_birthday: string;
|
||||
employee_phone_number: string;
|
||||
employee_start_date: string,
|
||||
employee_end_date: string;
|
||||
employee_type: string;
|
||||
employee_state: string;
|
||||
}) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/employee/edit/" + this.employee_id;
|
||||
axios({
|
||||
method: "post",
|
||||
url: path,
|
||||
data: payload,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.$router.push({name: "employeeProfile", params: { id: this.employee_id }});
|
||||
}
|
||||
if (response.data.error) {
|
||||
this.$router.push("/");
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getEmployee (userid:any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/employee/" + userid;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
this.employee_id = response.data.id
|
||||
this.CreateEmployeeForm.basicInfo.employee_last_name= response.data.employee_last_name;
|
||||
this.CreateEmployeeForm.basicInfo.employee_first_name= response.data.employee_first_name;
|
||||
this.CreateEmployeeForm.basicInfo.employee_town= response.data.employee_town;
|
||||
this.CreateEmployeeForm.basicInfo.employee_type= response.data.employee_type;
|
||||
this.CreateEmployeeForm.basicInfo.employee_state= response.data.employee_state;
|
||||
this.CreateEmployeeForm.basicInfo.employee_zip= response.data.employee_zip;
|
||||
this.CreateEmployeeForm.basicInfo.employee_apt= response.data.employee_apt;
|
||||
this.CreateEmployeeForm.basicInfo.employee_address= response.data.employee_address;
|
||||
this.CreateEmployeeForm.basicInfo.employee_birthday= response.data.employee_birthday;
|
||||
this.CreateEmployeeForm.basicInfo.employee_phone_number= response.data.employee_phone_number;
|
||||
this.CreateEmployeeForm.basicInfo.employee_start_date= response.data.employee_start_date;
|
||||
this.CreateEmployeeForm.basicInfo.employee_end_date= response.data.employee_end_date;
|
||||
|
||||
if (response.data.ok) {
|
||||
this.user = response.data.user;
|
||||
}
|
||||
})
|
||||
.catch(() => { this.user = null; });
|
||||
},
|
||||
EditEmployee(payload: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/employee/edit/${this.employee_id}`;
|
||||
axios.post(path, payload, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data.ok) {
|
||||
this.$router.push({ name: "employeeProfile", params: { id: this.employee_id } });
|
||||
} else {
|
||||
console.error("Failed to edit employee:", response.data.error);
|
||||
}
|
||||
});
|
||||
},
|
||||
getEmployee(userid: any) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/employee/${userid}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
this.CreateEmployeeForm = response.data;
|
||||
});
|
||||
},
|
||||
onSubmit() {
|
||||
let payload = {
|
||||
employee_last_name: this.CreateEmployeeForm.basicInfo.employee_last_name,
|
||||
employee_first_name: this.CreateEmployeeForm.basicInfo.employee_first_name,
|
||||
employee_town: this.CreateEmployeeForm.basicInfo.employee_town,
|
||||
employee_address: this.CreateEmployeeForm.basicInfo.employee_address,
|
||||
employee_zip: this.CreateEmployeeForm.basicInfo.employee_zip,
|
||||
employee_apt: this.CreateEmployeeForm.basicInfo.employee_apt,
|
||||
employee_birthday: this.CreateEmployeeForm.basicInfo.employee_birthday,
|
||||
employee_phone_number: this.CreateEmployeeForm.basicInfo.employee_phone_number,
|
||||
employee_start_date: this.CreateEmployeeForm.basicInfo.employee_start_date,
|
||||
employee_end_date: this.CreateEmployeeForm.basicInfo.employee_end_date,
|
||||
employee_type: this.CreateEmployeeForm.basicInfo.employee_type,
|
||||
employee_state: this.CreateEmployeeForm.basicInfo.employee_state,
|
||||
};
|
||||
this.EditEmployee(payload);
|
||||
this.v$.$validate();
|
||||
if (!this.v$.$error) {
|
||||
this.EditEmployee(this.CreateEmployeeForm);
|
||||
} else {
|
||||
console.log("Form validation failed.");
|
||||
}
|
||||
},
|
||||
getEmployeeTypeList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/employeetype";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.employList = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
const path = import.meta.env.VITE_BASE_URL + "/query/employeetype";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { this.employList = response.data; });
|
||||
},
|
||||
getStatesList() {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/query/states";
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
})
|
||||
.then((response: any) => {
|
||||
this.stateList = response.data;
|
||||
})
|
||||
.catch(() => {
|
||||
});
|
||||
const path = import.meta.env.VITE_BASE_URL + "/query/states";
|
||||
axios.get(path, { withCredentials: true })
|
||||
.then((response: any) => { this.stateList = response.data; });
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
</script>
|
||||
@@ -1,109 +1,109 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'employee' }">
|
||||
employees
|
||||
</router-link>
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Employees</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mt-4">Employees</h1>
|
||||
|
||||
<div class="flex start pb-10 text-2xl">Employees </div>
|
||||
<div class="flex justify-end mb-10">
|
||||
<router-link :to="{ name: 'employeeCreate' }">
|
||||
<button class="btn btn-secondary btn-sm">Create Employee</button>
|
||||
</router-link>
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Count and Add Button -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<div class="badge badge-ghost">{{ recordsLength }} employees found</div>
|
||||
<router-link :to="{ name: 'employeeCreate' }" class="btn btn-primary btn-sm">
|
||||
Create New Employee
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Employee ID</th>
|
||||
<th>Name</th>
|
||||
<th>Role</th>
|
||||
<th>Town</th>
|
||||
<th>Phone Number</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="person in employees" :key="person.id" class="hover:bg-blue-600 hover:text-white">
|
||||
<td>{{ person.id }}</td>
|
||||
<td>{{ person.employee_first_name }} {{ person.employee_last_name }}</td>
|
||||
<td><span class="badge badge-ghost badge-sm">{{ getEmployeeTypeName(person.employee_type) }}</span></td>
|
||||
<td>{{ person.employee_town }}</td>
|
||||
<td>{{ person.employee_phone_number }}</td>
|
||||
<td class="text-right">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<router-link :to="{ name: 'employeeEdit', params: { id: person.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'employeeProfile', params: { id: person.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="person in employees" :key="person.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ person.employee_first_name }} {{ person.employee_last_name }}</h2>
|
||||
<p class="text-xs text-gray-400">ID: #{{ person.id }}</p>
|
||||
</div>
|
||||
<div class="badge badge-ghost">
|
||||
{{ getEmployeeTypeName(person.employee_type) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-sm mt-2">
|
||||
<p>{{ person.employee_town }}</p>
|
||||
<p>{{ person.employee_phone_number }}</p>
|
||||
</div>
|
||||
<div class="card-actions justify-end flex-wrap gap-2 mt-2">
|
||||
<router-link :to="{ name: 'employeeEdit', params: { id: person.id } }" class="btn btn-sm btn-secondary">Edit</router-link>
|
||||
<router-link :to="{ name: 'employeeProfile', params: { id: person.id } }" class="btn btn-sm btn-ghost">View</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto bg-neutral">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Employee ID</th>
|
||||
<th>Name</th>
|
||||
<th>Type</th>
|
||||
<th>Town</th>
|
||||
<th>Phone Number</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
<tr v-for="person in employees" :key="person['id']">
|
||||
<td>{{ person['id'] }} </td>
|
||||
<td>{{ person['employee_first_name'] }} {{ person['employee_last_name'] }}</td>
|
||||
<td>
|
||||
<div v-if="person['employee_type'] == 0">Owner</div>
|
||||
<div v-else-if="person['employee_type'] == 1">Manager</div>
|
||||
<div v-else-if="person['employee_type'] == 2">Secretary</div>
|
||||
<div v-else-if="person['employee_type'] == 3">Office</div>
|
||||
<div v-else-if="person['employee_type'] == 4">Driver</div>
|
||||
<div v-else-if="person['employee_type'] == 5">Service Tech</div>
|
||||
<div v-else-if="person['employee_type'] == 6">Contract</div>
|
||||
<div v-else-if="person['employee_type'] == 7">Cash</div>
|
||||
<div v-else-if="person['employee_type'] == 8">Driver/Tech</div>
|
||||
<div v-else></div>
|
||||
</td>
|
||||
|
||||
<td>{{ person['employee_town'] }}</td>
|
||||
|
||||
<td>{{ person['employee_phone_number'] }}</td>
|
||||
<td class="flex gap-5">
|
||||
<router-link :to="{ name: 'employeeEdit', params: { id: person['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">Edit</button>
|
||||
</router-link>
|
||||
<router-link :to="{ name: 'employeeProfile', params: { id: person['id'] } }">
|
||||
<button class="btn btn-secondary btn-sm">View</button>
|
||||
</router-link>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options" class="mt-10">
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="mt-6 flex justify-center">
|
||||
<pagination @paginate="getPage" :records="recordsLength" v-model="page" :per-page="50" :options="options">
|
||||
</pagination>
|
||||
<div class="flex justify-center mb-10"> {{ recordsLength }} items Found</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
</template><script lang="ts">
|
||||
import { defineComponent } from 'vue'
|
||||
import axios from 'axios'
|
||||
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'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'EmployeeHome',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user: null,
|
||||
employees: [],
|
||||
employees: [] as any[],
|
||||
page: 1,
|
||||
perPage: 50,
|
||||
recordsLength: 0,
|
||||
@@ -114,49 +114,55 @@ export default defineComponent({
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.userStatus()
|
||||
this.userStatus();
|
||||
},
|
||||
mounted() {
|
||||
this.getPage(this.page)
|
||||
|
||||
this.getPage(this.page);
|
||||
},
|
||||
methods: {
|
||||
getPage: function (page: any) {
|
||||
// we simulate an api call that fetch the records from a backend
|
||||
this.employees = [];
|
||||
this.get_employees(page)
|
||||
getPage: function (page: number) {
|
||||
this.get_employees(page);
|
||||
},
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
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
|
||||
this.user = null;
|
||||
});
|
||||
},
|
||||
// --- METHOD CORRECTED TO MATCH YOUR SIMPLE ARRAY API RESPONSE ---
|
||||
get_employees(page: number) {
|
||||
// Using your original, working URL
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/employee/all/${page}`;
|
||||
|
||||
axios.get(path, { headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
// --- FIX 1: Assign the response data directly, as it's an array ---
|
||||
this.employees = response.data;
|
||||
|
||||
// --- FIX 2: Set the recordsLength from the array length ---
|
||||
// NOTE: For full pagination, your API will eventually need to send the *total* count.
|
||||
// For now, this will show the count of items on the current page.
|
||||
// If you update your API to send { data: [], total_records: X }, this is the only line you'd change.
|
||||
this.recordsLength = response.data.length;
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Failed to fetch employees:", error);
|
||||
});
|
||||
},
|
||||
get_employees(page: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/employee/all/' + page;
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
headers: authHeader(),
|
||||
}).then((response: any) => {
|
||||
this.employees = response.data
|
||||
})
|
||||
},
|
||||
getEmployeeTypeName(typeId: number | string): string {
|
||||
const typeMap: { [key: string]: string } = {
|
||||
'0': 'Owner', '1': 'Manager', '2': 'Secretary', '3': 'Office',
|
||||
'4': 'Driver', '5': 'Service Tech', '6': 'Contractor', '7': 'Cash Driver', '8': 'Driver/Tech'
|
||||
};
|
||||
return typeMap[String(typeId)] || 'Unknown Role';
|
||||
}
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
</script>
|
||||
@@ -1,108 +1,84 @@
|
||||
<template>
|
||||
<Header />
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
<div class="flex">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Employees</li>
|
||||
<li>Profile</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: 'customer' }">
|
||||
Customers
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class=" w-full mt-10" v-if="loaded">
|
||||
<div class="grid grid-cols-12 gap-5 ">
|
||||
<div class="col-span-3 ">
|
||||
<img src="../../../assets/images/user_placeholder.png" alt="Drone Image" width="200" height="250" />
|
||||
<!-- Loading State -->
|
||||
<div v-if="!loaded" class="text-center p-10">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
|
||||
<!-- Main Content -->
|
||||
<div v-else>
|
||||
<!-- Employee Header Card -->
|
||||
<div class="bg-neutral rounded-lg p-6 mt-6">
|
||||
<div class="flex flex-col md:flex-row gap-6">
|
||||
|
||||
<!-- Left Side: Avatar and Name -->
|
||||
<div class="flex-none flex flex-col items-center md:items-start text-center md:text-left">
|
||||
<div class="avatar">
|
||||
<div class="w-32 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2">
|
||||
<img src="../../../assets/images/user_placeholder.png" alt="Employee Avatar" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-9">
|
||||
<div class="grid grid-cols-12">
|
||||
<div class="col-span-12 font-bold flex justify-end">
|
||||
<div class="btn btn-sm btn-secondary">
|
||||
<router-link :to="{ name: 'employeeEdit', params: { id: employee.id } }">
|
||||
Edit Employee
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 font-bold flex">
|
||||
{{ employee.employee_first_name }}
|
||||
{{ employee.employee_last_name }}
|
||||
</div>
|
||||
<div class="col-span-12 font-bold flex">
|
||||
{{ employee.employee_address }}
|
||||
<div v-if="employee.employee_apt != 'None'">
|
||||
{{ employee.employee_apt }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 font-bold flex">
|
||||
<div class="pr-2">
|
||||
{{ employee.employee_town }},
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold mt-4">{{ employee.employee_first_name }} {{ employee.employee_last_name }}</h1>
|
||||
<span class="badge badge-ghost mt-1">{{ employeeTypeName }}</span>
|
||||
<router-link :to="{ name: 'employeeEdit', params: { id: employee.id } }" class="btn btn-sm btn-secondary mt-4">
|
||||
Edit Employee
|
||||
</router-link>
|
||||
</div>
|
||||
|
||||
<div class="pr-2">
|
||||
<div v-if="employee.employee_state == '0'">Massachusetts</div>
|
||||
<div v-else-if="employee.employee_state == '1'">Rhode Island</div>
|
||||
<div v-else-if="employee.employee_state == '2'">New Hampshire</div>
|
||||
<div v-else-if="employee.employee_state == '3'">Maine</div>
|
||||
<div v-else-if="employee.employee_state == '4'">Vermont</div>
|
||||
<div v-else-if="employee.employee_state == '5'">Maine</div>
|
||||
<div v-else-if="employee.employee_state == '6'">New York</div>
|
||||
<div v-else>Unknown state</div>
|
||||
</div>
|
||||
<div class="pr-2">
|
||||
{{ employee.employee_zip }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-span-12 font-bold flex" v-if="employee.employee_apt !== 'None'">
|
||||
{{ employee.employee_apt }}
|
||||
</div>
|
||||
<div class="col-span-12 font-bold flex">
|
||||
{{ employee.employee_phone_number }}
|
||||
</div>
|
||||
<div class="col-span-12 font-bold flex">
|
||||
<div v-if="employee.employee_type == '0'">owner</div>
|
||||
<div v-else-if="employee.employee_type == '1'">manager</div>
|
||||
<div v-else-if="employee.employee_type == '2'">secretary</div>
|
||||
<div v-else-if="employee.employee_type == '3'">office</div>
|
||||
<div v-else-if="employee.employee_type == '4'">driver</div>
|
||||
<div v-else-if="employee.employee_type == '5'">tech</div>
|
||||
<div v-else-if="employee.employee_type == '6'">contract</div>
|
||||
<div v-else-if="employee.employee_type == '7'">cash</div>
|
||||
<div v-else-if="employee.employee_type == '8'">driver/tech</div>
|
||||
<div v-else>Unknown employee type</div>
|
||||
</div>
|
||||
<!-- Right Side: Details Grid -->
|
||||
<div class="flex-grow grid grid-cols-1 sm:grid-cols-2 gap-6 pt-4 border-t md:border-t-0 md:border-l border-base-100 md:pl-6">
|
||||
<div>
|
||||
<h3 class="font-bold text-sm uppercase text-gray-400">Contact Information</h3>
|
||||
<div class="mt-2 space-y-1 text-sm">
|
||||
<p>{{ employee.employee_phone_number }}</p>
|
||||
<p class="link link-hover">{{ employee.employee_email }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-bold text-sm uppercase text-gray-400">Address</h3>
|
||||
<div class="mt-2 space-y-1 text-sm">
|
||||
<p>{{ employee.employee_address }}</p>
|
||||
<p v-if="employee.employee_apt && employee.employee_apt !== 'None'">{{ employee.employee_apt }}</p>
|
||||
<p>{{ employee.employee_town }}, {{ employeeStateName }} {{ employee.employee_zip }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-bold text-sm uppercase text-gray-400">Employment Dates</h3>
|
||||
<div class="mt-2 space-y-1 text-sm">
|
||||
<p><strong>Start Date:</strong> {{ employee.employee_start_date || 'N/A' }}</p>
|
||||
<p><strong>End Date:</strong> {{ employee.employee_end_date || 'N/A' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-span-12 p-5 mt-5">
|
||||
<div class="grid grid-cols-12">
|
||||
<div class="col-span-12 font-bold flex text-2xl">
|
||||
Delivery
|
||||
</div>
|
||||
<div class="col-span-6">
|
||||
<div class="col-span-12 py-2">
|
||||
Total Deliverys Done: {{ total_deliviers_done }}
|
||||
</div>
|
||||
<div class="col-span-12 py-2">
|
||||
Total Gallons Delivered: {{ total_gallons_delivered }}
|
||||
</div>
|
||||
<div class="col-span-12 py-2">
|
||||
Total Prime: {{ total_primes }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Statistics Card -->
|
||||
<div class="bg-neutral rounded-lg p-6 mt-6">
|
||||
<h2 class="text-xl font-bold mb-4">Delivery Statistics</h2>
|
||||
<div class="stats stats-vertical lg:stats-horizontal shadow bg-base-100 w-full">
|
||||
<div class="stat">
|
||||
<div class="stat-title">Total Deliveries</div>
|
||||
<div class="stat-value">{{ total_deliviers_done }}</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">Total Gallons</div>
|
||||
<div class="stat-value">{{ total_gallons_delivered }}</div>
|
||||
<div class="stat-desc">Delivered</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-title">Total Primes</div>
|
||||
<div class="stat-value text-error">{{ total_primes }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,24 +92,15 @@
|
||||
import { defineComponent } 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'
|
||||
|
||||
|
||||
export default defineComponent({
|
||||
name: 'employeeProfile',
|
||||
|
||||
components: {
|
||||
Header,
|
||||
SideBar,
|
||||
Footer,
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
token: null,
|
||||
user_placeholder: '',
|
||||
user: null,
|
||||
loaded: false,
|
||||
employee: {
|
||||
@@ -144,128 +111,101 @@ export default defineComponent({
|
||||
employee_address: "",
|
||||
employee_apt: "",
|
||||
employee_zip: "",
|
||||
employee_birthday: "",
|
||||
employee_phone_number: "",
|
||||
employee_start_date: "",
|
||||
employee_end_date: "",
|
||||
employee_type: '',
|
||||
employee_state: '',
|
||||
employee_email: '', // Added for completeness
|
||||
},
|
||||
total_deliviers_done: 0,
|
||||
total_gallons_delivered: 0,
|
||||
total_primes: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
employeeTypeName(): string {
|
||||
const typeMap: Record<string, string> = {
|
||||
'0': 'Owner', '1': 'Manager', '2': 'Secretary', '3': 'Office',
|
||||
'4': 'Driver', '5': 'Tech', '6': 'Contractor', '7': 'Cash Driver', '8': 'Driver/Tech'
|
||||
};
|
||||
return typeMap[this.employee.employee_type] || 'Unknown Role';
|
||||
},
|
||||
employeeStateName(): string {
|
||||
const stateMap: Record<string, string> = {
|
||||
'0': 'MA', '1': 'RI', '2': 'NH', '3': 'ME', '4': 'VT', '5': 'CT', '6': 'NY'
|
||||
};
|
||||
return stateMap[this.employee.employee_state] || 'Unknown State';
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.userStatus()
|
||||
this.userStatus();
|
||||
this.getEmployee(this.$route.params.id);
|
||||
},
|
||||
mounted() {
|
||||
this.getEmployee(this.$route.params.id)
|
||||
},
|
||||
|
||||
watch: {
|
||||
$route() {
|
||||
this.getEmployee(this.$route.params.id);
|
||||
// Watch the route to refetch data if the employee ID changes
|
||||
'$route.params.id'(newId) {
|
||||
if (newId) {
|
||||
this.getEmployee(newId);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getPage: function (page: any) {
|
||||
// we simulate an api call that fetch the records from a backend
|
||||
this.getEmployee(page)
|
||||
},
|
||||
userStatus() {
|
||||
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||
axios({
|
||||
method: 'get',
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
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
|
||||
})
|
||||
this.user = null;
|
||||
});
|
||||
},
|
||||
|
||||
getEmployee(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/employee/" + userid;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
this.loaded = false; // Set loading state
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/employee/${userid}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
|
||||
this.employee = response.data;
|
||||
if (this.employee.id){
|
||||
this.getEmployeeStatsDeliveriesDone(this.employee.id)
|
||||
this.getEmployeeStatsGallonsDone(this.employee.id)
|
||||
this.getEmployeeStatsPrimesDone(this.employee.id)
|
||||
// Fetch stats only after we confirm we have a valid employee
|
||||
if (this.employee.id) {
|
||||
this.getEmployeeStatsDeliveriesDone(this.employee.id);
|
||||
this.getEmployeeStatsGallonsDone(this.employee.id);
|
||||
this.getEmployeeStatsPrimesDone(this.employee.id);
|
||||
}
|
||||
|
||||
this.loaded = true
|
||||
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error("Failed to fetch employee data:", error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loaded = true; // End loading state
|
||||
});
|
||||
},
|
||||
getEmployeeStatsDeliveriesDone(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/stats/delivery/total/" + userid;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
getEmployeeStatsDeliveriesDone(userid: string) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/stats/delivery/total/${userid}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
|
||||
this.total_deliviers_done = response.data.data;
|
||||
|
||||
}
|
||||
})
|
||||
this.total_deliviers_done = response.data.data || 0;
|
||||
});
|
||||
},
|
||||
getEmployeeStatsGallonsDone(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/stats/gallons/total/" + userid;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
getEmployeeStatsGallonsDone(userid: string) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/stats/gallons/total/${userid}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
this.total_gallons_delivered = response.data.data;
|
||||
|
||||
}
|
||||
})
|
||||
this.total_gallons_delivered = response.data.data || 0;
|
||||
});
|
||||
},
|
||||
getEmployeeStatsPrimesDone(userid: any) {
|
||||
let path = import.meta.env.VITE_BASE_URL + "/stats/primes/total/" + userid;
|
||||
axios({
|
||||
method: "get",
|
||||
url: path,
|
||||
withCredentials: true,
|
||||
headers: authHeader(),
|
||||
})
|
||||
getEmployeeStatsPrimesDone(userid: string) {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/stats/primes/total/${userid}`;
|
||||
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||
.then((response: any) => {
|
||||
if (response.data) {
|
||||
|
||||
this.total_primes = response.data.data;
|
||||
|
||||
}
|
||||
})
|
||||
this.total_primes = response.data.data || 0;
|
||||
});
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
});
|
||||
</script>
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<ul>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
|
||||
<div class=" w-full px-10">
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
|
||||
<div class="w-full px-10">
|
||||
<div class="text-sm breadcrumbs mb-4">
|
||||
<ul>
|
||||
@@ -78,7 +75,7 @@ export default defineComponent({
|
||||
events: `${import.meta.env.VITE_BASE_URL}/service/all`,
|
||||
eventClick: this.handleEventClick,
|
||||
// Add headers for authentication if needed by your API
|
||||
eventSourceSuccess: (content, response) => {
|
||||
eventSourceSuccess: (content) => {
|
||||
// This is where you could transform data if needed
|
||||
return content;
|
||||
},
|
||||
|
||||
@@ -1,66 +1,125 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class=" w-full px-10 ">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
Service Calls
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Service Calls</li>
|
||||
</ul>
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mt-4">Service Calls</h1>
|
||||
|
||||
<div class="flex text-2xl mb-5 font-bold">
|
||||
Service Calls
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Title and Count -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Upcoming and Active Service Calls</h2>
|
||||
<div v-if="!isLoading" class="badge badge-ghost">{{ services.length }} calls found</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="text-center p-10">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
<p class="mt-2">Loading service calls...</p>
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="services.length === 0" class="text-center p-10">
|
||||
<p>No active service calls found.</p>
|
||||
</div>
|
||||
|
||||
<!-- Data Display -->
|
||||
<div v-else>
|
||||
<!-- DESKTOP VIEW: Table -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date / Time</th>
|
||||
<th>Customer</th>
|
||||
<th>Address</th>
|
||||
<th>Service Type</th>
|
||||
<th>Description</th>
|
||||
<th class="text-right">Cost</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Removed @click from tr to avoid conflicting actions -->
|
||||
<tr v-for="service in services" :key="service.id" class="hover">
|
||||
<td class="align-top">
|
||||
<div>{{ formatDate(service.scheduled_date) }}</div>
|
||||
<div class="text-xs opacity-70">{{ formatTime(service.scheduled_date) }}</div>
|
||||
</td>
|
||||
<td class="align-top">{{ service.customer_name }}</td>
|
||||
<td class="align-top">{{ service.customer_address }}, {{ service.customer_town }}</td>
|
||||
<td class="align-top">
|
||||
<span class="font-medium" :style="{ color: getServiceTypeColor(service.type_service_call) }">
|
||||
{{ getServiceTypeName(service.type_service_call) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="whitespace-normal text-sm align-top">
|
||||
<!-- TRUNCATION LOGIC FOR DESKTOP -->
|
||||
<div v-if="!isLongDescription(service.description) || isExpanded(service.id)">
|
||||
{{ service.description }}
|
||||
<a v-if="isLongDescription(service.description)" @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Show less</a>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ truncateDescription(service.description) }}
|
||||
<a @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Read more</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right font-mono align-top">{{ formatCurrency(service.service_cost) }}</td>
|
||||
<td class="text-right align-top">
|
||||
<button @click="openEditModal(service)" class="btn btn-sm btn-primary">View</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- MOBILE VIEW: Cards -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<div v-for="service in services" :key="service.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ service.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">{{ service.customer_address }}, {{ service.customer_town }}</p>
|
||||
</div>
|
||||
<div class="badge badge-outline text-right" :style="{ 'border-color': getServiceTypeColor(service.type_service_call), color: getServiceTypeColor(service.type_service_call) }">
|
||||
{{ getServiceTypeName(service.type_service_call) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Date:</strong> {{ formatDate(service.scheduled_date) }}</p>
|
||||
<p><strong class="font-semibold">Time:</strong> {{ formatTime(service.scheduled_date) }}</p>
|
||||
<p><strong class="font-semibold">Cost:</strong> <span class="font-mono">{{ formatCurrency(service.service_cost) }}</span></p>
|
||||
</div>
|
||||
|
||||
<!-- TRUNCATION LOGIC FOR MOBILE -->
|
||||
<div v-if="service.description" class="text-sm mt-2 p-2 bg-base-200 rounded-md prose max-w-none">
|
||||
<div v-if="!isLongDescription(service.description) || isExpanded(service.id)">
|
||||
{{ service.description }}
|
||||
<a v-if="isLongDescription(service.description)" @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Show less</a>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ truncateDescription(service.description) }}
|
||||
<a @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Read more</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end mt-2">
|
||||
<button @click="openEditModal(service)" class="btn btn-sm btn-primary">View</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="text-center p-10">
|
||||
<p>Loading service calls...</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="services.length === 0" class="text-center p-10 bg-base-200 rounded-md">
|
||||
<p>No service calls found.</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="overflow-x-auto rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-700">
|
||||
<thead class="bg-base-200">
|
||||
<tr>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Scheduled Date</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Time</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Customer Name</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Address</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Service Type</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="bg-base-100 divide-y divide-gray-700">
|
||||
<tr v-for="service in services" :key="service.id" @click="openEditModal(service)" class="hover:bg-base-300 cursor-pointer">
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ formatDate(service.scheduled_date) }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ formatTime(service.scheduled_date) }}</td>
|
||||
|
||||
<td class="px-6 py-4 whitespace-nowrap hover:text-blue-600">{{ service.customer_name }}</td>
|
||||
|
||||
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ service.customer_address }}, {{ service.customer_town }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap font-medium" :style="{ color: getServiceTypeColor(service.type_service_call) }">
|
||||
{{ getServiceTypeName(service.type_service_call) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-normal text-sm">{{ service.description }}</td>
|
||||
<td class="px-6 py-4 whitespace-normal text-sm">{{ service.service_cost }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -74,16 +133,13 @@
|
||||
@delete-service="handleDeleteService"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } 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 ServiceEditModal from './ServiceEditModal.vue'
|
||||
import dayjs from 'dayjs'; // Import dayjs to handle date/time formatting
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
interface ServiceCall {
|
||||
id: number;
|
||||
@@ -99,13 +155,16 @@ interface ServiceCall {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServiceHome',
|
||||
components: { Header, SideBar, Footer, ServiceEditModal },
|
||||
components: { Footer, ServiceEditModal },
|
||||
data() {
|
||||
return {
|
||||
user: null,
|
||||
services: [] as ServiceCall[],
|
||||
isLoading: true,
|
||||
selectedServiceForEdit: null as ServiceCall | null,
|
||||
// --- ADDITIONS FOR TRUNCATION ---
|
||||
wordLimit: 50,
|
||||
expandedIds: [] as number[],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -154,7 +213,26 @@ export default defineComponent({
|
||||
closeEditModal() {
|
||||
this.selectedServiceForEdit = null;
|
||||
},
|
||||
|
||||
isLongDescription(text: string): boolean {
|
||||
if (!text) return false;
|
||||
return text.split(/\s+/).length > this.wordLimit;
|
||||
},
|
||||
truncateDescription(text: string): string {
|
||||
if (!this.isLongDescription(text)) return text;
|
||||
const words = text.split(/\s+/);
|
||||
return words.slice(0, this.wordLimit).join(' ') + '...';
|
||||
},
|
||||
isExpanded(id: number): boolean {
|
||||
return this.expandedIds.includes(id);
|
||||
},
|
||||
toggleExpand(id: number): void {
|
||||
const index = this.expandedIds.indexOf(id);
|
||||
if (index === -1) {
|
||||
this.expandedIds.push(id);
|
||||
} else {
|
||||
this.expandedIds.splice(index, 1);
|
||||
}
|
||||
},
|
||||
async handleSaveChanges(updatedService: ServiceCall) {
|
||||
try {
|
||||
const path = `${import.meta.env.VITE_BASE_URL}/service/update/${updatedService.id}`;
|
||||
@@ -206,7 +284,17 @@ export default defineComponent({
|
||||
};
|
||||
return typeMap[typeId] || 'Unknown Service';
|
||||
},
|
||||
|
||||
// --- ADD THIS METHOD ---
|
||||
formatCurrency(value: string | number): string {
|
||||
if (value === null || value === undefined || value === '') return '$0.00';
|
||||
const numberValue = Number(value);
|
||||
if (isNaN(numberValue)) return '$0.00';
|
||||
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(numberValue);
|
||||
},
|
||||
getServiceTypeColor(typeId: number): string {
|
||||
const colorMap: { [key: number]: string } = {
|
||||
0: 'blue',
|
||||
|
||||
@@ -1,75 +1,126 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="">
|
||||
<SideBar />
|
||||
</div>
|
||||
<div class="w-full px-10">
|
||||
<div class="text-sm breadcrumbs mb-10">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: 'home' }">
|
||||
Home
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
PastService Calls
|
||||
</li>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li>Past Service Calls</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex text-2xl mb-5 font-bold">
|
||||
Past Service Calls
|
||||
</div>
|
||||
|
||||
<div v-if="isLoading" class="text-center p-10">
|
||||
<p>Loading service calls...</p>
|
||||
</div>
|
||||
<!-- Main Content Card -->
|
||||
<div class="bg-neutral rounded-lg p-4 sm:p-6 mt-6">
|
||||
<!-- Header: Title and Count -->
|
||||
<div class="flex flex-col sm:flex-row sm:justify-between sm:items-center gap-4 mb-4">
|
||||
<h2 class="text-lg font-bold">Service Call History</h2>
|
||||
<div v-if="!isLoading" class="badge badge-ghost">{{ services.length }} calls found</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
|
||||
<div v-else-if="services.length === 0" class="text-center p-10 bg-base-200 rounded-md">
|
||||
<p>No service calls found.</p>
|
||||
</div>
|
||||
<!-- Loading State -->
|
||||
<div v-if="isLoading" class="text-center p-10">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
<p class="mt-2">Loading service calls...</p>
|
||||
</div>
|
||||
|
||||
<div v-else class="overflow-x-auto rounded-lg">
|
||||
<table class="min-w-full divide-y divide-gray-700 table-fixed">
|
||||
|
||||
<!-- =================== THIS IS THE CORRECTED SECTION =================== -->
|
||||
<thead class="bg-base-200">
|
||||
<tr>
|
||||
<!-- Columns with predictable, shorter content get fixed widths -->
|
||||
<th scope="col" class="w-48 px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Scheduled Date</th>
|
||||
<th scope="col" class="w-32 px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Time</th>
|
||||
|
||||
<!-- Columns with variable text content can share the remaining space -->
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Customer Name</th>
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Address</th>
|
||||
|
||||
<!-- Another fixed-width column -->
|
||||
<th scope="col" class="w-32 px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Service Type</th>
|
||||
<!-- Empty State -->
|
||||
<div v-else-if="services.length === 0" class="text-center p-10">
|
||||
<p>No past service calls found.</p>
|
||||
</div>
|
||||
|
||||
<!-- Description can be left to fill remaining space -->
|
||||
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Description</th>
|
||||
|
||||
<!-- Service Cost now has a smaller, fixed width -->
|
||||
<th scope="col" class="w-20 px-6 py-3 text-left text-xs font-medium text-base-content uppercase tracking-wider">Service Cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<!-- =================== END OF CORRECTED SECTION =================== -->
|
||||
<!-- Data Display -->
|
||||
<div v-else>
|
||||
<!-- DESKTOP VIEW: Table (Revamped) -->
|
||||
<div class="overflow-x-auto hidden xl:block">
|
||||
<table class="table w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Date / Time</th>
|
||||
<th>Customer</th>
|
||||
<th>Address</th>
|
||||
<th>Service Type</th>
|
||||
<th>Description</th>
|
||||
<th class="text-right">Cost</th>
|
||||
<th class="text-right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- Removed @click from tr to avoid conflicting with "Read more" -->
|
||||
<tr v-for="service in services" :key="service.id" class="hover">
|
||||
<td class="align-top">
|
||||
<div>{{ formatDate(service.scheduled_date) }}</div>
|
||||
<div class="text-xs opacity-70">{{ formatTime(service.scheduled_date) }}</div>
|
||||
</td>
|
||||
<td class="align-top">{{ service.customer_name }}</td>
|
||||
<td class="align-top">{{ service.customer_address }}, {{ service.customer_town }}</td>
|
||||
<td class="align-top">
|
||||
<span class="font-medium" :style="{ color: getServiceTypeColor(service.type_service_call) }">
|
||||
{{ getServiceTypeName(service.type_service_call) }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="whitespace-normal text-sm align-top">
|
||||
<!-- TRUNCATION LOGIC FOR DESKTOP -->
|
||||
<div v-if="!isLongDescription(service.description) || isExpanded(service.id)">
|
||||
{{ service.description }}
|
||||
<a v-if="isLongDescription(service.description)" @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Show less</a>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ truncateDescription(service.description) }}
|
||||
<a @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Read more</a>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right font-mono align-top">{{ formatCurrency(service.service_cost) }}</td>
|
||||
<td class="text-right align-top">
|
||||
<!-- Moved @click handler to the button for explicit action -->
|
||||
<button @click="openEditModal(service)" class="btn btn-sm btn-primary">View</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<tbody class="bg-base-100 divide-y divide-gray-700">
|
||||
<tr v-for="service in services" :key="service.id" @click="openEditModal(service)" class="hover:bg-base-300 cursor-pointer">
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ formatDate(service.scheduled_date) }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">{{ formatTime(service.scheduled_date) }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap hover:text-blue-600 truncate">{{ service.customer_name }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap truncate">{{ service.customer_address }}, {{ service.customer_town }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap font-medium" :style="{ color: getServiceTypeColor(service.type_service_call) }">
|
||||
{{ getServiceTypeName(service.type_service_call) }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-normal text-sm">{{ service.description }}</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap text-sm text-right">{{ formatCurrency(service.service_cost) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- MOBILE VIEW: Cards (Revamped) -->
|
||||
<div class="xl:hidden space-y-4">
|
||||
<!-- Removed @click from card div -->
|
||||
<div v-for="service in services" :key="service.id" class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<div class="flex justify-between items-start">
|
||||
<div>
|
||||
<h2 class="card-title text-base">{{ service.customer_name }}</h2>
|
||||
<p class="text-xs text-gray-400">{{ service.customer_address }}, {{ service.customer_town }}</p>
|
||||
</div>
|
||||
<div class="badge badge-outline text-right" :style="{ 'border-color': getServiceTypeColor(service.type_service_call), color: getServiceTypeColor(service.type_service_call) }">
|
||||
{{ getServiceTypeName(service.type_service_call) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-sm mt-2 grid grid-cols-2 gap-x-4 gap-y-1">
|
||||
<p><strong class="font-semibold">Date:</strong> {{ formatDate(service.scheduled_date) }}</p>
|
||||
<p><strong class="font-semibold">Time:</strong> {{ formatTime(service.scheduled_date) }}</p>
|
||||
<p><strong class="font-semibold">Cost:</strong> <span class="font-mono">{{ formatCurrency(service.service_cost) }}</span></p>
|
||||
</div>
|
||||
|
||||
<!-- TRUNCATION LOGIC FOR MOBILE -->
|
||||
<div v-if="service.description" class="text-sm mt-2 p-2 bg-base-200 rounded-md prose max-w-none">
|
||||
<div v-if="!isLongDescription(service.description) || isExpanded(service.id)">
|
||||
{{ service.description }}
|
||||
<a v-if="isLongDescription(service.description)" @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Show less</a>
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ truncateDescription(service.description) }}
|
||||
<a @click.prevent="toggleExpand(service.id)" href="#" class="link link-info link-hover text-xs ml-1 whitespace-nowrap">Read more</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-actions justify-end mt-2">
|
||||
<!-- Moved @click handler to the button -->
|
||||
<button @click="openEditModal(service)" class="btn btn-sm btn-primary">View</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -84,13 +135,10 @@
|
||||
@delete-service="handleDeleteService"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } 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 ServiceEditModal from './ServiceEditModal.vue'
|
||||
import dayjs from 'dayjs';
|
||||
@@ -109,22 +157,47 @@ interface ServiceCall {
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ServiceHPast',
|
||||
components: { Header, SideBar, Footer, ServiceEditModal },
|
||||
components: { Footer, ServiceEditModal },
|
||||
data() {
|
||||
return {
|
||||
user: null,
|
||||
services: [] as ServiceCall[],
|
||||
isLoading: true,
|
||||
selectedServiceForEdit: null as ServiceCall | null,
|
||||
// --- ADDITIONS FOR TRUNCATION ---
|
||||
wordLimit: 50,
|
||||
expandedIds: [] as number[],
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.userStatus();
|
||||
this.fetchUpcomingServices();
|
||||
this.fetchPastServices();
|
||||
},
|
||||
methods: {
|
||||
// --- NEW METHODS FOR TRUNCATION ---
|
||||
isLongDescription(text: string): boolean {
|
||||
if (!text) return false;
|
||||
return text.split(/\s+/).length > this.wordLimit;
|
||||
},
|
||||
truncateDescription(text: string): string {
|
||||
if (!this.isLongDescription(text)) return text;
|
||||
const words = text.split(/\s+/);
|
||||
return words.slice(0, this.wordLimit).join(' ') + '...';
|
||||
},
|
||||
isExpanded(id: number): boolean {
|
||||
return this.expandedIds.includes(id);
|
||||
},
|
||||
toggleExpand(id: number): void {
|
||||
const index = this.expandedIds.indexOf(id);
|
||||
if (index === -1) {
|
||||
this.expandedIds.push(id);
|
||||
} else {
|
||||
this.expandedIds.splice(index, 1);
|
||||
}
|
||||
},
|
||||
|
||||
// --- API and Data Handling Methods ---
|
||||
async fetchUpcomingServices(): Promise<void> {
|
||||
async fetchPastServices(): Promise<void> {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const path = import.meta.env.VITE_BASE_URL + '/service/past';
|
||||
@@ -134,7 +207,7 @@ export default defineComponent({
|
||||
});
|
||||
this.services = response.data;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch upcoming service calls:", error);
|
||||
console.error("Failed to fetch past service calls:", error);
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
@@ -198,16 +271,15 @@ export default defineComponent({
|
||||
},
|
||||
|
||||
// --- Formatting and Display Methods ---
|
||||
formatCurrency(value: string): string {
|
||||
if (!value) return '$0.00';
|
||||
const costAsNumber = parseFloat(value);
|
||||
if (isNaN(costAsNumber)) {
|
||||
return value;
|
||||
}
|
||||
formatCurrency(value: string | number): string {
|
||||
if (value === null || value === undefined || value === '') return '$0.00';
|
||||
const numberValue = Number(value);
|
||||
if (isNaN(numberValue)) return '$0.00';
|
||||
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: 'USD',
|
||||
}).format(costAsNumber);
|
||||
}).format(numberValue);
|
||||
},
|
||||
|
||||
formatDate(dateString: string): string {
|
||||
|
||||
@@ -1,39 +1,57 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="flex">
|
||||
<div class="w-full px-10">
|
||||
<div class="text-sm breadcrumbs mb-4">
|
||||
<div class="w-full px-4 md:px-10 py-4">
|
||||
<!-- Breadcrumbs & Title -->
|
||||
<div class="text-sm breadcrumbs">
|
||||
<ul>
|
||||
<li><router-link :to="{ name: 'home' }">Home</router-link></li>
|
||||
<li><router-link :to="{ name: 'customer' }">Customers</router-link></li>
|
||||
<li v-if="customer">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</li>
|
||||
<li>Service Calendar</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="flex h-screen font-sans">
|
||||
<div v-if="isLoading" class="w-1/4 p-4 border-r">
|
||||
<h2 class="text-xl font-bold">Loading Customer...</h2>
|
||||
</div>
|
||||
<EventSidebar v-else-if="customer" :customer="customer" @event-scheduled="handleEventScheduled" />
|
||||
<div v-else class="w-1/4 p-4 border-r">
|
||||
<h2 class="text-xl font-bold text-red-500">Error</h2>
|
||||
<p>Could not load customer data. You can still view the master calendar.</p>
|
||||
<!--
|
||||
Main Responsive Container:
|
||||
- Stacks vertically on mobile (flex-col)
|
||||
- Sits side-by-side on large screens (lg:flex-row)
|
||||
-->
|
||||
<div class="flex flex-col lg:flex-row gap-6 mt-6">
|
||||
|
||||
<!-- Sidebar Area (Uses our new responsive EventSidebar) -->
|
||||
<EventSidebar v-if="!isLoading && customer" :customer="customer" @event-scheduled="handleEventScheduled" />
|
||||
|
||||
<!-- Loading/Error States (Styled to match the sidebar) -->
|
||||
<div v-else class="w-full lg:w-96 lg:flex-none p-4">
|
||||
<div class="bg-neutral rounded-lg p-6 sticky top-4 text-center">
|
||||
<div v-if="isLoading">
|
||||
<span class="loading loading-spinner"></span>
|
||||
<p class="mt-2">Loading Customer...</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<h2 class="text-xl font-bold text-error">Error</h2>
|
||||
<p>Could not load customer data. You can still view the master calendar.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-1 p-4 overflow-auto">
|
||||
<!-- Main Calendar Area -->
|
||||
<div class="flex-grow bg-neutral rounded-lg p-4">
|
||||
<FullCalendar ref="fullCalendar" :options="calendarOptions" />
|
||||
</div>
|
||||
|
||||
<ServiceEditModal
|
||||
v-if="selectedServiceForEdit"
|
||||
:service="selectedServiceForEdit"
|
||||
@close-modal="closeEditModal"
|
||||
@save-changes="handleSaveChanges"
|
||||
@delete-service="handleDeleteService"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Modal remains at the root level, which is correct -->
|
||||
<ServiceEditModal
|
||||
v-if="selectedServiceForEdit"
|
||||
:service="selectedServiceForEdit"
|
||||
@close-modal="closeEditModal"
|
||||
@save-changes="handleSaveChanges"
|
||||
@delete-service="handleDeleteService"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
@@ -1,65 +1,77 @@
|
||||
<template>
|
||||
<div class="w-1/4 p-4 border-r">
|
||||
<h2 class="text-xl font-bold mb-4">Schedule Service</h2>
|
||||
|
||||
<form @submit.prevent="submitEvent">
|
||||
<div class="mb-4">
|
||||
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
|
||||
<label for="event-label" class="block text-sm font-medium text-gray-200">Calendar Label</label>
|
||||
<input type="text" id="event-label" v-model="event.title" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
|
||||
</div>
|
||||
<!--
|
||||
This container is now responsive. It's full-width on mobile
|
||||
and becomes a fixed-width sidebar on large screens.
|
||||
-->
|
||||
<div class="w-full lg:w-96 lg:flex-none p-4">
|
||||
<!-- The sidebar is now sticky to the top on large screens -->
|
||||
<div class="bg-neutral rounded-lg p-6 sticky top-4">
|
||||
<h2 class="text-xl font-bold mb-4">Schedule Service</h2>
|
||||
|
||||
<form @submit.prevent="submitEvent" class="space-y-4">
|
||||
<!-- Calendar Label -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Calendar Label</span></label>
|
||||
<input type="text" v-model="event.title" required class="input input-bordered input-sm w-full" placeholder="e.g., Boiler Tune-up">
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
|
||||
<label for="service_type" class="block text-sm font-medium text-gray-200">Type of Service</label>
|
||||
<select class="select select-bordered select-sm w-full max-w-xs bg-white text-black" id="service_type" v-model="selectedService" required>
|
||||
<option disabled value="">Please select one</option>
|
||||
<option v-for="option in serviceOptions" :key="option.value" :value="option.value">
|
||||
{{ option.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- Service Type -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Type of Service</span></label>
|
||||
<select class="select select-bordered select-sm w-full" v-model="selectedService" required>
|
||||
<option disabled value="">Please select one</option>
|
||||
<option v-for="option in serviceOptions" :key="option.value" :value="option.value">
|
||||
{{ option.text }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
|
||||
<label for="event-description" class="block text-sm font-medium text-gray-200">Description</label>
|
||||
<textarea id="event-description" v-model="event.description" rows="3" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black"></textarea>
|
||||
</div>
|
||||
<!-- Description -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Description</span></label>
|
||||
<textarea v-model="event.description" rows="3" required class="textarea textarea-bordered textarea-sm" placeholder="Notes for the technician..."></textarea>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
|
||||
<label for="event-date" class="block text-sm font-medium text-gray-200">Day / Month</label>
|
||||
<input type="date" id="event-date" v-model="event.date" required class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
|
||||
</div>
|
||||
<!-- Date & Time Grid -->
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- Date -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Date</span></label>
|
||||
<input type="date" v-model="event.date" required class="input input-bordered input-sm w-full">
|
||||
</div>
|
||||
<!-- Time -->
|
||||
<div class="form-control">
|
||||
<label class="label"><span class="label-text">Time</span></label>
|
||||
<select v-model="event.time" class="select select-bordered select-sm w-full">
|
||||
<option v-for="hour in 24" :key="hour" :value="hour - 1">{{ (hour - 1).toString().padStart(2, '0') }}:00</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<!-- CHANGED: Class updated to 'text-gray-200' for visibility on dark backgrounds -->
|
||||
<label for="event-time" class="block text-sm font-medium text-gray-200">Time (Hour)</label>
|
||||
<select id="event-time" v-model="event.time" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm text-black">
|
||||
<option v-for="hour in 24" :key="hour" :value="hour - 1">{{ (hour - 1).toString().padStart(2, '0') }}:00</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm w-full mt-4">
|
||||
Add Event
|
||||
</button>
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
<button type="submit" class="w-full bg-green-600 text-white py-2 px-4 rounded-md hover:bg-green-700">
|
||||
Add Event
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div v-if="customer" class="mt-10 border-t pt-4">
|
||||
<div class="font-bold text-lg">
|
||||
{{ customer.customer_first_name }} {{ customer.customer_last_name }}
|
||||
<!-- Customer Info Section -->
|
||||
<div v-if="customer" class="mt-6">
|
||||
<div class="divider">For Customer</div>
|
||||
<!-- Customer Info "Card within a Card" -->
|
||||
<div class="card bg-base-100 shadow-md">
|
||||
<div class="card-body p-4">
|
||||
<h3 class="card-title text-base">{{ customer.customer_first_name }} {{ customer.customer_last_name }}</h3>
|
||||
<div class="text-sm mt-2 space-y-1">
|
||||
<p>{{ customer.customer_address }}</p>
|
||||
<p v-if="customer.customer_apt && customer.customer_apt !== 'None'">{{ customer.customer_apt }}</p>
|
||||
<p>{{ customer.customer_town }}, {{ customerStateName }} {{ customer.customer_zip }}</p>
|
||||
<p class="pt-2 font-semibold">{{ customer.customer_phone_number }}</p>
|
||||
</div>
|
||||
<div class="card-actions justify-end">
|
||||
<div class="badge badge-outline">{{ customerHomeType }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>{{ customer.customer_address }}</div>
|
||||
<div v-if="customer.customer_apt">{{ customer.customer_apt }}</div>
|
||||
<div>
|
||||
<span>{{ customer.customer_town }},</span>
|
||||
<span class="pl-1">{{ customerStateName }}</span>
|
||||
<span class="pl-1">{{ customer.customer_zip }}</span>
|
||||
</div>
|
||||
<div>{{ customer.customer_phone_number }}</div>
|
||||
<div class="text-sm text-gray-500 mt-2">{{ customerHomeType }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
89
src/stores/search.ts
Normal file
89
src/stores/search.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
// src/stores/search.ts
|
||||
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
import axios from 'axios'
|
||||
|
||||
// Define a type for what a search result looks like. This is good practice.
|
||||
interface CustomerSearchResult {
|
||||
id: number;
|
||||
customer_first_name: string;
|
||||
customer_last_name: string;
|
||||
customer_address: string;
|
||||
customer_town: string;
|
||||
customer_state: string;
|
||||
customer_phone_number: string;
|
||||
}
|
||||
|
||||
export const useSearchStore = defineStore('search', () => {
|
||||
// --- STATE ---
|
||||
const searchTerm = ref('');
|
||||
const searchResults = ref<CustomerSearchResult[]>([]);
|
||||
const isLoading = ref(false);
|
||||
|
||||
// --- NEW: A variable to hold our timer ID for debouncing ---
|
||||
// Using `any` is okay here, but `number` for browser or `Timeout` for Node is more specific.
|
||||
let debounceTimer: any = null;
|
||||
|
||||
|
||||
// --- GETTERS ---
|
||||
const showResults = computed(() => {
|
||||
return searchTerm.value.length > 1 && searchResults.value.length > 0;
|
||||
});
|
||||
|
||||
// --- ACTIONS ---
|
||||
|
||||
// This is the original function that makes the API call. We'll keep it.
|
||||
async function fetchSearchResults() {
|
||||
if (searchTerm.value.length < 2) {
|
||||
searchResults.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const response = await axios.get(`/search/customer?q=${searchTerm.value}`);
|
||||
searchResults.value = response.data;
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch search results:", error);
|
||||
searchResults.value = [];
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// --- NEW: This is the debounced action your component will call ---
|
||||
function debouncedSearch() {
|
||||
// 1. Clear any existing timer. If the user is still typing, this
|
||||
// cancels the previous plan to make an API call.
|
||||
clearTimeout(debounceTimer);
|
||||
|
||||
// 2. Set a new timer. We will only call `fetchSearchResults` after the
|
||||
// user has stopped typing for 400 milliseconds.
|
||||
debounceTimer = setTimeout(() => {
|
||||
fetchSearchResults();
|
||||
}, 400); // 400ms is a good balance between responsiveness and efficiency
|
||||
}
|
||||
|
||||
|
||||
function clearSearch() {
|
||||
searchTerm.value = '';
|
||||
searchResults.value = [];
|
||||
// Also clear the timer if a search is in progress
|
||||
clearTimeout(debounceTimer);
|
||||
}
|
||||
|
||||
// --- RETURN ---
|
||||
return {
|
||||
// State
|
||||
searchTerm,
|
||||
searchResults,
|
||||
isLoading,
|
||||
// Getters
|
||||
showResults,
|
||||
// Actions
|
||||
fetchSearchResults, // You might not need to export this anymore
|
||||
debouncedSearch, // Export the new debounced function
|
||||
clearSearch,
|
||||
}
|
||||
})
|
||||
@@ -5,7 +5,7 @@
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
"esModuleInterop": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
|
||||
@@ -1,13 +1,29 @@
|
||||
// vite.config.ts
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
resolve: {},
|
||||
build: {
|
||||
sourcemap: true,
|
||||
chunkSizeWarningLimit: 1600,
|
||||
assetsInlineLimit: 2048, // 2kb
|
||||
},
|
||||
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 5173,
|
||||
proxy: {
|
||||
// Rule #1: Handle all search requests
|
||||
'/search': {
|
||||
target: 'http://backend_office_dev:4056',
|
||||
changeOrigin: true,
|
||||
},
|
||||
// Rule #2 (NEW): Handle all authentication requests
|
||||
'/auth': {
|
||||
target: 'http://backend_office_dev:4056',
|
||||
changeOrigin: true,
|
||||
},
|
||||
// Add more rules here for your other API blueprints if needed
|
||||
// For example, if you have delivery routes:
|
||||
// '/delivery': {
|
||||
// target: 'http://backend_office_dev:4056',
|
||||
// changeOrigin: true,
|
||||
// },
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user