Merge: integrar cambios del trabajo con chat en tiempo real

This commit is contained in:
2026-04-28 16:30:01 -06:00
parent 47d29b7f13
commit b469b884f5
10 changed files with 402 additions and 384 deletions
@@ -19,6 +19,8 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
{ {
Validator::make($input, [ Validator::make($input, [
'name' => ['required', 'string', 'max:255'], 'name' => ['required', 'string', 'max:255'],
'apellidoPaterno' => ['nullable', 'string', 'max:255'],
'apellidoMaterno' => ['nullable', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)], 'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'], 'photo' => ['nullable', 'mimes:jpg,jpeg,png', 'max:1024'],
])->validateWithBag('updateProfileInformation'); ])->validateWithBag('updateProfileInformation');
@@ -33,6 +35,8 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation
} else { } else {
$user->forceFill([ $user->forceFill([
'name' => $input['name'], 'name' => $input['name'],
'apellidoPaterno' => $input['apellidoPaterno'],
'apellidoMaterno' => $input['apellidoMaterno'],
'email' => $input['email'], 'email' => $input['email'],
])->save(); ])->save();
} }
+1
View File
@@ -24,6 +24,7 @@ class User extends Authenticatable
use Notifiable; use Notifiable;
use TwoFactorAuthenticatable; use TwoFactorAuthenticatable;
use HasRoles; use HasRoles;
use HasProfilePhoto;
protected $fillable = [ protected $fillable = [
'name', 'name',
+1
View File
@@ -386,3 +386,4 @@ transform:translateY(0);
} }
/* carrusel */ /* carrusel */
@@ -146,7 +146,13 @@
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<span class="mr-2 d-none d-lg-inline text-gray-600 small">{{ Auth::user()->name }}</span> <span class="mr-2 d-none d-lg-inline text-gray-600 small">{{ Auth::user()->name }}</span>
<<<<<<< Updated upstream
<img class="img-profile rounded-circle" src="{{ Auth::user()->profile_photo_url }}"> <img class="img-profile rounded-circle" src="{{ Auth::user()->profile_photo_url }}">
=======
{{-- <img class="img-profile rounded-circle"
src="{{ Auth::user()->profile_photo_url }}"
alt="{{ Auth::user()->name }}"> --}}
>>>>>>> Stashed changes
</a> </a>
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in"
aria-labelledby="userDropdown"> aria-labelledby="userDropdown">
@@ -4,9 +4,8 @@
<!-- Sidebar - Brand --> <!-- Sidebar - Brand -->
<a class="sidebar-brand d-flex align-items-center justify-content-center" href=""> <a class="sidebar-brand d-flex align-items-center justify-content-center" href="">
<div class="sidebar-brand-icon"> <div class="sidebar-brand-icon">
<img src="https://1000marcas.net/wp-content/uploads/2020/03/logo-Xbox.png" alt="SECUIEP Logo" style="width: 40px; height: auto;"> <img src="https://alumnos.sistemasecuiep.com/logo.png" alt="SECUIEP Logo" style="width: 100px; height: auto;">
</div> </div>
<div class="sidebar-brand-text mx-3">SECUIEP</div>
</a> </a>
<!-- Divider --> <!-- Divider -->
<hr class="sidebar-divider my-0"> <hr class="sidebar-divider my-0">
@@ -1,49 +1,44 @@
<div x-data="{
init() {
Livewire.on('confirming-logout-other-browser-sessions', () => {
$('#sesionesModal').modal('show');
});
}
}">
<x-action-section> <x-action-section>
<x-slot name="title"> <x-slot name="title"></x-slot>
{{ __('Browser Sessions') }} <x-slot name="description"></x-slot>
</x-slot>
<x-slot name="description">
{{ __('Manage and log out your active sessions on other browsers and devices.') }}
</x-slot>
<x-slot name="content"> <x-slot name="content">
<div class="max-w-xl text-sm text-gray-600">
{{ __('If necessary, you may log out of all of your other browser sessions across all of your devices. Some of your recent sessions are listed below; however, this list may not be exhaustive. If you feel your account has been compromised, you should also update your password.') }} <p class="text-muted small mb-4">
</div> Si es necesario, puedes cerrar sesión en todos los demás dispositivos. Si crees que tu cuenta fue comprometida, también deberías actualizar tu contraseña.
</p>
@if (count($this->sessions) > 0) @if (count($this->sessions) > 0)
<div class="mt-5 space-y-6"> <div class="mb-4">
<!-- Other Browser Sessions -->
@foreach ($this->sessions as $session) @foreach ($this->sessions as $session)
<div class="flex items-center"> <div class="d-flex align-items-center mb-3">
<div> <div class="mr-3 text-secondary">
@if ($session->agent->isDesktop()) @if ($session->agent->isDesktop())
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-8 text-gray-500"> <i class="fas fa-desktop fa-2x"></i>
<path stroke-linecap="round" stroke-linejoin="round" d="M9 17.25v1.007a3 3 0 01-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0115 18.257V17.25m6-12V15a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 15V5.25m18 0A2.25 2.25 0 0018.75 3H5.25A2.25 2.25 0 003 5.25m18 0V12a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 12V5.25" />
</svg>
@else @else
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-8 text-gray-500"> <i class="fas fa-mobile-alt fa-2x"></i>
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 1.5H8.25A2.25 2.25 0 006 3.75v16.5a2.25 2.25 0 002.25 2.25h7.5A2.25 2.25 0 0018 20.25V3.75a2.25 2.25 0 00-2.25-2.25H13.5m-3 0V3h3V1.5m-3 0h3m-3 18.75h3" />
</svg>
@endif @endif
</div> </div>
<div class="ms-3">
<div class="text-sm text-gray-600">
{{ $session->agent->platform() ? $session->agent->platform() : __('Unknown') }} - {{ $session->agent->browser() ? $session->agent->browser() : __('Unknown') }}
</div>
<div> <div>
<div class="text-xs text-gray-500"> <div class="font-weight-bold small">
{{ $session->ip_address }}, {{ $session->agent->platform() ?: 'Desconocido' }}
@if ($session->is_current_device) {{ $session->agent->browser() ?: 'Desconocido' }}
<span class="text-green-500 font-semibold">{{ __('This device') }}</span>
@else
{{ __('Last active') }} {{ $session->last_active }}
@endif
</div> </div>
<div class="text-muted small">
{{ $session->ip_address }},
@if ($session->is_current_device)
<span class="text-success font-weight-bold">Este dispositivo</span>
@else
Última actividad: {{ $session->last_active }}
@endif
</div> </div>
</div> </div>
</div> </div>
@@ -51,48 +46,54 @@
</div> </div>
@endif @endif
<div class="flex items-center mt-5"> <button class="btn btn-danger"
<x-button wire:click="confirmLogout" wire:loading.attr="disabled"> wire:click="confirmLogout"
{{ __('Log Out Other Browser Sessions') }}
</x-button>
<x-action-message class="ms-3" on="loggedOut">
{{ __('Done.') }}
</x-action-message>
</div>
<!-- Log Out Other Devices Confirmation Modal -->
<x-dialog-modal wire:model.live="confirmingLogout">
<x-slot name="title">
{{ __('Log Out Other Browser Sessions') }}
</x-slot>
<x-slot name="content">
{{ __('Please enter your password to confirm you would like to log out of your other browser sessions across all of your devices.') }}
<div class="mt-4" x-data="{}" x-on:confirming-logout-other-browser-sessions.window="setTimeout(() => $refs.password.focus(), 250)">
<x-input type="password" class="mt-1 block w-3/4"
autocomplete="current-password"
placeholder="{{ __('Password') }}"
x-ref="password"
wire:model="password"
wire:keydown.enter="logoutOtherBrowserSessions" />
<x-input-error for="password" class="mt-2" />
</div>
</x-slot>
<x-slot name="footer">
<x-secondary-button wire:click="$toggle('confirmingLogout')" wire:loading.attr="disabled">
{{ __('Cancel') }}
</x-secondary-button>
<x-button class="ms-3"
wire:click="logoutOtherBrowserSessions"
wire:loading.attr="disabled"> wire:loading.attr="disabled">
{{ __('Log Out Other Browser Sessions') }} <span wire:loading class="spinner-border spinner-border-sm mr-1" role="status"></span>
</x-button> <i class="fas fa-sign-out-alt mr-1"></i>
</x-slot> Cerrar otras sesiones
</x-dialog-modal> </button>
</x-slot> </x-slot>
</x-action-section> </x-action-section>
<!-- Modal Bootstrap -->
<div class="modal fade" id="sesionesModal" tabindex="-1" role="dialog"
wire:ignore>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Cerrar otras sesiones</h5>
<button type="button" class="close" data-dismiss="modal">
<span>&times;</span>
</button>
</div>
<div class="modal-body">
<p class="text-muted small mb-3">
Ingresa tu contraseña para confirmar que deseas cerrar sesión en los demás dispositivos.
</p>
<input type="password"
class="form-control"
placeholder="Contraseña"
autocomplete="current-password"
wire:model="password"
wire:keydown.enter="logoutOtherBrowserSessions" />
<x-input-error for="password" class="mt-2" />
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-dismiss="modal">
Cancelar
</button>
<button type="button" class="btn btn-danger"
wire:click="logoutOtherBrowserSessions"
wire:loading.attr="disabled">
<span wire:loading class="spinner-border spinner-border-sm mr-1" role="status"></span>
Confirmar
</button>
</div>
</div>
</div>
</div>
</div>
+156 -190
View File
@@ -1,247 +1,213 @@
@extends('layouts.landing') @extends('layouts.landing')
@section('title',"Editar") @section('title', 'Mi perfil')
@section('content') @section('content')
<style>
.custom-file-upload {
display: flex;
align-items: center;
gap: 10px;
}
.custom-file-upload span {
font-size: 13px;
}
</style>
@hasrole('Alumno')
<div class="container-fluid"> <div class="container-fluid">
{{-- Card editar grupo --}}
<div class="card shadow mb-4"> <div class="card shadow mb-4">
<div class="card-header py-3"> <div class="card-header py-3">
<div class="d-sm-flex align-items-center justify-content-between"> <div class="d-sm-flex align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">Editar grupo</h6> <h6 class="m-0 font-weight-bold text-primary">Expediente</h6>
<a href="{{route('grupo.index')}}" class="d-sm-inline-block btn btn-sm btn-primary shadow-sm">
<i class="fas fa-download fa-sm text-white-50"></i> Regresar @hasrole('Alumno')
</a> <button onclick="Livewire.dispatch('documentos')"
class="btn btn-sm btn-success shadow-sm">
<i class="fas fa-file fa-sm text-white-50"></i> Expediente
</button>
@endrole
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
@if (session('info'))
<div class="alert alert-success">
<strong>{{session('info')}}</strong>
</div>
@endif
<form action="{{route('grupo.update',$grupo->id)}}" method="POST">
@method('PUT')
@csrf
<div class="mb-3">
<label>Seleccionar Carrera</label>
<select class="form-control" name="plan_id">
<option value="">Seleccionar plan</option>
@foreach ($plans as $nivel => $grupos)
<optgroup label="{{ $nivel }}">
@foreach ($grupos as $plan)
<option value="{{ $plan->id }}" {{ $plan->id == $grupo->plan_id ? 'selected' : '' }}>
{{ $plan->carreraRel->name }}
({{ $plan->name }}-{{ $plan->rvoe }})
</option>
@endforeach
</optgroup>
@endforeach
</select>
</div>
<div class="mb-3">
<label>Seleccionar ciclo</label>
<select class="form-control" name="ciclo">
<option value="0">Seleccionar ciclo</option>
@for ($i=1 ; $i <= $ciclos->duracion ; $i++)
<option value="{{ $i }}" @selected($i == $grupo->ciclo)>{{$i}}</option>
@endfor
</select>
</div>
<div class="mb-3"> <livewire:documentos/>
<label>Seleccionar turno</label>
<select class="form-control" name="turno">
<option value="0">Seleccionar turno</option>
@foreach ($turnos as $turno)
<option value="{{ $turno->id }}" {{ $turno->id == $grupo->turno ? 'selected' : '' }}>
{{$turno->name}}
</option>
@endforeach
</select>
</div>
<div class="mb-3">
<label>Seleccionar plantel</label>
<select class="form-control" name="plantel">
<option value="0">Seleccionar plantel</option>
@foreach ($planteles as $plantel)
<option value="{{ $plantel->id }}" {{ $plantel->id == $grupo->plantel ? 'selected' : '' }}>
{{$plantel->name}}
</option>
@endforeach
</select>
</div>
<div class="mb-3">
<label>Seleccionar status</label>
<select class="form-control" name="status">
<option value="1" {{ 1 == $grupo->status ? 'selected' : '' }}>Activo</option>
<option value="0" {{ 0 == $grupo->status ? 'selected' : '' }}>Inactivo</option>
</select>
</div>
<input class="btn btn-primary" type="submit" value="Guardar">
</form>
</div> </div>
</div> </div>
{{-- Card Materias y Docentes --}} </div>
@endrole
<div class="container-fluid">
<div class="card shadow mb-4"> <div class="card shadow mb-4">
<div class="card-header py-3"> <div class="card-header py-3">
<div class="d-sm-flex align-items-center justify-content-between"> <div class="d-sm-flex align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">Materias</h6> <h6 class="m-0 font-weight-bold text-primary">Mis Datos</h6>
@can('grupo.create')
<button onclick="$('#modalDocente').modal('show')"
class="d-sm-inline-block btn btn-sm btn-success shadow-sm">
<i class="fas fa-plus fa-sm text-white-50"></i> Asignar materia a docente
</button>
@endcan
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
@if (session('infogrupo'))
<div class="alert alert-success">
<strong>{{session('infogrupo')}}</strong> @livewire('profile.update-profile-information-form')
</div> </div>
@endif </div>
</div>
<div class="container-fluid"> <div class="container-fluid">
<table id="tcont" class="table table-striped table-bordered nowrap table-hover" style="width:100%;">
<thead>
<tr>
<th>Id</th>
<th>Ciclo</th>
<th>Nombre de la materia</th>
<th>Docente</th>
<th>Docente asignado</th>
</tr>
</thead>
<tbody>
@forelse ($grupo->plan->carreraRel->carreraMaterial as $material)
@php
$docenteAsignado = $grupo->docentes->firstWhere('pivot.materia_id', $material->id);
$usr = $docenteAsignado?->docentes->first();
@endphp
<tr>
<td>{{ $material->id }}</td>
<td>{{ $material->ciclo }}</td>
<td>{{ $material->name }}</td>
<td>{{ $usr?->name }} {{ $usr?->apellidoPaterno ?? 'Sin docente' }}</td>
<td>
@if($docenteAsignado)
@can('grupo.destroy')
<form class="delete-form"
action="{{route('grupo.destroy',$grupo->id)}}"
method="POST">
@csrf
@method('DELETE')
<input class="d-none" name="docente" value="{{$docenteAsignado->id}}">
<button type="submit" style="border:none; background:none;">
<i class="fa-solid fa-circle-xmark fa-xl" style="color:#e66565;"></i>
</button>
</form>
@endcan
@endif
</td>
</tr>
@empty
<tr><td colspan="5">Sin materias en este plan.</td></tr>
@endforelse
</tbody>
</table>
</div>
<livewire:grupo-docente :grupo="$grupo"/>
</div>
</div>
{{-- Card Alumnos --}}
<div class="card shadow mb-4"> <div class="card shadow mb-4">
<div class="card-header py-3"> <div class="card-header py-3">
<div class="d-sm-flex align-items-center justify-content-between"> <div class="d-sm-flex align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">Alumnos</h6> <h6 class="m-0 font-weight-bold text-primary">Contraseña</h6>
@can('grupo.create')
<button onclick="Livewire.dispatch('create-grupo')"
class="d-sm-inline-block btn btn-sm btn-success shadow-sm">
<i class="fas fa-plus fa-sm text-white-50"></i> Agregar alumno
</button>
@endcan
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="container-fluid"> @livewire('profile.update-password-form')
<table id="tcont2" class="table table-striped table-bordered nowrap table-hover" style="width:100%;">
<thead> </div>
<tr>
<th>Id</th>
<th>Nombre</th>
<th>Opciones</th>
</tr>
</thead>
<tbody>
@forelse ($grupo->alumnos as $alumno)
<tr>
<td>{{$alumno->id}}</td>
<td>
{{$alumno->usuario->first()?->name}}
{{$alumno->usuario->first()?->apellidoPaterno}}
{{$alumno->usuario->first()?->apellidoMaterno}}
</td>
<td>
@can('grupo.destroy')
<form class="delete-form"
action="{{route('grupo.destroy',$grupo->id)}}"
method="POST">
@csrf
@method('DELETE')
<input class="d-none" name="alumno" value="{{$alumno->id}}">
<button type="submit" style="border:none; background:none;">
<i class="fa-solid fa-circle-xmark fa-xl" style="color:#e66565;"></i>
</button>
</form>
@endcan
</td>
</tr>
@empty
<tr><td colspan="3">Sin alumnos.</td></tr>
@endforelse
</tbody>
</table>
</div> </div>
<livewire:grupo-alumno :grupo="$grupo"/> </div>
<div class="container-fluid">
<div class="card shadow mb-4">
<div class="card-header py-3">
<div class="d-sm-flex align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">Sesiones activas</h6>
</div> </div>
</div> </div>
<div class="card-body">
@livewire('profile.logout-other-browser-sessions-form')
</div>
</div>
</div>
<div class="modal fade" id="visorModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Vista previa</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<iframe id="visorFrame" width="100%" height="600px" style="border:none;"></iframe>
</div>
</div>
</div>
</div> </div>
@endsection @endsection
@section('scripts') @section('scripts')
@include('layouts._partials.tablaScript')
@include('layouts._partials.delete')
<script> <script>
document.addEventListener('livewire:init', () => { document.addEventListener('livewire:init', () => {
// Modal agregar alumno
Livewire.on('open-grupo-modal', () => { Livewire.on('open-documentos-modal', () => {
$('#exampleModal').modal('show'); $('#exampleModal').modal('show');
}); });
Livewire.on('close-modal', () => {
$('#exampleModal').modal('hide'); Livewire.on('saved', (event) => {
const msgs = {
profile: { title: '¡Guardado!', text: 'Tu perfil ha sido actualizado.' },
password: { title: '¡Contraseña actualizada!', text: 'Tu contraseña ha sido cambiada correctamente.' },
};
const msg = msgs[event[0]?.component] || { title: '¡Guardado!', text: 'Los cambios han sido guardados.' };
Swal.fire({
icon: 'success',
title: msg.title,
text: msg.text,
timer: 2500,
showConfirmButton: false,
});
}); });
// Modal agregar docente Livewire.on('loggedOut', () => {
Livewire.on('open-modal-docente', () => { $('#sesionesModal').modal('hide');
$('#modalDocente').modal('show'); Swal.fire({
icon: 'success',
title: '¡Listo!',
text: 'Se cerraron las otras sesiones correctamente.',
timer: 2500,
showConfirmButton: false,
}); });
Livewire.on('close-modal-docente', () => { });
$('#modalDocente').modal('hide');
Livewire.on('photo-updated', () => {
Swal.fire({
icon: 'success',
title: '¡Foto actualizada!',
text: 'Tu foto de perfil ha sido actualizada.',
timer: 2500,
showConfirmButton: false,
});
});
});
function verDocumento(url) {
document.getElementById('visorFrame').src = url + '?t=' + new Date().getTime();
var modal = new bootstrap.Modal(document.getElementById('visorModal'));
modal.show();
}
// 🔥 limpiar iframe al cerrar
document.getElementById('visorModal').addEventListener('hidden.bs.modal', function () {
document.getElementById('visorFrame').src = '';
});
</script>
<script>
function updateFileName(input, targetId) {
const fileName = input.files[0]?.name || 'Ningún archivo seleccionado';
document.getElementById(targetId).textContent = fileName;
}
</script>
<script>
Livewire.on('saved', () => {
Swal.fire({
icon: 'success',
title: '¡Guardado!',
text: 'Tu perfil ha sido actualizado correctamente.',
timer: 2500,
showConfirmButton: false,
});
});
Livewire.on('photo-updated', () => {
Swal.fire({
icon: 'success',
title: '¡Foto actualizada!',
text: 'Tu foto de perfil ha sido actualizada.',
timer: 2500,
showConfirmButton: false,
}); });
}); });
</script> </script>
@@ -1,39 +1,37 @@
<x-form-section submit="updatePassword"> <x-form-section submit="updatePassword">
<x-slot name="title"> <x-slot name="title"></x-slot>
{{ __('Update Password') }} <x-slot name="description"></x-slot>
</x-slot>
<x-slot name="description">
{{ __('Ensure your account is using a long, random password to stay secure.') }}
</x-slot>
<x-slot name="form"> <x-slot name="form">
<div class="col-span-6 sm:col-span-4"> <div class="col-12 col-md-6 mb-3">
<x-label for="current_password" value="{{ __('Current Password') }}" /> <label for="current_password" class="form-label fw-bold">Contraseña actual</label>
<x-input id="current_password" type="password" class="mt-1 block w-full" wire:model="state.current_password" autocomplete="current-password" /> <input id="current_password" type="password" class="form-control"
<x-input-error for="current_password" class="mt-2" /> wire:model="state.current_password" autocomplete="current-password" />
<x-input-error for="current_password" class="mt-1" />
</div> </div>
<div class="col-span-6 sm:col-span-4"> <div class="col-12 col-md-6 mb-3">
<x-label for="password" value="{{ __('New Password') }}" /> <label for="password" class="form-label fw-bold">Nueva contraseña</label>
<x-input id="password" type="password" class="mt-1 block w-full" wire:model="state.password" autocomplete="new-password" /> <input id="password" type="password" class="form-control"
<x-input-error for="password" class="mt-2" /> wire:model="state.password" autocomplete="new-password" />
<x-input-error for="password" class="mt-1" />
</div> </div>
<div class="col-span-6 sm:col-span-4"> <div class="col-12 col-md-6 mb-3">
<x-label for="password_confirmation" value="{{ __('Confirm Password') }}" /> <label for="password_confirmation" class="form-label fw-bold">Confirmar contraseña</label>
<x-input id="password_confirmation" type="password" class="mt-1 block w-full" wire:model="state.password_confirmation" autocomplete="new-password" /> <input id="password_confirmation" type="password" class="form-control"
<x-input-error for="password_confirmation" class="mt-2" /> wire:model="state.password_confirmation" autocomplete="new-password" />
<x-input-error for="password_confirmation" class="mt-1" />
</div> </div>
</x-slot> </x-slot>
<x-slot name="actions"> <x-slot name="actions">
<x-action-message class="me-3" on="saved"> <button type="submit" class="btn btn-primary"
{{ __('Saved.') }} wire:loading.attr="disabled">
</x-action-message> <span wire:loading
class="spinner-border spinner-border-sm mr-1"
<x-button> role="status" aria-hidden="true"></span>
{{ __('Save') }} Guardar
</x-button> </button>
</x-slot> </x-slot>
</x-form-section> </x-form-section>
@@ -1,95 +1,137 @@
<x-form-section submit="updateProfileInformation"> <x-form-section submit="updateProfileInformation">
<x-slot name="title"> <x-slot name="title">
{{ __('Profile Information') }} {{ __('Información de perfil') }}
</x-slot> </x-slot>
<x-slot name="description"> <x-slot name="description">
{{ __('Update your account\'s profile information and email address.') }} {{ __('Actualice la información de su cuenta y dirección de correo electrónico.') }}
</x-slot> </x-slot>
<x-slot name="form"> <x-slot name="form">
<!-- Profile Photo --> <div class="col-12">
<div class="row">
<!-- Columna derecha: Nombre y correo -->
<div class="col-12 col-md-8">
<div class="mb-3">
<label for="name" class="form-label fw-bold">Nombre</label>
<input id="name" type="text" class="form-control"
wire:model="state.name" required autocomplete="name" />
<x-input-error for="name" class="mt-1" />
</div>
<div class=" mb-3">
<label for="apellidoPaterno" class="form-label fw-bold">Apellido paterno</label>
<input id="apellidoPaterno" type="text" class="form-control"
wire:model="state.apellidoPaterno" autocomplete="off" />
<x-input-error for="apellidoPaterno" class="mt-1" />
</div>
<div class="mb-3">
<label for="apellidoMaterno" class="form-label fw-bold">Apellido materno</label>
<input id="apellidoMaterno" type="text" class="form-control"
wire:model="state.apellidoMaterno" autocomplete="off" />
<x-input-error for="apellidoMaterno" class="mt-1" />
</div>
<div class="mb-3">
<label for="email" class="form-label fw-bold">Correo electrónico</label>
<input id="email" type="email" class="form-control"
wire:model="state.email" required autocomplete="username" />
<x-input-error for="email" class="mt-1" />
@if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::emailVerification()) && ! $this->user->hasVerifiedEmail())
<p class="text-muted small mt-2">
Tu dirección de correo no está verificada.
<button type="button" class="btn btn-link btn-sm p-0"
wire:click.prevent="sendEmailVerification">
Reenviar verificación
</button>
</p>
@if ($this->verificationLinkSent)
<p class="text-success small mt-1">
<i class="fas fa-check mr-1"></i>
Se envió un nuevo enlace de verificación a tu correo.
</p>
@endif
@endif
</div>
</div>
<!-- Columna izquierda: Foto -->
@if (Laravel\Jetstream\Jetstream::managesProfilePhotos()) @if (Laravel\Jetstream\Jetstream::managesProfilePhotos())
<div x-data="{photoName: null, photoPreview: null}" class="col-span-6 sm:col-span-4"> <div x-data="{photoName: null, photoPreview: null}" class="col-12 col-md-4 d-flex flex-column align-items-center mb-4">
<!-- Profile Photo File Input -->
<input type="file" id="photo" class="hidden" <input type="file" id="photo" class="d-none"
wire:model.live="photo" wire:model.live="photo"
x-ref="photo" x-ref="photo"
x-on:change=" x-on:change="
photoName = $refs.photo.files[0].name; photoName = $refs.photo.files[0].name;
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { reader.onload = (e) => { photoPreview = e.target.result; };
photoPreview = e.target.result;
};
reader.readAsDataURL($refs.photo.files[0]); reader.readAsDataURL($refs.photo.files[0]);
" /> " />
<x-label for="photo" value="{{ __('Photo') }}" /> <label class="form-label fw-bold mb-3">Foto de perfil</label>
<!-- Current Profile Photo --> <!-- Foto actual -->
<div class="mt-2" x-show="! photoPreview"> <div x-show="! photoPreview" class="mb-3">
<img src="{{ $this->user->profile_photo_url }}" alt="{{ $this->user->name }}" class="rounded-full size-20 object-cover"> <img src="{{ $this->user->profile_photo_url }}"
alt="{{ $this->user->name }}"
class="rounded-circle border shadow-sm"
style="width: 120px; height: 120px; object-fit: cover;">
</div> </div>
<!-- New Profile Photo Preview --> <!-- Preview nueva foto -->
<div class="mt-2" x-show="photoPreview" style="display: none;"> <div x-show="photoPreview" style="display: none;" class="mb-3">
<span class="block rounded-full size-20 bg-cover bg-no-repeat bg-center" <span class="rounded-circle d-block border shadow-sm"
x-bind:style="'background-image: url(\'' + photoPreview + '\');'"> x-bind:style="'background-image: url(\'' + photoPreview + '\'); width:120px; height:120px; border-radius:50%; background-size:cover; background-position:center;'">
</span> </span>
</div> </div>
<x-secondary-button class="mt-2 me-2" type="button" x-on:click.prevent="$refs.photo.click()"> <!-- Botones debajo de la foto -->
{{ __('Select A New Photo') }} <div class="d-flex flex-column align-items-center gap-2" style="width: 160px;">
</x-secondary-button> <button type="button" class="btn btn-sm btn-outline-secondary w-100"
x-on:click.prevent="$refs.photo.click()">
<i class="fas fa-camera mr-1"></i> Cambiar foto
</button>
@if ($this->user->profile_photo_path) @if ($this->user->profile_photo_path)
<x-secondary-button type="button" class="mt-2" wire:click="deleteProfilePhoto"> <button type="button" class="btn btn-sm btn-outline-danger w-100"
{{ __('Remove Photo') }} wire:click="deleteProfilePhoto">
</x-secondary-button> <i class="fas fa-trash mr-1"></i> Eliminar foto
</button>
@endif @endif
</div>
<x-input-error for="photo" class="mt-2" /> <x-input-error for="photo" class="mt-2" />
</div> </div>
@endif @endif
<!-- Name -->
<div class="col-span-6 sm:col-span-4">
<x-label for="name" value="{{ __('Name') }}" />
<x-input id="name" type="text" class="mt-1 block w-full" wire:model="state.name" required autocomplete="name" />
<x-input-error for="name" class="mt-2" />
</div> </div>
<!-- Email -->
<div class="col-span-6 sm:col-span-4">
<x-label for="email" value="{{ __('Email') }}" />
<x-input id="email" type="email" class="mt-1 block w-full" wire:model="state.email" required autocomplete="username" />
<x-input-error for="email" class="mt-2" />
@if (Laravel\Fortify\Features::enabled(Laravel\Fortify\Features::emailVerification()) && ! $this->user->hasVerifiedEmail())
<p class="text-sm mt-2">
{{ __('Your email address is unverified.') }}
<button type="button" class="underline text-sm text-gray-600 hover:text-gray-900 rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" wire:click.prevent="sendEmailVerification">
{{ __('Click here to re-send the verification email.') }}
</button>
</p>
@if ($this->verificationLinkSent)
<p class="mt-2 font-medium text-sm text-green-600">
{{ __('A new verification link has been sent to your email address.') }}
</p>
@endif
@endif
</div> </div>
</x-slot> </x-slot>
<x-slot name="actions"> <x-slot name="actions">
<x-action-message class="me-3" on="saved">
{{ __('Saved.') }}
</x-action-message>
<x-button wire:loading.attr="disabled" wire:target="photo">
{{ __('Save') }} <button type="submit" class="btn btn-primary"
</x-button> wire:loading.attr="disabled" wire:target="photo">
<span wire:loading wire:target="photo"
class="spinner-border spinner-border-sm mr-1"
role="status" aria-hidden="true"></span>
Guardar
</button>
</x-slot> </x-slot>
</x-form-section> </x-form-section>
+1 -1
View File
@@ -37,7 +37,7 @@
@forelse ($users as $user) @forelse ($users as $user)
<tr style="width:100%"> <tr style="width:100%">
<td>{{$user->id}}</td> <td>{{$user->id}}</td>
<td><a href="{{route('user.show',$user->id)}}">{{$user->name}}</a></td> <td><a href="{{route('user.show',$user->id)}}">{{$user->name}} {{$user->apellidoPaterno}} {{$user->apellidoMaterno}}</a></td>
<td> <td>
@forelse($user->plantelUsuarios as $plantel) @forelse($user->plantelUsuarios as $plantel)