Files
Jobhero_back/resources/views/reports/comments.blade.php
Carlos 0ed98c96ee 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>
2026-06-23 17:17:14 -06:00

280 lines
10 KiB
PHP
Executable File

@extends('layouts.app')
@push('styles')
<style>
.chat-wrapper {
display: flex;
flex-direction: column;
height: 100%;
padding: 1rem;
}
.chat-info {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 0.75rem 3.5rem 0.75rem 1rem;
margin-bottom: 1rem;
font-size: 0.85rem;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
position: relative;
}
.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; }
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 0.5rem;
display: flex;
flex-direction: column;
gap: 0.75rem;
margin-bottom: 1rem;
}
.bubble-row {
display: flex;
align-items: flex-end;
gap: 0.5rem;
}
.bubble-row.right { justify-content: flex-end; }
.bubble-row.left { justify-content: flex-start; }
.bubble-row.center { justify-content: center; }
.bubble {
max-width: 60%;
padding: 0.6rem 0.9rem;
border-radius: 16px;
font-size: 0.875rem;
line-height: 1.4;
position: relative;
}
.bubble-row.right .bubble {
background: #0d6efd;
color: #fff;
border-bottom-right-radius: 4px;
}
.bubble-row.left .bubble {
background: #198754;
color: #fff;
border-bottom-left-radius: 4px;
}
.bubble-row.center .bubble {
background: #e9ecef;
color: #495057;
border-radius: 16px;
font-style: italic;
text-align: center;
max-width: 70%;
}
.bubble-meta {
font-size: 0.7rem;
margin-top: 0.25rem;
opacity: 0.75;
}
.bubble-row.right .bubble-meta { text-align: right; color: rgba(255,255,255,0.85); }
.bubble-row.left .bubble-meta { text-align: left; color: rgba(255,255,255,0.85); }
.bubble-row.center .bubble-meta { text-align: center; color: #6c757d; }
.bubble-label {
font-size: 0.7rem;
font-weight: 600;
margin-bottom: 0.2rem;
opacity: 0.85;
}
.chat-input {
border-top: 1px solid #dee2e6;
padding-top: 0.75rem;
}
.chat-input form {
display: flex;
gap: 0.5rem;
align-items: flex-start;
}
.chat-input textarea {
flex: 1;
resize: none;
padding: 0.5rem 1rem;
font-size: 0.875rem;
}
.chat-input button {
border-radius: 20px;
padding: 0.5rem 1.25rem;
}
.verdict-badge {
background: #fff3cd;
border: 1px solid #ffc107;
border-radius: 8px;
padding: 0.5rem 1rem;
font-size: 0.85rem;
margin-bottom: 1rem;
}
</style>
@endpush
@section('content')
@php
$clientId = $contract->user_id;
$supplierId = $contract->suppliers->user_id ?? null;
@endphp
<div class="chat-wrapper">
{{-- Info del contrato --}}
<div class="chat-info">
<span><strong>Contrato #{{ $contract->id }}</strong></span>
<span>Cliente: <strong>{{ $contract->user->name ?? '—' }}</strong></span>
<span>Proveedor: <strong>{{ $contract->suppliers->company_name ?? '—' }}</strong></span>
<span>Categoría: <strong>{{ $contract->categories->name ?? '—' }}</strong></span>
<span>Monto: <strong>${{ $contract->amount }}</strong></span>
<span>Cita: <strong>{{ $contract->appointment }}</strong></span>
@if($report->veredict)
<span>Veredicto: <strong>{{ $report->veredict }}</strong></span>
@endif
<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 --}}
<div class="d-flex gap-3 mb-2" style="font-size:0.78rem; gap:1rem;">
<span><span style="display:inline-block;width:12px;height:12px;background:#0d6efd;border-radius:3px;"></span> Cliente</span>
<span><span style="display:inline-block;width:12px;height:12px;background:#198754;border-radius:3px;"></span> Proveedor</span>
<span><span style="display:inline-block;width:12px;height:12px;background:#e9ecef;border:1px solid #ccc;border-radius:3px;"></span> Moderador</span>
</div>
{{-- Mensajes --}}
<div class="chat-messages">
@forelse($comments as $comment)
@php
if ($comment->user_id == $clientId) {
$side = 'right';
$label = 'Cliente';
} elseif ($comment->user_id == $supplierId) {
$side = 'left';
$label = 'Proveedor';
} else {
$side = 'center';
$label = 'Moderador';
}
@endphp
<div class="bubble-row {{ $side }}">
<div>
<div class="bubble-label text-muted">{{ $label }} {{ $comment->user->name ?? '—' }}</div>
<div class="bubble">
{{ $comment->comment }}
<div class="bubble-meta">{{ $comment->created_at->format('d/m/Y H:i') }}</div>
</div>
</div>
</div>
@empty
<p class="text-center text-muted mt-4">Sin comentarios aún.</p>
@endforelse
</div>
{{-- Paginación --}}
@if($comments->hasPages())
<div class="mb-2">{{ $comments->links() }}</div>
@endif
{{-- Input del moderador --}}
<div class="chat-input">
<form method="POST" action="{{ url('reports/' . $report->id . '/comments') }}">
@csrf
@error('comment')
<div class="text-danger mb-1" style="font-size:0.8rem;">{{ $message }}</div>
@enderror
<textarea name="comment" rows="2" class="form-control" placeholder="Escribir comentario como moderador..."></textarea>
<button type="submit" class="btn btn-primary mt-2">Enviar</button>
</form>
</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>&times;</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