feat: update sidebar roles link, refactor role navigation, and add user management component

- Changed the sidebar link for roles from '/users/roles' to '/roles'.
- Updated the navigation in RoleForm component to redirect to 'RoleIndex' instead of 'Roles'.
- Introduced a new UserIndex component for user management, including user listing, filtering, and actions.
- Modified the index.html structure to accommodate the new UserIndex component and improved layout for user management.
- Updated router configuration to include the new UserIndex route and adjusted roles routing structure.
This commit is contained in:
Edgar Mendez Mendoza 2025-11-13 09:20:53 -06:00
parent 29d4f5c9c7
commit fa161a9e0e
5 changed files with 553 additions and 208 deletions

View File

@ -54,7 +54,7 @@ const menuItems = ref<MenuItem[]>([
icon: 'pi pi-users', icon: 'pi pi-users',
items: [ items: [
{ label: 'Usuarios', icon: 'pi pi-user', to: '/users' }, { label: 'Usuarios', icon: 'pi pi-user', to: '/users' },
{ label: 'Roles', icon: 'pi pi-shield', to: '/users/roles' } { label: 'Roles', icon: 'pi pi-shield', to: '/roles' }
] ]
} }
]); ]);

View File

@ -272,7 +272,7 @@ function onCancel() {
} }
// Volver a la página de roles // Volver a la página de roles
router.push({ name: 'Roles' }); router.push({ name: 'RoleIndex' });
} }
async function onSave() { async function onSave() {

View File

@ -0,0 +1,299 @@
<template>
<div class="space-y-6">
<!-- Toast Notifications -->
<Toast position="bottom-right" />
<!-- Header -->
<div class="flex flex-wrap justify-between gap-4 items-center">
<div class="flex min-w-72 flex-col gap-1">
<h1 class="text-surface-900 dark:text-white text-3xl md:text-4xl font-black leading-tight tracking-tight">
Gestión de Usuarios
</h1>
<p class="text-surface-500 dark:text-surface-400 text-base font-normal leading-normal">
Administra usuarios, asigna roles y gestiona el acceso al sistema.
</p>
</div>
<Button
label="Añadir Usuario"
icon="pi pi-plus"
@click="createUser"
class="min-w-[180px]"
/>
</div>
<!-- Table Card -->
<Card class="shadow-sm">
<template #content>
<!-- Search and Filters -->
<div class="flex flex-col md:flex-row justify-between gap-4 mb-4">
<div class="flex-1">
<IconField iconPosition="left">
<InputIcon class="pi pi-search" />
<InputText
v-model="searchQuery"
placeholder="Buscar usuario..."
class="w-full"
/>
</IconField>
</div>
<div class="flex gap-2 flex-wrap">
<Dropdown
v-model="selectedRole"
:options="roleOptions"
optionLabel="label"
optionValue="value"
placeholder="Rol: Todos"
class="w-40"
/>
<Dropdown
v-model="selectedStatus"
:options="statusOptions"
optionLabel="label"
optionValue="value"
placeholder="Estado: Todos"
class="w-40"
/>
<Button
label="Limpiar"
icon="pi pi-times"
outlined
@click="clearFilters"
/>
</div>
</div>
<!-- Data Table -->
<DataTable
:value="users"
:loading="loading"
:paginator="true"
:rows="10"
:rowsPerPageOptions="[5, 10, 20]"
stripedRows
responsiveLayout="scroll"
selectionMode="multiple"
v-model:selection="selectedUsers"
dataKey="id"
paginatorTemplate="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink RowsPerPageDropdown CurrentPageReport"
currentPageReportTemplate="Mostrando {first} a {last} de {totalRecords} resultados"
>
<!-- Selection Column -->
<Column selectionMode="multiple" headerStyle="width: 3rem"></Column>
<!-- User Column -->
<Column field="user" header="Usuario" sortable>
<template #body="slotProps">
<div class="flex items-center gap-3">
<Avatar
:image="slotProps.data.avatar"
:label="slotProps.data.name.charAt(0)"
size="large"
shape="circle"
/>
<div>
<div class="font-medium text-surface-900 dark:text-white">{{ slotProps.data.name }}</div>
<div class="text-sm text-surface-500 dark:text-surface-400">{{ slotProps.data.email }}</div>
</div>
</div>
</template>
</Column>
<!-- Role Column -->
<Column field="role" header="Rol" sortable>
<template #body="slotProps">
<Tag
:value="slotProps.data.role"
:severity="getRoleSeverity(slotProps.data.role)"
/>
</template>
</Column>
<!-- Store Column -->
<Column field="store" header="Punto de Venta" sortable></Column>
<!-- Status Column -->
<Column field="status" header="Estado" sortable>
<template #body="slotProps">
<Tag
:value="slotProps.data.status ? 'Activo' : 'Inactivo'"
:severity="slotProps.data.status ? 'success' : 'danger'"
/>
</template>
</Column>
<!-- Actions Column -->
<Column header="Acciones" style="width: 8rem">
<template #body="slotProps">
<div class="flex gap-2">
<Button icon="pi pi-pencil" size="small" text @click="editUser(slotProps.data)" v-tooltip="'Editar Usuario'" />
<Button icon="pi pi-trash" size="small" text severity="danger" @click="deleteUser(slotProps.data)" v-tooltip="'Eliminar Usuario'" />
</div>
</template>
</Column>
</DataTable>
</template>
</Card>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { useToast } from 'primevue/usetoast';
import Button from 'primevue/button';
import Card from 'primevue/card';
import DataTable from 'primevue/datatable';
import Column from 'primevue/column';
import InputText from 'primevue/inputtext';
import IconField from 'primevue/iconfield';
import InputIcon from 'primevue/inputicon';
import Dropdown from 'primevue/dropdown';
import Toast from 'primevue/toast';
import Avatar from 'primevue/avatar';
import Tag from 'primevue/tag';
const toast = useToast();
// Interface for User
interface User {
id: number;
name: string;
email: string;
role: string;
store: string;
status: boolean;
avatar?: string;
}
// State management
const loading = ref(false);
const users = ref<User[]>([]);
const selectedUsers = ref<User[]>([]);
const searchQuery = ref('');
const selectedRole = ref('all');
const selectedStatus = ref('all');
// Options for filters
const roleOptions = [
{ label: 'Todos', value: 'all' },
{ label: 'Administrador', value: 'admin' },
{ label: 'Gerente', value: 'manager' },
{ label: 'Cajero', value: 'cashier' },
{ label: 'Bodeguero', value: 'warehouse' }
];
const statusOptions = [
{ label: 'Todos', value: 'all' },
{ label: 'Activo', value: true },
{ label: 'Inactivo', value: false }
];
// Mock data - Replace with real API call
const mockUsers: User[] = [
{
id: 1,
name: 'Ana García',
email: 'ana.garcia@example.com',
role: 'Administrador',
store: 'Tienda Principal',
status: true,
avatar: 'https://lh3.googleusercontent.com/aida-public/AB6AXuAAUt9oWSPuyd50GQnJ5fd2b0p63cP-W-fsfYDxFBygy6_EfzdSvJhKlginsHW4OE8NV2DhDHMtBWKUakrC_3jjp_CAEAsKYUzU9AJRMluAd62CcEuNH-9H8bPVNW2QJbW8qJk5_2MiSDtMA1sHvMl3DdExiUw03E2PdM5TA2NJtfdTdz110QQu80ISND_LqkzBoEBIA6qiljOmgvseqDeT2lQZpqFS0qP1uQTiUhHyheUwJo3Btl7RSXBNI-xO-1mURbDsxdLd1qA'
},
{
id: 2,
name: 'Carlos Rodríguez',
email: 'carlos.r@example.com',
role: 'Gerente',
store: 'Sucursal Centro',
status: true,
avatar: 'https://lh3.googleusercontent.com/aida-public/AB6AXuAb5FcsyMz_xnPvPkcz0OkaQWVnxCOirv1s1RR-nRodSKZOUTCDtGm5fEZl2buVNboeobU9uaG7Aon_Bht1l2Q2G2i79oqG4EoCR7MfPogGMSkdOBmEDkxASI5e3DnZ9Xvh9FALi_-elb_in_epquYWCECNvgE3Wcj0qIa98dPSk1pfwHCBZ7V3fRPT8vinqZAv-wBXraWFV8q8eNszE4j5103w8UNgxnOR2taESORJ1-goutNgK-GSkREIMVN74DejqJuW3XJVk40'
},
{
id: 3,
name: 'Laura Martínez',
email: 'laura.m@example.com',
role: 'Cajero',
store: 'Tienda Principal',
status: false,
avatar: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCRYXLv_wMrIojd7wq2GxOeFsx_EN4Y0ncQB6Mzr8fy_48eE8jUZCauQHC6x0QgDlY4mDgFq1MGTE54EBRHlHuUBN8b6tYGC-nLOGcn2mV9Pm1W53YA_aCMQWdNCX_x4ySQAQdKgH-w6eEti7Z3mYwkSF-6eSxNcOpiT1xxk8XOiesSfMX5zEAxgEu39MVICmKW3G1O75BnCkb1tquT110W7CLamK69PokGMCYVujglXI0kPCuS2WblMTPtEXCBSZUhrCpFuwNTpDE'
},
{
id: 4,
name: 'Javier López',
email: 'javier.l@example.com',
role: 'Cajero',
store: 'Sucursal Norte',
status: true,
avatar: 'https://lh3.googleusercontent.com/aida-public/AB6AXuCGXNBCLaQbJ6aBRqlj7z-XYsBmZLDCh81kFIw7QzG0ysu6lOzLtal09wr5txyZrb01R50aM84-1O7OwwtAymfF9-HWydjP1VkBrsPxy__3ftAq4UeUwRFfZBMZi1yKFWdDUFux2hGhMEqjCu3GX8UOUNIn_N1yy7YIspfNLUQYEglElZDYWgLUtG2UVIJ-fhD6iE2992nwamjzeJindFclFPq2vlRB7jj-1aJBNT35SHo4glcLHAxYWdgLACcbo6lCIkNwdweX9q8'
}
];
// Helper functions
function getRoleSeverity(role: string) {
switch (role) {
case 'Administrador': return 'danger';
case 'Gerente': return 'warning';
case 'Cajero': return 'info';
case 'Bodeguero': return 'success';
default: return 'secondary';
}
}
function clearFilters() {
searchQuery.value = '';
selectedRole.value = 'all';
selectedStatus.value = 'all';
}
function createUser() {
// TODO: Navigate to user creation page
toast.add({
severity: 'info',
summary: 'Próximamente',
detail: 'Funcionalidad de creación de usuarios en desarrollo',
life: 3000
});
}
function editUser(user: User) {
// TODO: Navigate to user edit page
toast.add({
severity: 'info',
summary: 'Editar Usuario',
detail: `Editando usuario: ${user.name}`,
life: 3000
});
}
function deleteUser(user: User) {
// TODO: Implement delete functionality
toast.add({
severity: 'warn',
summary: 'Eliminar Usuario',
detail: `¿Eliminar usuario: ${user.name}?`,
life: 3000
});
}
// Lifecycle
onMounted(() => {
loadUsers();
});
async function loadUsers() {
try {
loading.value = true;
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
users.value = mockUsers;
} catch (error) {
toast.add({
severity: 'error',
summary: 'Error',
detail: 'No se pudieron cargar los usuarios',
life: 3000
});
} finally {
loading.value = false;
}
}
</script>

View File

@ -1,214 +1,243 @@
<div class="grid grid-cols-1 lg:grid-cols-12 gap-8"> <main class="flex-1 p-6 lg:p-10">
<!-- Left Column: Roles List --> <div class="max-w-7xl mx-auto">
<div class="lg:col-span-4 xl:col-span-3"> <!-- PageHeading -->
<div class="bg-white dark:bg-slate-900/50 rounded-xl border border-slate-200 dark:border-slate-800 p-4 h-full"> <div class="flex flex-wrap justify-between items-center gap-4 mb-6">
<div class="flex items-center justify-between mb-4"> <h1 class="text-gray-900 dark:text-white text-3xl font-bold tracking-tight">Gestión de Usuarios</h1>
<h3 class="text-lg font-bold text-slate-800 dark:text-slate-200">Roles de Usuario</h3> </div>
</div> <!-- ToolBar -->
<div class="flex flex-col gap-2"> <div
<a class="flex items-center gap-4 bg-primary/10 dark:bg-primary/20 text-primary dark:text-white p-3 rounded-lg border border-primary/50" class="flex flex-col md:flex-row justify-between items-center gap-4 p-4 bg-white dark:bg-gray-800/50 rounded-xl border border-gray-200 dark:border-gray-700 mb-6">
href="#"> <div class="flex items-center gap-2 w-full md:w-auto">
<span class="material-symbols-outlined">shield_person</span> <div class="relative w-full max-w-xs">
<span class="text-sm font-bold flex-1 truncate">Administrador General</span> <div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
<span class="text-primary font-bold text-lg">*</span> <span class="material-symbols-outlined text-gray-400">search</span>
</a> </div>
<a class="flex items-center gap-4 hover:bg-slate-100 dark:hover:bg-slate-800/50 p-3 rounded-lg" <input
href="#"> class="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-primary focus:border-primary"
<span class="material-symbols-outlined text-slate-500 dark:text-slate-400">storefront</span> placeholder="Buscar usuario..." type="text" />
<span class="text-sm font-medium text-slate-700 dark:text-slate-300 flex-1 truncate">Gerente de </div>
Tienda</span>
</a>
<a class="flex items-center gap-4 hover:bg-slate-100 dark:hover:bg-slate-800/50 p-3 rounded-lg"
href="#">
<span class="material-symbols-outlined text-slate-500 dark:text-slate-400">point_of_sale</span>
<span class="text-sm font-medium text-slate-700 dark:text-slate-300 flex-1 truncate">Cajero</span>
</a>
<a class="flex items-center gap-4 hover:bg-slate-100 dark:hover:bg-slate-800/50 p-3 rounded-lg"
href="#">
<span class="material-symbols-outlined text-slate-500 dark:text-slate-400">inventory_2</span>
<span
class="text-sm font-medium text-slate-700 dark:text-slate-300 flex-1 truncate">Bodeguero</span>
</a>
<div class="border-t border-slate-200 dark:border-slate-800 my-2"></div>
<button <button
class="w-full flex items-center justify-center gap-2 text-primary dark:text-primary hover:bg-primary/10 dark:hover:bg-primary/20 p-3 rounded-lg text-sm font-bold"> class="flex items-center justify-center p-2 text-gray-600 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 hover:bg-gray-50 dark:hover:bg-gray-600">
<span class="material-symbols-outlined text-base">add_circle</span> <span class="material-symbols-outlined">filter_list</span>
<span>Añadir Nuevo Rol</span>
</button> </button>
</div> </div>
<button
class="flex w-full md:w-auto items-center justify-center rounded-lg h-10 bg-primary text-white gap-2 text-sm font-bold leading-normal tracking-wide px-4 hover:bg-primary/90 transition-colors">
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">add</span>
<span class="truncate">Añadir Usuario</span>
</button>
</div> </div>
</div> <!-- Table -->
<!-- Right Column: Permissions Area -->
<div class="lg:col-span-8 xl:col-span-9">
<!-- Action Bar -->
<div <div
class="sticky top-20 z-10 flex flex-wrap items-center justify-between gap-4 bg-white/80 dark:bg-slate-900/80 backdrop-blur-md p-4 mb-6 rounded-xl border border-slate-200 dark:border-slate-800 shadow-sm"> class="bg-white dark:bg-gray-800/50 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden">
<div class="flex-1 min-w-[200px]"> <div class="overflow-x-auto">
<p class="text-lg font-bold text-slate-900 dark:text-white">Permisos para <span <table class="w-full text-left">
class="text-primary">Administrador General *</span></p> <thead class="bg-gray-50 dark:bg-gray-800">
<p class="text-sm text-slate-500 dark:text-slate-400">Hay cambios sin guardar</p> <tr class="border-b border-gray-200 dark:border-gray-700">
<th class="px-4 py-3 w-12 text-center">
<input
class="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-primary focus:ring-primary bg-transparent dark:bg-gray-900"
type="checkbox" />
</th>
<th
class="px-4 py-3 text-left text-gray-600 dark:text-gray-300 text-xs font-medium uppercase tracking-wider">
Usuario</th>
<th
class="px-4 py-3 text-left text-gray-600 dark:text-gray-300 text-xs font-medium uppercase tracking-wider">
Rol</th>
<th
class="px-4 py-3 text-left text-gray-600 dark:text-gray-300 text-xs font-medium uppercase tracking-wider">
Punto de Venta</th>
<th
class="px-4 py-3 text-left text-gray-600 dark:text-gray-300 text-xs font-medium uppercase tracking-wider">
Estado</th>
<th
class="px-4 py-3 text-left text-gray-600 dark:text-gray-300 text-xs font-medium uppercase tracking-wider">
Acciones</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
<td class="px-4 py-3 text-center">
<input
class="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-primary focus:ring-primary bg-transparent dark:bg-gray-900"
type="checkbox" />
</td>
<td class="px-4 py-3 whitespace-nowrap">
<div class="flex items-center gap-3">
<div class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-10"
data-alt="Avatar de Ana García"
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuAAUt9oWSPuyd50GQnJ5fd2b0p63cP-W-fsfYDxFBygy6_EfzdSvJhKlginsHW4OE8NV2DhDHMtBWKUakrC_3jjp_CAEAsKYUzU9AJRMluAd62CcEuNH-9H8bPVNW2QJbW8qJk5_2MiSDtMA1sHvMl3DdExiUw03E2PdM5TA2NJtfdTdz110QQu80ISND_LqkzBoEBIA6qiljOmgvseqDeT2lQZpqFS0qP1uQTiUhHyheUwJo3Btl7RSXBNI-xO-1mURbDsxdLd1qA");'>
</div>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">Ana García</div>
<div class="text-sm text-gray-500 dark:text-gray-400">ana.garcia@example.com
</div>
</div>
</div>
</td>
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">
Administrador</td>
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">Tienda
Principal</td>
<td class="px-4 py-3 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300">Activo</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium">
<div class="flex items-center gap-2">
<button
class="text-gray-500 dark:text-gray-400 hover:text-primary dark:hover:text-primary transition-colors"><span
class="material-symbols-outlined text-xl">edit</span></button>
<button
class="text-gray-500 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-500 transition-colors"><span
class="material-symbols-outlined text-xl">delete</span></button>
</div>
</td>
</tr>
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
<td class="px-4 py-3 text-center">
<input
class="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-primary focus:ring-primary bg-transparent dark:bg-gray-900"
type="checkbox" />
</td>
<td class="px-4 py-3 whitespace-nowrap">
<div class="flex items-center gap-3">
<div class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-10"
data-alt="Avatar de Carlos Rodríguez"
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuAb5FcsyMz_xnPvPkcz0OkaQWVnxCOirv1s1RR-nRodSKZOUTCDtGm5fEZl2buVNboeobU9uaG7Aon_Bht1l2Q2G2i79oqG4EoCR7MfPogGMSkdOBmEDkxASI5e3DnZ9Xvh9FALi_-elb_in_epquYWCECNvgE3Wcj0qIa98dPSk1pfwHCBZ7V3fRPT8vinqZAv-wBXraWFV8q8eNszE4j5103w8UNgxnOR2taESORJ1-goutNgK-GSkREIMVN74DejqJuW3XJVk40");'>
</div>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">Carlos Rodríguez
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">carlos.r@example.com</div>
</div>
</div>
</td>
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">Gerente
</td>
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">Sucursal
Centro</td>
<td class="px-4 py-3 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300">Activo</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium">
<div class="flex items-center gap-2">
<button
class="text-gray-500 dark:text-gray-400 hover:text-primary dark:hover:text-primary transition-colors"><span
class="material-symbols-outlined text-xl">edit</span></button>
<button
class="text-gray-500 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-500 transition-colors"><span
class="material-symbols-outlined text-xl">delete</span></button>
</div>
</td>
</tr>
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
<td class="px-4 py-3 text-center">
<input
class="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-primary focus:ring-primary bg-transparent dark:bg-gray-900"
type="checkbox" />
</td>
<td class="px-4 py-3 whitespace-nowrap">
<div class="flex items-center gap-3">
<div class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-10"
data-alt="Avatar de Laura Martínez"
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuCRYXLv_wMrIojd7wq2GxOeFsx_EN4Y0ncQB6Mzr8fy_48eE8jUZCauQHC6x0QgDlY4mDgFq1MGTE54EBRHlHuUBN8b6tYGC-nLOGcn2mV9Pm1W53YA_aCMQWdNCX_x4ySQAQdKgH-w6eEti7Z3mYwkSF-6eSxNcOpiT1xxk8XOiesSfMX5zEAxgEu39MVICmKW3G1O75BnCkb1tquT110W7CLamK69PokGMCYVujglXI0kPCuS2WblMTPtEXCBSZUhrCpFuwNTpDE");'>
</div>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">Laura Martínez
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">laura.m@example.com</div>
</div>
</div>
</td>
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">Cajero</td>
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">Tienda
Principal</td>
<td class="px-4 py-3 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300">Inactivo</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium">
<div class="flex items-center gap-2">
<button
class="text-gray-500 dark:text-gray-400 hover:text-primary dark:hover:text-primary transition-colors"><span
class="material-symbols-outlined text-xl">edit</span></button>
<button
class="text-gray-500 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-500 transition-colors"><span
class="material-symbols-outlined text-xl">delete</span></button>
</div>
</td>
</tr>
<tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
<td class="px-4 py-3 text-center">
<input
class="h-4 w-4 rounded border-gray-300 dark:border-gray-600 text-primary focus:ring-primary bg-transparent dark:bg-gray-900"
type="checkbox" />
</td>
<td class="px-4 py-3 whitespace-nowrap">
<div class="flex items-center gap-3">
<div class="bg-center bg-no-repeat aspect-square bg-cover rounded-full size-10"
data-alt="Avatar de Javier López"
style='background-image: url("https://lh3.googleusercontent.com/aida-public/AB6AXuCGXNBCLaQbJ6aBRqlj7z-XYsBmZLDCh81kFIw7QzG0ysu6lOzLtal09wr5txyZrb01R50aM84-1O7OwwtAymfF9-HWydjP1VkBrsPxy__3ftAq4UeUwRFfZBMZi1yKFWdDUFux2hGhMEqjCu3GX8UOUNIn_N1yy7YIspfNLUQYEglElZDYWgLUtG2UVIJ-fhD6iE2992nwamjzeJindFclFPq2vlRB7jj-1aJBNT35SHo4glcLHAxYWdgLACcbo6lCIkNwdweX9q8");'>
</div>
<div>
<div class="text-sm font-medium text-gray-900 dark:text-white">Javier López
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">javier.l@example.com</div>
</div>
</div>
</td>
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">Cajero</td>
<td class="px-4 py-3 text-sm text-gray-500 dark:text-gray-400 whitespace-nowrap">Sucursal
Norte</td>
<td class="px-4 py-3 whitespace-nowrap">
<span
class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300">Activo</span>
</td>
<td class="px-4 py-3 whitespace-nowrap text-sm font-medium">
<div class="flex items-center gap-2">
<button
class="text-gray-500 dark:text-gray-400 hover:text-primary dark:hover:text-primary transition-colors"><span
class="material-symbols-outlined text-xl">edit</span></button>
<button
class="text-gray-500 dark:text-gray-400 hover:text-red-600 dark:hover:text-red-500 transition-colors"><span
class="material-symbols-outlined text-xl">delete</span></button>
</div>
</td>
</tr>
</tbody>
</table>
</div> </div>
<div class="flex items-center gap-3"> <!-- Pagination -->
<button <div class="flex items-center justify-between border-t border-gray-200 dark:border-gray-700 px-4 py-3">
class="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-4 bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-200 hover:bg-slate-200 dark:hover:bg-slate-700 text-sm font-bold">Cancelar</button> <div class="text-sm text-gray-600 dark:text-gray-400">
<button Mostrando <span class="font-medium text-gray-800 dark:text-white">1</span> a <span
class="flex min-w-[84px] max-w-[480px] cursor-pointer items-center justify-center overflow-hidden rounded-lg h-10 px-5 bg-primary text-white text-sm font-bold hover:bg-primary/90">Guardar class="font-medium text-gray-800 dark:text-white">4</span> de <span
Cambios</button> class="font-medium text-gray-800 dark:text-white">25</span> resultados
</div>
</div>
<!-- Permissions Content -->
<div class="space-y-6">
<!-- Module Card: Ventas (POS) -->
<div class="bg-white dark:bg-slate-900/50 rounded-xl border border-slate-200 dark:border-slate-800">
<div class="flex items-center justify-between p-4 border-b border-slate-200 dark:border-slate-800">
<h4 class="text-base font-bold text-slate-800 dark:text-slate-200">Módulo de Ventas (POS)</h4>
<button class="text-primary dark:text-primary text-xs font-bold hover:underline">SELECCIONAR
TODO</button>
</div> </div>
<div class="p-4 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4"> <div class="flex items-center gap-1">
<!-- Permission Item --> <a class="flex size-8 items-center justify-center rounded-md text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg"> href="#">
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Acceso al TPV</p> <span class="material-symbols-outlined text-xl">chevron_left</span>
<label </a>
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary"> <a class="text-sm font-bold leading-normal flex size-8 items-center justify-center text-white rounded-md bg-primary"
<div href="#">1</a>
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]"> <a class="text-sm font-normal leading-normal flex size-8 items-center justify-center text-gray-600 dark:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
</div> href="#">2</a>
<input checked="" class="invisible absolute" type="checkbox" /> <a class="text-sm font-normal leading-normal flex size-8 items-center justify-center text-gray-600 dark:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
</label> href="#">3</a>
</div> <span
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg"> class="text-sm font-normal leading-normal flex size-8 items-center justify-center text-gray-500 dark:text-gray-400 rounded-md">...</span>
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Crear Ventas</p> <a class="text-sm font-normal leading-normal flex size-8 items-center justify-center text-gray-600 dark:text-gray-300 rounded-md hover:bg-gray-100 dark:hover:bg-gray-800"
<label href="#">10</a>
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary"> <a class="flex size-8 items-center justify-center rounded-md text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-800"
<div href="#">
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]"> <span class="material-symbols-outlined text-xl">chevron_right</span>
</div> </a>
<input checked="" class="invisible absolute" type="checkbox" />
</label>
</div>
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg">
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Aplicar Descuentos</p>
<label
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary">
<div
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]">
</div>
<input checked="" class="invisible absolute" type="checkbox" />
</label>
</div>
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg">
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Realizar Devoluciones</p>
<label
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary">
<div
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]">
</div>
<input checked="" class="invisible absolute" type="checkbox" />
</label>
</div>
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg">
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Anular Tickets</p>
<label
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary">
<div
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]">
</div>
<input class="invisible absolute" type="checkbox" />
</label>
</div>
</div>
</div>
<!-- Module Card: Inventario -->
<div class="bg-white dark:bg-slate-900/50 rounded-xl border border-slate-200 dark:border-slate-800">
<div class="flex items-center justify-between p-4 border-b border-slate-200 dark:border-slate-800">
<h4 class="text-base font-bold text-slate-800 dark:text-slate-200">Módulo de Inventario</h4>
<button class="text-primary dark:text-primary text-xs font-bold hover:underline">SELECCIONAR
TODO</button>
</div>
<div class="p-4 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg">
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Ver Productos</p>
<label
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary">
<div
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]">
</div>
<input checked="" class="invisible absolute" type="checkbox" />
</label>
</div>
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg">
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Crear/Editar Productos</p>
<label
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary">
<div
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]">
</div>
<input checked="" class="invisible absolute" type="checkbox" />
</label>
</div>
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg">
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Ajustes de Stock</p>
<label
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary">
<div
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]">
</div>
<input checked="" class="invisible absolute" type="checkbox" />
</label>
</div>
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg">
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Gestionar Proveedores</p>
<label
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary">
<div
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]">
</div>
<input class="invisible absolute" type="checkbox" />
</label>
</div>
</div>
</div>
<!-- Module Card: Reportes -->
<div class="bg-white dark:bg-slate-900/50 rounded-xl border border-slate-200 dark:border-slate-800">
<div class="flex items-center justify-between p-4 border-b border-slate-200 dark:border-slate-800">
<h4 class="text-base font-bold text-slate-800 dark:text-slate-200">Módulo de Reportes</h4>
<button class="text-primary dark:text-primary text-xs font-bold hover:underline">DESELECCIONAR
TODO</button>
</div>
<div class="p-4 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4">
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg">
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Acceder a Reportes</p>
<label
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary">
<div
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]">
</div>
<input checked="" class="invisible absolute" type="checkbox" />
</label>
</div>
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg">
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Ver Dashboard</p>
<label
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary">
<div
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]">
</div>
<input checked="" class="invisible absolute" type="checkbox" />
</label>
</div>
<div class="flex items-center justify-between bg-slate-50 dark:bg-slate-800/50 p-3 rounded-lg">
<p class="text-sm font-medium text-slate-700 dark:text-slate-300">Exportar Datos</p>
<label
class="relative flex h-[26px] w-[44px] cursor-pointer items-center rounded-full border-none bg-slate-200 dark:bg-slate-700 p-0.5 has-[:checked]:bg-primary">
<div
class="h-full w-[22px] rounded-full bg-white transition-transform duration-200 ease-in-out transform translate-x-0 has-[:checked]:translate-x-[18px]">
</div>
<input checked="" class="invisible absolute" type="checkbox" />
</label>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </main>

View File

@ -16,6 +16,7 @@ import StoresIndex from '../modules/stores/components/StoresIndex.vue';
import RolesIndex from '../modules/users/components/RoleIndex.vue'; import RolesIndex from '../modules/users/components/RoleIndex.vue';
import RoleForm from '../modules/users/components/RoleForm.vue'; import RoleForm from '../modules/users/components/RoleForm.vue';
import UserIndex from '../modules/users/components/UserIndex.vue';
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
{ {
@ -165,8 +166,24 @@ const routes: RouteRecordRaw[] = [
}, },
children: [ children: [
{ {
path: 'roles', path: '',
name: 'Roles', name: 'UserIndex',
component: UserIndex,
},
]
},
{
path: 'roles',
name: 'Roles',
meta: {
title: 'Roles',
requiresAuth: true
},
children: [
{
path: '',
name: 'RoleIndex',
component: RolesIndex, component: RolesIndex,
meta: { meta: {
title: 'Roles', title: 'Roles',
@ -174,7 +191,7 @@ const routes: RouteRecordRaw[] = [
} }
}, },
{ {
path: 'roles/permissions/:id', path: 'permissions/:id',
name: 'RolePermissions', name: 'RolePermissions',
component: RoleForm, component: RoleForm,
meta: { meta: {