feat: profile_photo en contratos activos, notificaciones bilingues en reportes y foto en panel usuarios
- API contratos activos (cliente y proveedor): agrega campo profile_photo (técnico asignado o supplier, null si no tiene) - Notificaciones push bilingues en comentarios de reporte: "Nueva actividad en tu reporte" / "New activity on your report" - Moderador en web panel notifica a cliente y proveedor al comentar, con prefijo Moderador/Moderator - Panel usuarios: columna de foto de perfil entre ID y Nombre Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
<h1>{{isset($bank)?'Editar':'Nuevo'}} Banco</h1>
|
||||
<hr>
|
||||
@if(isset($bank))
|
||||
{!! Form::model($bank,['method'=>'put','id'=>'frm']) !!}
|
||||
@else
|
||||
{!! Form::open(['id'=>'frm']) !!}
|
||||
@endif
|
||||
<div class="form-group row required">
|
||||
{!! Form::label("code","Código",["class"=>"col-form-label col-md-3 col-lg-2"]) !!}
|
||||
<div class="col-md-8">
|
||||
{!! Form::number("code",null,["class"=>"form-control".($errors->has('code')?" is-invalid":""),"autofocus",'placeholder'=>'Código del banco']) !!}
|
||||
<span id="error-name" class="invalid-feedback"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row required">
|
||||
{!! Form::label("name","Banco",["class"=>"col-form-label col-md-3 col-lg-2"]) !!}
|
||||
<div class="col-md-8">
|
||||
{!! Form::text("name",null,["class"=>"form-control".($errors->has('name')?" is-invalid":""),"autofocus",'placeholder'=>'Nombre del banco']) !!}
|
||||
<span id="error-name" class="invalid-feedback"></span>
|
||||
</div>
|
||||
</div>
|
||||
@if ($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul>
|
||||
@foreach ($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
<div class="form-group row">
|
||||
<div class="col-md-3 col-lg-2"></div>
|
||||
<div class="col-md-4">
|
||||
<a href="{{url('banks')}}" class="btn btn-danger btn-xs">
|
||||
Atrás</a>
|
||||
{!! Form::button("Guardar",["type" => "submit","class"=>"btn btn-primary btn-xs"])!!}
|
||||
</div>
|
||||
</div>
|
||||
{!! Form::close() !!}
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -1,72 +0,0 @@
|
||||
|
||||
@if (Auth::user()->role_id >= 5)
|
||||
<div class="container-fluid" style="height:100%">
|
||||
<div>
|
||||
@else
|
||||
<div class="container" style="margin:0 1em">
|
||||
@endif
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-7">
|
||||
<h3>Tarjetas</h3>
|
||||
</div>
|
||||
<div class="col-sm-5">
|
||||
<div class="pull-right">
|
||||
{!! Form::open(['method'=>'GET','url'=>'cards','class'=>'navbar-form navbar-left','role'=>'search']) !!}
|
||||
<div class="input-group">
|
||||
<input class="form-control" id="search"
|
||||
value="{{ request()->session()->get('search') }}"
|
||||
placeholder="Buscar" name="search"
|
||||
type="text" id="search"/>
|
||||
<div class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="fa fa-search" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{!! Form::close() !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="vertical-align: middle"><a href="{{url('cards?field=id&sort='.(request()->session()->get('sort')=='asc'?'desc':'asc'))}}">ID</a></th>
|
||||
{{request()->session()->get('field')=='id'?(request()->session()->get('sort')=='asc'?'':''):''}}
|
||||
|
||||
<th style="vertical-align: middle"><a href="{{url('cards?field=user_id&sort='.(request()->session()->get('sort')=='asc'?'desc':'asc'))}}">Usuario</a></th>
|
||||
{{request()->session()->get('field')=='user_id'?(request()->session()->get('sort')=='asc'?'':''):''}}
|
||||
|
||||
<th style="vertical-align: middle"><a href="{{url('cards?field=user_id&sort='.(request()->session()->get('sort')=='asc'?'desc':'asc'))}}">Openpay ID</a></th>
|
||||
{{request()->session()->get('field')=='user_id'?(request()->session()->get('sort')=='asc'?'':''):''}}
|
||||
|
||||
<th style="vertical-align: middle"><a href="{{url('cards?field=token&sort='.(request()->session()->get('sort')=='asc'?'desc':'asc'))}}">Token</a></th>
|
||||
{{request()->session()->get('field')=='token'?(request()->session()->get('sort')=='asc'?'':''):''}}
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@php
|
||||
$i=1;
|
||||
@endphp
|
||||
@foreach ($cards as $card)
|
||||
<tr>
|
||||
<th>{{ $card->id }}</th>
|
||||
<th>{{ $card->user->name }}</th>
|
||||
<td>{{ $card->user->openpay_id }}</td>
|
||||
<td>{{ $card->token}}</td>
|
||||
<td>
|
||||
<input type="hidden" name="_method" value="delete"/>
|
||||
<a class="btn btn-danger btn-xs" title="Delete"
|
||||
href="javascript:if(confirm('¿Estás seguro de que quieres eliminar esta tarjeta?')) javascript:if(confirm('Usualmente no se deben eliminar tarjetas, ¿Estás seguro?')) ajaxDelete('{{url('cards/delete/'.$card->id)}}','{{csrf_token()}}')">
|
||||
<i class="fa fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ $cards->links() }}
|
||||
</div>
|
||||
@@ -111,7 +111,7 @@
|
||||
@else
|
||||
<li class="nav-item dropdown">
|
||||
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre style="padding-left:55px;">
|
||||
<img src="{{ isset(Auth::user()->profile_photo) ? asset('img/users/' . Auth::user()->id . '/' . Auth::user()->profile_photo) : asset('img/users/default.png') }}" style="width:48px; height:auto; position:absolute; top:-5px; left:-1px; border-radius:50%"/>
|
||||
<img src="{{ Auth::user()->profile_photo ?? asset('img/users/default.png') }}" style="width:48px; height:auto; position:absolute; top:-5px; left:-1px; border-radius:50%"/>
|
||||
{{ Auth::user()->name }} <span class="caret"></span>
|
||||
</a>
|
||||
|
||||
|
||||
2
resources/views/cards/ajax.blade.php → resources/views/payment-batches/ajax.blade.php
Executable file → Normal file
2
resources/views/cards/ajax.blade.php → resources/views/payment-batches/ajax.blade.php
Executable file → Normal file
@@ -2,7 +2,7 @@
|
||||
|
||||
@section('content')
|
||||
<div id="content">
|
||||
@include('cards.index')
|
||||
@include('payment-batches.index')
|
||||
</div>
|
||||
<div class="loading">
|
||||
<i class="fa fa-refresh fa-spin fa-2x fa-tw"></i>
|
||||
56
resources/views/payment-batches/index.blade.php
Normal file
56
resources/views/payment-batches/index.blade.php
Normal file
@@ -0,0 +1,56 @@
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-3">
|
||||
<div class="col">
|
||||
<h3>Pagos Realizados</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(session('error'))
|
||||
<div class="alert alert-danger">{{ session('error') }}</div>
|
||||
@endif
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Archivo</th>
|
||||
<th>Generado por</th>
|
||||
<th>Fecha de generación</th>
|
||||
<th>Rango de fechas</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($batches as $batch)
|
||||
<tr>
|
||||
<td>{{ $batch->id }}</td>
|
||||
<td>{{ $batch->file_name }}</td>
|
||||
<td>{{ optional($batch->user)->name ?? '—' }}</td>
|
||||
<td>{{ $batch->created_at->format('d/m/Y H:i') }}</td>
|
||||
<td>
|
||||
@if($batch->date_from || $batch->date_to)
|
||||
{{ $batch->date_from ? \Carbon\Carbon::parse($batch->date_from)->format('d/m/Y') : '—' }}
|
||||
al
|
||||
{{ $batch->date_to ? \Carbon\Carbon::parse($batch->date_to)->format('d/m/Y') : '—' }}
|
||||
@else
|
||||
Todos los registros
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<a href="{{ url('payment-batches/' . $batch->id . '/download') }}"
|
||||
class="btn btn-sm btn-outline-primary">
|
||||
<i class="fa fa-download"></i> Descargar
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="text-center text-muted">Sin pagos realizados aún.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ $batches->links() }}
|
||||
</div>
|
||||
@@ -13,27 +13,53 @@
|
||||
$sortUrl = fn($f) => url('payments') . '?' . http_build_query(array_filter(['date_from' => $dateFrom, 'date_to' => $dateTo, 'field' => $f, 'sort' => $currentField === $f ? $nextSort : 'desc']));
|
||||
@endphp
|
||||
|
||||
<div class="row">
|
||||
<div class="col-sm-5">
|
||||
<h3>Pagos a Proveedores</h3>
|
||||
@if(session('warning'))
|
||||
<div class="alert alert-warning">{{ session('warning') }}</div>
|
||||
@endif
|
||||
|
||||
<div class="row align-items-center mb-2">
|
||||
<div class="col-sm-4">
|
||||
<h3 class="mb-0">Pagos a Proveedores</h3>
|
||||
</div>
|
||||
<div class="col-sm-7">
|
||||
<div class="pull-right">
|
||||
{!! Form::open(['method' => 'GET', 'url' => 'payments', 'class' => 'form-inline', 'role' => 'search']) !!}
|
||||
<label class="mr-2 mb-0">Filtrar</label>
|
||||
<input type="date" class="form-control mr-1" name="date_from" value="{{ $dateFrom }}">
|
||||
<input type="date" class="form-control mr-1" name="date_to" value="{{ $dateTo }}">
|
||||
<button type="submit" class="btn btn-primary mr-1">
|
||||
<i class="fa fa-filter" aria-hidden="true"></i>
|
||||
<div class="col-sm-8">
|
||||
<div class="d-flex justify-content-end align-items-center flex-wrap" style="gap:.5rem">
|
||||
{!! Form::open(['method' => 'GET', 'url' => 'payments', 'class' => 'd-flex align-items-center', 'style' => 'gap:.4rem']) !!}
|
||||
<label class="mb-0 mr-1">Filtrar</label>
|
||||
<input type="date" class="form-control form-control-sm" name="date_from" value="{{ $dateFrom }}">
|
||||
<input type="date" class="form-control form-control-sm" name="date_to" value="{{ $dateTo }}">
|
||||
<button type="submit" class="btn btn-sm btn-primary">
|
||||
<i class="fa fa-filter"></i>
|
||||
</button>
|
||||
<a href="{{ url('payments') }}" class="btn btn-default">
|
||||
<i class="fa fa-times" aria-hidden="true"></i>
|
||||
<a href="{{ url('payments') }}" class="btn btn-sm btn-default">
|
||||
<i class="fa fa-times"></i>
|
||||
</a>
|
||||
{!! Form::close() !!}
|
||||
|
||||
<form method="POST" action="{{ url('payments/generate') }}" id="generateForm">
|
||||
@csrf
|
||||
<input type="hidden" name="date_from" value="{{ $dateFrom }}">
|
||||
<input type="hidden" name="date_to" value="{{ $dateTo }}">
|
||||
<button type="button" class="btn btn-sm btn-success" onclick="confirmGenerate()">
|
||||
<i class="fa fa-file-excel-o"></i> Generar Pagos
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function confirmGenerate() {
|
||||
var total = {{ $payments->total() }};
|
||||
if (total === 0) {
|
||||
alert('No hay contratos pendientes de pago.');
|
||||
return;
|
||||
}
|
||||
if (confirm('Se generará un archivo Excel con ' + total + ' proveedor(es) y se marcarán sus contratos como pagados. ¿Continuar?')) {
|
||||
document.getElementById('generateForm').submit();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -21,11 +21,13 @@
|
||||
gap: 1rem;
|
||||
position: relative;
|
||||
}
|
||||
.chat-info .btn-verdict {
|
||||
.chat-info .btn-actions {
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
.chat-info span { color: #495057; }
|
||||
.chat-info strong { color: #212529; }
|
||||
@@ -135,10 +137,16 @@
|
||||
@if($report->veredict)
|
||||
<span>Veredicto: <strong>{{ $report->veredict }}</strong></span>
|
||||
@endif
|
||||
<a class="btn btn-secondary btn-xs btn-verdict" title="Veredict"
|
||||
href="{{ url('reports/veredict/' . $report->id) }}">
|
||||
<i class="fa fa-gavel"></i>
|
||||
</a>
|
||||
<div class="btn-actions">
|
||||
<button type="button" class="btn btn-info btn-xs" title="Ver detalles"
|
||||
data-toggle="modal" data-target="#modalDetalles">
|
||||
<i class="fa fa-info-circle"></i>
|
||||
</button>
|
||||
<a class="btn btn-secondary btn-xs" title="Veredicto"
|
||||
href="{{ url('reports/veredict/' . $report->id) }}">
|
||||
<i class="fa fa-gavel"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Leyenda --}}
|
||||
@@ -196,4 +204,76 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{{-- Modal fuera del chat-wrapper para evitar stacking context del flex --}}
|
||||
<div class="modal fade" id="modalDetalles" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Detalles del Contrato #{{ $contract->id }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal">
|
||||
<span>×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
||||
<h6 class="font-weight-bold mb-2">Información del contrato</h6>
|
||||
<table class="table table-sm table-borderless mb-4">
|
||||
<tr><th style="width:35%">Cliente</th><td>{{ $contract->user->name ?? '—' }}</td></tr>
|
||||
<tr><th>Proveedor</th><td>{{ $contract->suppliers->company_name ?? '—' }}</td></tr>
|
||||
<tr><th>Categoría</th><td>{{ $contract->categories->name ?? '—' }}</td></tr>
|
||||
<tr><th>Dirección</th><td>{{ $contract->address }}</td></tr>
|
||||
<tr><th>Cita</th><td>{{ $contract->appointment }}</td></tr>
|
||||
<tr><th>Monto</th><td>${{ $contract->amount }}</td></tr>
|
||||
<tr><th>Estado</th><td>{{ $contract->status->name ?? $contract->status_id }}</td></tr>
|
||||
<tr><th>Transaction ID</th><td><code>{{ $contract->transaction_id ?? '—' }}</code></td></tr>
|
||||
</table>
|
||||
|
||||
@if($nohome)
|
||||
<hr>
|
||||
<h6 class="font-weight-bold mb-2">Evidencia del proveedor (No Home)</h6>
|
||||
<table class="table table-sm table-borderless mb-3">
|
||||
<tr>
|
||||
<th style="width:35%">Descripción</th>
|
||||
<td>{{ $nohome->house_description ?? '—' }}</td>
|
||||
</tr>
|
||||
@if($nohome->location)
|
||||
<tr>
|
||||
<th>Coordenadas GPS</th>
|
||||
<td>
|
||||
{{ $nohome->location->getLatitude() }}, {{ $nohome->location->getLongitude() }}
|
||||
<a href="https://www.google.com/maps?q={{ $nohome->location->getLatitude() }},{{ $nohome->location->getLongitude() }}"
|
||||
target="_blank" class="btn btn-xs btn-outline-secondary ml-2">
|
||||
<i class="fa fa-map-marker"></i> Ver en mapa
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
@endif
|
||||
</table>
|
||||
@if($nohome->house_photo)
|
||||
<img src="{{ $nohome->house_photo }}" alt="Foto del domicilio"
|
||||
class="img-fluid rounded" style="max-height:400px;">
|
||||
@endif
|
||||
@else
|
||||
<hr>
|
||||
<p class="text-muted mb-0"><em>El proveedor no registró evidencia de visita.</em></p>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cerrar</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('js')
|
||||
<script>
|
||||
// main-content tiene overflow-y:auto que crea stacking context
|
||||
// Mover el modal a body para que Bootstrap pueda posicionarlo correctamente
|
||||
$(document).ready(function () {
|
||||
$('#modalDetalles').appendTo('body');
|
||||
});
|
||||
</script>
|
||||
@endsection
|
||||
|
||||
@@ -17,6 +17,20 @@
|
||||
<span id="error-name" class="invalid-feedback"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
{!! Form::label("contract_status","Acción sobre el contrato",["class"=>"col-form-label col-md-3 col-lg-2"]) !!}
|
||||
<div class="col-md-8">
|
||||
{!! Form::select("contract_status", [
|
||||
'' => '— Sin cambio de estatus —',
|
||||
'8' => 'Devuelto al cliente (reembolso Stripe)',
|
||||
'9' => 'Disputa terminada (pagar al proveedor)',
|
||||
], null, ["class"=>"form-control"]) !!}
|
||||
<small class="text-muted">
|
||||
"Devuelto al cliente" emite el reembolso en Stripe automáticamente.
|
||||
"Disputa terminada" aparece en la sección de Pagos.
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
@if ($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul>
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
<a href="/banks" class="sidebar-link {{ Request::is('banks*') ? 'active' : '' }}">
|
||||
<i class="fa fa-university"></i> Bancos
|
||||
</a>
|
||||
<a href="/cards" class="sidebar-link {{ Request::is('cards*') ? 'active' : '' }}">
|
||||
<i class="fa fa-credit-card"></i> Tarjetas
|
||||
</a>
|
||||
<a href="/payments" class="sidebar-link {{ Request::is('payments*') ? 'active' : '' }}">
|
||||
<a href="/payments" class="sidebar-link {{ Request::is('payments') ? 'active' : '' }}">
|
||||
<i class="fa fa-money"></i> Pagos
|
||||
</a>
|
||||
<a href="/payment-batches" class="sidebar-link {{ Request::is('payment-batches*') ? 'active' : '' }}">
|
||||
<i class="fa fa-history"></i> Pagos Realizados
|
||||
</a>
|
||||
<a href="/postulations" class="sidebar-link {{ Request::is('postulations*') ? 'active' : '' }}">
|
||||
<i class="fa fa-file-text"></i> Postulaciones
|
||||
</a>
|
||||
|
||||
@@ -28,7 +28,8 @@
|
||||
'4' => 'Facturación',
|
||||
'5' => 'Moderador',
|
||||
'6' => 'Administrador',
|
||||
'7' => 'SuperAdmin'
|
||||
'7' => 'SuperAdmin',
|
||||
'8' => 'Técnico'
|
||||
), $user->roles->id) !!}
|
||||
<span id="error-name" class="invalid-feedback"></span>
|
||||
</div>
|
||||
@@ -42,13 +43,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row required">
|
||||
{!! Form::label("openpay_id","Openpay ID",["class"=>"col-form-label col-md-3 col-lg-2"]) !!}
|
||||
<div class="col-md-8">
|
||||
{!! Form::text("openpay_id",null,["class"=>"form-control".($errors->has('openpay_id')?" is-invalid":""),'placeholder'=>'Openpay ID']) !!}
|
||||
<span id="error-openpay_id" class="invalid-feedback"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if ($errors->any())
|
||||
<div class="alert alert-danger">
|
||||
<ul>
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
<th style="vertical-align: middle"><a href="{{url('users?field=id&sort='.(request()->session()->get('sort')=='asc'?'desc':'asc'))}}">ID</a></th>
|
||||
{{request()->session()->get('field')=='id'?(request()->session()->get('sort')=='asc'?'':''):''}}
|
||||
|
||||
<th style="vertical-align: middle">Foto</th>
|
||||
|
||||
<th style="vertical-align: middle"><a href="{{url('users?field=name&sort='.(request()->session()->get('sort')=='asc'?'desc':'asc'))}}">Nombre</a></th>
|
||||
{{request()->session()->get('field')=='name'?(request()->session()->get('sort')=='asc'?'':''):''}}
|
||||
|
||||
@@ -70,6 +72,13 @@
|
||||
@foreach ($users as $user)
|
||||
<tr>
|
||||
<th>{{ $user->id }}</th>
|
||||
<td>
|
||||
@if($user->profile_photo)
|
||||
<img src="{{ $user->profile_photo }}" style="width:36px;height:36px;object-fit:cover;border-radius:50%;">
|
||||
@else
|
||||
<span class="text-muted">—</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $user->name }}</td>
|
||||
<td >{{ $user->email }}</td>
|
||||
<td >{{ $user->roles->name }}</td>
|
||||
|
||||
Reference in New Issue
Block a user