Files
eamco_office_frontend/src/layouts/headers/headerauth.vue
2026-01-28 21:55:14 -05:00

413 lines
16 KiB
Vue
Executable File

<!-- headerauth.vue -->
<template>
<!-- Main header container, contains both rows -->
<header class="sticky top-0 z-50 bg-base-200 shadow-sm">
<!-- Row 1: The primary navbar -->
<div class="navbar px-4">
<!-- Navbar Start: Logo & Mobile Menu Toggle -->
<div class="navbar-start">
<label for="my-drawer-2" class="btn btn-ghost btn-circle drawer-button lg:hidden">
<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>
<div class="flex flex-col items-center">
<span class="text-xs text-base-content/60">{{ dayOfWeek }}</span>
<span class="normal-case text-xl font-bold">
{{ currentDate }}
</span>
</div>
</div>
<!--
Navbar End: Contains the Search Bar (on tablet+) and all action buttons.
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
id="customer-search-input-desktop"
type="text"
placeholder="Search customers..."
v-model="searchStore.searchTerm"
class="input input-bordered w-full pl-10"
@input="searchStore.debouncedSearch"
/>
</div>
</div>
<!-- Action Buttons Wrapper -->
<!-- This group is set to not shrink, keeping it always visible. -->
<div class="flex-shrink-0 flex items-center gap-2">
<!-- 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>
<!-- User Dropdown (Always visible) -->
<div v-if="user.user_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: user.user_id } }">Profile</router-link></li>
<li><router-link :to="{ name: 'changePassword' }">Change Password</router-link></li>
<li><a @click="logout">Logout</a></li>
</ul>
</div>
<div v-else class="skeleton w-10 h-10 rounded-full"></div>
</div>
</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>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import axios from 'axios'
import authHeader from '../../services/auth.header'
import { useSearchStore } from '../../stores/search' // Adjust path if needed
import { useAuthStore } from '../../stores/auth'
// Define the shape of your data for internal type safety
interface User {
user_name: string;
user_id: number;
}
interface RoutingOption {
value: string;
label: string;
}
// Router
const router = useRouter()
// Reactive data
const user = ref({} as User)
const currentPhone = ref('')
const routingOptions = ref([
{ value: 'main', label: '407323' },
{ value: 'sip', label: '407323_auburnoil' },
{ value: 'cellphone1', label: 'Ed Cell' },
{ value: 'cellphone2', label: 'Aneta Cell' },
{ value: 'test_did', label: 'Test DID' }
] as RoutingOption[])
const selectedOption = ref<RoutingOption | null>(null)
const isRouteModalVisible = ref(false)
const routeModalMode = ref('confirm')
const routeResponse = ref(null as any)
const isTestModalVisible = ref(false)
const isTestLoading = ref(false)
const testResponse = ref(null as any)
// Computed properties
const searchStore = computed(() => useSearchStore())
const userInitials = computed((): string => {
if (!user.value || !user.value.user_name) return '';
const parts = user.value.user_name.split(' ');
return parts.length > 1
? `${parts[0][0]}${parts[1][0]}`.toUpperCase()
: user.value.user_name.substring(0, 2).toUpperCase();
})
const currentDate = computed((): string => {
const now = new Date();
const month = (now.getMonth() + 1).toString().padStart(2, '0');
const day = now.getDate().toString().padStart(2, '0');
const year = now.getFullYear().toString().slice(-2);
return `${month}/${day}/${year}`;
})
const dayOfWeek = computed((): string => {
const now = new Date();
return now.toLocaleDateString('en-US', { weekday: 'long' });
})
// Lifecycle
onMounted(() => {
userStatus()
updatestatus()
fetchCurrentPhone()
})
// Functions
const userStatus = () => {
let path = import.meta.env.VITE_BASE_URL + '/auth/whoami';
axios({
method: "get",
url: path,
withCredentials: true,
headers: authHeader(),
})
.then((response: any) => {
if (response.data.ok) {
user.value = response.data.user;
} else {
localStorage.removeItem('user');
router.push('/login');
}
})
}
const 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("Updated Status of Deliveries")
})
}
const logout = () => {
// Clear auth data
const authStore = useAuthStore();
authStore.clearAuth();
// Redirect to login
router.push({ name: 'login' });
}
const fetchCurrentPhone = () => {
const path = import.meta.env.VITE_BASE_URL + '/admin/voip_routing';
axios({
method: 'get',
url: path,
headers: authHeader(),
withCredentials: true,
})
.then((response: any) => {
if (response.data.current_phone) {
currentPhone.value = response.data.current_phone;
}
})
.catch((error: any) => {
console.error('Failed to fetch current routing:', error);
});
}
const routeTo = (route: string): Promise<any> => {
const path = `${import.meta.env.VITE_VOIPMS_URL}/route/${route}`;
return axios({
method: 'post',
url: path,
withCredentials: true, headers: authHeader()
})
.then((response: any) => {
routeResponse.value = response.data;
// Find the corresponding label
const option = routingOptions.value.find(opt => opt.value === route);
if (option) {
currentPhone.value = option.label;
}
return response.data;
});
}
const showConfirmRoute = (option: RoutingOption) => {
selectedOption.value = option;
if (option.value === 'test_did') {
testDid();
} else {
isRouteModalVisible.value = true;
routeModalMode.value = 'confirm';
}
}
const proceedRoute = () => {
if (selectedOption.value && selectedOption.value.value !== 'test_did') {
routeModalMode.value = 'loading';
routeTo(selectedOption.value.value)
.then(() => {
routeModalMode.value = 'result';
})
.catch((error: any) => {
routeResponse.value = { error: error.message };
routeModalMode.value = 'result';
});
}
}
const closeRouteModal = () => {
isRouteModalVisible.value = false;
routeModalMode.value = 'confirm';
routeResponse.value = null;
selectedOption.value = null;
}
const testDid = () => {
isTestModalVisible.value = true;
isTestLoading.value = true;
const path = `${import.meta.env.VITE_VOIPMS_URL}/test/did`;
axios({
method: 'get',
url: path,
withCredentials: true, headers: authHeader()
})
.then((response: any) => {
testResponse.value = response.data;
isTestLoading.value = false;
})
.catch((error: any) => {
testResponse.value = { status: 'error', message: error.message };
isTestLoading.value = false;
});
}
</script>