mirror of https://github.com/Lukibeg/OmniBoard.git
115 lines
5.7 KiB
Vue
115 lines
5.7 KiB
Vue
<script setup>
|
|
import { computed, ref } from 'vue';
|
|
import { Link } from '@inertiajs/vue3';
|
|
|
|
const props = defineProps({
|
|
agents: Array
|
|
});
|
|
|
|
const search = ref('');
|
|
|
|
// Estatísticas Rápidas
|
|
const stats = computed(() => {
|
|
const total = props.agents.length;
|
|
if (total === 0) return { online: 0, talking: 0, paused: 0 };
|
|
return {
|
|
online: props.agents.filter(a => a.status !== 'offline').length,
|
|
talking: props.agents.filter(a => a.status === 'talking').length,
|
|
paused: props.agents.filter(a => a.status === 'paused').length
|
|
};
|
|
});
|
|
|
|
// Filtro e Ordenação
|
|
const filteredAgents = computed(() => {
|
|
const term = search.value.toLowerCase();
|
|
|
|
// 1. Filtra por nome ou ramal
|
|
let list = props.agents.filter(a =>
|
|
a.name.toLowerCase().includes(term) ||
|
|
a.interface.toLowerCase().includes(term)
|
|
);
|
|
|
|
// 2. Ordena: Falando > Pausado > Livre > Offline
|
|
const priority = { talking: 1, paused: 2, available: 3, offline: 4 };
|
|
return list.sort((a, b) => (priority[a.status] || 99) - (priority[b.status] || 99));
|
|
});
|
|
|
|
const getStatusColor = (status) => {
|
|
switch(status) {
|
|
case 'available': return 'text-green-600 bg-green-50 border-green-100';
|
|
case 'paused': return 'text-yellow-600 bg-yellow-50 border-yellow-100';
|
|
case 'talking': return 'text-blue-600 bg-blue-50 border-blue-100';
|
|
default: return 'text-gray-400 bg-gray-50 border-gray-100';
|
|
}
|
|
};
|
|
|
|
const getStatusDot = (status) => {
|
|
switch(status) {
|
|
case 'available': return 'bg-green-500 shadow-[0_0_8px_rgba(34,197,94,0.6)]';
|
|
case 'paused': return 'bg-yellow-500';
|
|
case 'talking': return 'bg-blue-500 shadow-[0_0_8px_rgba(59,130,246,0.6)]';
|
|
default: return 'bg-gray-300';
|
|
}
|
|
};
|
|
</script>
|
|
|
|
<template>
|
|
<div class="bg-white rounded-xl shadow-sm border border-gray-100 flex flex-col h-full max-h-[calc(100vh-200px)]">
|
|
|
|
<div class="p-4 border-b border-gray-50">
|
|
<div class="flex justify-between items-center mb-3">
|
|
<h3 class="font-bold text-gray-800">Equipe</h3>
|
|
<Link :href="route('agents.index')" class="text-xs text-indigo-600 hover:underline">
|
|
Ver Detalhes →
|
|
</Link>
|
|
</div>
|
|
|
|
<div class="flex gap-1 h-1.5 mb-3 rounded-full overflow-hidden bg-gray-100">
|
|
<div class="bg-blue-500 transition-all duration-500" :style="{ width: (stats.talking / agents.length * 100) + '%' }"></div>
|
|
<div class="bg-yellow-500 transition-all duration-500" :style="{ width: (stats.paused / agents.length * 100) + '%' }"></div>
|
|
<div class="bg-green-500 transition-all duration-500" :style="{ width: ((stats.online - stats.talking - stats.paused) / agents.length * 100) + '%' }"></div>
|
|
</div>
|
|
|
|
<div class="relative">
|
|
<input v-model="search" type="text" placeholder="Buscar agente..."
|
|
class="w-full pl-8 pr-3 py-1.5 text-xs border border-gray-200 rounded-lg focus:ring-indigo-500 focus:border-indigo-500 bg-gray-50 transition focus:bg-white" />
|
|
<svg class="w-3.5 h-3.5 text-gray-400 absolute left-2.5 top-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex-1 overflow-y-auto custom-scrollbar p-2 space-y-1">
|
|
<div v-for="agent in filteredAgents" :key="agent.id"
|
|
class="group p-2.5 rounded-lg hover:bg-gray-50 transition flex items-center justify-between border border-transparent hover:border-gray-100">
|
|
|
|
<div class="flex items-center gap-3 overflow-hidden">
|
|
<div :class="`flex-shrink-0 h-2.5 w-2.5 rounded-full ${getStatusDot(agent.status)} transition-colors`"></div>
|
|
<div class="min-w-0">
|
|
<p class="text-sm font-semibold text-gray-700 truncate group-hover:text-indigo-600 transition-colors">
|
|
{{ agent.name }}
|
|
</p>
|
|
<p class="text-[10px] text-gray-400 font-mono truncate">{{ agent.interface }}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="agent.status !== 'offline'" class="flex-shrink-0">
|
|
<span :class="`px-2 py-0.5 rounded text-[9px] font-bold uppercase border ${getStatusColor(agent.status)}`">
|
|
<span v-if="agent.status === 'paused'">{{ agent.pause_reason || 'Pausa' }}</span>
|
|
<span v-else-if="agent.status === 'talking'">Falando</span>
|
|
<span v-else>Livre</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-if="filteredAgents.length === 0" class="text-center py-6">
|
|
<p class="text-xs text-gray-400">Ninguém encontrado.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Scrollbar fina e elegante */
|
|
.custom-scrollbar::-webkit-scrollbar { width: 4px; }
|
|
.custom-scrollbar::-webkit-scrollbar-track { background: transparent; }
|
|
.custom-scrollbar::-webkit-scrollbar-thumb { background-color: #e5e7eb; border-radius: 20px; }
|
|
</style> |