Added call dropdown
This commit is contained in:
@@ -4,6 +4,8 @@ ENV VITE_BASE_URL="http://localhost:9510"
|
|||||||
ENV VITE_AUTO_URL="http://localhost:9514"
|
ENV VITE_AUTO_URL="http://localhost:9514"
|
||||||
ENV VITE_MONEY_URL="http://localhost:9513"
|
ENV VITE_MONEY_URL="http://localhost:9513"
|
||||||
ENV VITE_AUTHORIZE_URL="http://localhost:9516"
|
ENV VITE_AUTHORIZE_URL="http://localhost:9516"
|
||||||
|
ENV VITE_VOIPMS_URL="http://localhost:9517"
|
||||||
|
ENV VITE_VOIPMS_TOKEN="my_secret_token"
|
||||||
ENV VITE_COMPANY_ID='1'
|
ENV VITE_COMPANY_ID='1'
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ ENV VITE_BASE_URL="http://192.168.1.204:9610"
|
|||||||
ENV VITE_AUTO_URL="http://192.168.1.204:9614"
|
ENV VITE_AUTO_URL="http://192.168.1.204:9614"
|
||||||
ENV VITE_MONEY_URL="http://192.168.1.204:9613"
|
ENV VITE_MONEY_URL="http://192.168.1.204:9613"
|
||||||
ENV VITE_AUTHORIZE_URL="http://localhost:9516"
|
ENV VITE_AUTHORIZE_URL="http://localhost:9516"
|
||||||
|
ENV VITE_VOIPMS_URL="http://localhost:9517"
|
||||||
ENV VITE_COMPANY_ID='1'
|
ENV VITE_COMPANY_ID='1'
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ ENV VITE_BASE_URL="https://apioil.edwineames.com"
|
|||||||
ENV VITE_AUTO_URL="https://apiauto.edwineames.com"
|
ENV VITE_AUTO_URL="https://apiauto.edwineames.com"
|
||||||
ENV VITE_MONEY_URL="https://apimoney.edwineames.com"
|
ENV VITE_MONEY_URL="https://apimoney.edwineames.com"
|
||||||
ENV VITE_AUTHORIZE_URL="https://apicard.edwineames.com"
|
ENV VITE_AUTHORIZE_URL="https://apicard.edwineames.com"
|
||||||
|
ENV VITE_VOIPMS_URL="http://apiphone.edwineames.com:9516"
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json ./
|
COPY package.json ./
|
||||||
|
|||||||
@@ -1,44 +1,76 @@
|
|||||||
<!-- headerauth.vue -->
|
<!-- headerauth.vue -->
|
||||||
<template>
|
<template>
|
||||||
<div class="navbar bg-primary border-b border-gray-700 sticky top-0 z-30">
|
<!-- Main header container, contains both rows -->
|
||||||
|
<header class="sticky top-0 z-50 bg-base-200 shadow-sm">
|
||||||
|
|
||||||
<!-- Navbar Start Section -->
|
<!-- Row 1: The primary navbar -->
|
||||||
|
<div class="navbar px-4">
|
||||||
|
|
||||||
|
<!-- Navbar Start: Logo & Mobile Menu Toggle -->
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<!-- Hamburger Menu Toggle for Mobile -->
|
|
||||||
<label for="my-drawer-2" class="btn btn-ghost btn-circle drawer-button lg:hidden">
|
<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>
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||||
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
|
<router-link :to="{ name: 'home' }" class="btn btn-ghost normal-case text-xl font-bold">
|
||||||
<!-- Logo -->
|
|
||||||
<router-link :to="{ name: 'home' }" class="btn btn-ghost normal-case text-xl">
|
|
||||||
Auburn Oil
|
Auburn Oil
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Navbar Center Section (Desktop Search Bar) -->
|
|
||||||
<div class="navbar-center hidden lg:flex">
|
|
||||||
<!--
|
<!--
|
||||||
THIS IS THE ONLY CHANGE NEEDED ON THIS ENTIRE PAGE.
|
Navbar End: Contains the Search Bar (on tablet+) and all action buttons.
|
||||||
We are adding the @input event listener to trigger the search.
|
Using flexbox to manage the space distribution.
|
||||||
-->
|
-->
|
||||||
|
<div class="navbar-end w-full gap-4">
|
||||||
|
|
||||||
|
<!-- Search Bar Wrapper (for tablet and up) -->
|
||||||
|
<!-- This is the growing element. Hidden on mobile. -->
|
||||||
|
<div class="hidden md:flex flex-grow max-w-xl">
|
||||||
|
<div class="relative w-full">
|
||||||
|
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-base-content/50">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
<input
|
<input
|
||||||
id="customer-search-input"
|
id="customer-search-input-desktop"
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search customers..."
|
placeholder="Search customers..."
|
||||||
v-model="searchStore.searchTerm"
|
v-model="searchStore.searchTerm"
|
||||||
class="input input-bordered"
|
class="input input-bordered w-full pl-10"
|
||||||
@input="searchStore.debouncedSearch"
|
@input="searchStore.debouncedSearch"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Navbar End Section (Buttons and User Dropdown) -->
|
<!-- Action Buttons Wrapper -->
|
||||||
<div class="navbar-end gap-2">
|
<!-- This group is set to not shrink, keeping it always visible. -->
|
||||||
<!-- Create Customer Button (Desktop Only) -->
|
<div class="flex-shrink-0 flex items-center gap-2">
|
||||||
<router-link :to="{ name: 'customerCreate' }" class="btn btn-accent btn-sm hidden lg:inline-flex">
|
|
||||||
Create Customer
|
<!-- VOIP Routing Dropdown (visible tablet+) -->
|
||||||
|
<div class="dropdown dropdown-end hidden md:flex">
|
||||||
|
<label tabindex="0" class="btn btn-ghost gap-2">
|
||||||
|
<svg xmlns="http://www.w.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-success">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 6.75c0 8.284 6.716 15 15 15h2.25a2.25 2.25 0 002.25-2.25v-1.372c0-.516-.351-.966-.852-1.091l-4.423-1.106c-.44-.11-.902.055-1.173.417l-.97 1.293c-.282.376-.769.542-1.21.38a12.035 12.035 0 01-7.143-7.143c-.162-.441.004-.928.38-1.21l1.293-.97c.363-.271.527-.734.417-1.173L6.963 3.102a1.125 1.125 0 00-1.091-.852H4.5A2.25 2.25 0 002.25 6.75z" />
|
||||||
|
</svg>
|
||||||
|
<span class="font-semibold whitespace-nowrap">{{ currentPhone || 'Routing' }}</span>
|
||||||
|
</label>
|
||||||
|
<ul tabindex="0" class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52 mt-4">
|
||||||
|
<li v-for="option in routingOptions" :key="option.value">
|
||||||
|
<a @click.prevent="showConfirmRoute(option)" href="#">
|
||||||
|
<span class="font-mono">{{ option.label }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Create Customer Button (visible tablet+) -->
|
||||||
|
<router-link :to="{ name: 'customerCreate' }" class="btn btn-success btn-sm hidden md:inline-flex">
|
||||||
|
New Customer
|
||||||
</router-link>
|
</router-link>
|
||||||
|
|
||||||
<!-- User Dropdown -->
|
<!-- User Dropdown (Always visible) -->
|
||||||
<div v-if="user.user_id" class="dropdown dropdown-end">
|
<div v-if="user.user_id" class="dropdown dropdown-end">
|
||||||
<label tabindex="0" class="btn btn-ghost btn-circle avatar">
|
<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">
|
<div class="w-10 rounded-full bg-neutral text-neutral-content flex items-center justify-center">
|
||||||
@@ -48,27 +80,137 @@
|
|||||||
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
|
<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>
|
<li class="p-2 font-semibold">{{ user.user_name }}</li>
|
||||||
<div class="divider my-0"></div>
|
<div class="divider my-0"></div>
|
||||||
<li>
|
<li><router-link :to="{ name: 'employeeProfile', params: { id: user.user_id } }">Profile</router-link></li>
|
||||||
<router-link :to="{ name: 'employeeProfile', params: { id: user.user_id } }">
|
<li><router-link :to="{ name: 'changePassword' }">Change Password</router-link></li>
|
||||||
Profile
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<router-link :to="{ name: 'changePassword' }">
|
|
||||||
Change Password
|
|
||||||
</router-link>
|
|
||||||
</li>
|
|
||||||
<li><a @click="logout">Logout</a></li>
|
<li><a @click="logout">Logout</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- This provides the loading indicator while we wait for the API call to finish. -->
|
<div v-else class="skeleton w-10 h-10 rounded-full"></div>
|
||||||
<div v-else class="px-4">
|
</div>
|
||||||
{{user}}
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 2: MOBILE SEARCH BAR -->
|
||||||
|
<!-- This full-width search bar only appears on screens smaller than md -->
|
||||||
|
<div class="w-full px-4 pb-3 md:hidden">
|
||||||
|
<div class="relative w-full">
|
||||||
|
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5 text-base-content/50">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
id="customer-search-input-mobile"
|
||||||
|
type="text"
|
||||||
|
placeholder="Search customers..."
|
||||||
|
v-model="searchStore.searchTerm"
|
||||||
|
class="input input-bordered w-full pl-10"
|
||||||
|
@input="searchStore.debouncedSearch"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- MODAL SECTION REMAINS UNCHANGED -->
|
||||||
|
<!-- ... (paste your existing modals here) ... -->
|
||||||
|
|
||||||
|
<div class="modal" :class="{ 'modal-open': isRouteModalVisible }">
|
||||||
|
<div class="modal-box">
|
||||||
|
<div v-if="routeModalMode === 'confirm'">
|
||||||
|
<h3 class="font-bold text-lg">Confirm Routing Change</h3>
|
||||||
|
<p class="py-4">FROM: {{ currentPhone }} → TO: {{ selectedOption?.label }}</p>
|
||||||
|
<div class="modal-action">
|
||||||
|
<button @click="proceedRoute" class="btn btn-success">Confirm</button>
|
||||||
|
<button @click="closeRouteModal" class="btn btn-error">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="routeModalMode === 'loading'">
|
||||||
|
<h3 class="font-bold text-lg">Switching Route</h3>
|
||||||
|
<div class="py-4 text-center">
|
||||||
|
<p class="text-lg mb-3">Switching phone routing from {{ currentPhone }} to {{ selectedOption?.label }}...</p>
|
||||||
|
<div class="loading loading-spinner loading-lg text-primary mb-3"></div>
|
||||||
|
<p class="text-sm text-gray-600">Please wait while we update the routing.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="routeModalMode === 'result'">
|
||||||
|
<h3 class="font-bold text-lg">Route Change Result</h3>
|
||||||
|
<div class="py-4">
|
||||||
|
<div v-if="routeResponse && !routeResponse.error" class="alert alert-success mb-4">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>Routing change completed successfully.</span>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="routeResponse && routeResponse.error" class="alert alert-error mb-4">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>Error occurred during routing change.</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="routeResponse.message || routeResponse.status" class="bg-base-200 p-3 rounded">
|
||||||
|
<p>{{ routeResponse.message || routeResponse.status }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-else class="bg-base-200 p-3 rounded">
|
||||||
|
<p>Response received successfully.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-action">
|
||||||
|
<button @click="closeRouteModal" class="btn">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" :class="{ 'modal-open': isTestModalVisible }">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="font-bold text-lg">DID Test Result</h3>
|
||||||
|
<div class="py-4">
|
||||||
|
<div v-if="isTestLoading" class="text-center">
|
||||||
|
<span class="text-lg mb-3">Testing DID connection...</span>
|
||||||
|
<div class="loading loading-spinner loading-lg text-primary mb-3"></div>
|
||||||
|
<p class="text-sm text-gray-600">Please wait while we fetch DID information.</p>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="testResponse">
|
||||||
|
<div v-if="testResponse.status === 'success' && testResponse.dids && testResponse.dids.length > 0" class="space-y-4">
|
||||||
|
<div class="alert alert-success">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<span>DID Test Successful! Connection is working.</span>
|
||||||
|
</div>
|
||||||
|
<div v-for="did in testResponse.dids" :key="did.did" class="border rounded-lg p-3 bg-base-100">
|
||||||
|
<h4 class="font-semibold mb-2">DID: {{ did.did }}</h4>
|
||||||
|
<div class="grid grid-cols-2 gap-2 text-sm">
|
||||||
|
<div><strong>Description:</strong> {{ did.description }}</div>
|
||||||
|
<div><strong>Routing:</strong> {{ did.routing }}</div>
|
||||||
|
<div><strong>Call Recording:</strong> {{ did.record_calls === '1' ? 'Enabled' : 'Disabled' }}</div>
|
||||||
|
<div><strong>Voicemail:</strong> {{ did.voicemail === '1' ? 'Enabled' : 'Disabled' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else-if="testResponse.error" class="text-center text-error">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6 mx-auto mb-2" fill="none" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||||
|
</svg>
|
||||||
|
<p class="font-semibold">API Error</p>
|
||||||
|
<p>{{ testResponse.error }}</p>
|
||||||
|
</div>
|
||||||
|
<div v-else class="bg-base-200 p-3 rounded text-sm overflow-auto max-h-40">
|
||||||
|
<pre>{{ JSON.stringify(testResponse, null, 2) }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="text-center">
|
||||||
|
<p>No response received.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-action" v-if="!isTestLoading">
|
||||||
|
<button @click="isTestModalVisible = false; testResponse = null;" class="btn">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
// NO CHANGES to the script block were made. All your logic remains intact.
|
||||||
import { defineComponent } from 'vue'
|
import { defineComponent } from 'vue'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import authHeader from '../../services/auth.header'
|
import authHeader from '../../services/auth.header'
|
||||||
@@ -81,12 +223,31 @@ interface User {
|
|||||||
user_id: number;
|
user_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface RoutingOption {
|
||||||
|
value: string;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
// Initialize with empty objects to prevent template errors
|
// Initialize with empty objects to prevent template errors
|
||||||
user: {} as User,
|
user: {} as User,
|
||||||
|
currentPhone: '',
|
||||||
|
routingOptions: [
|
||||||
|
{ value: 'sip', label: '407323_auburnoil' },
|
||||||
|
{ value: 'cellphone1', label: 'Ed Cell' },
|
||||||
|
{ value: 'cellphone2', label: 'Aneta Cell' },
|
||||||
|
{ value: 'test_did', label: 'Test DID' }
|
||||||
|
] as RoutingOption[],
|
||||||
|
selectedOption: null as RoutingOption | null,
|
||||||
|
isRouteModalVisible: false,
|
||||||
|
routeModalMode: 'confirm',
|
||||||
|
routeResponse: null as any,
|
||||||
|
isTestModalVisible: false,
|
||||||
|
isTestLoading: false,
|
||||||
|
testResponse: null as any
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -108,7 +269,10 @@ export default defineComponent({
|
|||||||
this.userStatus();
|
this.userStatus();
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
console.log('VITE_VOIPMS_URL:', import.meta.env.VITE_VOIPMS_URL);
|
||||||
|
console.log('VITE_VOIPMS_TOKEN:', import.meta.env.VITE_VOIPMS_TOKEN);
|
||||||
this.updatestatus();
|
this.updatestatus();
|
||||||
|
this.fetchCurrentPhone();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
userStatus() {
|
userStatus() {
|
||||||
@@ -118,6 +282,7 @@ export default defineComponent({
|
|||||||
url: path,
|
url: path,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
headers: authHeader(),
|
headers: authHeader(),
|
||||||
|
|
||||||
})
|
})
|
||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
console.log(this.user)
|
console.log(this.user)
|
||||||
@@ -148,6 +313,93 @@ export default defineComponent({
|
|||||||
authStore.clearAuth();
|
authStore.clearAuth();
|
||||||
// Redirect to login
|
// Redirect to login
|
||||||
this.$router.push({ name: 'login' });
|
this.$router.push({ name: 'login' });
|
||||||
|
},
|
||||||
|
fetchCurrentPhone() {
|
||||||
|
const path = import.meta.env.VITE_BASE_URL + '/admin/voip_routing';
|
||||||
|
axios({
|
||||||
|
method: 'get',
|
||||||
|
url: path,
|
||||||
|
headers: authHeader(),
|
||||||
|
})
|
||||||
|
.then((response: any) => {
|
||||||
|
if (response.data.current_phone) {
|
||||||
|
this.currentPhone = response.data.current_phone;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
console.error('Failed to fetch current routing:', error);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
routeTo(route: string): Promise<any> {
|
||||||
|
console.log('Routing to:', route);
|
||||||
|
const path = `${import.meta.env.VITE_VOIPMS_URL}/route/${route}`;
|
||||||
|
console.log('API path:', path);
|
||||||
|
return axios({
|
||||||
|
method: 'post',
|
||||||
|
url: path,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${import.meta.env.VITE_VOIPMS_TOKEN}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response: any) => {
|
||||||
|
console.log('Success routing to:', route);
|
||||||
|
this.routeResponse = response.data;
|
||||||
|
// Find the corresponding label
|
||||||
|
const option = this.routingOptions.find(opt => opt.value === route);
|
||||||
|
if (option) {
|
||||||
|
this.currentPhone = option.label;
|
||||||
|
console.log('Updated currentPhone to:', this.currentPhone);
|
||||||
|
}
|
||||||
|
return response.data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showConfirmRoute(option: RoutingOption) {
|
||||||
|
this.selectedOption = option;
|
||||||
|
if (option.value === 'test_did') {
|
||||||
|
this.testDid();
|
||||||
|
} else {
|
||||||
|
this.isRouteModalVisible = true;
|
||||||
|
this.routeModalMode = 'confirm';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
proceedRoute() {
|
||||||
|
if (this.selectedOption && this.selectedOption.value !== 'test_did') {
|
||||||
|
this.routeModalMode = 'loading';
|
||||||
|
this.routeTo(this.selectedOption.value)
|
||||||
|
.then(() => {
|
||||||
|
this.routeModalMode = 'result';
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
this.routeResponse = { error: error.message };
|
||||||
|
this.routeModalMode = 'result';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeRouteModal() {
|
||||||
|
this.isRouteModalVisible = false;
|
||||||
|
this.routeModalMode = 'confirm';
|
||||||
|
this.routeResponse = null;
|
||||||
|
this.selectedOption = null;
|
||||||
|
},
|
||||||
|
testDid() {
|
||||||
|
this.isTestModalVisible = true;
|
||||||
|
this.isTestLoading = true;
|
||||||
|
const path = `${import.meta.env.VITE_VOIPMS_URL}/test/did`;
|
||||||
|
axios({
|
||||||
|
method: 'get',
|
||||||
|
url: path,
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${import.meta.env.VITE_VOIPMS_TOKEN}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((response: any) => {
|
||||||
|
this.testResponse = response.data;
|
||||||
|
this.isTestLoading = false;
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
this.testResponse = { status: 'error', message: error.message };
|
||||||
|
this.isTestLoading = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
175
src/pages/admin/authorize.vue
Normal file
175
src/pages/admin/authorize.vue
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
<!-- src/pages/admin/oilprice.vue -->
|
||||||
|
<template>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- 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 Footer from '../../layouts/footers/footer.vue'
|
||||||
|
import { notify } from "@kyvg/vue3-notification";
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'auth',
|
||||||
|
components: {
|
||||||
|
Footer,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
user: null,
|
||||||
|
// --- REFACTORED: Simplified, flat form object ---
|
||||||
|
OilForm: {
|
||||||
|
price_from_supplier: 0,
|
||||||
|
price_for_customer: 0,
|
||||||
|
price_for_employee: 0,
|
||||||
|
price_same_day: 0,
|
||||||
|
price_prime: 0,
|
||||||
|
price_emergency: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.userStatus();
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.getCurrentPrices();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
userStatus() {
|
||||||
|
const path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
|
||||||
|
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||||
|
.then((response: any) => {
|
||||||
|
if (response.data.ok) { this.user = response.data.user; }
|
||||||
|
})
|
||||||
|
.catch(() => { this.user = null; });
|
||||||
|
},
|
||||||
|
getCurrentPrices() {
|
||||||
|
const path = import.meta.env.VITE_BASE_URL + "/admin/oil/get";
|
||||||
|
axios.get(path, { withCredentials: true, headers: authHeader() })
|
||||||
|
.then((response: any) => {
|
||||||
|
if (response.data) {
|
||||||
|
// --- REFACTORED: Populate the flat form object ---
|
||||||
|
this.OilForm = response.data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
CreatePricing(payload: any) {
|
||||||
|
const path = import.meta.env.VITE_BASE_URL + "/admin/oil/create";
|
||||||
|
axios.post(path, payload, { withCredentials: true, headers: authHeader() })
|
||||||
|
.then((response: any) => {
|
||||||
|
if (response.data.ok) {
|
||||||
|
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",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onSubmit() {
|
||||||
|
// --- REFACTORED: Submit the flat form object ---
|
||||||
|
this.CreatePricing(this.OilForm);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -28,11 +28,23 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<!-- SORTABLE HEADERS -->
|
<!-- SORTABLE HEADERS -->
|
||||||
<th @click="sortBy('tank_level_percent')" class="cursor-pointer hover:text-white">Tank Level</th>
|
<th @click="sortBy('tank_level_percent')" :class="sortKey === 'tank_level_percent' ? 'cursor-pointer hover:text-white bg-orange-500' : 'cursor-pointer hover:text-white'">
|
||||||
<th @click="sortBy('days_since_last_fill')" class="cursor-pointer hover:text-white">Days Since Fill</th>
|
Tank Level
|
||||||
<th @click="sortBy('customer_full_name')" class="cursor-pointer hover:text-white">Name</th>
|
<span v-if="sortKey === 'tank_level_percent'">{{ sortAsc ? '▲' : '▼' }}</span>
|
||||||
|
</th>
|
||||||
|
<th @click="sortBy('days_since_last_fill')" :class="sortKey === 'days_since_last_fill' ? 'cursor-pointer hover:text-white bg-orange-500' : 'cursor-pointer hover:text-white'">
|
||||||
|
Days Since Fill
|
||||||
|
<span v-if="sortKey === 'days_since_last_fill'">{{ sortAsc ? '▲' : '▼' }}</span>
|
||||||
|
</th>
|
||||||
|
<th @click="sortBy('customer_full_name')" :class="sortKey === 'customer_full_name' ? 'cursor-pointer hover:text-white bg-orange-500' : 'cursor-pointer hover:text-white'">
|
||||||
|
Name
|
||||||
|
<span v-if="sortKey === 'customer_full_name'">{{ sortAsc ? '▲' : '▼' }}</span>
|
||||||
|
</th>
|
||||||
|
<th @click="sortBy('house_factor')" :class="sortKey === 'house_factor' ? 'cursor-pointer hover:text-white bg-orange-500' : 'cursor-pointer hover:text-white'">
|
||||||
|
Usage Factor
|
||||||
|
<span v-if="sortKey === 'house_factor'">{{ sortAsc ? '▲' : '▼' }}</span>
|
||||||
|
</th>
|
||||||
<th>Address</th>
|
<th>Address</th>
|
||||||
<th @click="sortBy('house_factor')" class="cursor-pointer hover:text-white">Usage Factor</th>
|
|
||||||
<th class="text-right">Actions</th>
|
<th class="text-right">Actions</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -60,8 +72,8 @@
|
|||||||
{{ oil.customer_full_name }}
|
{{ oil.customer_full_name }}
|
||||||
</router-link>
|
</router-link>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ oil.customer_address }}, {{ oil.customer_town }}</td>
|
|
||||||
<td>{{ oil.house_factor }}</td>
|
<td>{{ oil.house_factor }}</td>
|
||||||
|
<td>{{ oil.customer_address }}, {{ oil.customer_town }}</td>
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<div class="flex items-center justify-end gap-2">
|
<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: 'customerEdit', params: { id: oil.customer_id } }" class="btn btn-sm btn-secondary">Edit Customer</router-link>
|
||||||
@@ -162,7 +174,7 @@ export default defineComponent({
|
|||||||
user: null,
|
user: null,
|
||||||
deliveries: [] as AutoDelivery[],
|
deliveries: [] as AutoDelivery[],
|
||||||
// --- NEW: Data properties for sorting ---
|
// --- NEW: Data properties for sorting ---
|
||||||
sortKey: 'estimated_gallons_left' as keyof AutoDelivery | 'tank_level_percent',
|
sortKey: 'tank_level_percent' as keyof AutoDelivery | 'tank_level_percent',
|
||||||
sortAsc: true,
|
sortAsc: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user