Lot de travaux accumulés non commités (plusieurs sessions), vérifiés : hub `node --check` tout OK + build SPA propre. - ops : AssignmentField, CommandPalette, IssueDetail/DetailModal + detail-sections/modules, UserAvatar/useAvatar, UserProfileDialog/EmployeeEditDialog, HelpHint, useSkillIcons, pages (Dashboard/Equipe/Clients/Tickets/Reports/Settings/Evaluations/LegacySync…), usePermissions/useUserGroups/useDetailModal, MainLayout, TicketStatusControl - hub : staff-agent.js (couche commandes NL), agent.js/agent-tools.json/voice-agent.js, avatars.js, conversation.js, payments.js, sync-orchestrator.js, auth.js, campaigns.js, legacy-dispatch-sync.js, server.js Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
124 lines
5.0 KiB
Vue
124 lines
5.0 KiB
Vue
<template>
|
|
<q-page padding>
|
|
<div class="row items-center q-mb-md">
|
|
<div class="text-h6 text-weight-bold">Équipe</div>
|
|
<q-space />
|
|
<div class="text-caption text-grey-6">Module en développement</div>
|
|
</div>
|
|
|
|
<div class="row q-col-gutter-md q-mb-lg">
|
|
<div class="col-6 col-md-3" v-for="stat in stats" :key="stat.label">
|
|
<div class="ops-card ops-stat">
|
|
<div class="ops-stat-value" :style="{ color: stat.color }">{{ stat.value }}</div>
|
|
<div class="ops-stat-label">{{ stat.label }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="ops-card">
|
|
<div class="text-subtitle1 text-weight-bold q-mb-sm">Techniciens</div>
|
|
<DataTable
|
|
:rows="techs" :columns="columns" row-key="name"
|
|
flat dense class="ops-table"
|
|
:loading="loading"
|
|
:pagination="{ rowsPerPage: 20 }"
|
|
>
|
|
<template #body-cell-employee_name="props">
|
|
<q-td :props="props">
|
|
<div class="row items-center no-wrap">
|
|
<UserAvatar :email="props.row.company_email" :name="props.row.employee_name" :size="28" class="q-mr-sm" />
|
|
<span>{{ props.row.employee_name }}</span>
|
|
</div>
|
|
</q-td>
|
|
</template>
|
|
<template #body-cell-login="props">
|
|
<q-td :props="props" class="text-center">
|
|
<q-icon v-if="props.row.user_id" name="link" color="green-6" size="18px">
|
|
<q-tooltip>Compte de connexion lié : {{ props.row.user_id }}</q-tooltip>
|
|
</q-icon>
|
|
<q-icon v-else name="link_off" color="grey-4" size="18px">
|
|
<q-tooltip>Aucun compte de connexion lié</q-tooltip>
|
|
</q-icon>
|
|
</q-td>
|
|
</template>
|
|
<template #body-cell-actions="props">
|
|
<q-td :props="props" class="text-right">
|
|
<q-btn flat dense round icon="edit" color="primary" size="sm" @click="editEmployee(props.row)">
|
|
<q-tooltip>Modifier le profil employé</q-tooltip>
|
|
</q-btn>
|
|
<q-btn flat dense round icon="admin_panel_settings" color="blue-grey-6" size="sm"
|
|
:disable="!(props.row.user_id || props.row.company_email)" @click="openPermissions(props.row)">
|
|
<q-tooltip>{{ (props.row.user_id || props.row.company_email) ? 'Permissions & compte (Administration)' : 'Aucun compte lié' }}</q-tooltip>
|
|
</q-btn>
|
|
</q-td>
|
|
</template>
|
|
</DataTable>
|
|
</div>
|
|
|
|
<EmployeeEditDialog v-model="empDialogOpen" :employee-name="empDialogName" @saved="reload" />
|
|
</q-page>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, onMounted } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
import { listDocs } from 'src/api/erp'
|
|
import DataTable from 'src/components/shared/DataTable.vue'
|
|
import UserAvatar from 'src/components/shared/UserAvatar.vue'
|
|
import EmployeeEditDialog from 'src/components/shared/EmployeeEditDialog.vue'
|
|
|
|
const router = useRouter()
|
|
|
|
// Édition du profil employé (fiche ERPNext) — dialogue partagé.
|
|
const empDialogOpen = ref(false)
|
|
const empDialogName = ref('')
|
|
function editEmployee (row) { empDialogName.value = row.name; empDialogOpen.value = true }
|
|
|
|
// Permissions & compte de connexion → Administration (Authentik), jointure sur le courriel du login.
|
|
function openPermissions (row) {
|
|
const login = row.user_id || row.company_email
|
|
if (!login) return
|
|
router.push({ path: '/settings', query: { user: login } })
|
|
}
|
|
|
|
const loading = ref(false)
|
|
const techs = ref([])
|
|
const stats = ref([
|
|
{ label: 'Techniciens', value: '...', color: 'var(--ops-accent)' },
|
|
{ label: 'En service', value: '...', color: 'var(--ops-success)' },
|
|
{ label: 'Congé', value: '...', color: 'var(--ops-warning)' },
|
|
{ label: 'Tâches aujourd\'hui', value: '...', color: 'var(--ops-primary)' },
|
|
])
|
|
|
|
const columns = [
|
|
{ name: 'employee_name', label: 'Nom', field: 'employee_name', align: 'left' },
|
|
{ name: 'designation', label: 'Poste', field: 'designation', align: 'left' },
|
|
{ name: 'department', label: 'Département', field: 'department', align: 'left' },
|
|
{ name: 'cell_number', label: 'Téléphone', field: 'cell_number', align: 'left' },
|
|
{ name: 'office_extension', label: 'Ext.', field: 'office_extension', align: 'center' },
|
|
{ name: 'company_email', label: 'Courriel', field: 'company_email', align: 'left' },
|
|
{ name: 'login', label: 'Compte', field: 'user_id', align: 'center' },
|
|
{ name: 'status', label: 'Statut', field: 'status', align: 'center' },
|
|
{ name: 'actions', label: '', field: 'actions', align: 'right' },
|
|
]
|
|
|
|
async function reload () {
|
|
loading.value = true
|
|
try {
|
|
techs.value = await listDocs('Employee', {
|
|
filters: { status: 'Active' },
|
|
fields: ['name', 'employee_name', 'designation', 'department', 'status', 'cell_number', 'company_email', 'office_extension', 'user_id'],
|
|
limit: 50,
|
|
orderBy: 'employee_name asc',
|
|
})
|
|
stats.value[0].value = techs.value.length.toString()
|
|
stats.value[1].value = techs.value.filter(t => t.status === 'Active').length.toString()
|
|
} catch {
|
|
techs.value = []
|
|
}
|
|
loading.value = false
|
|
}
|
|
|
|
onMounted(reload)
|
|
</script>
|