✨ feat(etatLieux): Implémente l'état des lieux de retour (médias, commentaires, points de contrôle, signatures et refus client).
This commit is contained in:
@@ -21,6 +21,26 @@
|
||||
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Photos & Vidéos</h3>
|
||||
|
||||
<div class="grid grid-cols-3 gap-2 mb-4">
|
||||
{% for file in etatLieux.fileReturn %}
|
||||
<div class="aspect-square bg-slate-100 rounded-xl overflow-hidden relative group">
|
||||
{% if file.type == 'photo' %}
|
||||
<img src="{{ vich_uploader_asset(file, 'file') }}" alt="Photo" class="w-full h-full object-cover cursor-pointer lightbox-trigger" data-type="photo" data-src="{{ vich_uploader_asset(file, 'file') }}">
|
||||
{% else %}
|
||||
<div class="w-full h-full relative cursor-pointer lightbox-trigger" data-type="video" data-src="{{ vich_uploader_asset(file, 'file') }}">
|
||||
<video src="{{ vich_uploader_asset(file, 'file') }}" class="w-full h-full object-cover pointer-events-none"></video>
|
||||
<div class="absolute inset-0 flex items-center justify-center bg-black/20 text-white pointer-events-none">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /></svg>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form action="{{ path('etl_edl_delete_file', {id: mission.id, fileId: file.id}) }}" method="post" class="absolute top-1 right-1 z-10">
|
||||
<button type="submit" class="bg-red-500/80 text-white p-1 rounded-full hover:bg-red-600 transition-colors shadow-sm" onclick="return confirm('Supprimer ce fichier ?')">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for file in etatLieux.files %}
|
||||
<div class="aspect-square bg-slate-100 rounded-xl overflow-hidden relative group">
|
||||
{% if file.type == 'photo' %}
|
||||
@@ -33,16 +53,14 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<form action="{{ path('etl_edl_delete_file', {id: mission.id, fileId: file.id}) }}" method="post" class="absolute top-1 right-1 z-10">
|
||||
<button type="submit" class="bg-red-500/80 text-white p-1 rounded-full hover:bg-red-600 transition-colors shadow-sm" onclick="return confirm('Supprimer ce fichier ?')">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" /></svg>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-span-3 text-center py-4 text-slate-400 text-xs italic">Aucun média ajouté.</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{# LIGHTBOX MODAL #}
|
||||
@@ -69,7 +87,7 @@
|
||||
<input type="file" name="videos[]" accept="video/*" multiple class="hidden">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
<button type="submit" class="w-full py-3 bg-slate-900 text-white rounded-xl text-xs font-bold uppercase tracking-wide hover:bg-slate-800 transition-colors shadow-lg">
|
||||
Envoyer les fichiers
|
||||
</button>
|
||||
@@ -79,7 +97,7 @@
|
||||
{# POINTS DE CONTROLE #}
|
||||
<div class="bg-white rounded-[2rem] p-6 border border-slate-100 shadow-sm">
|
||||
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Points de Contrôle</h3>
|
||||
|
||||
|
||||
<form action="{{ path('etl_edl_save_points', {id: mission.id}) }}" method="post" id="form-points">
|
||||
{% for reserve in mission.productReserves %}
|
||||
{% set product = reserve.product %}
|
||||
@@ -93,24 +111,31 @@
|
||||
{% for point in product.productPointControlls %}
|
||||
{# Try to find existing status/comment in etatLieux.pointControls #}
|
||||
{% set existingPoint = null %}
|
||||
{% for ep in etatLieux.pointControls %}
|
||||
{% if ep.name == point.name %}
|
||||
{% set existingPoint = ep %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if etatLieux.status == "return_edl_progress" %}
|
||||
{% for ep in etatLieux.pointControlsReturn %}
|
||||
{% if ep.name == point.name %}
|
||||
{% set existingPoint = ep %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for ep in etatLieux.pointControls %}
|
||||
{% if ep.name == point.name %}
|
||||
{% set existingPoint = ep %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<div class="bg-slate-50 p-3 rounded-xl">
|
||||
<div class="flex items-start gap-3">
|
||||
<div class="pt-1">
|
||||
<input type="checkbox" name="points[{{ product.id }}][{{ point.id }}][status]" value="1"
|
||||
<input type="checkbox" name="points[{{ product.id }}][{{ point.id }}][status]" value="1"
|
||||
class="w-5 h-5 rounded-md border-slate-300 text-blue-600 focus:ring-blue-500"
|
||||
{{ existingPoint and existingPoint.status ? 'checked' : '' }}>
|
||||
</div>
|
||||
<div class="flex-1">
|
||||
<p class="text-sm font-bold text-slate-700 mb-1">{{ point.name }}</p>
|
||||
<input type="text" name="points[{{ product.id }}][{{ point.id }}][details]"
|
||||
<input type="text" name="points[{{ product.id }}][{{ point.id }}][details]"
|
||||
value="{{ existingPoint ? existingPoint.details : '' }}"
|
||||
placeholder="Commentaire (optionnel)..."
|
||||
placeholder="Commentaire (optionnel)..."
|
||||
class="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-xs focus:outline-none focus:border-blue-500 transition-colors">
|
||||
</div>
|
||||
</div>
|
||||
@@ -120,7 +145,7 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
|
||||
<button type="submit" class="w-full py-3 bg-slate-900 text-white rounded-xl text-xs font-bold uppercase tracking-wide hover:bg-slate-800 transition-colors shadow-lg mt-4">
|
||||
Enregistrer les contrôles
|
||||
</button>
|
||||
@@ -132,14 +157,22 @@
|
||||
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Commentaires</h3>
|
||||
|
||||
<div class="space-y-3 mb-4">
|
||||
{% for comment in etatLieux.comments %}
|
||||
<div class="bg-slate-50 p-3 rounded-xl">
|
||||
<p class="text-[10px] font-bold text-slate-400 mb-1">{{ comment.createdAt|date('d/m H:i') }}</p>
|
||||
<p class="text-sm text-slate-700">{{ comment.content }}</p>
|
||||
</div>
|
||||
{% if etatLieux.status == "return_edl_progress" %}
|
||||
{% for comment in etatLieux.commentsReturn %}
|
||||
<div class="bg-slate-50 p-3 rounded-xl">
|
||||
<p class="text-[10px] font-bold text-slate-400 mb-1">{{ comment.createdAt|date('d/m H:i') }}</p>
|
||||
<p class="text-sm text-slate-700">{{ comment.content }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<p class="text-center py-2 text-slate-400 text-xs italic">Aucun commentaire.</p>
|
||||
{% endfor %}
|
||||
{% for comment in etatLieux.comments %}
|
||||
<div class="bg-slate-50 p-3 rounded-xl">
|
||||
<p class="text-[10px] font-bold text-slate-400 mb-1">{{ comment.createdAt|date('d/m H:i') }}</p>
|
||||
<p class="text-sm text-slate-700">{{ comment.content }}</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
<form action="{{ path('etl_edl_add_comment', {id: mission.id}) }}" method="post" class="flex gap-2">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block body %}
|
||||
<div class="space-y-8 animate-in fade-in slide-in-from-bottom-4 duration-500">
|
||||
|
||||
|
||||
{# HEADER #}
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="{{ path('etl_contrat_view', {id: mission.id}) }}" class="w-10 h-10 bg-white rounded-xl border border-slate-100 flex items-center justify-center text-slate-400 hover:text-blue-600 transition-all shadow-sm">
|
||||
@@ -20,7 +20,7 @@
|
||||
<div class="absolute top-0 right-0 -mr-8 -mt-8 w-32 h-32 bg-white/10 rounded-full blur-2xl"></div>
|
||||
<h2 class="text-2xl font-black mb-2">État des Lieux Terminé</h2>
|
||||
<p class="text-sm font-medium opacity-90 mb-6">Veuillez procéder à la signature du document.</p>
|
||||
|
||||
|
||||
<a href="{{ path('etl_edl_regenerate_view', {id: mission.id}) }}" target="_blank" class="inline-flex items-center px-6 py-3 bg-white/20 hover:bg-white/30 text-white rounded-xl text-xs font-bold uppercase tracking-widest backdrop-blur-sm transition-all border border-white/30">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>
|
||||
Voir le PDF (Actualiser)
|
||||
@@ -31,7 +31,7 @@
|
||||
<div class="space-y-4">
|
||||
<div class="p-6 bg-white rounded-[2rem] border border-slate-100 shadow-sm">
|
||||
<h3 class="text-xs font-black text-slate-400 uppercase tracking-widest mb-4">Signatures Requises</h3>
|
||||
|
||||
|
||||
<div class="space-y-3">
|
||||
{% if providerSigned %}
|
||||
<div class="w-full py-4 bg-emerald-500/10 border border-emerald-500/20 text-emerald-600 rounded-2xl font-black uppercase text-sm tracking-widest flex items-center justify-center gap-3">
|
||||
@@ -55,6 +55,32 @@
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" /></svg>
|
||||
Faire Signer (Client)
|
||||
</a>
|
||||
{# ... (sous le bouton "Faire Signer (Client)") ... #}
|
||||
|
||||
{% if etatLieux.status == "edl_return_done" %}
|
||||
<div class="mt-4 p-4 bg-red-50 rounded-2xl border border-red-100">
|
||||
<form action="{{ path('etl_edl_customer_refused', {id: mission.id}) }}" method="post" onsubmit="return confirm('Confirmer le refus de signature ?');">
|
||||
<label for="refusal_reason" class="block text-[10px] font-black text-red-400 uppercase tracking-widest mb-2 ml-1">
|
||||
Raison du refus (Optionnel)
|
||||
</label>
|
||||
|
||||
<textarea
|
||||
name="refusal_reason"
|
||||
id="refusal_reason"
|
||||
rows="2"
|
||||
placeholder="Ex: Désaccord sur les dommages carrosserie..."
|
||||
class="w-full px-4 py-3 rounded-xl border-none text-sm text-slate-900 placeholder:text-slate-300 focus:ring-2 focus:ring-red-200 transition-all mb-3 shadow-inner"
|
||||
></textarea>
|
||||
|
||||
<button type="submit" class="w-full py-3 bg-red-600 text-white rounded-xl font-black uppercase text-[10px] tracking-widest hover:bg-red-700 transition-all flex items-center justify-center gap-2 shadow-lg shadow-red-600/20">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636" />
|
||||
</svg>
|
||||
Valider le refus de signer
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -117,10 +117,12 @@
|
||||
Reprendre l'état des lieux
|
||||
</a>
|
||||
{% elseif mission.etatLieux.status == 'edl_validated' %}
|
||||
<a href="{{ path('etl_mission_edl_return', {id: mission.id}) }}" class="w-full py-4 bg-indigo-600 hover:bg-indigo-500 text-white rounded-2xl font-black uppercase text-sm tracking-widest shadow-lg shadow-indigo-600/30 transition-all active:scale-95 flex items-center justify-center gap-3 mb-6">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z" /></svg>
|
||||
Commenter l'état des lieux de retour
|
||||
</a>
|
||||
<form action="{{ path('etl_mission_edl_return_start', {id: mission.id}) }}" method="post" class="mt-6">
|
||||
<button type="submit" class="w-full py-4 bg-indigo-600 hover:bg-indigo-500 text-white rounded-2xl font-black uppercase text-sm tracking-widest shadow-lg shadow-indigo-600/30 transition-all active:scale-95 flex items-center justify-center gap-3">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" /></svg>
|
||||
Commenter l'état des lieux de retour
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{# DATES #}
|
||||
|
||||
54
templates/mails/etl/edl_refused_alert.twig
Normal file
54
templates/mails/etl/edl_refused_alert.twig
Normal file
@@ -0,0 +1,54 @@
|
||||
{% extends 'mails/base.twig' %}
|
||||
|
||||
{% block content %}
|
||||
<mj-section background-color="#fef2f2" border-radius="12px" padding="20px">
|
||||
<mj-column>
|
||||
<mj-text color="#b91c1c" font-weight="black" font-size="18px" text-transform="uppercase" align="center">
|
||||
⚠️ ALERTE : Signature Refusée (Retour)
|
||||
</mj-text>
|
||||
</mj-column>
|
||||
</mj-section>
|
||||
|
||||
<mj-text padding-top="20px">
|
||||
Bonjour l'équipe <strong>Ludikevent</strong>,
|
||||
</mj-text>
|
||||
<mj-text font-size="14px">
|
||||
Lors de l'état des lieux de <strong>retour</strong> pour la réservation <strong>#{{ datas.contrat.numReservation }}</strong>, le client a refusé de procéder à la signature.
|
||||
</mj-text>
|
||||
|
||||
<mj-text font-weight="bold" font-size="14px" padding-top="20px" color="#b91c1c">
|
||||
RAISON DU REFUS INDIQUÉE :
|
||||
</mj-text>
|
||||
<mj-text font-style="italic" padding="10px 20px" background-color="#f8fafc" border-left="4px solid #b91c1c">
|
||||
{{ datas.reason|default('Aucune raison spécifique n\'a été saisie par le prestataire.') }}
|
||||
</mj-text>
|
||||
|
||||
<mj-text font-weight="bold" font-size="14px" padding-top="20px">
|
||||
Détails de la mission :
|
||||
</mj-text>
|
||||
<mj-text>
|
||||
<strong>Client :</strong> {{ datas.contrat.customer.surname }} {{ datas.contrat.customer.name }}<br>
|
||||
<strong>Intervenant :</strong> {{ datas.prestataire.surname|default('') }} {{ datas.prestataire.name|default('Non assigné') }}<br>
|
||||
<strong>Lieu :</strong> {{ datas.contrat.addressEvent }} {{ datas.contrat.zipCodeEvent }} {{ datas.contrat.townEvent }}
|
||||
</mj-text>
|
||||
|
||||
{# Observations de retour (on utilise commentsReturn si c'est le champ lié aux remarques de sortie) #}
|
||||
{% if datas.contrat.etatLieux.commentsReturn|length > 0 %}
|
||||
<mj-text font-weight="bold" font-size="14px" padding-top="20px">
|
||||
Observations saisies lors du retour :
|
||||
</mj-text>
|
||||
{% for comment in datas.contrat.etatLieux.commentsReturn %}
|
||||
<mj-text padding-bottom="2px">
|
||||
- {{ comment.content }}
|
||||
</mj-text>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<mj-button href="{{ system.path }}{{ path('etl_contrat_view', {id: datas.contrat.id}) }}" background-color="#3b82f6" border-radius="8px" font-weight="bold" padding-top="30px">
|
||||
Consulter le dossier complet
|
||||
</mj-button>
|
||||
|
||||
<mj-text padding-top="30px" font-size="12px" color="#64748b" align="center">
|
||||
L'état des lieux a été marqué avec le statut <strong>"Refusé"</strong>. Veuillez vérifier les fichiers médias joints pour constater d'éventuels dommages.
|
||||
</mj-text>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user