- Nuevos assets compartidos /assets/donna-export.{css,js}: botón
"Exportar ▾" + menú dropdown Excel/PDF y helpers DonnaExport.toExcel
/ .toPDF / .attach / .buttonHtml
- escrituras: refactor de exportExcel/exportPDF para usar el módulo
compartido (sin cambio de comportamiento)
- tramites: nuevo botón en la toolbar de cada tab (adm/corp/mig/lit),
exporta los rows filtrados de ese tab; PDF excluye Involucrados
- calendario: nuevo botón al final de la toolbar, exporta visibleEvents()
(respeta chips de origen y búsqueda)
- horas: reemplaza CSV server-side por XLSX client-side + agrega PDF;
removida la acción ?action=export del api.php
180 lines
8.1 KiB
JavaScript
180 lines
8.1 KiB
JavaScript
/* ============================================================
|
|
* Donna — Helpers compartidos para exportación a Excel y PDF
|
|
* ============================================================
|
|
* Requiere XLSX (https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js)
|
|
* cargado antes de este script.
|
|
*
|
|
* Uso:
|
|
* <div class="export-wrap" id="export-wrap">…botón + menú…</div>
|
|
* DonnaExport.attach('export-wrap', { onExcel, onPDF });
|
|
* DonnaExport.toExcel({ filename, sheetName, headers, rows, columnWidths });
|
|
* DonnaExport.toPDF({ filename, title, subtitle, headers, rows,
|
|
* pdfCols, orientation, statusColIndex, statusColors, footer });
|
|
*/
|
|
(function(){
|
|
'use strict';
|
|
|
|
const NAVY = '#1B3A6B';
|
|
const GOLD = '#C9A84C';
|
|
const FIRM = 'AALN Law — Arias, Abrego, López & Noriega';
|
|
const ADDR = 'P.H. Midtown, Piso 16, Of. 1605, Calle 74 Este, San Francisco, Ciudad de Panamá';
|
|
|
|
function escHtml(s){
|
|
return (s==null?'':String(s))
|
|
.replace(/&/g,'&').replace(/"/g,'"')
|
|
.replace(/</g,'<').replace(/>/g,'>');
|
|
}
|
|
|
|
function todayStamp(){
|
|
const d = new Date();
|
|
const pad = n => String(n).padStart(2,'0');
|
|
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}`;
|
|
}
|
|
|
|
function attach(wrapId, handlers){
|
|
const wrap = document.getElementById(wrapId);
|
|
if (!wrap) return;
|
|
const btn = wrap.querySelector('.btn-export');
|
|
const menu = wrap.querySelector('.export-menu');
|
|
const xl = wrap.querySelector('.export-menu button.xl');
|
|
const pdf = wrap.querySelector('.export-menu button.pdf');
|
|
if (btn && menu) {
|
|
btn.addEventListener('click', e => {
|
|
e.stopPropagation();
|
|
menu.classList.toggle('open');
|
|
});
|
|
}
|
|
document.addEventListener('click', e => {
|
|
if (!wrap.contains(e.target)) menu && menu.classList.remove('open');
|
|
});
|
|
if (xl && handlers.onExcel) xl.addEventListener('click', () => { menu.classList.remove('open'); handlers.onExcel(); });
|
|
if (pdf && handlers.onPDF) pdf.addEventListener('click', () => { menu.classList.remove('open'); handlers.onPDF(); });
|
|
}
|
|
|
|
function toExcel(opts){
|
|
if (typeof XLSX === 'undefined') { alert('XLSX no está cargado'); return; }
|
|
const headers = opts.headers || [];
|
|
const rows = opts.rows || [];
|
|
const sheetName = opts.sheetName || 'Datos';
|
|
const fileBase = opts.filename || 'Donna_Export';
|
|
if (!rows.length) { opts.onEmpty && opts.onEmpty(); return; }
|
|
|
|
const wb = XLSX.utils.book_new();
|
|
const data = [headers, ...rows];
|
|
const ws = XLSX.utils.aoa_to_sheet(data);
|
|
|
|
const widths = opts.columnWidths || headers.map(() => 18);
|
|
ws['!cols'] = widths.map(w => ({ wch: w }));
|
|
|
|
const range = XLSX.utils.decode_range(ws['!ref']);
|
|
for (let C = range.s.c; C <= range.e.c; C++) {
|
|
const addr = XLSX.utils.encode_cell({r:0, c:C});
|
|
if (!ws[addr]) continue;
|
|
ws[addr].s = {
|
|
fill: { fgColor: { rgb: '1B3A6B' } },
|
|
font: { bold: true, color: { rgb: 'FFFFFF' }, sz: 11 },
|
|
alignment: { horizontal: 'center', vertical: 'center', wrapText: true }
|
|
};
|
|
}
|
|
XLSX.utils.book_append_sheet(wb, ws, sheetName);
|
|
XLSX.writeFile(wb, `${fileBase}_${todayStamp()}.xlsx`);
|
|
}
|
|
|
|
function toPDF(opts){
|
|
const headers = opts.headers || [];
|
|
const rows = opts.rows || [];
|
|
const title = opts.title || 'Reporte';
|
|
const subtitle = opts.subtitle || '';
|
|
const orientation = opts.orientation || 'landscape';
|
|
const fileBase = opts.filename || 'Donna_Export';
|
|
const footer = opts.footer != null ? opts.footer : ADDR;
|
|
const pdfCols = opts.pdfCols || headers.map((_,i) => i);
|
|
const statusCol = opts.statusColIndex;
|
|
const statusColors = opts.statusColors || {};
|
|
if (!rows.length) { opts.onEmpty && opts.onEmpty(); return; }
|
|
|
|
const pdfHeaders = pdfCols.map(i => headers[i]);
|
|
const styledRows = rows.map((r, ri) => {
|
|
const cells = pdfCols.map(ci => {
|
|
const val = r[ci] == null ? '' : String(r[ci]);
|
|
if (statusCol === ci && statusColors[val]) {
|
|
const [bg, fg] = statusColors[val];
|
|
return `<td><span style="background:${bg};color:${fg};padding:2px 8px;border-radius:12px;font-size:9px;font-weight:600;white-space:nowrap;">${escHtml(val)}</span></td>`;
|
|
}
|
|
return `<td>${escHtml(val)}</td>`;
|
|
}).join('');
|
|
return `<tr class="${ri%2===0?'even':''}">${cells}</tr>`;
|
|
}).join('');
|
|
|
|
const fechaStr = new Date().toLocaleDateString('es-PA');
|
|
const html = `<!DOCTYPE html><html lang="es"><head>
|
|
<meta charset="UTF-8">
|
|
<title>${escHtml(title)} — AALN Law</title>
|
|
<style>
|
|
@page { size: A4 ${orientation}; margin: 12mm 10mm; }
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body { font-family: Arial, sans-serif; font-size: 9px; color: #1a1916; }
|
|
.header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; border-bottom: 3px solid ${GOLD}; padding-bottom: 8px; }
|
|
.header-left { display: flex; align-items: center; gap: 10px; }
|
|
.logo-mark { width: 28px; height: 28px; background: ${NAVY}; border-radius: 5px; display: flex; align-items: center; justify-content: center; color: ${GOLD}; font-weight: 700; font-size: 14px; }
|
|
.firm-name { font-size: 13px; font-weight: 700; color: ${NAVY}; }
|
|
.doc-title { font-size: 9px; color: #666; }
|
|
.meta { font-size: 8px; color: #888; text-align: right; }
|
|
table { width: 100%; border-collapse: collapse; }
|
|
th { background: ${NAVY}; color: #fff; padding: 6px 5px; text-align: left; font-size: 8px; font-weight: 700; text-transform: uppercase; letter-spacing: .03em; white-space: nowrap; }
|
|
td { padding: 5px; border-bottom: 1px solid #EEEDE9; font-size: 8.5px; vertical-align: middle; }
|
|
tr.even td { background: #F7F6F3; }
|
|
.footer { margin-top: 10px; font-size: 7.5px; color: #aaa; text-align: center; border-top: 1px solid #eee; padding-top: 6px; }
|
|
@media print { .no-print { display:none; } }
|
|
</style>
|
|
</head><body>
|
|
<div class="header">
|
|
<div class="header-left">
|
|
<div class="logo-mark">A</div>
|
|
<div>
|
|
<div class="firm-name">${FIRM}</div>
|
|
<div class="doc-title">${escHtml(title)}${subtitle ? ' — ' + escHtml(subtitle) : ''}</div>
|
|
</div>
|
|
</div>
|
|
<div class="meta">
|
|
Exportado: ${escHtml(fechaStr)}<br>
|
|
Total registros: ${rows.length}
|
|
</div>
|
|
</div>
|
|
<table>
|
|
<thead><tr>${pdfHeaders.map(h => `<th>${escHtml(h)}</th>`).join('')}</tr></thead>
|
|
<tbody>${styledRows}</tbody>
|
|
</table>
|
|
<div class="footer">${footer}</div>
|
|
<script>window.onload=()=>{ document.title=${JSON.stringify(fileBase + '_' + todayStamp())}; window.print(); }<\/script>
|
|
</body></html>`;
|
|
|
|
const win = window.open('', '_blank');
|
|
win.document.write(html);
|
|
win.document.close();
|
|
}
|
|
|
|
function buttonHtml(id){
|
|
const wrapId = id || 'export-wrap';
|
|
return `<div class="export-wrap" id="${wrapId}">
|
|
<button class="btn-export" type="button" title="Exportar resultados visibles">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
|
Exportar
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="width:12px;height:12px;margin-left:2px;"><path d="m6 9 6 6 6-6"/></svg>
|
|
</button>
|
|
<div class="export-menu">
|
|
<button class="xl" type="button">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="m9 9 6 6M15 9l-6 6"/></svg>
|
|
Exportar a Excel (.xlsx)
|
|
</button>
|
|
<button class="pdf" type="button">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
|
Exportar a PDF
|
|
</button>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
window.DonnaExport = { attach, toExcel, toPDF, buttonHtml };
|
|
})();
|