diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php index bdb91dc..98b144e 100644 --- a/app/Http/Controllers/ProjectController.php +++ b/app/Http/Controllers/ProjectController.php @@ -47,24 +47,24 @@ class ProjectController extends Controller { $validated = $request->validate([ 'name' => 'required|string|max:255', - 'description' => 'required|string', - 'status' => 'required|in:active,inactive', - 'team' => 'sometimes|array', - 'team.*' => 'exists:users,id', + 'description' => 'nullable|string', + 'status' => 'required|in:Activo,Inactivo', + //'team' => 'sometimes|array', + //'team.*' => 'exists:users,id', 'project_image' => 'nullable|image|mimes:jpeg,png,jpg,gif|max:2048', 'address' => 'nullable|string|max:255', 'province' => 'nullable|string|max:100', - 'country' => 'nullable|string|size:2', + //'country' => 'nullable|string|size:2', 'postal_code' => 'nullable|string|max:10', 'latitude' => 'required|numeric|between:-90,90', 'longitude' => 'required|numeric|between:-180,180', - 'icon' => 'nullable|in:'.implode(',', config('project.icons')), + //'icon' => 'nullable|in:'.implode(',', config('project.icons')), 'start_date' => 'nullable|date', 'deadline' => 'nullable|date|after:start_date', - 'categories' => 'array|exists:categories,id', + 'categories' => 'nullable|array|exists:categories,id', //'categories' => 'required|array', - 'categories.*' => 'exists:categories,id', - 'documents.*' => 'file|max:5120|mimes:pdf,docx,xlsx,jpg,png' + //'categories.*' => 'exists:categories,id', + //'documents.*' => 'file|max:5120|mimes:pdf,docx,xlsx,jpg,png' ]); @@ -98,12 +98,10 @@ class ProjectController extends Controller } } - return redirect()->route('projects.show', $project) - ->with('success', 'Proyecto creado exitosamente'); + return redirect()->route('projects.show', $project)->with('success', 'Proyecto creado exitosamente'); } catch (\Exception $e) { - return back()->withInput() - ->with('error', 'Error al crear el proyecto: ' . $e->getMessage()); + return back()->withInput()->with('error', 'Error al crear el proyecto: ' . $e->getMessage()); } } diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index d1f8824..c512035 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -5,7 +5,14 @@ namespace App\Http\Controllers; use Illuminate\Http\Request; use App\Models\User; use Illuminate\Support\Facades\Hash; -use App\Http\Requests\UpdateUserRequest; +use App\Rules\PasswordRule; +use Illuminate\Database\QueryException; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Storage; +use Illuminate\Validation\Rule; +use Illuminate\Validation\Rules\Password; +use Illuminate\Validation\ValidationException; +use Spatie\Permission\Models\Permission; use Spatie\Permission\Models\Role; class UserController extends Controller @@ -13,7 +20,7 @@ class UserController extends Controller public function index() { $this->authorize('viewAny', User::class); - $users = User::with('roles')->paginate(10); + $users = User::paginate(10); return view('users.index', compact('users')); } @@ -27,24 +34,60 @@ class UserController extends Controller public function store(Request $request) { $this->authorize('create', User::class); - - $data = $request->validate([ - 'name' => 'required|string|max:255', - 'email' => 'required|email|unique:users', - 'password' => 'required|min:8|confirmed', - 'roles' => 'array' - ]); - - $user = User::create([ - 'name' => $data['name'], - 'email' => $data['email'], - 'password' => Hash::make($data['password']) - ]); - - $user->syncRoles($data['roles'] ?? []); - - return redirect()->route('users.index') - ->with('success', 'Usuario creado exitosamente'); + try { + // Validación de datos + $validated = $request->validate([ + 'title' => 'nullable|string|max:10', + 'first_name' => 'required|string|max:50', + 'last_name' => 'required|string|max:50', + 'username' => 'required|string|unique:users|max:30', + 'password' => ['required', + new PasswordRule( + minLength: 12, + requireUppercase: true, + requireNumeric: true, + requireSpecialCharacter: true, + //uncompromised: true, // Verificar contra Have I Been Pwned + //requireLetters: true + ), + Password::defaults()->mixedCase()->numbers()->symbols() + ->uncompromised(3) // Número mínimo de apariciones en brechas + ], + 'start_date' => 'nullable|date', + 'end_date' => 'nullable|date|after_or_equal:start_date', + 'email' => 'required|email|unique:users', + 'phone' => 'nullable|string|max:20', + 'address' => 'nullable|string|max:255' + ]); + + // Creación del usuario + $user = User::create([ + 'title' => $validated['title'], + 'first_name' => $validated['first_name'], + 'last_name' => $validated['last_name'], + 'username' => $validated['username'], + 'password' => Hash::make($validated['password']), + 'email' => $validated['email'], + 'phone' => $validated['phone'], + 'address' => $validated['address'], + 'access_start' => $validated['start_date'], + 'access_end' => $validated['end_date'], + 'is_active' => true + ]); + + if ($request->hasFile('photo')) { + $path = $request->file('photo')->store('public/photos'); + $user->profile_photo_path = basename($path); + $user->save(); + } + + // Asignación de roles (opcional, usando Spatie Permissions) + // $user->assignRole('user'); + + return redirect()->route('users.index')->with('success', 'Usuario creado exitosamente.')->with('temp_password', $validated['password']);; + } catch (\Exception $e) { + return back()->withInput()->with('error', 'Error al crear el usuario: ' . $e->getMessage()); + } } public function edit(User $user) @@ -52,17 +95,114 @@ class UserController extends Controller $this->authorize('update', $user); $roles = Role::all(); $userRoles = $user->roles->pluck('id')->toArray(); - - return view('users.edit', compact('user', 'roles', 'userRoles')); + return view('users.create', compact('user', 'roles', 'userRoles')); } - public function update(UpdateUserRequest $request, User $user) + public function update(Request $request, User $user) { - $user->update($request->validated()); - $user->syncRoles($request->roles); - - return redirect()->route('users.index') - ->with('success', 'Usuario actualizado correctamente'); + try { + // Validación de datos + $validated = $request->validate([ + 'title' => 'nullable|string|max:10', + 'first_name' => 'required|string|max:50', + 'last_name' => 'required|string|max:50', + 'username' => [ + 'required', + 'string', + 'max:30', + Rule::unique('users')->ignore($user->id) + ], + 'password' => [ + 'nullable', + Password::min(12) + ->mixedCase() + ->numbers() + ->symbols() + ->uncompromised(3) + ], + 'start_date' => 'nullable|date', + 'end_date' => 'nullable|date|after_or_equal:start_date', + 'email' => [ + 'required', + 'email', + Rule::unique('users')->ignore($user->id) + ], + 'phone' => 'nullable|string|max:20', + 'address' => 'nullable|string|max:255' + ]); + + // Preparar datos para actualización + $updateData = [ + 'title' => $validated['title'], + 'first_name' => $validated['first_name'], + 'last_name' => $validated['last_name'], + 'username' => $validated['username'], + 'email' => $validated['email'], + 'phone' => $validated['phone'], + 'address' => $validated['address'], + 'access_start' => $validated['start_date'], + 'access_end' => $validated['end_date'], + 'is_active' => $request->has('is_active') // Si usas un checkbox + ]; + + // Actualizar contraseña solo si se proporciona + if (!empty($validated['password'])) { + $updateData['password'] = Hash::make($validated['password']); + } + + if ($request->hasFile('photo')) { + // Eliminar foto anterior si existe + if ($user->prfile_photo_path) { + Storage::delete('public/photos/'.$user->profile_photo_path); + } + + $path = $request->file('photo')->store('public/photos'); + $user->update(['profile_photo_path' => basename($path)]); + } + + // Actualizar el usuario + $user->update($updateData); + + // Redireccionar con mensaje de éxito + return redirect()->route('users.show', $user) + ->with('success', 'Usuario actualizado exitosamente'); + + } catch (ValidationException $e) { + // Redireccionar con errores de validación + return redirect()->back()->withErrors($e->validator)->withInput(); + + } catch (QueryException $e) { + // Manejar errores de base de datos + $errorCode = $e->errorInfo[1]; + $errorMessage = 'Error al actualizar el usuario: '; + + if ($errorCode == 1062) { + $errorMessage .= 'El nombre de usuario o correo electrónico ya está en uso'; + } else { + $errorMessage .= 'Error en la base de datos'; + } + + Log::error("Error actualizando usuario ID {$user->id}: " . $e->getMessage()); + return redirect()->back()->with('error', $errorMessage)->withInput(); + + } catch (\Exception $e) { + // Manejar otros errores + Log::error("Error general actualizando usuario ID {$user->id}: " . $e->getMessage()); + return redirect()->back()->with('error', 'Ocurrió un error inesperado al actualizar el usuario')->withInput(); + } + } + + public function show(User $user) + { + $previousUser = User::where('id', '<', $user->id)->latest('id')->first(); + $nextUser = User::where('id', '>', $user->id)->oldest('id')->first(); + + return view('users.show', [ + 'user' => $user, + 'previousUser' => $previousUser, + 'nextUser' => $nextUser, + 'permissionGroups' => Permission::all()->groupBy('group') + ]); } public function updatePassword(Request $request, User $user) diff --git a/app/Livewire/CountrySelect.php b/app/Livewire/CountrySelect.php new file mode 100644 index 0000000..3e8098c --- /dev/null +++ b/app/Livewire/CountrySelect.php @@ -0,0 +1,66 @@ + 'required', + ]; + + public function mount($initialCountry = null) + { + $this->selectedCountry = $initialCountry; + } + + public function selectCountry($code) + { + $this->selectedCountry = $code; + $this->isOpen = false; + $this->search = ''; // Limpiar la búsqueda al seleccionar + } + + public function render() + { + $countries = collect(config('countries')) + ->when($this->search, function($collection) { + // Corregimos el filtrado aquí + return $collection->filter(function($name, $code) { + $searchLower = strtolower($this->search); + $nameLower = strtolower($name); + $codeLower = strtolower($code); + + return str_contains($nameLower, $searchLower) || + str_contains($codeLower, $searchLower); + }); + }); + + return view('livewire.country-select', compact('countries')); + } + + public function formattedCountry($code, $name) + { + return $this->getFlagEmoji($code).' '.$name.' ('.strtoupper($code).')'; + } + + protected function getFlagEmoji($countryCode) + { + $countryCode = strtoupper($countryCode); + $flagOffset = 0x1F1E6; + $asciiOffset = 0x41; + + $firstChar = ord($countryCode[0]) - $asciiOffset + $flagOffset; + $secondChar = ord($countryCode[1]) - $asciiOffset + $flagOffset; + + return mb_convert_encoding('&#'.intval($firstChar).';', 'UTF-8', 'HTML-ENTITIES') + . mb_convert_encoding('&#'.intval($secondChar).';', 'UTF-8', 'HTML-ENTITIES'); + } +} diff --git a/app/Livewire/ImageUploader.php b/app/Livewire/ImageUploader.php new file mode 100644 index 0000000..97e1cd5 --- /dev/null +++ b/app/Livewire/ImageUploader.php @@ -0,0 +1,79 @@ + 'nullable|image|max:2048', // 2MB Max + ]; + + public function mount($fieldName = 'photo', $currentImage = null, $placeholder = null) + { + $this->fieldName = $fieldName; + $this->currentImage = $currentImage; + $this->placeholder = $placeholder ?? asset('images/default-avatar.png'); + } + + public function updatedPhoto() + { + $this->validate([ + 'photo' => 'image|max:2048', // 2MB Max + ]); + } + + public function removePhoto() + { + $this->photo = null; + $this->currentImage = null; + } + + public function save() + { + $this->validate(); + + if ($this->photo) { + $path = $this->photo->store($this->storagePath); + + if ($this->model) { + // Eliminar imagen anterior si existe + if ($this->model->{$this->fieldName}) { + Storage::delete($this->model->{$this->fieldName}); + } + + $this->model->{$this->fieldName} = $path; + $this->model->save(); + } + + $this->currentUrl = Storage::url($path); + $this->showSavedMessage = true; + $this->photo = null; // Limpiar el input de subida + } + } + + protected function getCurrentImageUrl() + { + if ($this->model && $this->model->{$this->fieldName}) { + return Storage::url($this->model->{$this->fieldName}); + } + + return $this->placeholder; + } + + public function render() + { + return view('livewire.image-uploader'); + } +} diff --git a/app/Livewire/ProjectShow.php b/app/Livewire/ProjectShow.php index 90f8a66..d338fc7 100644 --- a/app/Livewire/ProjectShow.php +++ b/app/Livewire/ProjectShow.php @@ -65,7 +65,7 @@ class ProjectShow extends Component ]); $this->reset('folderName'); - $this->project->load('rootFolders'); // Recargar carpetas raíz + $this->project->load('rootFolders'); // Recargar carpetas raíz if ($this->currentFolder) { $this->currentFolder->load('children'); // Recargar hijos si está en una subcarpeta } @@ -118,9 +118,6 @@ class ProjectShow extends Component public function render() { - return view('livewire.project-show') - ->layout('layouts.livewire-app', [ - 'title' => $this->project->name - ]); + return view('livewire.project-show'); } } diff --git a/app/Livewire/UserPhotoUpload.php b/app/Livewire/UserPhotoUpload.php new file mode 100644 index 0000000..0877948 --- /dev/null +++ b/app/Livewire/UserPhotoUpload.php @@ -0,0 +1,81 @@ + 'refreshPhoto']; + + public function mount($existingPhoto = null, $userId = null) + { + $this->existingPhoto = $existingPhoto; + $this->userId = $userId; + } + + public function uploadPhoto() + { + $this->validate([ + 'photo' => 'image|max:2048|mimes:jpg,png,jpeg,gif', + ]); + + $this->tempPhoto = $this->photo->temporaryUrl(); + } + + public function updatedPhoto() + { + $this->validate([ + 'photo' => 'image|max:2048|mimes:jpg,png,jpeg,gif', + ]); + + $this->tempPhoto = $this->photo->temporaryUrl(); + + // Emitir evento con la foto temporal + $this->emit('photoUploaded', $this->photo->getRealPath()); + } + + public function removePhoto() + { + $this->reset('photo', 'tempPhoto'); + } + + public function deletePhoto() + { + if ($this->existingPhoto) { + Storage::delete('public/photos/' . $this->existingPhoto); + + if ($this->userId) { + $user = User::find($this->userId); + $user->update(['profile_photo_path' => null]); + } + + $this->existingPhoto = null; + $this->emit('photoDeleted'); + } + } + + public function refreshPhoto() + { + $this->existingPhoto = User::find($this->userId)->profile_photo_path; + } + + public function render() + { + return view('livewire.user-photo-upload', [ + //'photo' => $this->photo, + 'tempPhoto' => $this->tempPhoto, + 'existingPhoto' => $this->existingPhoto, + ]); + } +} diff --git a/app/Livewire/UserTable.php b/app/Livewire/UserTable.php new file mode 100644 index 0000000..33a6f45 --- /dev/null +++ b/app/Livewire/UserTable.php @@ -0,0 +1,109 @@ + true, + 'is_active' => true, + 'username' => true, + 'email' => true, + 'phone' => false, + 'access_start' => false, + 'created_at' => false, + 'is_active' => false, + ]; + + public $filters = [ + 'full_name' => '', + 'is_active' => '', + 'username' => '', + 'email' => '', + 'phone' => '', + 'access_start' => '', + 'created_at' => '' + ]; + + protected $queryString = [ + 'filters' => ['except' => ''], + 'columns' => ['except' => ''] + ]; + + public function mount() + { + // Recuperar preferencias de columnas de la sesión + $this->columns = session('user_columns', $this->columns); + } + + public function updatedColumns() + { + // Guardar preferencias en sesión + session(['user_columns' => $this->columns]); + } + + public function sortBy($field) + { + if ($this->sortField === $field) { + $this->sortDirection = $this->sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + $this->sortDirection = 'asc'; + } + + $this->sortField = $field; + } + + public function render() + { + $users = User::query() + ->when($this->filters['full_name'], fn($q, $search) => + $q->whereRaw("CONCAT(title, ' ', first_name, ' ', last_name) LIKE ?", ["%$search%"])) + ->when($this->sortField === 'full_name', fn($q) => + $q->orderByRaw("CONCAT(title, ' ', first_name, ' ', last_name) " . $this->sortDirection) + ) + ->when($this->sortField === 'created_at', fn($q) => + $q->orderBy('created_at', $this->sortDirection) + ) + ->when($this->sortField === 'access_start', fn($q) => + $q->orderBy('access_start', $this->sortDirection) + ) + ->when(in_array($this->sortField, ['username', 'email', 'phone']), fn($q) => + $q->orderBy($this->sortField, $this->sortDirection) + ) + ->when($this->filters['full_name'], fn($q, $search) => + $q->whereRaw("CONCAT(title, ' ', first_name, ' ', last_name) LIKE ?", ["%$search%"])) + ->when($this->filters['username'], fn($q, $search) => + $q->where('username', 'LIKE', "%$search%")) + ->when($this->filters['email'], fn($q, $search) => + $q->where('email', 'LIKE', "%$search%")) + ->when($this->filters['phone'], fn($q, $search) => + $q->where('phone', 'LIKE', "%$search%")) + ->when($this->filters['access_start'], fn($q, $date) => + $q->whereDate('access_start', $date)) + ->when($this->filters['created_at'], fn($q, $date) => + $q->whereDate('created_at', $date)) + ->paginate(10); + + return view('livewire.user-table', [ + 'users' => $users, + 'available_columns' => [ + 'full_name' => 'Nombre Completo', + 'username' => 'Usuario', + 'email' => 'Correo', + 'phone' => 'Teléfono', + 'access_start' => 'Fecha de acceso', + 'created_at' => 'Fecha creación', + 'is_active' => 'Estado' + ] + ]); + } +} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 4d18b93..38d0396 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,6 +7,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; use Spatie\Permission\Traits\HasRoles; @@ -21,9 +22,18 @@ class User extends Authenticatable * @var list */ protected $fillable = [ - 'name', + 'title', + 'first_name', + 'last_name', + 'username', 'email', 'password', + 'phone', + 'address', + 'access_start', + 'access_end', + 'is_active', + 'profile_photo_path', ]; /** @@ -39,26 +49,25 @@ class User extends Authenticatable /** * Get the attributes that should be cast. * - * @return array + * @var list */ - protected function casts(): array - { - return [ - 'email_verified_at' => 'datetime', - 'password' => 'hashed', - 'is_protected' => 'boolean', - ]; - } + protected $casts = [ + 'email_verified_at' => 'datetime', + 'access_start' => 'date', + 'access_end' => 'date', + 'is_active' => 'boolean' + ]; /** * Get the user's initials */ public function initials(): string { - return Str::of($this->name) + /*return Str::of($this->name) ->explode(' ') ->map(fn (string $name) => Str::of($name)->substr(0, 1)) - ->implode(''); + ->implode('');*/ + return Str::of(''); } public function groups() @@ -89,5 +98,16 @@ class User extends Authenticatable return $group->hasAnyPermission($permissions); }); } + + public function getFullNameAttribute() + { + return "{$this->title} {$this->first_name} {$this->last_name}"; + } + + // Accesor para la URL completa + public function getProfilePhotoUrlAttribute() + { + return $this->profile_photo ? Storage::url($this->profile_photo) : asset('images/default-user.png'); + } } diff --git a/app/Rules/PasswordRule.php b/app/Rules/PasswordRule.php new file mode 100644 index 0000000..cf2b8fe --- /dev/null +++ b/app/Rules/PasswordRule.php @@ -0,0 +1,69 @@ +minLength = $minLength; + $this->requireUppercase = $requireUppercase; + $this->requireNumeric = $requireNumeric; + $this->requireSpecialCharacter = $requireSpecialCharacter; + } + + /** + * Run the validation rule. + * + * @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail + */ + public function validate(string $attribute, mixed $value, Closure $fail): void + { + // + } + + public function passes($attribute, $value) + { + $passes = true; + + if (strlen($value) < $this->minLength) { + $passes = false; + } + + if ($this->requireUppercase && !preg_match('/[A-Z]/', $value)) { + $passes = false; + } + + if ($this->requireNumeric && !preg_match('/[0-9]/', $value)) { + $passes = false; + } + + if ($this->requireSpecialCharacter && !preg_match('/[\W_]/', $value)) { + $passes = false; + } + + return $passes; + } + + public function message() + { + return 'La contraseña debe contener al menos '.$this->minLength.' caracteres'. + ($this->requireUppercase ? ', una mayúscula' : ''). + ($this->requireNumeric ? ', un número' : ''). + ($this->requireSpecialCharacter ? ' y un carácter especial' : ''); + } +} diff --git a/config/countries.php b/config/countries.php index 0e6fa1d..678c667 100644 --- a/config/countries.php +++ b/config/countries.php @@ -1,8 +1,253 @@ 'España', - 'FR' => 'Francia', + 'AD' => 'Andorra', + 'AE' => 'United Arab Emirates', + 'AF' => 'Afghanistan', + 'AG' => 'Antigua and Barbuda', + 'AI' => 'Anguilla', + 'AL' => 'Albania', + 'AM' => 'Armenia', + 'AO' => 'Angola', + 'AQ' => 'Antarctica', + 'AR' => 'Argentina', + 'AS' => 'American Samoa', + 'AT' => 'Austria', + 'AU' => 'Australia', + 'AW' => 'Aruba', + 'AX' => 'Åland Islands', + 'AZ' => 'Azerbaijan', + 'BA' => 'Bosnia and Herzegovina', + 'BB' => 'Barbados', + 'BD' => 'Bangladesh', + 'BE' => 'Belgium', + 'BF' => 'Burkina Faso', + 'BG' => 'Bulgaria', + 'BH' => 'Bahrain', + 'BI' => 'Burundi', + 'BJ' => 'Benin', + 'BL' => 'Saint Barthélemy', + 'BM' => 'Bermuda', + 'BN' => 'Brunei Darussalam', + 'BO' => 'Bolivia, Plurinational State of', + 'BQ' => 'Bonaire, Sint Eustatius and Saba', + 'BR' => 'Brazil', + 'BS' => 'Bahamas', + 'BT' => 'Bhutan', + 'BV' => 'Bouvet Island', + 'BW' => 'Botswana', + 'BY' => 'Belarus', + 'BZ' => 'Belize', + 'CA' => 'Canada', + 'CC' => 'Cocos (Keeling) Islands', + 'CD' => 'Congo, the Democratic Republic of the', + 'CF' => 'Central African Republic', + 'CG' => 'Congo', + 'CH' => 'Switzerland', + 'CI' => "Côte d'Ivoire", + 'CK' => 'Cook Islands', + 'CL' => 'Chile', + 'CM' => 'Cameroon', + 'CN' => 'China', + 'CO' => 'Colombia', + 'CR' => 'Costa Rica', + 'CU' => 'Cuba', + 'CV' => 'Cape Verde', + 'CW' => 'Curaçao', + 'CX' => 'Christmas Island', + 'CY' => 'Cyprus', + 'CZ' => 'Czech Republic', + 'DE' => 'Germany', + 'DJ' => 'Djibouti', + 'DK' => 'Denmark', + 'DM' => 'Dominica', + 'DO' => 'Dominican Republic', + 'DZ' => 'Algeria', + 'EC' => 'Ecuador', + 'EE' => 'Estonia', + 'EG' => 'Egypt', + 'EH' => 'Western Sahara', + 'ER' => 'Eritrea', + 'ES' => 'Spain', + 'ET' => 'Ethiopia', + 'FI' => 'Finland', + 'FJ' => 'Fiji', + 'FK' => 'Falkland Islands (Malvinas)', + 'FM' => 'Micronesia, Federated States of', + 'FO' => 'Faroe Islands', + 'FR' => 'France', + 'GA' => 'Gabon', + 'GB' => 'United Kingdom', + 'GD' => 'Grenada', + 'GE' => 'Georgia', + 'GF' => 'French Guiana', + 'GG' => 'Guernsey', + 'GH' => 'Ghana', + 'GI' => 'Gibraltar', + 'GL' => 'Greenland', + 'GM' => 'Gambia', + 'GN' => 'Guinea', + 'GP' => 'Guadeloupe', + 'GQ' => 'Equatorial Guinea', + 'GR' => 'Greece', + 'GS' => 'South Georgia and the South Sandwich Islands', + 'GT' => 'Guatemala', + 'GU' => 'Guam', + 'GW' => 'Guinea-Bissau', + 'GY' => 'Guyana', + 'HK' => 'Hong Kong', + 'HM' => 'Heard Island and McDonald Islands', + 'HN' => 'Honduras', + 'HR' => 'Croatia', + 'HT' => 'Haiti', + 'HU' => 'Hungary', + 'ID' => 'Indonesia', + 'IE' => 'Ireland', + 'IL' => 'Israel', + 'IM' => 'Isle of Man', + 'IN' => 'India', + 'IO' => 'British Indian Ocean Territory', + 'IQ' => 'Iraq', + 'IR' => 'Iran, Islamic Republic of', + 'IS' => 'Iceland', + 'IT' => 'Italy', + 'JE' => 'Jersey', + 'JM' => 'Jamaica', + 'JO' => 'Jordan', + 'JP' => 'Japan', + 'KE' => 'Kenya', + 'KG' => 'Kyrgyzstan', + 'KH' => 'Cambodia', + 'KI' => 'Kiribati', + 'KM' => 'Comoros', + 'KN' => 'Saint Kitts and Nevis', + 'KP' => "Korea, Democratic People's Republic of", + 'KR' => 'Korea, Republic of', + 'KW' => 'Kuwait', + 'KY' => 'Cayman Islands', + 'KZ' => 'Kazakhstan', + 'LA' => "Lao People's Democratic Republic", + 'LB' => 'Lebanon', + 'LC' => 'Saint Lucia', + 'LI' => 'Liechtenstein', + 'LK' => 'Sri Lanka', + 'LR' => 'Liberia', + 'LS' => 'Lesotho', + 'LT' => 'Lithuania', + 'LU' => 'Luxembourg', + 'LV' => 'Latvia', + 'LY' => 'Libyan Arab Jamahiriya', + 'MA' => 'Morocco', + 'MC' => 'Monaco', + 'MD' => 'Moldova, Republic of', + 'ME' => 'Montenegro', + 'MF' => 'Saint Martin (French part)', + 'MG' => 'Madagascar', + 'MH' => 'Marshall Islands', + 'MK' => 'Macedonia, the former Yugoslav Republic of', + 'ML' => 'Mali', + 'MM' => 'Myanmar', + 'MN' => 'Mongolia', + 'MO' => 'Macao', + 'MP' => 'Northern Mariana Islands', + 'MQ' => 'Martinique', + 'MR' => 'Mauritania', + 'MS' => 'Montserrat', + 'MT' => 'Malta', + 'MU' => 'Mauritius', + 'MV' => 'Maldives', + 'MW' => 'Malawi', + 'MX' => 'Mexico', + 'MY' => 'Malaysia', + 'MZ' => 'Mozambique', + 'NA' => 'Namibia', + 'NC' => 'New Caledonia', + 'NE' => 'Niger', + 'NF' => 'Norfolk Island', + 'NG' => 'Nigeria', + 'NI' => 'Nicaragua', + 'NL' => 'Netherlands', + 'NO' => 'Norway', + 'NP' => 'Nepal', + 'NR' => 'Nauru', + 'NU' => 'Niue', + 'NZ' => 'New Zealand', + 'OM' => 'Oman', + 'PA' => 'Panama', + 'PE' => 'Peru', + 'PF' => 'French Polynesia', + 'PG' => 'Papua New Guinea', + 'PH' => 'Philippines', + 'PK' => 'Pakistan', + 'PL' => 'Poland', + 'PM' => 'Saint Pierre and Miquelon', + 'PN' => 'Pitcairn', + 'PR' => 'Puerto Rico', + 'PS' => 'Palestinian Territory, Occupied', 'PT' => 'Portugal', - // ... resto de países + 'PW' => 'Palau', + 'PY' => 'Paraguay', + 'QA' => 'Qatar', + 'RE' => 'Réunion', + 'RO' => 'Romania', + 'RS' => 'Serbia', + 'RU' => 'Russian Federation', + 'RW' => 'Rwanda', + 'SA' => 'Saudi Arabia', + 'SB' => 'Solomon Islands', + 'SC' => 'Seychelles', + 'SD' => 'Sudan', + 'SE' => 'Sweden', + 'SG' => 'Singapore', + 'SH' => 'Saint Helena, Ascension and Tristan da Cunha', + 'SI' => 'Slovenia', + 'SJ' => 'Svalbard and Jan Mayen', + 'SK' => 'Slovakia', + 'SL' => 'Sierra Leone', + 'SM' => 'San Marino', + 'SN' => 'Senegal', + 'SO' => 'Somalia', + 'SR' => 'Suriname', + 'SS' => 'South Sudan', + 'ST' => 'Sao Tome and Principe', + 'SV' => 'El Salvador', + 'SX' => 'Sint Maarten', + 'SY' => 'Syrian Arab Republic', + 'SZ' => 'Swaziland', + 'TC' => 'Turks and Caicos Islands', + 'TD' => 'Chad', + 'TF' => 'French Southern Territories', + 'TG' => 'Togo', + 'TH' => 'Thailand', + 'TJ' => 'Tajikistan', + 'TK' => 'Tokelau', + 'TL' => 'Timor-Leste', + 'TM' => 'Turkmenistan', + 'TN' => 'Tunisia', + 'TO' => 'Tonga', + 'TR' => 'Turkey', + 'TT' => 'Trinidad and Tobago', + 'TV' => 'Tuvalu', + 'TW' => 'Taiwan, Province of China', + 'TZ' => 'Tanzania, United Republic of', + 'UA' => 'Ukraine', + 'UG' => 'Uganda', + 'UM' => 'United States Minor Outlying Islands', + 'US' => 'United States', + 'UY' => 'Uruguay', + 'UZ' => 'Uzbekistan', + 'VA' => 'Holy See (Vatican City State)', + 'VC' => 'Saint Vincent and the Grenadines', + 'VE' => 'Venezuela, Bolivarian Republic of', + 'VG' => 'Virgin Islands, British', + 'VI' => 'Virgin Islands, U.S.', + 'VN' => 'Viet Nam', + 'VU' => 'Vanuatu', + 'WF' => 'Wallis and Futuna', + 'WS' => 'Samoa', + 'YE' => 'Yemen', + 'YT' => 'Mayotte', + 'ZA' => 'South Africa', + 'ZM' => 'Zambia', + 'ZW' => 'Zimbabwe', ]; \ No newline at end of file diff --git a/database/migrations/2025_04_29_115501_update_users_table.php b/database/migrations/2025_04_29_115501_update_users_table.php new file mode 100644 index 0000000..909e911 --- /dev/null +++ b/database/migrations/2025_04_29_115501_update_users_table.php @@ -0,0 +1,51 @@ +string('title')->nullable()->after('id'); + $table->string('first_name')->after('title'); + $table->string('last_name')->after('first_name'); + $table->string('username')->unique()->after('last_name'); + $table->date('access_start')->nullable()->after('password'); + $table->date('access_end')->nullable()->after('access_start'); + $table->string('phone')->nullable()->after('email'); + $table->text('address')->nullable()->after('phone'); + $table->boolean('is_active')->default(true)->after('address'); + $table->string('profile_photo_path')->nullable(); + + // Eliminar campos no necesarios + $table->dropColumn('name'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('title'); + $table->dropColumn('first_name'); + $table->dropColumn('last_name'); + $table->dropColumn('username'); + $table->dropColumn('access_start'); + $table->dropColumn('access_end'); + $table->dropColumn('phone'); + $table->dropColumn('address'); + $table->dropColumn('is_active'); + $table->dropColumn('profile_photo_path '); + + $table->string('name')->after('id'); + }); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 30d4447..0abc822 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -3,9 +3,8 @@ namespace Database\Seeders; use App\Models\User; -// use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; -use Spatie\Permission\Models\Permission; +use Illuminate\Support\Facades\Hash; class DatabaseSeeder extends Seeder { @@ -16,10 +15,14 @@ class DatabaseSeeder extends Seeder { // User::factory(10)->create(); - User::factory()->create([ - 'name' => 'admin', + User::create([ + 'first_name' => 'Administrador', + 'last_name' => '', + 'username' => 'admin', 'email' => 'admin@example.com', - 'password' => '12345678', + 'password' => Hash::make('12345678'), + 'is_active' => true, + 'access_start' => now(), ]); $this->call([ diff --git a/database/seeders/RolePermissionSeeder.php b/database/seeders/RolePermissionSeeder.php index 21505bd..2f91720 100644 --- a/database/seeders/RolePermissionSeeder.php +++ b/database/seeders/RolePermissionSeeder.php @@ -56,15 +56,6 @@ class RolePermissionSeeder extends Seeder $adminRole->syncPermissions($allPermissions); $adminRole->syncPermissions($permissions); - // Crear usuario admin si no existe - /*User::updateOrCreate( - ['email' => env('ADMIN_EMAIL', 'admin@example.com')], - [ - 'name' => 'Administrador', - 'password' => bcrypt(env('ADMIN_PASSWORD', 'password')), - 'email_verified_at' => now() - ] - )->assignRole($adminRole);*/ $adminEmail = env('ADMIN_EMAIL', 'admin@example.com'); $user = User::where('email', $adminEmail)->first(); if ($user) { @@ -75,9 +66,11 @@ class RolePermissionSeeder extends Seeder } else { // Crear solo si no existe User::create([ - 'name' => 'admin', - 'email' => $adminEmail, - 'password' => bcrypt(env('ADMIN_PASSWORD', '12345678')), + 'first_name' => 'Administrador', + 'username' => 'admin', + 'email' => 'admin@example.com', + 'password' => '12345678', + 'is_active' => true, 'email_verified_at' => now() ])->assignRole($adminRole); } diff --git a/resources/views/livewire/country-select.blade.php b/resources/views/livewire/country-select.blade.php new file mode 100644 index 0000000..32df4fd --- /dev/null +++ b/resources/views/livewire/country-select.blade.php @@ -0,0 +1,63 @@ + +
+ + + + +
+ +
+ +
+ + +
+ @forelse($countries as $code => $name) + + @empty +
+ No se encontraron resultados +
+ @endforelse +
+
+ + + +
\ No newline at end of file diff --git a/resources/views/livewire/image-uploader.blade.php b/resources/views/livewire/image-uploader.blade.php new file mode 100644 index 0000000..71084c7 --- /dev/null +++ b/resources/views/livewire/image-uploader.blade.php @@ -0,0 +1,44 @@ +
+ +
+ Preview + + @if($photo || $currentImage) + + @endif +
+ + + + + @error('photo') +

{{ $message }}

+ @enderror + +

PNG, JPG o JPEG (Max. 2MB)

+
\ No newline at end of file diff --git a/resources/views/livewire/project-show.blade.php b/resources/views/livewire/project-show.blade.php index 6ff3630..09753e5 100644 --- a/resources/views/livewire/project-show.blade.php +++ b/resources/views/livewire/project-show.blade.php @@ -31,8 +31,7 @@

Carpetas

-
@@ -67,8 +66,7 @@ multiple class="hidden" id="file-upload"> -
@@ -115,4 +113,5 @@ + \ No newline at end of file diff --git a/resources/views/livewire/settings/profile.blade.php b/resources/views/livewire/settings/profile.blade.php index cb08833..fd25295 100644 --- a/resources/views/livewire/settings/profile.blade.php +++ b/resources/views/livewire/settings/profile.blade.php @@ -15,7 +15,7 @@ new class extends Component { */ public function mount(): void { - $this->name = Auth::user()->name; + $this->name = Auth::user()->first_name; $this->email = Auth::user()->email; } diff --git a/resources/views/livewire/user-photo-upload.blade.php b/resources/views/livewire/user-photo-upload.blade.php new file mode 100644 index 0000000..5d70206 --- /dev/null +++ b/resources/views/livewire/user-photo-upload.blade.php @@ -0,0 +1,96 @@ + + +
+ + @if($existingPhoto) +
+ Foto actual + + +
+ @endif + + +
+ + + + +
+ + + @if($photo || $tempPhoto) +
+ +
+ @endif + + + @error('photo') +
{{ $message }}
+ @enderror +
\ No newline at end of file diff --git a/resources/views/livewire/user-table.blade.php b/resources/views/livewire/user-table.blade.php new file mode 100644 index 0000000..21aa70b --- /dev/null +++ b/resources/views/livewire/user-table.blade.php @@ -0,0 +1,193 @@ +
+ +
+
+ + +
+ @foreach($available_columns as $key => $label) + + @endforeach +
+
+
+ + +
+ + + + @foreach($available_columns as $key => $label) + @if($columns[$key]) + @if($key !== 'is_active') + + @else + + @endif + @endif + @endforeach + + + + + + + @foreach($users as $user) + + @if($columns['full_name']) + + @endif + + + + + + + + + + + + + + + + @endforeach + +
+
+
+ {{ $label }} + +
+ @if($sortField === $key) + + @if($sortDirection === 'asc') + ↑ + @else + ↓ + @endif + + @endif +
+
+
+ Estado + +
+
Acciones
+ + + @if($user->profile_photo_path) +
+ {{ $user->full_name }} +
+ @else +
+ + + +
+ @endif + + + + {{ $user->full_name }} + @if(!$user->is_active) + (Inactivo) + @endif + +
+
+ @if($columns['username']) + {{ $user->username }} + @else + N/A + @endif + + @if($columns['email']) + +
+ + + + {{ $user->email }} +
+ @else + N/A + @endif +
+ @if($columns['phone']) + +
+ + + + {{ $user->phone ?? 'N/A' }} +
+ @else + N/A + @endif +
+ @if($columns['access_start']) + {{ $user->access_start?->format('d/m/Y') }} + @else + N/A + @endif + + @if($columns['created_at']) + {{ $user->created_at->format('d/m/Y H:i') }} + @else + N/A + @endif + + @if($columns['is_active']) + {{ $user->is_active ? 'Activo' : 'Inactivo' }} + @else + N/A + @endif + + +
+ + + + + + + + + +
+
+
+ + +
+ {{ $users->links() }} +
+
\ No newline at end of file diff --git a/resources/views/projects/create.blade.php b/resources/views/projects/create.blade.php index 4bca5d0..fca495c 100644 --- a/resources/views/projects/create.blade.php +++ b/resources/views/projects/create.blade.php @@ -1,360 +1,378 @@ + - -

- - - - +
+
+
+

+ + + + + {{ __('Nuevo Proyecto') }} + + @error('error') +

{{ $message }}

+ @enderror + +

+
+
+ + +
+ @csrf - {{ __('Nuevo Proyecto') }} -

-
- -
-
-

- - - - {{ __('Nuevo Proyecto') }} -

-
-
- -
-
-
-
- - @csrf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - @error('name') -

{{ $message }}

- @enderror -
- - - - {{ old('description') }} - - @error('description') -

{{ $message }}

- @enderror -
- - -
- -
- - - - - - - - -
- - - -
{!! old('description') !!}
-
- @error('description') -

{{ $message }}

- @enderror -
- - - - - - - @error('status') -

{{ $message }}

- @enderror -
- - -
- - -
- @error('project_image') -

{{ $message }}

- @enderror -
- - -
-
- - @error('address') -

{{ $message }}

- @enderror -
- -
- - @error('postal_code') -

{{ $message }}

- @enderror -
- -
- - @error('province') -

{{ $message }}

- @enderror -
- -
- - - @foreach(config('countries') as $code => $name) - - @endforeach - - @error('country') -

{{ $message }}

- @enderror -
-
-
- - -
-
-
- - -
-
- - -
-
- @error('latitude') -

{{ $message }}

- @enderror - @error('longitude') -

{{ $message }}

- @enderror -
- - -
-
- -
- -
-
- -
- - -
-
-
- - -
- de - - a - -
-
- - -
-
-
- - - - - -
-
-
-
- - -
- @foreach($users as $user) - - @endforeach -
- @error('team') -

{{ $message }}

- @enderror -
- - -
- - {{ __('Cancelar') }} - - - - {{ __('Crear Proyecto') }} - -
- + +
+
+
+
+
+ Datos Generales
-
-
- @push('styles') +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + @error('name') +

{{ $message }}

+ @enderror +
+ + + + {{ old('description') }} + + @error('description') +

{{ $message }}

+ @enderror +
+ + + + + + + @error('status') +

{{ $message }}

+ @enderror +
+ + +
+ de + + a + +
+
+
+ +
+
+
+
+
+ Ubicación +
+
+ +
+ + + + + + + + + + + + + + +
+ + +
+
+ + + @error('address') +

{{ $message }}

+ @enderror +
+ +
+ + @error('postal_code') +

{{ $message }}

+ @enderror +
+ +
+ + @error('province') +

{{ $message }}

+ @enderror +
+ +
+ + @error('country') +

{{ $message }}

+ @enderror +
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ @error('latitude') +

{{ $message }}

+ @enderror + @error('longitude') +

{{ $message }}

+ @enderror +
+
+ +
+
+
+
+
+ Otros datos +
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+ @error('project_image') +

{{ $message }}

+ @enderror +
+ + +
+
+ +
+ +
+
+ +
+ + +
+
+
+ + +
+
+
+ + + + + +
+
+
+
+ + +
+ @foreach($users as $user) + + @endforeach +
+ @error('team') +

{{ $message }}

+ @enderror +
+
+ + +
+ + {{ __('Cancelar') }} + + + + {{ __('Crear Proyecto') }} + +
+ + +
+ @@ -363,11 +381,7 @@ integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A==" crossorigin=""/> - @endpush - - - @push('scripts') @@ -469,5 +483,4 @@ }; - @endpush \ No newline at end of file diff --git a/resources/views/projects/create.blade.php.back b/resources/views/projects/create.blade.php.back deleted file mode 100644 index 228fb61..0000000 --- a/resources/views/projects/create.blade.php.back +++ /dev/null @@ -1,386 +0,0 @@ - - - -

- Nuevo Proyecto -

-
- -
-
-
-
-
- @csrf - -
- -
- -
- - - @error('name') -

{{ $message }}

- @enderror -
- - -
- - - {{ old('description') }} - - @error('description') -

{{ $message }}

- @enderror -
- - -
- -
- -
- - - - - - - - - -
- - -
{!! old('description') !!}
-
- @error('description') -

{{ $message }}

- @enderror -
- - -
- - - - - - @error('status') -

{{ $message }}

- @enderror -
- - -
- -
- - -
- @error('project_image') -

{{ $message }}

- @enderror -
-
- - -
- -
- -
-
- - @error('address') -

{{ $message }}

- @enderror -
- -
- - @error('postal_code') -

{{ $message }}

- @enderror -
- -
- - @error('province') -

{{ $message }}

- @enderror -
- -
- - - @foreach(config('countries') as $code => $name) - - @endforeach - - @error('country') -

{{ $message }}

- @enderror -
-
-
- - -
- -
-
-
- - -
-
- - -
-
- @error('latitude') -

{{ $message }}

- @enderror - @error('longitude') -

{{ $message }}

- @enderror -
-
-
- - -
- -
- -
- -
-
- -
- -
-
- -
- - -
-
-
- - -
-
- - -
-
- - -
-
-
- - -
- -
- -
-
-
- - - - - -
-
-
-
- - - -
-
- - - - - -
- Miembros del Equipo - -
- @foreach($users as $user) - - @endforeach -
- @error('team') -

{{ $message }}

- @enderror -
- - -
- - {{ __('Cancelar') }} - - - - {{ __('Crear Proyecto') }} - -
-
-
-
-
-
- - @push('styles') - - - @endpush - - @push('scripts') - - - - - @endpush -
\ No newline at end of file diff --git a/resources/views/projects/show.blade.php b/resources/views/projects/show.blade.php new file mode 100644 index 0000000..73e5bd4 --- /dev/null +++ b/resources/views/projects/show.blade.php @@ -0,0 +1,29 @@ + +
+ +
+ +
+ + + + + + + + + +
+ +
\ No newline at end of file diff --git a/resources/views/projects/show.blade.php.back b/resources/views/projects/show.blade.php.back deleted file mode 100644 index a292e03..0000000 --- a/resources/views/projects/show.blade.php.back +++ /dev/null @@ -1,48 +0,0 @@ -@extends('layouts.app') - -@section('content') -
- -
- -
- - - - - - - - - @livewire('folder.create-modal', ['project' => $project, 'parentFolder' => $currentFolder ?? null]) - @livewire('document.upload-modal', ['project' => $project, 'currentFolder' => $currentFolder ?? null]) -
-@endsection \ No newline at end of file diff --git a/resources/views/users/create.blade.php b/resources/views/users/create.blade.php new file mode 100644 index 0000000..9ae403a --- /dev/null +++ b/resources/views/users/create.blade.php @@ -0,0 +1,342 @@ + +
+ +
+
+ + + +

+ {{ isset($user) ? 'Editar Usuario' : 'Nuevo Usuario' }} +

+
+

+ @isset($user) + Modifique los campos necesarios para actualizar la información del usuario. + @else + Complete todos los campos obligatorios para registrar un nuevo usuario en el sistema. + @endisset +

+
+ + + @if(session('error')) +
+ {{ session('error') }} +
+ @endif + + @if($errors->any()) +
+
    + @foreach($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + +
+ @csrf + @isset($user) + @method('PUT') + @endisset + + +
+
+
+
+
+ Datos Personales +
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+ + + +
+ + + +
+
+ + +
+
+
+
+
+ Configuración de acceso +
+
+ + +
+ + + + + + + + + + + + + + +
+ + +
+ de + + a + +
+
+ + +
+ + +
+ +
+
+ + +
+
+
+
+
+ Datos de contacto +
+
+ + +
+ + + + + + + + + + + + + + + + + +
+ + +
+ + + + +
+
+ + +
+ + + + +
+
+ + +
+ + + + + +
+
+
+ + +
+
+
+
+
+ Otros datos +
+
+ + +
+ + + + + + + + + + + + + php + + +
+ + +
+ +
+
+ + +
+ + @livewire('image-uploader', [ + 'fieldName' => 'profile_photo_path', + 'currentImage' => $user->profile_photo_path ?? null, + 'placeholder' => asset('images/default-user.png') + ]) +
+
+
+ + +
+ +
+
+
+ + + + +
\ No newline at end of file diff --git a/resources/views/users/index.blade.php b/resources/views/users/index.blade.php new file mode 100644 index 0000000..1d949ab --- /dev/null +++ b/resources/views/users/index.blade.php @@ -0,0 +1,17 @@ + +
+
+
+ + + +

Usuarios

+
+ + Nuevo + +
+ + @livewire('user-table') +
+
\ No newline at end of file diff --git a/resources/views/users/show.blade.php b/resources/views/users/show.blade.php new file mode 100644 index 0000000..79e9077 --- /dev/null +++ b/resources/views/users/show.blade.php @@ -0,0 +1,259 @@ + + +
+ +
+ +
+ + + + +
+

+ {{ $user->first_name }} {{ $user->last_name }} +

+ + +
+
+

+ {{ $user->first_name }} +

+
+ + + + @if($user->phone) + + @endif +
+
+
+ + +
+ +
+ + + + + + + @if($previousUser) + + + + + + @endif + + @if($nextUser) + + + + + + @endif +
+ + + + {{ $user->is_active ? 'Activo' : 'Inactivo' }} + +
+
+ + +
+ +
+ +
+ + +
+ +
+
+ + + @foreach(['name' => 'Nombre', 'last_name' => 'Apellido', 'email' => 'Email', 'phone' => 'Teléfono', 'created_at' => 'Fecha Registro'] as $field => $label) + + + + + @endforeach + +
{{ $label }} + {{ $user->$field ?? 'N/A' }} +
+
+ + +
+ + + + + Editar + + + {{-- Formulario de Edición --}} +
+ @csrf + @method('PUT') + +
+ + {{-- Formulario de Eliminación --}} +
+ @csrf + @method('DELETE') + +
+
+
+ + +
+
+ @foreach($permissionGroups as $group => $permissions) +
+

{{ ucfirst($group) }}

+
+ @foreach($permissions as $permission) +
+ {{ $permission->name }} + +
+ @endforeach +
+
+ @endforeach +
+
+ + +
+
+ +
+

Proyectos Asociados

+ +
+ falta implementar +
+
+ + + +
+
+
+
+
+ + +
\ No newline at end of file diff --git a/routes/web.php b/routes/web.php index c4a1959..5c17f31 100644 --- a/routes/web.php +++ b/routes/web.php @@ -34,12 +34,24 @@ require __DIR__.'/auth.php'; Route::middleware(['auth', 'verified'])->group(function () { // Dashboard Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); + + // Usuarios: + Route::get('users/{user}/edit', [UserController::class, 'edit'])->name('users.edit'); + Route::get('/users/create', [UserController::class, 'create'])->name('users.create')->middleware('can:create-users'); + // Procesar creación de usuario + Route::post('/users', [UserController::class, 'store'])->name('users.store')->middleware('can:create-users'); + Route::put('/usuarios/{user}', [UserController::class, 'update'])->name('users.update')->middleware('can:update,user'); // Opcional: si usas políticas + Route::post('/users', [UserController::class, 'store'])->name('users.store'); + Route::put('users/{user}', [UserController::class, 'update'])->name('users.update'); + Route::patch('users/{user}', [UserController::class, 'update']); + Route::delete('users/{user}', [UserController::class, 'destroy'])->name('users.destroy'); + Route::get('users/{user}', [UserController::class, 'show'])->name('users.show'); // Proyectos Route::resource('projects', ProjectController::class); Route::get('/projects/{project}', ProjectController::class)->name('projects.show'); - Route::get('/projects/{project}', ProjectController::class)->name('projects.show')->middleware('can:view,project'); // Opcional: política de acceso - Route::get('/projects/{project}', ProjectShow::class)->name('projects.show'); + //Route::get('/projects/{project}', ProjectController::class)->name('projects.show')->middleware('can:view,project'); // Opcional: política de acceso + //Route::get('/projects/{project}', ProjectShow::class)->name('projects.show'); // Documentos