feat(proyectos): mejorar feedback visual del toggle visibilidad en avances

- Botón de ojo reemplazado por badge con etiqueta Visible/Oculto
- Toast de confirmación al cambiar visibilidad (3s, estilo app)
- Tooltip descriptivo con acción esperada

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Lizandro Guarnizo
2026-05-19 16:57:47 -05:00
co-authored by Copilot
parent 94e945b5bc
commit 749cf7ef2f
+26 -3
View File
@@ -75,8 +75,12 @@
<p class="text-sm text-slate-600 mt-1" x-text="av.contenido"></p>
</div>
<div class="flex gap-1 ml-4">
<button @click="toggleAvanceVisible(av)" :title="av.visible ? 'Ocultar' : 'Publicar'" class="btn-icon" :class="av.visible ? 'text-green-500' : 'text-slate-400'">
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" 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>
<button @click="toggleAvanceVisible(av)"
:title="av.visible ? 'Visible en portal · clic para ocultar' : 'Oculto en portal · clic para publicar'"
class="btn-icon flex items-center gap-1 px-2 py-1 rounded-lg text-xs font-medium border transition-colors"
:class="av.visible ? 'text-green-600 border-green-200 bg-green-50 hover:bg-green-100' : 'text-slate-400 border-slate-200 bg-slate-50 hover:bg-slate-100'">
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/><path stroke-linecap="round" stroke-linejoin="round" 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>
<span x-text="av.visible ? 'Visible' : 'Oculto'"></span>
</button>
<button @click="deleteAvance(av.ID)" class="btn-icon text-red-400"><svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" 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>
</div>
@@ -306,6 +310,17 @@
</div>
</div>
<!-- Toast -->
<div x-show="toast.show" x-cloak x-transition
class="fixed bottom-6 right-6 z-50 px-5 py-3 rounded-xl shadow-lg text-white text-sm font-medium flex items-center gap-2"
:class="toast.type === 'error' ? 'bg-red-500' : 'bg-[#8eb02f]'">
<svg class="w-4 h-4 shrink-0" fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24">
<path x-show="toast.type !== 'error'" stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7"/>
<path x-show="toast.type === 'error'" stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12"/>
</svg>
<span x-text="toast.msg"></span>
</div>
</div>
<script>
@@ -325,6 +340,7 @@ function proyectoDetalle(proyectoId, slug) {
showDocumentoModal: false,
documentoForm: { tipo:'contrato', nombre:'', descripcion:'' },
documentoFile: null, documentoError:'',
toast: { show: false, msg: '', type: 'ok' },
async init() {
await Promise.all([this.loadFases(), this.loadAvances(), this.loadEntregables(), this.loadTickets(), this.loadDocumentos()]);
@@ -390,8 +406,10 @@ function proyectoDetalle(proyectoId, slug) {
await this.loadAvances();
},
async toggleAvanceVisible(av) {
await axios.put(`/app/proyectos/${this.proyectoId}/avances/${av.ID}`, {...av, visible:!av.visible});
const nuevoEstado = !av.visible;
await axios.put(`/app/proyectos/${this.proyectoId}/avances/${av.ID}`, {...av, visible:nuevoEstado});
await this.loadAvances();
this.showToast(nuevoEstado ? 'Avance visible en el portal del cliente' : 'Avance oculto en el portal del cliente');
},
// ─── Entregables ───
@@ -462,6 +480,11 @@ function proyectoDetalle(proyectoId, slug) {
t._reply='';
},
showToast(msg, type = 'ok') {
this.toast = { show: true, msg, type };
setTimeout(() => { this.toast.show = false; }, 3000);
},
// ─── Helpers ───
faseBadgeClass(e) { return {pendiente:'badge-slate',en_progreso:'badge-yellow',completado:'badge-green',bloqueado:'badge-red'}[e]||'badge-slate'; },
avanceBadgeClass(t) { return {update:'badge-blue',milestone:'badge-green',nota:'badge-slate',alerta:'badge-yellow'}[t]||'badge-slate'; },