Add citas module: scheduling, calendar, blocked schedules
This commit is contained in:
209
resources/views/admin/horarios/create.blade.php
Normal file
209
resources/views/admin/horarios/create.blade.php
Normal file
@@ -0,0 +1,209 @@
|
||||
@extends('admin.layouts.master')
|
||||
|
||||
@section('title', 'Bloquear Horario - Lash Vanshy')
|
||||
|
||||
@section('page-title', 'Bloquear Horario')
|
||||
|
||||
@section('content')
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.horarios.index') }}">Horarios Bloqueados</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Bloquear Horario</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card-admin">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-ban me-2"></i>Bloquear Horario
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('admin.horarios.store') }}">
|
||||
@csrf
|
||||
|
||||
<div class="row">
|
||||
<!-- Fecha -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="fecha" class="form-label">
|
||||
Fecha <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input type="date"
|
||||
class="form-control @error('fecha') is-invalid @enderror"
|
||||
id="fecha"
|
||||
name="fecha"
|
||||
value="{{ old('fecha') }}"
|
||||
min="{{ date('Y-m-d') }}"
|
||||
required>
|
||||
@error('fecha')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@else
|
||||
<small class="text-muted">
|
||||
<i class="fas fa-info-circle me-1"></i>
|
||||
No se podrán agendar citas en esta fecha
|
||||
</small>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Hora Inicio -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="hora_inicio" class="form-label">
|
||||
Hora de Inicio <span class="text-danger">*</span>
|
||||
</label>
|
||||
<select class="form-select @error('hora_inicio') is-invalid @enderror"
|
||||
id="hora_inicio"
|
||||
name="hora_inicio"
|
||||
required>
|
||||
<option value="">Seleccionar hora</option>
|
||||
@for($hora = 9; $hora < 19; $hora++)
|
||||
<option value="{{ sprintf('%02d:00:00', $hora) }}"
|
||||
{{ old('hora_inicio') == sprintf('%02d:00:00', $hora) ? 'selected' : '' }}>
|
||||
{{ sprintf('%02d:00', $hora) }} AM
|
||||
</option>
|
||||
@endfor
|
||||
@for($hora = 12; $hora < 19; $hora++)
|
||||
<option value="{{ sprintf('%02d:00:00', $hora) }}"
|
||||
{{ old('hora_inicio') == sprintf('%02d:00:00', $hora) ? 'selected' : '' }}>
|
||||
{{ $hora > 12 ? sprintf('%02d:00', $hora - 12) : sprintf('%02d:00', $hora) }}:00 {{ $hora >= 12 ? 'PM' : 'AM' }}
|
||||
</option>
|
||||
@endfor
|
||||
</select>
|
||||
@error('hora_inicio')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Hora Fin -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="hora_fin" class="form-label">
|
||||
Hora de Fin <span class="text-danger">*</span>
|
||||
</label>
|
||||
<select class="form-select @error('hora_fin') is-invalid @enderror"
|
||||
id="hora_fin"
|
||||
name="hora_fin"
|
||||
required>
|
||||
<option value="">Seleccionar hora</option>
|
||||
@for($hora = 9; $hora < 19; $hora++)
|
||||
<option value="{{ sprintf('%02d:00:00', $hora) }}"
|
||||
{{ old('hora_fin') == sprintf('%02d:00:00', $hora) ? 'selected' : '' }}>
|
||||
{{ sprintf('%02d:00', $hora) }}:00 {{ $hora >= 12 ? 'PM' : 'AM' }}
|
||||
</option>
|
||||
@endfor
|
||||
</select>
|
||||
@error('hora_fin')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@else
|
||||
<small class="text-muted">
|
||||
El bloqueo dura hasta esta hora
|
||||
</small>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Motivo -->
|
||||
<div class="mb-3">
|
||||
<label for="motivo" class="form-label">Motivo</label>
|
||||
<input type="text"
|
||||
class="form-control @error('motivo') is-invalid @enderror"
|
||||
id="motivo"
|
||||
name="motivo"
|
||||
value="{{ old('motivo') }}"
|
||||
placeholder="Ej: Vacaciones, Mantenimiento, Evento especial...">
|
||||
@error('motivo')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@else
|
||||
<small class="text-muted">
|
||||
Opcional. Ej: "Vacaciones de Semana Santa"
|
||||
</small>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Quick Options -->
|
||||
<div class="mb-4">
|
||||
<label class="form-label">Opciones Rápidas</label>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button type="button" class="btn btn-secondary-admin btn-sm" onclick="bloquearDiaCompleto()">
|
||||
<i class="fas fa-calendar me-2"></i>Bloquear Día Completo
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary-admin btn-sm" onclick="bloquearManana()">
|
||||
<i class="fas fa-sun me-2"></i>Bloquear Mañana (9am-12pm)
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary-admin btn-sm" onclick="bloquearTarde()">
|
||||
<i class="fas fa-moon me-2"></i>Bloquear Tarde (12pm-7pm)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a href="{{ route('admin.horarios.index') }}" class="btn btn-secondary-admin">
|
||||
<i class="fas fa-arrow-left me-2"></i>Volver
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary-admin">
|
||||
<i class="fas fa-lock me-2"></i>Bloquear Horario
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card-admin mb-3">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-info-circle me-2"></i>Información
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="text-muted">
|
||||
<li class="mb-2">Los horarios bloqueados prevents the agendamiento de citas.</li>
|
||||
<li class="mb-2">Puedes bloquear días completos o solo tramos específicos.</li>
|
||||
<li class="mb-2">El bloqueo no afecta las citas ya existentes.</li>
|
||||
<li class="mb-2">Los clientes no podrán ver estos horarios.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-admin">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-calendar-alt me-2"></i>Horario de Atención
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul class="text-muted mb-0">
|
||||
<li>Lunes a Sábado: <strong>9:00 AM - 7:00 PM</strong></li>
|
||||
<li>Último turno: <strong>6:00 PM</strong></li>
|
||||
<li>Duración cita: <strong>60 minutos</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@push('scripts')
|
||||
<script>
|
||||
const hoy = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('fecha').setAttribute('min', hoy);
|
||||
|
||||
function bloquearDiaCompleto() {
|
||||
document.getElementById('hora_inicio').value = '09:00:00';
|
||||
document.getElementById('hora_fin').value = '19:00:00';
|
||||
document.getElementById('motivo').value = 'Día completo bloqueado';
|
||||
}
|
||||
|
||||
function bloquearManana() {
|
||||
document.getElementById('hora_inicio').value = '09:00:00';
|
||||
document.getElementById('hora_fin').value = '12:00:00';
|
||||
document.getElementById('motivo').value = 'Mañana bloqueada';
|
||||
}
|
||||
|
||||
function bloquearTarde() {
|
||||
document.getElementById('hora_inicio').value = '12:00:00';
|
||||
document.getElementById('hora_fin').value = '19:00:00';
|
||||
document.getElementById('motivo').value = 'Tarde bloqueada';
|
||||
}
|
||||
</script>
|
||||
@endpush
|
||||
154
resources/views/admin/horarios/edit.blade.php
Normal file
154
resources/views/admin/horarios/edit.blade.php
Normal file
@@ -0,0 +1,154 @@
|
||||
@extends('admin.layouts.master')
|
||||
|
||||
@section('title', 'Editar Bloqueo - Lash Vanshy')
|
||||
|
||||
@section('page-title', 'Editar Bloqueo')
|
||||
|
||||
@section('content')
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.horarios.index') }}">Horarios Bloqueados</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Editar Bloqueo</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="card-admin">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-edit me-2"></i>Editar Bloqueo
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="POST" action="{{ route('admin.horarios.update', $bloqueado) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div class="row">
|
||||
<!-- Fecha -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="fecha" class="form-label">
|
||||
Fecha <span class="text-danger">*</span>
|
||||
</label>
|
||||
<input type="date"
|
||||
class="form-control @error('fecha') is-invalid @enderror"
|
||||
id="fecha"
|
||||
name="fecha"
|
||||
value="{{ old('fecha', $bloqueado->fecha->format('Y-m-d')) }}"
|
||||
required>
|
||||
@error('fecha')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<!-- Hora Inicio -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="hora_inicio" class="form-label">
|
||||
Hora de Inicio <span class="text-danger">*</span>
|
||||
</label>
|
||||
<select class="form-select @error('hora_inicio') is-invalid @enderror"
|
||||
id="hora_inicio"
|
||||
name="hora_inicio"
|
||||
required>
|
||||
@for($hora = 9; $hora < 19; $hora++)
|
||||
<option value="{{ sprintf('%02d:00:00', $hora) }}"
|
||||
{{ old('hora_inicio', $bloqueado->hora_inicio) == sprintf('%02d:00:00', $hora) ? 'selected' : '' }}>
|
||||
{{ $hora <= 12 ? sprintf('%02d:00', $hora) : sprintf('%02d', $hora - 12) }}:00 {{ $hora >= 12 ? 'PM' : 'AM' }}
|
||||
</option>
|
||||
@endfor
|
||||
</select>
|
||||
@error('hora_inicio')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<!-- Hora Fin -->
|
||||
<div class="col-md-6 mb-3">
|
||||
<label for="hora_fin" class="form-label">
|
||||
Hora de Fin <span class="text-danger">*</span>
|
||||
</label>
|
||||
<select class="form-select @error('hora_fin') is-invalid @enderror"
|
||||
id="hora_fin"
|
||||
name="hora_fin"
|
||||
required>
|
||||
@for($hora = 9; $hora < 19; $hora++)
|
||||
<option value="{{ sprintf('%02d:00:00', $hora) }}"
|
||||
{{ old('hora_fin', $bloqueado->hora_fin) == sprintf('%02d:00:00', $hora) ? 'selected' : '' }}>
|
||||
{{ $hora <= 12 ? sprintf('%02d:00', $hora) : sprintf('%02d', $hora - 12) }}:00 {{ $hora >= 12 ? 'PM' : 'AM' }}
|
||||
</option>
|
||||
@endfor
|
||||
</select>
|
||||
@error('hora_fin')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Motivo -->
|
||||
<div class="mb-3">
|
||||
<label for="motivo" class="form-label">Motivo</label>
|
||||
<input type="text"
|
||||
class="form-control @error('motivo') is-invalid @enderror"
|
||||
id="motivo"
|
||||
name="motivo"
|
||||
value="{{ old('motivo', $bloqueado->motivo) }}"
|
||||
placeholder="Ej: Vacaciones, Mantenimiento...">
|
||||
@error('motivo')
|
||||
<div class="invalid-feedback">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a href="{{ route('admin.horarios.index') }}" class="btn btn-secondary-admin">
|
||||
<i class="fas fa-arrow-left me-2"></i>Volver
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary-admin">
|
||||
<i class="fas fa-save me-2"></i>Guardar Cambios
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sidebar -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card-admin mb-3">
|
||||
<div class="card-header">
|
||||
<i class="fas fa-info-circle me-2"></i>Información del Bloqueo
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-5">Creado:</dt>
|
||||
<dd class="col-sm-7">{{ $bloqueado->created_at->format('d/m/Y H:i') }}</dd>
|
||||
|
||||
<dt class="col-sm-5">Actualizado:</dt>
|
||||
<dd class="col-sm-7">{{ $bloqueado->updated_at->format('d/m/Y H:i') }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-admin">
|
||||
<div class="card-header bg-danger text-white">
|
||||
<i class="fas fa-exclamation-triangle me-2"></i>Eliminar Bloqueo
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-muted mb-3">
|
||||
¿Estás seguro de desbloquear este horario? Los clientes podrán agendar citas en este espacio.
|
||||
</p>
|
||||
<form action="{{ route('admin.horarios.destroy', $bloqueado) }}" method="POST"
|
||||
onsubmit="return confirm('¿Estás seguro de eliminar este bloqueo?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-danger-admin w-100">
|
||||
<i class="fas fa-unlock me-2"></i>Desbloquear
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
140
resources/views/admin/horarios/index.blade.php
Normal file
140
resources/views/admin/horarios/index.blade.php
Normal file
@@ -0,0 +1,140 @@
|
||||
@extends('admin.layouts.master')
|
||||
|
||||
@section('title', 'Horarios Bloqueados - Lash Vanshy')
|
||||
|
||||
@section('page-title', 'Horarios Bloqueados')
|
||||
|
||||
@section('content')
|
||||
<!-- Breadcrumb -->
|
||||
<nav aria-label="breadcrumb" class="mb-4">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{{ route('admin.dashboard') }}">Dashboard</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Horarios Bloqueados</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Actions & Filters -->
|
||||
<div class="card-admin mb-4">
|
||||
<div class="card-body">
|
||||
<div class="filters-bar">
|
||||
<form method="GET" class="d-flex gap-3 flex-wrap align-items-center">
|
||||
<div class="search-box">
|
||||
<i class="fas fa-calendar"></i>
|
||||
<input type="date" name="fecha" class="form-control"
|
||||
value="{{ request('fecha') }}">
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn btn-primary-admin">
|
||||
<i class="fas fa-filter me-2"></i>Filtrar
|
||||
</button>
|
||||
|
||||
@if(request('fecha'))
|
||||
<a href="{{ route('admin.horarios.index') }}" class="btn btn-secondary-admin">
|
||||
<i class="fas fa-times me-2"></i>Limpiar
|
||||
</a>
|
||||
@endif
|
||||
</form>
|
||||
|
||||
<div class="d-flex gap-2 ms-auto">
|
||||
<a href="{{ route('admin.horarios.create') }}" class="btn btn-primary-admin">
|
||||
<i class="fas fa-plus me-2"></i>Bloquear Horario
|
||||
</a>
|
||||
<a href="{{ route('admin.citas.index') }}" class="btn btn-secondary-admin">
|
||||
<i class="fas fa-arrow-left me-2"></i>Volver a Citas
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Horarios Table -->
|
||||
<div class="card-admin">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span>
|
||||
<i class="fas fa-clock me-2"></i>Horarios Bloqueados
|
||||
</span>
|
||||
<span class="text-muted small">
|
||||
Total: {{ $bloqueados->total() }} bloqueos
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
@if($bloqueados->isEmpty())
|
||||
<div class="empty-state">
|
||||
<i class="fas fa-calendar-check"></i>
|
||||
<h4>No hay horarios bloqueados</h4>
|
||||
<p>No se encontraron horarios bloqueados.</p>
|
||||
<a href="{{ route('admin.horarios.create') }}" class="btn btn-primary-admin mt-3">
|
||||
<i class="fas fa-plus me-2"></i>Bloquear Primer Horario
|
||||
</a>
|
||||
</div>
|
||||
@else
|
||||
<div class="table-responsive">
|
||||
<table class="table-admin">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Fecha</th>
|
||||
<th>Hora Inicio</th>
|
||||
<th>Hora Fin</th>
|
||||
<th>Motivo</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($bloqueados as $bloqueado)
|
||||
<tr>
|
||||
<td>
|
||||
<span class="fw-bold">
|
||||
<i class="fas fa-calendar-alt me-1 text-primary"></i>
|
||||
{{ \Carbon\Carbon::parse($bloqueado->fecha)->format('d/m/Y') }}
|
||||
</span>
|
||||
<span class="text-muted small d-block">
|
||||
{{ \Carbon\Carbon::parse($bloqueado->fecha)->format('l') }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ \Carbon\Carbon::parse($bloqueado->hora_inicio)->format('h:i A') }}</td>
|
||||
<td>{{ \Carbon\Carbon::parse($bloqueado->hora_fin)->format('h:i A') }}</td>
|
||||
<td>
|
||||
@if($bloqueado->motivo)
|
||||
<span class="text-muted">{{ $bloqueado->motivo }}</span>
|
||||
@else
|
||||
<span class="text-muted">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<div class="actions">
|
||||
<a href="{{ route('admin.horarios.edit', $bloqueado) }}"
|
||||
class="btn btn-sm btn-secondary-admin"
|
||||
title="Editar">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<form action="{{ route('admin.horarios.destroy', $bloqueado) }}" method="POST"
|
||||
onsubmit="return confirm('¿Estás seguro de desbloquear este horario?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="btn btn-sm btn-danger-admin" title="Eliminar">
|
||||
<i class="fas fa-unlock"></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<div class="pagination-wrapper p-3">
|
||||
{{ $bloqueados->withQueryString()->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Box -->
|
||||
<div class="alert alert-info mt-4">
|
||||
<i class="fas fa-info-circle me-2"></i>
|
||||
<strong>Nota:</strong> Los horarios bloqueados impedirán que se agenden citas en esos horarios.
|
||||
Utilice esta función para bloquear días completos o tramos específicos (ej: vacaciones, mantenimiento).
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user