- 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>
280 lines
10 KiB
PHP
Executable File
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>×</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
|