From 42498f98c83833711c83f0421fa9e1f9fbfa8b13 Mon Sep 17 00:00:00 2001 From: Ricardo Gonzalez Date: Fri, 29 May 2026 02:05:59 -0500 Subject: [PATCH] =?UTF-8?q?UsersView:=20jerarqu=C3=ADa=20de=20roles=20en?= =?UTF-8?q?=20c=C3=A9lulas=20(PM=20=E2=86=92=20BA=20=E2=86=92=20TL=20?= =?UTF-8?q?=E2=86=92=20Dev=20=E2=86=92=20QA=20=E2=86=92=20Asistente)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Roles ordenados por jerarquía: PM, BA, TL, Dev, QA, Asistente - Selector de rol al añadir miembro a una célula - Badges con color por rol (morado, azul, ámbar, verde, rosa, celeste) - Miembros ordenados por jerarquía dentro de cada célula - Botón de eliminar miembro visible solo al hover (interfaz más limpia) --- src/views/UsersView.vue | 109 +++++++++++++++++++++++++++++++++------- 1 file changed, 90 insertions(+), 19 deletions(-) diff --git a/src/views/UsersView.vue b/src/views/UsersView.vue index 7b35753..016ff0c 100644 --- a/src/views/UsersView.vue +++ b/src/views/UsersView.vue @@ -2,7 +2,7 @@ import { ref, onMounted, computed } from 'vue' import { useI18n } from 'vue-i18n' import { useUsersStore } from '@/stores/users' -import { getAllCellsWithCounts, getMembers, saveCell, deleteCell, addMember, removeMember, createCellId, type CellRecord } from '@/services/cells-db' +import { getAllCellsWithCounts, getMembers, saveCell, deleteCell, addMember, removeMember, createCellId, type CellRecord, type CellMemberRecord } from '@/services/cells-db' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { Badge } from '@/components/ui/badge' @@ -31,15 +31,53 @@ import { IconUserPlus, IconUserMinus, IconBuildingFactory, + IconArrowUp, + IconArrowDown, + IconGripVertical, } from '@tabler/icons-vue' const { t } = useI18n() const store = useUsersStore() +// ─── Role hierarchy ────────────────────────────────────── +const ROLE_HIERARCHY = ['pm', 'ba', 'tl', 'dev', 'qa', 'asistente'] as const +type CellRole = (typeof ROLE_HIERARCHY)[number] + +const ROLE_LABELS: Record = { + pm: 'PM', + ba: 'BA', + tl: 'TL', + dev: 'Dev', + qa: 'QA', + asistente: 'Asistente', +} + +const ROLE_COLORS: Record = { + pm: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400', + ba: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400', + tl: 'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400', + dev: 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400', + qa: 'bg-rose-100 text-rose-700 dark:bg-rose-900/30 dark:text-rose-400', + asistente: 'bg-sky-100 text-sky-700 dark:bg-sky-900/30 dark:text-sky-400', +} + +function roleOrder(role: string): number { + const idx = ROLE_HIERARCHY.indexOf(role.toLowerCase() as CellRole) + return idx >= 0 ? idx : 99 +} + +function roleLabel(role: string): string { + return ROLE_LABELS[role.toLowerCase() as CellRole] || role +} + +function roleColor(role: string): string { + return ROLE_COLORS[role.toLowerCase() as CellRole] || 'bg-muted text-muted-foreground' +} + // ─── Cells ─────────────────────────────────────────────── const cells = ref<(CellRecord & { memberCount: number })[]>([]) const expandedCell = ref(null) -const cellMembers = ref>({}) +const cellMembers = ref>({}) const showNewCell = ref(false) const newCellName = ref('') const newCellDesc = ref('') @@ -48,6 +86,7 @@ const newCellType = ref<'normal' | 'transversal'>('normal') // Add member const addingToCell = ref(null) const selectedUserId = ref(null) +const selectedRole = ref('dev') async function loadCells() { cells.value = await getAllCellsWithCounts() @@ -57,7 +96,9 @@ async function loadCells() { } async function loadMembers(cellId: string) { - cellMembers.value[cellId] = await getMembers(cellId) + const members = await getMembers(cellId) + members.sort((a, b) => roleOrder(a.roleInCell) - roleOrder(b.roleInCell)) + cellMembers.value[cellId] = members } function toggleCell(cellId: string) { @@ -93,13 +134,20 @@ async function removeCell(id: string) { async function addUserToCell(cellId: string) { if (!selectedUserId.value) return - await addMember({ cellId, userId: selectedUserId.value, roleInCell: 'dev', addedAt: new Date().toISOString() }) + await addMember({ cellId, userId: selectedUserId.value, roleInCell: selectedRole.value, addedAt: new Date().toISOString() }) selectedUserId.value = null + selectedRole.value = 'dev' addingToCell.value = null await loadMembers(cellId) await loadCells() } +function startAddMember(cellId: string) { + selectedUserId.value = null + selectedRole.value = 'dev' + addingToCell.value = cellId +} + async function removeUserFromCell(cellId: string, userId: number) { await removeMember(cellId, userId) await loadMembers(cellId) @@ -114,6 +162,10 @@ function userName(id: number): string { return store.users.find(u => u.id === id)?.full_name || `Usuario ${id}` } +function sortedMembers(members: CellMemberRecord[]) { + return [...members].sort((a, b) => roleOrder(a.roleInCell) - roleOrder(b.roleInCell)) +} + // ─── Table ─────────────────────────────────────────────── const searchQuery = ref('') const currentPage = ref(1) @@ -258,27 +310,46 @@ onMounted(() => { -
-
- {{ userName(m.userId) }} - {{ m.roleInCell }} -

Sin miembros

-
- - +
+
+ + +
+
+ + +
-
-