archivo de configuracion de nginx agregado para el puerto 9000
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
@extends('layouts.landing')
|
||||
|
||||
@section('title', 'Nuevo Anuncio')
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid">
|
||||
<div class="card shadow mb-4" style="max-width:760px; margin:auto">
|
||||
<div class="card-header py-3 d-flex align-items-center justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-plus-circle mr-1"></i> Nuevo Anuncio
|
||||
</h6>
|
||||
<a href="{{ route('anuncio.index') }}" class="btn btn-sm btn-secondary">
|
||||
<i class="fas fa-arrow-left mr-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
@if($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
@foreach($errors->all() as $e)
|
||||
<li>{{ $e }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('anuncio.store') }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
|
||||
{{-- Título --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Título <span class="text-danger">*</span></label>
|
||||
<input type="text" name="title" class="form-control @error('title') is-invalid @enderror"
|
||||
value="{{ old('title') }}" maxlength="200" placeholder="Título del anuncio" required>
|
||||
@error('title')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
{{-- Cuerpo --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Descripción</label>
|
||||
<textarea name="body" class="form-control @error('body') is-invalid @enderror"
|
||||
rows="4" maxlength="3000" placeholder="Texto del anuncio (opcional)">{{ old('body') }}</textarea>
|
||||
@error('body')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
{{-- Tipo --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Tipo <span class="text-danger">*</span></label>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
@foreach($types as $key => $cfg)
|
||||
<div class="form-check form-check-inline mr-3">
|
||||
<input class="form-check-input" type="radio" name="type" id="type_{{ $key }}"
|
||||
value="{{ $key }}" {{ old('type', 'info') === $key ? 'checked' : '' }} required>
|
||||
<label class="form-check-label" for="type_{{ $key }}">
|
||||
<span class="badge badge-{{ $cfg['color'] }}">
|
||||
<i class="fas {{ $cfg['icon'] }} mr-1"></i>{{ $cfg['label'] }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@error('type')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
{{-- Audiencia --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Audiencia <span class="text-danger">*</span></label>
|
||||
<div class="border rounded p-3 bg-light">
|
||||
<div class="row">
|
||||
@foreach($roles as $key => $label)
|
||||
<div class="col-6 col-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input audience-check" type="checkbox"
|
||||
name="audience[]" id="aud_{{ $key }}" value="{{ $key }}"
|
||||
{{ in_array($key, old('audience', [])) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="aud_{{ $key }}">{{ $label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@error('audience')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
{{-- Imagen --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Imagen</label>
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input @error('image') is-invalid @enderror"
|
||||
id="imageInput" name="image" accept="image/*">
|
||||
<label class="custom-file-label" for="imageInput">Seleccionar imagen (máx. 5 MB)</label>
|
||||
</div>
|
||||
@error('image')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
|
||||
<div id="imagePreview" class="mt-2 d-none">
|
||||
<img id="previewImg" src="" alt="Vista previa" class="img-fluid rounded" style="max-height:200px">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Vencimiento --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Fecha de vencimiento</label>
|
||||
<input type="datetime-local" name="expires_at"
|
||||
class="form-control @error('expires_at') is-invalid @enderror"
|
||||
value="{{ old('expires_at') }}">
|
||||
<small class="text-muted">Dejar vacío para que no tenga vencimiento.</small>
|
||||
@error('expires_at')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
{{-- Activo --}}
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="activeSwitch"
|
||||
name="active" value="1" {{ old('active', '1') ? 'checked' : '' }}>
|
||||
<label class="custom-control-label font-weight-bold" for="activeSwitch">Publicar inmediatamente</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="{{ route('anuncio.index') }}" class="btn btn-secondary mr-2">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save mr-1"></i> Guardar anuncio
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
// Custom file label
|
||||
document.getElementById('imageInput').addEventListener('change', function () {
|
||||
const file = this.files[0];
|
||||
this.nextElementSibling.textContent = file ? file.name : 'Seleccionar imagen (máx. 5 MB)';
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
document.getElementById('previewImg').src = e.target.result;
|
||||
document.getElementById('imagePreview').classList.remove('d-none');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
document.getElementById('imagePreview').classList.add('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
// "Todos" exclusivo con los demás
|
||||
document.querySelectorAll('.audience-check').forEach(function (cb) {
|
||||
cb.addEventListener('change', function () {
|
||||
if (this.value === 'all' && this.checked) {
|
||||
document.querySelectorAll('.audience-check').forEach(c => { if (c !== this) c.checked = false; });
|
||||
} else if (this.value !== 'all' && this.checked) {
|
||||
const allCb = document.querySelector('.audience-check[value="all"]');
|
||||
if (allCb) allCb.checked = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,173 @@
|
||||
@extends('layouts.landing')
|
||||
|
||||
@section('title', 'Editar Anuncio')
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid">
|
||||
<div class="card shadow mb-4" style="max-width:760px; margin:auto">
|
||||
<div class="card-header py-3 d-flex align-items-center justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-edit mr-1"></i> Editar Anuncio
|
||||
</h6>
|
||||
<a href="{{ route('anuncio.index') }}" class="btn btn-sm btn-secondary">
|
||||
<i class="fas fa-arrow-left mr-1"></i> Volver
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
@if($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
@foreach($errors->all() as $e)
|
||||
<li>{{ $e }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form action="{{ route('anuncio.update', $anuncio) }}" method="POST" enctype="multipart/form-data">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
{{-- Título --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Título <span class="text-danger">*</span></label>
|
||||
<input type="text" name="title" class="form-control @error('title') is-invalid @enderror"
|
||||
value="{{ old('title', $anuncio->title) }}" maxlength="200" required>
|
||||
@error('title')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
{{-- Cuerpo --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Descripción</label>
|
||||
<textarea name="body" class="form-control @error('body') is-invalid @enderror"
|
||||
rows="4" maxlength="3000">{{ old('body', $anuncio->body) }}</textarea>
|
||||
@error('body')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
{{-- Tipo --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Tipo <span class="text-danger">*</span></label>
|
||||
<div class="d-flex flex-wrap">
|
||||
@foreach($types as $key => $cfg)
|
||||
<div class="form-check form-check-inline mr-3">
|
||||
<input class="form-check-input" type="radio" name="type" id="type_{{ $key }}"
|
||||
value="{{ $key }}" {{ old('type', $anuncio->type) === $key ? 'checked' : '' }} required>
|
||||
<label class="form-check-label" for="type_{{ $key }}">
|
||||
<span class="badge badge-{{ $cfg['color'] }}">
|
||||
<i class="fas {{ $cfg['icon'] }} mr-1"></i>{{ $cfg['label'] }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@error('type')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
{{-- Audiencia --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Audiencia <span class="text-danger">*</span></label>
|
||||
<div class="border rounded p-3 bg-light">
|
||||
<div class="row">
|
||||
@foreach($roles as $key => $label)
|
||||
<div class="col-6 col-md-4">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input audience-check" type="checkbox"
|
||||
name="audience[]" id="aud_{{ $key }}" value="{{ $key }}"
|
||||
{{ in_array($key, old('audience', $anuncio->audience ?? [])) ? 'checked' : '' }}>
|
||||
<label class="form-check-label" for="aud_{{ $key }}">{{ $label }}</label>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@error('audience')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
{{-- Imagen actual + nueva --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Imagen</label>
|
||||
@if($anuncio->image_path)
|
||||
<div class="mb-2">
|
||||
<img src="{{ Storage::url($anuncio->image_path) }}" alt="Imagen actual"
|
||||
class="img-fluid rounded" style="max-height:150px">
|
||||
<div class="form-check mt-1">
|
||||
<input class="form-check-input" type="checkbox" name="remove_image" id="removeImage" value="1">
|
||||
<label class="form-check-label text-danger" for="removeImage">Eliminar imagen actual</label>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input @error('image') is-invalid @enderror"
|
||||
id="imageInput" name="image" accept="image/*">
|
||||
<label class="custom-file-label" for="imageInput">
|
||||
{{ $anuncio->image_path ? 'Reemplazar imagen (máx. 5 MB)' : 'Seleccionar imagen (máx. 5 MB)' }}
|
||||
</label>
|
||||
</div>
|
||||
@error('image')<div class="text-danger small mt-1">{{ $message }}</div>@enderror
|
||||
<div id="imagePreview" class="mt-2 d-none">
|
||||
<img id="previewImg" src="" alt="Vista previa" class="img-fluid rounded" style="max-height:200px">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Vencimiento --}}
|
||||
<div class="form-group">
|
||||
<label class="font-weight-bold">Fecha de vencimiento</label>
|
||||
<input type="datetime-local" name="expires_at"
|
||||
class="form-control @error('expires_at') is-invalid @enderror"
|
||||
value="{{ old('expires_at', $anuncio->expires_at ? $anuncio->expires_at->format('Y-m-d\TH:i') : '') }}">
|
||||
<small class="text-muted">Dejar vacío para que no tenga vencimiento.</small>
|
||||
@error('expires_at')<div class="invalid-feedback">{{ $message }}</div>@enderror
|
||||
</div>
|
||||
|
||||
{{-- Activo --}}
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="activeSwitch"
|
||||
name="active" value="1" {{ old('active', $anuncio->active) ? 'checked' : '' }}>
|
||||
<label class="custom-control-label font-weight-bold" for="activeSwitch">Anuncio activo</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="{{ route('anuncio.index') }}" class="btn btn-secondary mr-2">Cancelar</a>
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save mr-1"></i> Actualizar anuncio
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
<script>
|
||||
document.getElementById('imageInput').addEventListener('change', function () {
|
||||
const file = this.files[0];
|
||||
this.nextElementSibling.textContent = file ? file.name : 'Seleccionar imagen (máx. 5 MB)';
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = e => {
|
||||
document.getElementById('previewImg').src = e.target.result;
|
||||
document.getElementById('imagePreview').classList.remove('d-none');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} else {
|
||||
document.getElementById('imagePreview').classList.add('d-none');
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.audience-check').forEach(function (cb) {
|
||||
cb.addEventListener('change', function () {
|
||||
if (this.value === 'all' && this.checked) {
|
||||
document.querySelectorAll('.audience-check').forEach(c => { if (c !== this) c.checked = false; });
|
||||
} else if (this.value !== 'all' && this.checked) {
|
||||
const allCb = document.querySelector('.audience-check[value="all"]');
|
||||
if (allCb) allCb.checked = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,115 @@
|
||||
@extends('layouts.landing')
|
||||
|
||||
@section('title', 'Anuncios')
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3 d-flex align-items-center justify-content-between">
|
||||
<h6 class="m-0 font-weight-bold text-primary">
|
||||
<i class="fas fa-bullhorn mr-1"></i> Anuncios
|
||||
</h6>
|
||||
<a href="{{ route('anuncio.create') }}" class="btn btn-sm btn-primary shadow-sm">
|
||||
<i class="fas fa-plus fa-sm text-white-50 mr-1"></i> Nuevo anuncio
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
@if(session('success'))
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
{{ session('success') }}
|
||||
<button type="button" class="close" data-dismiss="alert"><span>×</span></button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover" style="width:100%">
|
||||
<thead class="thead-light">
|
||||
<tr>
|
||||
<th style="width:40px">#</th>
|
||||
<th>Título</th>
|
||||
<th style="width:130px">Tipo</th>
|
||||
<th style="width:160px">Audiencia</th>
|
||||
<th style="width:130px">Vence</th>
|
||||
<th style="width:80px" class="text-center">Activo</th>
|
||||
<th style="width:130px" class="text-center">Opciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($anuncios as $anuncio)
|
||||
@php $cfg = $anuncio->typeConfig(); @endphp
|
||||
<tr class="{{ $anuncio->isExpired() ? 'table-secondary text-muted' : '' }}">
|
||||
<td>{{ $anuncio->id }}</td>
|
||||
<td>
|
||||
<span class="badge badge-{{ $cfg['color'] }} mr-1">
|
||||
<i class="fas {{ $cfg['icon'] }}"></i>
|
||||
</span>
|
||||
{{ $anuncio->title }}
|
||||
@if($anuncio->image_path)
|
||||
<i class="fas fa-image text-secondary ml-1" title="Tiene imagen"></i>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge badge-{{ $cfg['color'] }}">{{ $cfg['label'] }}</span>
|
||||
</td>
|
||||
<td>
|
||||
@foreach($anuncio->audience as $aud)
|
||||
<span class="badge badge-light border mr-1">{{ \App\Models\Anuncio::ROLES[$aud] ?? $aud }}</span>
|
||||
@endforeach
|
||||
</td>
|
||||
<td>
|
||||
@if($anuncio->expires_at)
|
||||
<span class="{{ $anuncio->isExpired() ? 'text-danger' : 'text-secondary' }}">
|
||||
<i class="fas fa-clock mr-1"></i>
|
||||
{{ $anuncio->expires_at->format('d/m/Y H:i') }}
|
||||
</span>
|
||||
@else
|
||||
<span class="text-muted">Sin vencimiento</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<form action="{{ route('anuncio.toggleActive', $anuncio) }}" method="POST">
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
<button type="submit" class="btn btn-sm {{ $anuncio->active ? 'btn-success' : 'btn-secondary' }}"
|
||||
title="{{ $anuncio->active ? 'Desactivar' : 'Activar' }}">
|
||||
<i class="fas {{ $anuncio->active ? 'fa-toggle-on' : 'fa-toggle-off' }}"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="{{ route('anuncio.edit', $anuncio) }}" class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<form action="{{ route('anuncio.destroy', $anuncio) }}" method="POST" class="d-inline delete-form">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="text-center text-muted py-4">
|
||||
<i class="fas fa-bullhorn fa-2x mb-2 d-block"></i>
|
||||
No hay anuncios creados.
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-center mt-3">
|
||||
{{ $anuncios->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
@include('layouts._partials.delete')
|
||||
@endsection
|
||||
@@ -9,23 +9,36 @@
|
||||
<h5 class="mb-0"><i class="fas fa-comments"></i> Chats</h5>
|
||||
</div>
|
||||
<div class="card-body p-0" style="overflow-y: auto;">
|
||||
@forelse($friends as $friend)
|
||||
<a href="{{ route('chat.show', $friend) }}" class="text-decoration-none">
|
||||
@forelse($users as $user)
|
||||
<a href="{{ route('chat.show', $user) }}" class="text-decoration-none">
|
||||
<div class="d-flex align-items-center p-3 border-bottom chat-user-item">
|
||||
<img src="{{ $friend->profile_photo_url }}"
|
||||
class="rounded-circle mr-3"
|
||||
width="45" height="45"
|
||||
style="object-fit:cover">
|
||||
<div>
|
||||
<div class="font-weight-bold text-dark">{{ $friend->name }}</div>
|
||||
<small class="text-muted">{{ $friend->getRoleNames()->first() }}</small>
|
||||
<div class="position-relative mr-3 flex-shrink-0">
|
||||
<img src="{{ $user->profile_photo_url }}"
|
||||
class="rounded-circle"
|
||||
width="45" height="45"
|
||||
style="object-fit:cover;
|
||||
{{ $user->is_friend ? '' : 'filter:grayscale(60%); opacity:.8' }}">
|
||||
@unless($user->is_friend)
|
||||
<span class="position-absolute"
|
||||
style="bottom:0;right:0;background:#dc3545;border-radius:50%;
|
||||
width:16px;height:16px;display:flex;align-items:center;
|
||||
justify-content:center;border:2px solid #fff">
|
||||
<i class="fas fa-lock" style="font-size:.45rem;color:#fff"></i>
|
||||
</span>
|
||||
@endunless
|
||||
</div>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<div class="font-weight-bold text-dark text-truncate">{{ $user->name }}</div>
|
||||
<small class="{{ $user->is_friend ? 'text-muted' : 'text-danger' }}">
|
||||
{{ $user->is_friend ? $user->getRoleNames()->first() : 'Ya no son amigos' }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@empty
|
||||
<div class="text-center text-muted py-5">
|
||||
<i class="fas fa-user-friends fa-3x mb-3 d-block"></i>
|
||||
<p class="mb-2">Aún no tienes amigos para chatear.</p>
|
||||
<p class="mb-2">Aún no tienes conversaciones.</p>
|
||||
<a href="{{ route('feed') }}" class="btn btn-primary btn-sm">
|
||||
<i class="fas fa-search mr-1"></i> Buscar personas
|
||||
</a>
|
||||
|
||||
@@ -1,15 +1,59 @@
|
||||
<x-app-layout>
|
||||
<x-slot name="header">
|
||||
<h2 class="font-semibold text-xl text-gray-800 leading-tight">
|
||||
{{ __('Dashboard') }}
|
||||
</h2>
|
||||
</x-slot>
|
||||
@extends('layouts.landing')
|
||||
|
||||
<div class="py-12">
|
||||
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||
<div class="bg-white overflow-hidden shadow-xl sm:rounded-lg">
|
||||
<x-welcome />
|
||||
@section('title', 'Inicio')
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid">
|
||||
|
||||
{{-- Anuncios vigentes para este usuario --}}
|
||||
@if(isset($anuncios) && $anuncios->count())
|
||||
<div class="mb-4">
|
||||
@foreach($anuncios as $anuncio)
|
||||
@php $cfg = $anuncio->typeConfig(); @endphp
|
||||
<div class="alert alert-{{ $cfg['color'] }} alert-dismissible fade show shadow-sm mb-3" role="alert">
|
||||
<div class="d-flex align-items-start">
|
||||
<i class="fas {{ $cfg['icon'] }} fa-lg mr-3 mt-1"></i>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="font-weight-bold mb-1">{{ $anuncio->title }}</h6>
|
||||
@if($anuncio->body)
|
||||
<p class="mb-1" style="white-space:pre-line">{{ $anuncio->body }}</p>
|
||||
@endif
|
||||
@if($anuncio->image_path)
|
||||
<img src="{{ Storage::url($anuncio->image_path) }}"
|
||||
alt="{{ $anuncio->title }}"
|
||||
class="img-fluid rounded mt-2"
|
||||
style="max-height:300px; max-width:100%; object-fit:contain">
|
||||
@endif
|
||||
@if($anuncio->expires_at)
|
||||
<div class="mt-1">
|
||||
<small class="opacity-75">
|
||||
<i class="fas fa-clock mr-1"></i>
|
||||
Válido hasta {{ $anuncio->expires_at->format('d/m/Y H:i') }}
|
||||
</small>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Cerrar">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
{{-- Contenido de bienvenida --}}
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card shadow">
|
||||
<div class="card-body text-center py-5">
|
||||
<i class="fas fa-graduation-cap fa-3x text-primary mb-3"></i>
|
||||
<h4 class="font-weight-bold">Bienvenido al Sistema Educativo SECUIEP</h4>
|
||||
<p class="text-muted">Utiliza el menú lateral para acceder a los módulos disponibles.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</x-app-layout>
|
||||
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
@extends('layouts.landing')
|
||||
|
||||
@section('head')
|
||||
@include('layouts._partials.tablaStyle')
|
||||
@endsection
|
||||
|
||||
@section('title', 'Administrar grupo')
|
||||
|
||||
@section('content')
|
||||
<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">
|
||||
Materias asignadas — Grupo: {{ $grupo->clave }}
|
||||
</h6>
|
||||
<a href="{{ route('grupo.index') }}" class="btn btn-sm btn-secondary shadow-sm">
|
||||
<i class="fas fa-arrow-left fa-sm"></i> Regresar
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
@if (session('info'))
|
||||
<div class="alert alert-success"><strong>{{ session('info') }}</strong></div>
|
||||
@endif
|
||||
|
||||
@if ($materiales->isEmpty())
|
||||
<div class="alert alert-warning">No tienes materias asignadas en este grupo.</div>
|
||||
@else
|
||||
<div class="row">
|
||||
@foreach ($materiales as $material)
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card border-left-primary shadow h-100">
|
||||
<div class="card-body">
|
||||
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">
|
||||
{{ $material->clave }}
|
||||
</div>
|
||||
<div class="h5 mb-2 font-weight-bold text-gray-800">
|
||||
{{ $material->name }}
|
||||
</div>
|
||||
<p class="text-muted small mb-3">
|
||||
Ciclo: {{ $material->ciclo }} · {{ $material->horas }} hrs
|
||||
</p>
|
||||
<a href="{{ route('grupo.material.tareas', [$grupo, $material]) }}"
|
||||
class="btn btn-primary btn-sm btn-block">
|
||||
<i class="fas fa-tasks fa-sm"></i> Gestionar Tareas
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -88,8 +88,12 @@
|
||||
<option value="0" {{ 0 == $grupo->status ? 'selected' : '' }}>Inactivo</option>
|
||||
</select>
|
||||
</div>
|
||||
@hasrole('Control escolar')
|
||||
|
||||
<input class="btn btn-primary" type="submit" value="Guardar">
|
||||
|
||||
@endhasrole
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,6 +54,22 @@
|
||||
</div>
|
||||
@endcan
|
||||
|
||||
@hasrole('Docente')
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<a class="btn btn-primary" href="{{ route('grupo.administrar', $grupo->id) }}">
|
||||
<i class="fas fa-tasks fa-sm"></i> Administrar
|
||||
</a>
|
||||
</div>
|
||||
@endhasrole
|
||||
|
||||
@hasrole('Coordinación académica')
|
||||
<div class="col-sm-6 col-md-6">
|
||||
<a class="btn btn-info" href="{{ route('grupo.ver.materias', $grupo->id) }}">
|
||||
<i class="fas fa-eye fa-sm"></i> Ver Tareas
|
||||
</a>
|
||||
</div>
|
||||
@endhasrole
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
@extends('layouts.landing')
|
||||
|
||||
@section('title', 'Materias del grupo')
|
||||
|
||||
@section('content')
|
||||
<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">
|
||||
Materias — Grupo: {{ $grupo->clave }}
|
||||
</h6>
|
||||
<a href="{{ route('grupo.index') }}" class="btn btn-sm btn-secondary shadow-sm">
|
||||
<i class="fas fa-arrow-left fa-sm"></i> Regresar
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
@if ($asignaciones->isEmpty())
|
||||
<div class="alert alert-warning">Este grupo no tiene materias asignadas.</div>
|
||||
@else
|
||||
<div class="row">
|
||||
@foreach ($asignaciones as $asig)
|
||||
<div class="col-md-4 mb-4">
|
||||
<div class="card border-left-info shadow h-100">
|
||||
<div class="card-body">
|
||||
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">
|
||||
{{ $asig->material_clave }}
|
||||
</div>
|
||||
<div class="h5 mb-1 font-weight-bold text-gray-800">
|
||||
{{ $asig->material_name }}
|
||||
</div>
|
||||
<p class="text-muted small mb-1">
|
||||
Ciclo: {{ $asig->ciclo }} · {{ $asig->horas }} hrs
|
||||
</p>
|
||||
<p class="small mb-3">
|
||||
<i class="fas fa-chalkboard-teacher fa-sm text-gray-500"></i>
|
||||
<span class="text-gray-700">
|
||||
{{ $asig->apellidoPaterno }} {{ $asig->apellidoMaterno }} {{ $asig->docente_nombre }}
|
||||
</span>
|
||||
</p>
|
||||
<a href="{{ route('grupo.material.tareas', [$grupo->id, $asig->material_id]) }}"
|
||||
class="btn btn-info btn-sm btn-block">
|
||||
<i class="fas fa-tasks fa-sm"></i> Ver Tareas
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -1,7 +1,7 @@
|
||||
<footer class="sticky-footer bg-white">
|
||||
<div class="container my-auto">
|
||||
<div class="copyright text-center my-auto">
|
||||
<span>Copyright © Your Website 2021</span>
|
||||
<span>Copyright © Your Website {{date('Y')}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -9,4 +9,4 @@
|
||||
|
||||
<a class="scroll-to-top rounded" href="#page-top">
|
||||
<i class="fas fa-angle-up"></i>
|
||||
</a>
|
||||
</a>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<i class="fa fa-bars"></i>
|
||||
</button>
|
||||
|
||||
@can(['comunidad','amigos'])
|
||||
<!-- Topbar Search -->
|
||||
<div class="d-none d-sm-inline-block form-inline mr-auto ml-md-3 my-2 my-md-0 mw-100 navbar-search position-relative">
|
||||
<div class="input-group">
|
||||
@@ -25,8 +26,10 @@
|
||||
min-width:300px"></div>
|
||||
</div>
|
||||
|
||||
@endcan
|
||||
<ul class="navbar-nav ml-auto">
|
||||
|
||||
|
||||
<!-- Nav Item - Search (XS) -->
|
||||
<li class="nav-item no-arrow d-sm-none position-relative">
|
||||
<a class="nav-link" href="#" id="mobile-search-toggle" role="button">
|
||||
@@ -50,6 +53,7 @@
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@can(['comunidad','amigos'])
|
||||
<!-- Nav Item - Notificaciones -->
|
||||
<li class="nav-item dropdown no-arrow mx-1">
|
||||
<a class="nav-link dropdown-toggle" href="#" id="alertsDropdown" role="button"
|
||||
@@ -98,6 +102,9 @@
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@endcan
|
||||
|
||||
@can('mensajes')
|
||||
<!-- Nav Item - Mensajes -->
|
||||
<li class="nav-item dropdown no-arrow mx-1">
|
||||
<a class="nav-link dropdown-toggle" href="{{ route('chat.index') }}" id="messagesDropdown"
|
||||
@@ -143,6 +150,7 @@
|
||||
</a>
|
||||
</div>
|
||||
</li>
|
||||
@endcan
|
||||
|
||||
<div class="topbar-divider d-none d-sm-block"></div>
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
<div>
|
||||
{{-- Modal --}}
|
||||
{{-- Modal: Agregar docente --}}
|
||||
<div wire:ignore.self class="modal fade" id="modalDocente" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Agregar Docente al Grupo</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
<h5 class="modal-title">Asignar Docente al Grupo</h5>
|
||||
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{{-- Docente --}}
|
||||
<div class="mb-3">
|
||||
<label>Docente</label>
|
||||
<select class="form-control" wire:model="docente_id">
|
||||
@@ -18,30 +15,23 @@
|
||||
@foreach ($docentes as $docente)
|
||||
@php $usr = $docente->docentes->first() @endphp
|
||||
<option value="{{ $docente->id }}">
|
||||
{{ $usr?->name }}
|
||||
{{ $usr?->apellidoPaterno }}
|
||||
{{ $usr?->apellidoPaterno }} {{ $usr?->apellidoMaterno }} {{ $usr?->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('docente_id')
|
||||
<span class="text-danger small">{{ $message }}</span>
|
||||
@enderror
|
||||
@error('docente_id')<span class="text-danger small">{{ $message }}</span>@enderror
|
||||
</div>
|
||||
|
||||
{{-- Materia --}}
|
||||
<div class="mb-3">
|
||||
<label>Materia</label>
|
||||
<select class="form-control" wire:model="materia_id">
|
||||
<option value="">Seleccionar materia...</option>
|
||||
@foreach ($materiasSelect as $materia)
|
||||
<option value="{{ $materia->id }}">
|
||||
ciclo {{ $materia->ciclo }} - {{ $materia->name }}
|
||||
Ciclo {{ $materia->ciclo }} — {{ $materia->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error('materia_id')
|
||||
<span class="text-danger small">{{ $message }}</span>
|
||||
@enderror
|
||||
@error('materia_id')<span class="text-danger small">{{ $message }}</span>@enderror
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
@@ -52,148 +42,176 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Tabla docentes asignados --}}
|
||||
<table id="tcont2" class="table table-striped table-bordered table-hover" style="width:100%;">
|
||||
<thead>
|
||||
{{-- Modal: Reemplazar docente --}}
|
||||
<div wire:ignore.self class="modal fade" id="modalReemplazo" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Reemplazar Docente</h5>
|
||||
<button type="button" class="close" data-dismiss="modal"><span>×</span></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="alert alert-info mb-3">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
Materia: <strong>{{ $reemplazoMateriaNombre }}</strong><br>
|
||||
<small>Las tareas ya asignadas por el docente actual se conservarán y quedarán bajo el nuevo docente.</small>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label>Nuevo docente</label>
|
||||
<select class="form-control" wire:model="reemplazoDocenteId">
|
||||
<option value="">Seleccionar docente...</option>
|
||||
@foreach ($docentes as $docente)
|
||||
@php $usr = $docente->docentes->first() @endphp
|
||||
@if ($docente->id != $reemplazoDocenteActualId)
|
||||
<option value="{{ $docente->id }}">
|
||||
{{ $usr?->apellidoPaterno }} {{ $usr?->apellidoMaterno }} {{ $usr?->name }}
|
||||
</option>
|
||||
@endif
|
||||
@endforeach
|
||||
</select>
|
||||
@error('reemplazoDocenteId')<span class="text-danger small">{{ $message }}</span>@enderror
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancelar</button>
|
||||
<button wire:click="reemplazar" class="btn btn-warning">
|
||||
<i class="fas fa-exchange-alt fa-sm"></i> Confirmar reemplazo
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Tabla materias / docentes --}}
|
||||
<table id="tcont2" class="table table-striped table-bordered table-hover" style="width:100%;">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Materia</th>
|
||||
<th>Ciclo</th>
|
||||
<th>Docente asignado</th>
|
||||
@hasrole('Coordinación académica')
|
||||
<th>Ver tareas</th>
|
||||
<th>Acciones</th>
|
||||
@endhasrole
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($materias as $materia)
|
||||
@php
|
||||
$docenteAsignado = $grupo->docentes->firstWhere('pivot.materia_id', $materia->id);
|
||||
$usr = $docenteAsignado?->docentes->first();
|
||||
@endphp
|
||||
<tr>
|
||||
<th>Id</th>
|
||||
<th>Materia</th>
|
||||
<th>Ciclo</th>
|
||||
<th>Docente asignado</th>
|
||||
<td>{{ $materia->name }}</td>
|
||||
<td>{{ $materia->ciclo }}</td>
|
||||
<td>
|
||||
@if ($usr)
|
||||
{{ $usr->apellidoPaterno }} {{ $usr->apellidoMaterno }} {{ $usr->name }}
|
||||
@else
|
||||
<span class="badge badge-secondary">Sin docente</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
@hasrole('Coordinación académica')
|
||||
<th>Opciones</th>
|
||||
@endhasrole
|
||||
{{-- Columna Ver tareas --}}
|
||||
<td>
|
||||
@if ($docenteAsignado)
|
||||
<a href="{{ route('grupo.material.tareas', [$grupo->id, $materia->id]) }}"
|
||||
class="btn btn-sm btn-info">
|
||||
<i class="fas fa-tasks fa-sm"></i> Ver tareas
|
||||
</a>
|
||||
@else
|
||||
<span class="text-muted small">Sin asignar</span>
|
||||
@endif
|
||||
</td>
|
||||
{{-- Columna Acciones --}}
|
||||
<td>
|
||||
@if ($docenteAsignado)
|
||||
<button
|
||||
wire:click="abrirReemplazo({{ $docenteAsignado->id }}, {{ $materia->id }}, '{{ addslashes($materia->name) }}')"
|
||||
class="btn btn-sm btn-warning" title="Reemplazar docente">
|
||||
<i class="fas fa-exchange-alt fa-sm"></i> Reemplazar
|
||||
</button>
|
||||
<button
|
||||
onclick="confirmarEliminar({{ $docenteAsignado->id }}, {{ $materia->id }}, '{{ $usr?->apellidoPaterno }} {{ $usr?->name }}', '{{ addslashes($materia->name) }}')"
|
||||
class="btn btn-sm btn-danger" title="Quitar docente">
|
||||
<i class="fas fa-user-minus fa-sm"></i>
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
@endhasrole
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse ($materias as $materia)
|
||||
@php
|
||||
$docenteAsignado = $grupo->docentes->firstWhere('pivot.materia_id', $materia->id);
|
||||
$usr = $docenteAsignado?->docentes->first();
|
||||
@endphp
|
||||
<tr>
|
||||
<td>{{ $materia->id }}</td>
|
||||
<td>{{ $materia->name }}</td>
|
||||
<td>{{ $materia->ciclo }}</td>
|
||||
<td>
|
||||
@if($usr)
|
||||
{{ $usr->name }} {{ $usr->apellidoPaterno }}
|
||||
@else
|
||||
<span class="badge badge-secondary">Sin docente</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
|
||||
@hasrole('Coordinación académica')
|
||||
<td>
|
||||
@if($docenteAsignado)
|
||||
<button
|
||||
onclick="confirmarEliminar({{ $docenteAsignado->id }}, {{ $materia->id }}, '{{ $usr?->name }} {{ $usr?->apellidoPaterno }}', '{{ $materia->name }}')"
|
||||
style="border:none; background:none;">
|
||||
<i class="fa-regular fa-circle-user-circle-minus fa-2xl" style="color: rgb(255, 0, 0);"></i>
|
||||
</button>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
@endhasrole
|
||||
|
||||
</tr>
|
||||
@empty
|
||||
<tr>Sin materias en este plan.</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
@empty
|
||||
<tr><td colspan="5">Sin materias en este plan.</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initTable);
|
||||
|
||||
document.addEventListener('livewire:init', () => {
|
||||
// ✅ Este hook es el correcto para Livewire v3
|
||||
Livewire.hook('commit', ({ component, succeed }) => {
|
||||
succeed(() => {
|
||||
setTimeout(() => initTable(), 50);
|
||||
});
|
||||
succeed(() => setTimeout(() => initTable(), 50));
|
||||
});
|
||||
|
||||
Livewire.on('open-modal-docente', () => $('#modalDocente').modal('show'));
|
||||
Livewire.on('close-modal-docente', () => $('#modalDocente').modal('hide'));
|
||||
Livewire.on('open-modal-reemplazo', () => $('#modalReemplazo').modal('show'));
|
||||
Livewire.on('close-modal-reemplazo',() => $('#modalReemplazo').modal('hide'));
|
||||
});
|
||||
|
||||
function confirmarEliminar(docenteId, materiaId, nombreDocente, nombreMateria) {
|
||||
Swal.fire({
|
||||
title: "¿Eliminar docente?",
|
||||
html: `¿Deseas quitar a <b>${nombreDocente}</b> de <b>${nombreMateria}</b>?`,
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Sí, eliminar",
|
||||
cancelButtonText: "Cancelar",
|
||||
confirmButtonColor: '#e74a3b',
|
||||
}).then((result) => {
|
||||
if (result.isConfirmed) {
|
||||
@this.eliminar(docenteId, materiaId);
|
||||
}
|
||||
});
|
||||
}
|
||||
function confirmarEliminar(docenteId, materiaId, nombreDocente, nombreMateria) {
|
||||
Swal.fire({
|
||||
title: "¿Quitar docente?",
|
||||
html: `¿Deseas quitar a <b>${nombreDocente}</b> de <b>${nombreMateria}</b>?`,
|
||||
icon: "warning",
|
||||
showCancelButton: true,
|
||||
confirmButtonText: "Sí, quitar",
|
||||
cancelButtonText: "Cancelar",
|
||||
confirmButtonColor: '#e74a3b',
|
||||
}).then(result => {
|
||||
if (result.isConfirmed) @this.eliminar(docenteId, materiaId);
|
||||
});
|
||||
}
|
||||
|
||||
function initTable() {
|
||||
// ✅ Destroy correcto
|
||||
function initTable() {
|
||||
if ($.fn.DataTable.isDataTable('#tcont2')) {
|
||||
$('#tcont2').DataTable().destroy();
|
||||
$('#tcont2').empty(); // limpia el DOM completamente
|
||||
$('#tcont2').empty();
|
||||
}
|
||||
|
||||
new DataTable('#tcont2', {
|
||||
retrieve: true, // ✅ evita el error de reinicialización
|
||||
retrieve: true,
|
||||
responsive: true,
|
||||
order: [],
|
||||
language: {
|
||||
"decimal": "",
|
||||
"emptyTable": "No hay información",
|
||||
"info": "Mostrando _START_ a _END_ de _TOTAL_ Entradas",
|
||||
"infoEmpty": "Mostrando 0 to 0 of 0 Entradas",
|
||||
"infoFiltered": "(Filtrado de _MAX_ total entradas)",
|
||||
"infoPostFix": "",
|
||||
"thousands": ",",
|
||||
"lengthMenu": "Mostrar _MENU_ Entradas",
|
||||
"loadingRecords": "Cargando...",
|
||||
"processing": "Procesando...",
|
||||
"search": "Buscar:",
|
||||
"zeroRecords": "Sin resultados encontrados"
|
||||
emptyTable: "No hay información",
|
||||
info: "Mostrando _START_ a _END_ de _TOTAL_ entradas",
|
||||
infoEmpty: "Mostrando 0 de 0 entradas",
|
||||
infoFiltered: "(filtrado de _MAX_ entradas)",
|
||||
lengthMenu: "Mostrar _MENU_ entradas",
|
||||
loadingRecords: "Cargando...",
|
||||
processing: "Procesando...",
|
||||
search: "Buscar:",
|
||||
zeroRecords: "Sin resultados",
|
||||
thousands: ",",
|
||||
},
|
||||
lengthMenu: [
|
||||
[10, 25, 50, -1],
|
||||
['10 filas', '25 filas', '50 filas', 'Todos']
|
||||
],
|
||||
lengthMenu: [[10, 25, 50, -1], ['10', '25', '50', 'Todos']],
|
||||
layout: {
|
||||
topStart: {
|
||||
buttons: ['colvis', 'pageLength']
|
||||
},
|
||||
topStart: { buttons: ['colvis', 'pageLength'] },
|
||||
topEnd: ['search'],
|
||||
bottomStart: [{
|
||||
buttons: [
|
||||
{ extend: 'excel', title: 'Excel' },
|
||||
{ extend: 'pdfHtml5', title: 'PDF' },
|
||||
{ extend: 'print' }
|
||||
]
|
||||
}, 'info'],
|
||||
}
|
||||
bottomStart: [{ buttons: [
|
||||
{ extend: 'excel', title: 'Excel' },
|
||||
{ extend: 'pdfHtml5', title: 'PDF' },
|
||||
{ extend: 'print' },
|
||||
]}, 'info'],
|
||||
},
|
||||
});
|
||||
|
||||
$("#tcont2_wrapper").removeClass("form-inline");
|
||||
$("#tcont2_wrapper").addClass("w-100");
|
||||
$("#tcont2_wrapper").removeClass("form-inline").addClass("w-100");
|
||||
}
|
||||
|
||||
document.addEventListener('livewire:init', () => {
|
||||
Livewire.on('open-modal-docente', () => {
|
||||
$('#modalDocente').modal('show');
|
||||
});
|
||||
Livewire.on('close-modal-docente', () => {
|
||||
$('#modalDocente').modal('hide');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
@extends('layouts.landing')
|
||||
|
||||
@section('title', isset($tarea) ? 'Editar Tarea' : 'Nueva Tarea')
|
||||
|
||||
@section('content')
|
||||
<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">
|
||||
{{ isset($tarea) ? 'Editar Tarea' : 'Nueva Tarea' }}
|
||||
— {{ $material->name }} · Grupo: {{ $grupo->clave }}
|
||||
</h6>
|
||||
<a href="{{ route('grupo.material.tareas', [$grupo, $material]) }}"
|
||||
class="btn btn-sm btn-secondary shadow-sm">
|
||||
<i class="fas fa-arrow-left fa-sm"></i> Regresar
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul class="mb-0">
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (isset($tarea))
|
||||
<form action="{{ route('grupo.material.tareas.update', [$grupo, $material, $tarea]) }}" method="POST">
|
||||
@method('PUT')
|
||||
@else
|
||||
<form action="{{ route('grupo.material.tareas.store', [$grupo, $material]) }}" method="POST">
|
||||
@endif
|
||||
@csrf
|
||||
|
||||
<div class="form-group">
|
||||
<label for="titulo">Título <span class="text-danger">*</span></label>
|
||||
<input type="text" id="titulo" name="titulo" class="form-control"
|
||||
value="{{ old('titulo', $tarea->titulo ?? '') }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="descripcion">Descripción / Instrucciones</label>
|
||||
<textarea id="descripcion" name="descripcion" class="form-control" rows="5">{{ old('descripcion', $tarea->descripcion ?? '') }}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="fecha_entrega">Fecha límite de entrega</label>
|
||||
<input type="date" id="fecha_entrega" name="fecha_entrega" class="form-control"
|
||||
value="{{ old('fecha_entrega', isset($tarea) && $tarea->fecha_entrega ? $tarea->fecha_entrega : '') }}">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fas fa-save fa-sm"></i>
|
||||
{{ isset($tarea) ? 'Guardar cambios' : 'Crear tarea' }}
|
||||
</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,109 @@
|
||||
@extends('layouts.landing')
|
||||
|
||||
@section('head')
|
||||
@include('layouts._partials.tablaStyle')
|
||||
@endsection
|
||||
|
||||
@section('title', 'Tareas')
|
||||
|
||||
@section('content')
|
||||
<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">
|
||||
Tareas — {{ $material->name }} · Grupo: {{ $grupo->clave }}
|
||||
</h6>
|
||||
<div>
|
||||
{{-- Botón regresar según rol --}}
|
||||
@hasrole('Coordinación académica')
|
||||
<a href="{{ route('grupo.ver.materias', $grupo) }}" class="btn btn-sm btn-secondary shadow-sm mr-2">
|
||||
<i class="fas fa-arrow-left fa-sm"></i> Regresar
|
||||
</a>
|
||||
@else
|
||||
<a href="{{ route('grupo.administrar', $grupo) }}" class="btn btn-sm btn-secondary shadow-sm mr-2">
|
||||
<i class="fas fa-arrow-left fa-sm"></i> Regresar
|
||||
</a>
|
||||
<a href="{{ route('grupo.material.tareas.create', [$grupo, $material]) }}"
|
||||
class="btn btn-sm btn-primary shadow-sm">
|
||||
<i class="fas fa-plus fa-sm text-white-50"></i> Nueva Tarea
|
||||
</a>
|
||||
@endhasrole
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
@if (session('info'))
|
||||
<div class="alert alert-success"><strong>{{ session('info') }}</strong></div>
|
||||
@endif
|
||||
|
||||
@if ($tareas->isEmpty())
|
||||
<div class="alert alert-info">Aún no hay tareas para esta materia.</div>
|
||||
@else
|
||||
<table id="tcont" class="table table-striped table-bordered table-hover" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Título</th>
|
||||
<th>Fecha límite</th>
|
||||
<th>Pendientes</th>
|
||||
<th>Calificadas</th>
|
||||
<th>Total alumnos</th>
|
||||
<th>Opciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($tareas as $tarea)
|
||||
<tr>
|
||||
<td>{{ $tarea->titulo }}</td>
|
||||
<td>
|
||||
@if ($tarea->fecha_entrega)
|
||||
{{ \Carbon\Carbon::parse($tarea->fecha_entrega)->format('d/m/Y') }}
|
||||
@if (\Carbon\Carbon::parse($tarea->fecha_entrega)->isPast())
|
||||
<span class="badge badge-danger ml-1">Vencida</span>
|
||||
@else
|
||||
<span class="badge badge-success ml-1">Activa</span>
|
||||
@endif
|
||||
@else
|
||||
<span class="text-muted">Sin fecha</span>
|
||||
@endif
|
||||
</td>
|
||||
<td><span class="badge badge-warning">{{ $tarea->pendientes_count }}</span></td>
|
||||
<td><span class="badge badge-success">{{ $tarea->calificadas_count }}</span></td>
|
||||
<td>{{ $tarea->entregas_count }}</td>
|
||||
<td>
|
||||
<a href="{{ route('grupo.material.tareas.show', [$grupo, $material, $tarea]) }}"
|
||||
class="btn btn-sm btn-info">
|
||||
<i class="fas fa-eye"></i> Ver
|
||||
</a>
|
||||
@hasrole('Docente')
|
||||
<a href="{{ route('grupo.material.tareas.edit', [$grupo, $material, $tarea]) }}"
|
||||
class="btn btn-sm btn-warning">
|
||||
<i class="fas fa-edit"></i> Editar
|
||||
</a>
|
||||
<form class="delete-form d-inline"
|
||||
action="{{ route('grupo.material.tareas.destroy', [$grupo, $material, $tarea]) }}"
|
||||
method="POST">
|
||||
@csrf @method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
@endhasrole
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
@include('layouts._partials.tablaScript')
|
||||
@include('layouts._partials.delete')
|
||||
@endsection
|
||||
@@ -0,0 +1,153 @@
|
||||
@extends('layouts.landing')
|
||||
|
||||
@section('head')
|
||||
@include('layouts._partials.tablaStyle')
|
||||
@endsection
|
||||
|
||||
@section('title', 'Mis Tareas')
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid">
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Mis Tareas</h6>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
|
||||
@if (session('info'))
|
||||
<div class="alert alert-success"><strong>{{ session('info') }}</strong></div>
|
||||
@endif
|
||||
@if (session('error'))
|
||||
<div class="alert alert-danger"><strong>{{ session('error') }}</strong></div>
|
||||
@endif
|
||||
@if ($errors->has('entrega'))
|
||||
<div class="alert alert-warning"><strong>{{ $errors->first('entrega') }}</strong></div>
|
||||
@endif
|
||||
|
||||
@if (!isset($tareas) || $tareas->isEmpty())
|
||||
<div class="alert alert-info">No tienes tareas asignadas en tus grupos.</div>
|
||||
@else
|
||||
<div class="table-responsive">
|
||||
<table id="tcont" class="table table-striped table-bordered table-hover" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tarea</th>
|
||||
<th>Materia</th>
|
||||
<th>Grupo</th>
|
||||
<th>Fecha límite</th>
|
||||
<th>Status</th>
|
||||
<th>Calificación</th>
|
||||
<th>Tu entrega / Acción</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($tareas as $tarea)
|
||||
@php $entrega = $entregasMap[$tarea->id] ?? null; $status = $entrega?->status ?? 'sin_subir'; @endphp
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ $tarea->titulo }}</strong>
|
||||
@if ($tarea->descripcion)
|
||||
<p class="text-muted small mb-0">{{ Str::limit($tarea->descripcion, 80) }}</p>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $tarea->material->name }}</td>
|
||||
<td>{{ $tarea->grupo->clave }}</td>
|
||||
<td>
|
||||
@if ($tarea->fecha_entrega)
|
||||
{{ \Carbon\Carbon::parse($tarea->fecha_entrega)->format('d/m/Y') }}
|
||||
@if (\Carbon\Carbon::parse($tarea->fecha_entrega)->isPast())
|
||||
<span class="badge badge-danger">Vencida</span>
|
||||
@else
|
||||
<span class="badge badge-success">Activa</span>
|
||||
@endif
|
||||
@else
|
||||
<span class="text-muted">Sin fecha</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if ($status === 'sin_subir')
|
||||
<span class="badge badge-secondary">Sin subir</span>
|
||||
@elseif ($status === 'pendiente')
|
||||
<span class="badge badge-warning">Pendiente</span>
|
||||
@else
|
||||
<span class="badge badge-success">Calificada</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if ($status === 'calificada' && $entrega)
|
||||
<strong class="text-success">{{ number_format($entrega->calificacion, 1) }}</strong>
|
||||
@if ($entrega->comentario)
|
||||
<br><small class="text-muted">{{ $entrega->comentario }}</small>
|
||||
@endif
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@if ($status === 'sin_subir')
|
||||
{{-- Formulario de entrega: texto y/o archivo --}}
|
||||
<form action="{{ route('tarea.entregar', $tarea) }}" method="POST"
|
||||
enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="form-group mb-1">
|
||||
<textarea name="texto" class="form-control form-control-sm"
|
||||
rows="3" placeholder="Escribe tu respuesta aquí (opcional)..."></textarea>
|
||||
</div>
|
||||
<div class="form-group mb-1">
|
||||
<div class="custom-file">
|
||||
<input type="file" class="custom-file-input" id="archivo_{{ $tarea->id }}" name="archivo">
|
||||
<label class="custom-file-label small" for="archivo_{{ $tarea->id }}">
|
||||
Adjuntar archivo (opcional)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary btn-sm btn-block mt-1">
|
||||
<i class="fas fa-upload fa-sm"></i> Entregar
|
||||
</button>
|
||||
</form>
|
||||
|
||||
@elseif ($status === 'pendiente' || $status === 'calificada')
|
||||
{{-- Mostrar lo que entregó --}}
|
||||
@if ($entrega?->texto)
|
||||
<div class="border rounded p-2 bg-light mb-2" style="max-height:120px;overflow-y:auto;font-size:.85rem;">
|
||||
{{ $entrega->texto }}
|
||||
</div>
|
||||
@endif
|
||||
@if ($entrega?->archivo)
|
||||
<a href="{{ route('files.serve', $entrega->archivo) }}"
|
||||
target="_blank" class="btn btn-sm btn-outline-info">
|
||||
<i class="fas fa-eye fa-sm"></i> Ver archivo entregado
|
||||
</a>
|
||||
@endif
|
||||
@if (!$entrega?->texto && !$entrega?->archivo)
|
||||
<span class="text-muted small">Sin contenido registrado</span>
|
||||
@endif
|
||||
@if ($status === 'pendiente')
|
||||
<br><small class="text-warning"><i class="fas fa-clock"></i> Esperando calificación</small>
|
||||
@endif
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
@include('layouts._partials.tablaScript')
|
||||
<script>
|
||||
document.querySelectorAll('.custom-file-input').forEach(input => {
|
||||
input.addEventListener('change', function () {
|
||||
const fileName = this.files[0]?.name || 'Adjuntar archivo (opcional)';
|
||||
this.nextElementSibling.textContent = fileName;
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
@@ -0,0 +1,156 @@
|
||||
@extends('layouts.landing')
|
||||
|
||||
@section('head')
|
||||
@include('layouts._partials.tablaStyle')
|
||||
@endsection
|
||||
|
||||
@section('title', 'Detalle de Tarea')
|
||||
|
||||
@section('content')
|
||||
<div class="container-fluid">
|
||||
|
||||
{{-- Info de la tarea --}}
|
||||
<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">
|
||||
{{ $tarea->titulo }}
|
||||
— {{ $material->name }} · Grupo: {{ $grupo->clave }}
|
||||
</h6>
|
||||
<a href="{{ route('grupo.material.tareas', [$grupo, $material]) }}"
|
||||
class="btn btn-sm btn-secondary shadow-sm">
|
||||
<i class="fas fa-arrow-left fa-sm"></i> Regresar
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@if (session('info'))
|
||||
<div class="alert alert-success"><strong>{{ session('info') }}</strong></div>
|
||||
@endif
|
||||
@if ($tarea->descripcion)
|
||||
<p class="mb-2"><strong>Instrucciones:</strong> {{ $tarea->descripcion }}</p>
|
||||
@endif
|
||||
@if ($tarea->fecha_entrega)
|
||||
<p class="mb-0">
|
||||
<strong>Fecha límite:</strong>
|
||||
{{ \Carbon\Carbon::parse($tarea->fecha_entrega)->format('d/m/Y') }}
|
||||
@if (\Carbon\Carbon::parse($tarea->fecha_entrega)->isPast())
|
||||
<span class="badge badge-danger ml-1">Vencida</span>
|
||||
@else
|
||||
<span class="badge badge-success ml-1">Activa</span>
|
||||
@endif
|
||||
</p>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Entregas de alumnos --}}
|
||||
<div class="card shadow mb-4">
|
||||
<div class="card-header py-3">
|
||||
<h6 class="m-0 font-weight-bold text-primary">Entregas de alumnos</h6>
|
||||
</div>
|
||||
<div class="card-body table-responsive">
|
||||
<table id="tcont" class="table table-striped table-bordered table-hover" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Alumno</th>
|
||||
<th>Status</th>
|
||||
<th>Entrega</th>
|
||||
@hasrole('Docente')
|
||||
<th>Calificar / Calificación</th>
|
||||
@endhasrole
|
||||
@hasrole('Coordinación académica')
|
||||
<th>Calificación</th>
|
||||
@endhasrole
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach ($alumnos as $alumno)
|
||||
@php $entrega = $entregas[$alumno->id] ?? null; @endphp
|
||||
<tr>
|
||||
<td class="align-middle">
|
||||
{{ $alumno->alumnos->first()?->apellidoPaterno }}
|
||||
{{ $alumno->alumnos->first()?->apellidoMaterno }}
|
||||
{{ $alumno->alumnos->first()?->name }}
|
||||
</td>
|
||||
|
||||
<td class="align-middle">
|
||||
@if (!$entrega || $entrega->status === 'sin_subir')
|
||||
<span class="badge badge-secondary">Sin subir</span>
|
||||
@elseif ($entrega->status === 'pendiente')
|
||||
<span class="badge badge-warning">Pendiente</span>
|
||||
@else
|
||||
<span class="badge badge-success">Calificada</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
<td class="align-middle" style="max-width:280px">
|
||||
@if ($entrega && ($entrega->texto || $entrega->archivo))
|
||||
@if ($entrega->texto)
|
||||
<div class="border rounded p-2 bg-light mb-1"
|
||||
style="max-height:100px;overflow-y:auto;font-size:.85rem;white-space:pre-wrap;">{{ $entrega->texto }}</div>
|
||||
@endif
|
||||
@if ($entrega->archivo)
|
||||
<a href="{{ route('files.serve', $entrega->archivo) }}"
|
||||
target="_blank" class="btn btn-sm btn-outline-info">
|
||||
<i class="fas fa-file fa-sm"></i> Ver archivo
|
||||
</a>
|
||||
@endif
|
||||
@else
|
||||
<span class="text-muted small">Sin entrega</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- Docente: puede calificar --}}
|
||||
@hasrole('Docente')
|
||||
<td class="align-middle" style="min-width:300px">
|
||||
@if ($entrega && in_array($entrega->status, ['pendiente', 'calificada']))
|
||||
<form action="{{ route('tarea.calificar', [$tarea, $alumno]) }}" method="POST"
|
||||
class="d-flex align-items-center flex-wrap" style="gap:4px">
|
||||
@csrf
|
||||
<input type="number" name="calificacion" min="0" max="10" step="0.1"
|
||||
class="form-control form-control-sm" style="width:80px"
|
||||
placeholder="0–10"
|
||||
value="{{ $entrega->status === 'calificada' ? $entrega->calificacion : '' }}"
|
||||
required>
|
||||
<input type="text" name="comentario" class="form-control form-control-sm"
|
||||
style="width:140px" placeholder="Comentario"
|
||||
value="{{ $entrega->comentario ?? '' }}">
|
||||
<button type="submit"
|
||||
class="btn btn-sm {{ $entrega->status === 'calificada' ? 'btn-warning' : 'btn-success' }}">
|
||||
<i class="fas fa-{{ $entrega->status === 'calificada' ? 'redo' : 'check' }} fa-sm"></i>
|
||||
{{ $entrega->status === 'calificada' ? 'Recalificar' : 'Calificar' }}
|
||||
</button>
|
||||
</form>
|
||||
@else
|
||||
<span class="text-muted small">Sin entrega aún</span>
|
||||
@endif
|
||||
</td>
|
||||
@endhasrole
|
||||
|
||||
{{-- Coordinación: solo lectura de calificación --}}
|
||||
@hasrole('Coordinación académica')
|
||||
<td class="align-middle">
|
||||
@if ($entrega?->status === 'calificada')
|
||||
<strong class="text-success">{{ number_format($entrega->calificacion, 1) }}</strong>
|
||||
@if ($entrega->comentario)
|
||||
<br><small class="text-muted">{{ $entrega->comentario }}</small>
|
||||
@endif
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
@endhasrole
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('scripts')
|
||||
@include('layouts._partials.tablaScript')
|
||||
@endsection
|
||||
Reference in New Issue
Block a user