diff --git a/app/Http/Controllers/AreaController.php b/app/Http/Controllers/AreaController.php new file mode 100644 index 0000000..cfdd50d --- /dev/null +++ b/app/Http/Controllers/AreaController.php @@ -0,0 +1,74 @@ +validate([ + 'name' => 'required', + ]); + + $user = Area::create([ + 'name' => $request->name + ]); + + return redirect()->back()->with('success', 'Usuario creado correctamente.'); + } + + /** + * Display the specified resource. + */ + public function show(Area $area) + { + // + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Area $area) + { + // + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Area $area) + { + // + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Area $area) + { + // + } +} diff --git a/app/Http/Controllers/AreaRoleUserController.php b/app/Http/Controllers/AreaRoleUserController.php new file mode 100644 index 0000000..32ac7a8 --- /dev/null +++ b/app/Http/Controllers/AreaRoleUserController.php @@ -0,0 +1,10 @@ +get(); + + return view('admin.users.index', compact('users')); + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + $areas = Area::all(); + $roles = Role::all(); + return view('admin.users.create', compact('areas', 'roles')); + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request) + { + $request->validate([ + 'name' => 'required', + 'email' => 'required|email|unique:users,email', + 'password' => 'required|min:8|confirmed', + 'role' => 'required', + ]); + + // Crear usuario + $user = User::create([ + 'name' => $request->name, + 'email' => $request->email, + 'password' => bcrypt($request->password), + ]); + + // Buscar el rol por nombre + $role = Role::where('name', $request->role)->first(); + + // Verificar si es supervisor + if ($role && $role->name === 'Supervisor') { + // Preparar datos para mostrar antes de guardar + $areasSeleccionadas = []; + + foreach ($request->areas ?? [] as $areaName) { + $area = Area::where('name', $areaName)->first(); + if ($area) { + $areasSeleccionadas[] = [ + 'user_id' => $user->id, + 'role_id' => $role->id, + 'area_id' => $area->id, + ]; + } + } + foreach ($areasSeleccionadas as $relacion) { + AreaRoleUser::create($relacion); + } + } + + if ($role && in_array($role->name, ['Admin', 'Soporte'])) { + // Asignar todas las áreas + $todasLasAreas = Area::all(); + foreach ($todasLasAreas as $area) { + AreaRoleUser::create([ + 'user_id' => $user->id, + 'role_id' => $role->id, + 'area_id' => $area->id, + ]); + } + } + + return redirect()->back()->with('success', 'Usuario creado correctamente.'); + } + + /** + * Display the specified resource. + */ + public function show(User $user, $id) + { + $user = User::with(['areaRoles.role', 'areaRoles.area'])->find($id); + + if (!$user) { + return redirect()->back()->with('error', 'Usuario no encontrado.'); + } + + $areas = Area::all(); + $userRole = optional($user->areaRoles->first())->role->name ?? null; + + return view('admin.users.show', compact('user', 'areas', 'userRole')); + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(User $user) + { + // + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, User $user) + { + $request->validate([ + 'name' => 'required|string|max:255', + 'email' => 'required|email|unique:users,email,' . $user->id, + 'password' => 'nullable|string|min:8|confirmed', + 'role' => 'required|string|exists:roles,name', + 'areas' => 'array', + 'areas.*' => 'integer|exists:areas,id', + ]); + + // 1. Actualizar datos básicos + $user->name = $request->name; + $user->email = $request->email; + + if ($request->filled('password')) { + $user->password = bcrypt($request->password); + } + + $user->save(); + + // 2. Obtener el rol por nombre + $role = Role::where('name', $request->role)->firstOrFail(); + + // 3. Eliminar asignaciones anteriores del usuario en area_role_users + AreaRoleUser::where('user_id', $user->id)->delete(); + + // 4. Determinar áreas a asignar + $areas = []; + + if (in_array($role->name, ['Admin', 'Soporte'])) { + $areas = Area::pluck('id')->toArray(); // Todas las áreas + } elseif ($role->name === 'Supervisor') { + $areas = $request->areas ?? []; + } + + // 5. Crear las nuevas asignaciones + foreach ($areas as $areaId) { + AreaRoleUser::create([ + 'user_id' => $user->id, + 'role_id' => $role->id, + 'area_id' => $areaId, + ]); + } + + return redirect()->route('users.index')->with('success', 'Usuario actualizado correctamente.'); + } + + + /** + * Remove the specified resource from storage. + */ + public function destroy(User $user) + { + // + } +} diff --git a/app/Models/Area.php b/app/Models/Area.php new file mode 100644 index 0000000..1516c52 --- /dev/null +++ b/app/Models/Area.php @@ -0,0 +1,21 @@ +belongsToMany(User::class); + } + +} diff --git a/app/Models/AreaRoleUser.php b/app/Models/AreaRoleUser.php new file mode 100644 index 0000000..8f23edf --- /dev/null +++ b/app/Models/AreaRoleUser.php @@ -0,0 +1,29 @@ +belongsTo(User::class); + } + + public function role() + { + return $this->belongsTo(Role::class); + } + + public function area() + { + return $this->belongsTo(Area::class); + } +} diff --git a/app/Models/Role.php b/app/Models/Role.php new file mode 100644 index 0000000..816474a --- /dev/null +++ b/app/Models/Role.php @@ -0,0 +1,21 @@ +belongsToMany(User::class, 'roles'); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 749c7b7..bfb2bff 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -6,9 +6,29 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use App\Models\AreaRoleUser; +use App\Models\Area; +use App\Models\Role; class User extends Authenticatable { + + + public function areaRoles() + { + return $this->hasMany(AreaRoleUser::class); + } + public function roles(): BelongsToMany + { + return $this->belongsToMany(Role::class, 'roles'); + } + + // Áreas a las que pertenece el usuario + public function areas(): BelongsToMany + { + return $this->belongsToMany(Area::class); + } /** @use HasFactory<\Database\Factories\UserFactory> */ use HasFactory, Notifiable; diff --git a/resources/js/components/SidebarDropdown.jsx b/resources/js/components/SidebarDropdown.jsx new file mode 100644 index 0000000..3babcfe --- /dev/null +++ b/resources/js/components/SidebarDropdown.jsx @@ -0,0 +1,48 @@ +import { useState } from "react" +import { ChevronDown, ChevronUp } from "lucide-react" +import { cn } from "@/lib/utils" +import { Link, usePage } from '@inertiajs/react'; +import {SidebarMenuItem, SidebarMenuButton} from "@/components/ui/sidebar" + + +export default function SidebarDropdown({ item, currentPath }) { + const [open, setOpen] = useState(false) + + const toggle = () => setOpen(!open) + + return ( + + {/* Botón principal del dropdown */} + child.href === currentPath)} + className="w-full flex items-center justify-between" + > + + {item.icon && } + {item.title} + + {open ? : } + + + {/* Submenú desplegable */} + {open && item.children?.length > 0 && ( + + {item.children.map((child) => ( + + + {child.title} + + + ))} + + )} + + ) +} diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index b9daba5..68694e9 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -4,7 +4,7 @@ import { NavUser } from '@/components/nav-user'; import { Sidebar, SidebarContent, SidebarFooter, SidebarHeader, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar'; import { type NavItem } from '@/types'; import { Link } from '@inertiajs/react'; -import { BookOpen, Folder, LayoutGrid } from 'lucide-react'; +import { BookOpen, Folder, LayoutGrid, TableProperties, SquareUserRound, Tags } from 'lucide-react'; import AppLogo from './app-logo'; const mainNavItems: NavItem[] = [ @@ -16,7 +16,25 @@ const mainNavItems: NavItem[] = [ { title: 'Tabla de agentes', href: '/showTableAgents', - icon: LayoutGrid, + icon: TableProperties, + }, + { + title: 'Usuarios', + href: '/users', + children: [ + { title: 'Administrar', href: '/users' }, + { title: 'Agregar', href: '/users/create' } + ], + icon: SquareUserRound, + }, + { + title: 'Areas', + href: '/Areas', + children: [ + { title: 'Administrar', href: '/users' }, + { title: 'Agregar', href: '/users/create' } + ], + icon: Tags, }, ]; diff --git a/resources/js/components/nav-main.tsx b/resources/js/components/nav-main.tsx index 35c4594..118bfff 100644 --- a/resources/js/components/nav-main.tsx +++ b/resources/js/components/nav-main.tsx @@ -2,6 +2,7 @@ import { SidebarGroup, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, Sideba import { type NavItem } from '@/types'; import { Link, usePage } from '@inertiajs/react'; import DarkModeDash from './dark-modeDash'; +import SidebarDropdown from './SidebarDropdown'; export function NavMain({ items = [] }: { items: NavItem[] }) { const page = usePage(); @@ -10,19 +11,20 @@ export function NavMain({ items = [] }: { items: NavItem[] }) { Dashboard - {items.map((item) => ( - - - - {item.icon && } - {item.title} + {items.map((item) => + item.children ? ( + + ) : ( + + + + {item.icon && } + {item.title} - - ))} + + ) + )} ); diff --git a/resources/js/types/index.d.ts b/resources/js/types/index.d.ts index 1a82d8e..7f28f5c 100644 --- a/resources/js/types/index.d.ts +++ b/resources/js/types/index.d.ts @@ -20,6 +20,7 @@ export interface NavItem { href: string; icon?: LucideIcon | null; isActive?: boolean; + children?: NavItem[]; } export interface SharedData {