up
This commit is contained in:
@@ -25,12 +25,15 @@
|
||||
<!-- Tabs -->
|
||||
<div class="flex border-b border-slate-200 mb-6 gap-0 overflow-x-auto">
|
||||
<template x-for="tab in ['Roadmap','Avances','Entregables','Tickets','Facturas']" :key="tab">
|
||||
<button @click="activeTab=tab; if(tab==='Tickets') marcarTicketsPendientesLeidos()" class="relative px-4 py-2 text-sm whitespace-nowrap border-b-2 -mb-px transition-colors"
|
||||
<button @click="activeTab=tab; if(tab==='Tickets') marcarTicketsPendientesLeidos(); if(tab==='Facturas') marcarFacturasLeidas()" class="relative px-4 py-2 text-sm whitespace-nowrap border-b-2 -mb-px transition-colors"
|
||||
:class="activeTab===tab ? 'font-semibold border-[#8eb02f] text-[#8eb02f]' : 'border-transparent text-slate-500 hover:text-slate-700'">
|
||||
<span x-text="tab"></span>
|
||||
<template x-if="tab === 'Tickets' && ticketsNoLeidos > 0">
|
||||
<span class="absolute -top-1 -right-1 min-w-[18px] h-[18px] bg-red-500 text-white text-[10px] font-bold rounded-full flex items-center justify-center px-1" x-text="ticketsNoLeidos > 9 ? '9+' : ticketsNoLeidos"></span>
|
||||
</template>
|
||||
<template x-if="tab === 'Facturas' && facturasNoLeidas > 0">
|
||||
<span class="absolute -top-1 -right-1 min-w-[18px] h-[18px] bg-amber-500 text-white text-[10px] font-bold rounded-full flex items-center justify-center px-1" x-text="facturasNoLeidas > 9 ? '9+' : facturasNoLeidas"></span>
|
||||
</template>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
@@ -212,31 +215,32 @@
|
||||
<!-- ─── FACTURAS ──────────────────────────────────────────────────────────── -->
|
||||
<div x-show="activeTab==='Facturas'" x-cloak>
|
||||
<div class="space-y-3">
|
||||
{{ range .facturas }}
|
||||
<div class="bg-white rounded-xl border border-slate-200 p-4 flex items-center gap-4">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="font-semibold text-sm text-slate-800">{{ if .Numero }}{{ .Numero }}{{ else }}Factura #{{ .ID }}{{ end }}</span>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full font-semibold capitalize
|
||||
{{ if eq .Estado "pagada" }}bg-green-100 text-green-700
|
||||
{{ else if eq .Estado "vencida" }}bg-red-100 text-red-700
|
||||
{{ else if eq .Estado "cancelada" }}bg-slate-100 text-slate-500
|
||||
{{ else }}bg-yellow-100 text-yellow-700{{ end }}">{{ .Estado }}</span>
|
||||
<template x-for="f in facturas" :key="f.ID">
|
||||
<div class="bg-white rounded-xl border border-slate-200 p-4 flex items-center gap-4">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center gap-2 mb-1">
|
||||
<span class="font-semibold text-sm text-slate-800" x-text="f.numero || ('Factura #' + f.ID)"></span>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full font-semibold capitalize"
|
||||
:class="{
|
||||
'bg-green-100 text-green-700': f.estado === 'pagada',
|
||||
'bg-red-100 text-red-700': f.estado === 'vencida',
|
||||
'bg-slate-100 text-slate-500': f.estado === 'cancelada',
|
||||
'bg-yellow-100 text-yellow-700': !['pagada','vencida','cancelada'].includes(f.estado)
|
||||
}" x-text="f.estado"></span>
|
||||
<span x-show="f._nuevo" class="text-[10px] font-bold bg-amber-500 text-white px-1.5 py-0.5 rounded-full">Nuevo</span>
|
||||
</div>
|
||||
<p class="text-xs text-slate-500" x-text="f.descripcion"></p>
|
||||
<p class="text-sm font-bold text-slate-700 mt-1" x-text="(f.moneda || '') + ' ' + Number(f.monto || 0).toLocaleString('es-CO')"></p>
|
||||
<p x-show="f.fecha_vencimiento" class="text-xs text-slate-400" x-text="'Vence: ' + formatDate(f.fecha_vencimiento)"></p>
|
||||
</div>
|
||||
<p class="text-xs text-slate-500">{{ .Descripcion }}</p>
|
||||
<p class="text-sm font-bold text-slate-700 mt-1">{{ .Moneda }} {{ .Monto }}</p>
|
||||
{{ if .FechaVencimiento }}<p class="text-xs text-slate-400">Vence: {{ .FechaVencimiento.Format "02 Jan 2006" }}</p>{{ end }}
|
||||
<template x-if="f.archivo">
|
||||
<a :href="`/portal/facturas/${f.ID}/download`" class="text-[#8eb02f] hover:text-[#6d8c24] flex-shrink-0" title="Descargar factura">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
{{ if .Archivo }}
|
||||
<a href="/portal/facturas/{{ .ID }}/download" class="text-[#8eb02f] hover:text-[#6d8c24]">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/></svg>
|
||||
</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ if not .facturas }}
|
||||
<p class="text-slate-400 text-sm text-center py-10">Sin facturas disponibles.</p>
|
||||
{{ end }}
|
||||
</template>
|
||||
<p x-show="!facturas.length && !loadingTickets" class="text-slate-400 text-sm text-center py-10">Sin facturas disponibles.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -253,9 +257,19 @@ function portalProyecto() {
|
||||
loadingTickets: false,
|
||||
creandoTicket: false,
|
||||
ticketForm: { titulo:'', descripcion:'', prioridad:'media' },
|
||||
facturas: [],
|
||||
facturasNoLeidas: 0,
|
||||
|
||||
async init() {
|
||||
// Soporte de URL param ?tab=Facturas
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const tabParam = params.get('tab');
|
||||
if (tabParam) this.activeTab = tabParam;
|
||||
await this.loadTickets();
|
||||
// Si la tab activa es Facturas, marcar como leídas automáticamente
|
||||
if (this.activeTab === 'Facturas') {
|
||||
this.marcarFacturasLeidas();
|
||||
}
|
||||
},
|
||||
|
||||
async loadTickets() {
|
||||
@@ -269,6 +283,12 @@ function portalProyecto() {
|
||||
_noLeidos: (t.mensajes||[]).filter(m => m.es_admin && !m.leido_portal).length,
|
||||
}));
|
||||
this.recalcNoLeidos();
|
||||
// Cargar facturas y conteo de no leídas
|
||||
this.facturasNoLeidas = r.data.facturasNoLeidas || 0;
|
||||
this.facturas = (r.data.facturas||[]).map(f => ({
|
||||
...f,
|
||||
_nuevo: this.facturasNoLeidas > 0,
|
||||
}));
|
||||
} catch(e){} finally { this.loadingTickets=false; }
|
||||
},
|
||||
|
||||
@@ -302,6 +322,14 @@ function portalProyecto() {
|
||||
this.recalcNoLeidos();
|
||||
},
|
||||
|
||||
async marcarFacturasLeidas() {
|
||||
if (this.facturasNoLeidas > 0) {
|
||||
this.facturasNoLeidas = 0;
|
||||
this.facturas.forEach(f => { f._nuevo = false; });
|
||||
axios.post('/portal/mis-notifs/marcar-facturas').catch(()=>{});
|
||||
}
|
||||
},
|
||||
|
||||
async crearTicket() {
|
||||
this.creandoTicket=true;
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user