feat: add Excel export, keyboard shortcuts, and print view for CFDIs

- Add export to Excel button with xlsx library for filtered data
- Add keyboard shortcuts (Esc to close popovers/forms)
- Add print button to invoice viewer modal with optimized print styles

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Consultoria AS
2026-02-17 07:40:11 +00:00
parent 562e23d8bf
commit 2994de4ce0
4 changed files with 235 additions and 13 deletions

View File

@@ -12,8 +12,10 @@ import { useCfdis, useCreateCfdi, useDeleteCfdi } from '@/lib/hooks/use-cfdi';
import { createManyCfdis, searchEmisores, searchReceptores, type EmisorReceptor } from '@/lib/api/cfdi'; import { createManyCfdis, searchEmisores, searchReceptores, type EmisorReceptor } from '@/lib/api/cfdi';
import type { CfdiFilters, TipoCfdi, Cfdi } from '@horux/shared'; import type { CfdiFilters, TipoCfdi, Cfdi } from '@horux/shared';
import type { CreateCfdiData } from '@/lib/api/cfdi'; import type { CreateCfdiData } from '@/lib/api/cfdi';
import { FileText, Search, ChevronLeft, ChevronRight, Plus, Upload, Trash2, X, FileUp, CheckCircle, AlertCircle, Loader2, Eye, Filter, XCircle, Calendar, User, Building2 } from 'lucide-react'; import { FileText, Search, ChevronLeft, ChevronRight, Plus, Upload, Trash2, X, FileUp, CheckCircle, AlertCircle, Loader2, Eye, Filter, XCircle, Calendar, User, Building2, Download, Printer } from 'lucide-react';
import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover'; import { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';
import * as XLSX from 'xlsx';
import { saveAs } from 'file-saver';
import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal'; import { CfdiViewerModal } from '@/components/cfdi/cfdi-viewer-modal';
import { getCfdiById } from '@/lib/api/cfdi'; import { getCfdiById } from '@/lib/api/cfdi';
import { useAuthStore } from '@/stores/auth-store'; import { useAuthStore } from '@/stores/auth-store';
@@ -274,6 +276,7 @@ export default function CfdiPage() {
.catch(() => setReceptorSuggestions([])) .catch(() => setReceptorSuggestions([]))
.finally(() => setLoadingReceptor(false)); .finally(() => setLoadingReceptor(false));
}, [debouncedReceptor]); }, [debouncedReceptor]);
const [showBulkForm, setShowBulkForm] = useState(false); const [showBulkForm, setShowBulkForm] = useState(false);
const [formData, setFormData] = useState<CreateCfdiData>(initialFormData); const [formData, setFormData] = useState<CreateCfdiData>(initialFormData);
const [bulkData, setBulkData] = useState(''); const [bulkData, setBulkData] = useState('');
@@ -323,6 +326,54 @@ export default function CfdiPage() {
setFilters({ ...filters, search: searchTerm, page: 1 }); setFilters({ ...filters, search: searchTerm, page: 1 });
}; };
// Export to Excel
const [exporting, setExporting] = useState(false);
const exportToExcel = async () => {
if (!data?.data.length) return;
setExporting(true);
try {
const exportData = data.data.map(cfdi => ({
'Fecha Emisión': new Date(cfdi.fechaEmision).toLocaleDateString('es-MX'),
'Tipo': cfdi.tipo === 'ingreso' ? 'Ingreso' : 'Egreso',
'Serie': cfdi.serie || '',
'Folio': cfdi.folio || '',
'RFC Emisor': cfdi.rfcEmisor,
'Nombre Emisor': cfdi.nombreEmisor,
'RFC Receptor': cfdi.rfcReceptor,
'Nombre Receptor': cfdi.nombreReceptor,
'Subtotal': cfdi.subtotal,
'IVA': cfdi.iva,
'Total': cfdi.total,
'Moneda': cfdi.moneda,
'Estado': cfdi.estado === 'vigente' ? 'Vigente' : 'Cancelado',
'UUID': cfdi.uuidFiscal,
}));
const ws = XLSX.utils.json_to_sheet(exportData);
const wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, 'CFDIs');
// Auto-size columns
const colWidths = Object.keys(exportData[0]).map(key => ({
wch: Math.max(key.length, ...exportData.map(row => String(row[key as keyof typeof row]).length))
}));
ws['!cols'] = colWidths;
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const fileName = `cfdis_${new Date().toISOString().split('T')[0]}.xlsx`;
saveAs(blob, fileName);
} catch (error) {
console.error('Error exporting:', error);
alert('Error al exportar');
} finally {
setExporting(false);
}
};
const selectEmisor = (emisor: EmisorReceptor) => { const selectEmisor = (emisor: EmisorReceptor) => {
setColumnFilters(prev => ({ ...prev, emisor: emisor.nombre })); setColumnFilters(prev => ({ ...prev, emisor: emisor.nombre }));
setEmisorSuggestions([]); setEmisorSuggestions([]);
@@ -594,6 +645,32 @@ export default function CfdiPage() {
} }
}; };
// Keyboard shortcuts - Esc to close popovers and forms
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
// Close open filter popovers
if (openFilter !== null) {
setOpenFilter(null);
return;
}
// Close forms
if (showForm) {
setShowForm(false);
return;
}
if (showBulkForm) {
setShowBulkForm(false);
clearXmlFiles();
return;
}
}
};
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [openFilter, showForm, showBulkForm]);
const cancelUpload = () => { const cancelUpload = () => {
uploadAbortRef.current = true; uploadAbortRef.current = true;
setUploadProgress(prev => ({ ...prev, status: 'idle' })); setUploadProgress(prev => ({ ...prev, status: 'idle' }));
@@ -681,8 +758,19 @@ export default function CfdiPage() {
Egresos Egresos
</Button> </Button>
</div> </div>
{canEdit && (
<div className="flex gap-2"> <div className="flex gap-2">
{data && data.data.length > 0 && (
<Button variant="outline" onClick={exportToExcel} disabled={exporting}>
{exporting ? (
<Loader2 className="h-4 w-4 mr-1 animate-spin" />
) : (
<Download className="h-4 w-4 mr-1" />
)}
Exportar
</Button>
)}
{canEdit && (
<>
<Button onClick={() => { setShowForm(true); setShowBulkForm(false); }}> <Button onClick={() => { setShowForm(true); setShowBulkForm(false); }}>
<Plus className="h-4 w-4 mr-1" /> <Plus className="h-4 w-4 mr-1" />
Agregar Agregar
@@ -691,9 +779,10 @@ export default function CfdiPage() {
<Upload className="h-4 w-4 mr-1" /> <Upload className="h-4 w-4 mr-1" />
Carga Masiva Carga Masiva
</Button> </Button>
</div> </>
)} )}
</div> </div>
</div>
</CardContent> </CardContent>
</Card> </Card>

View File

@@ -6,7 +6,7 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/u
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { CfdiInvoice } from './cfdi-invoice'; import { CfdiInvoice } from './cfdi-invoice';
import { getCfdiXml } from '@/lib/api/cfdi'; import { getCfdiXml } from '@/lib/api/cfdi';
import { Download, FileText, Loader2 } from 'lucide-react'; import { Download, FileText, Loader2, Printer } from 'lucide-react';
interface CfdiConcepto { interface CfdiConcepto {
descripcion: string; descripcion: string;
@@ -120,6 +120,45 @@ export function CfdiViewerModal({ cfdi, open, onClose }: CfdiViewerModalProps) {
} }
}; };
const handlePrint = () => {
if (!invoiceRef.current) return;
// Create a print-specific stylesheet
const printStyles = document.createElement('style');
printStyles.innerHTML = `
@media print {
body * {
visibility: hidden;
}
#cfdi-print-area, #cfdi-print-area * {
visibility: visible;
}
#cfdi-print-area {
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: 20px;
}
@page {
size: A4;
margin: 15mm;
}
}
`;
document.head.appendChild(printStyles);
// Add ID to the invoice container for print targeting
invoiceRef.current.id = 'cfdi-print-area';
// Trigger print
window.print();
// Clean up
document.head.removeChild(printStyles);
invoiceRef.current.removeAttribute('id');
};
if (!cfdi) return null; if (!cfdi) return null;
return ( return (
@@ -156,6 +195,16 @@ export function CfdiViewerModal({ cfdi, open, onClose }: CfdiViewerModalProps) {
)} )}
XML XML
</Button> </Button>
<Button
variant="outline"
size="sm"
onClick={handlePrint}
disabled={downloading !== null}
title="Imprimir factura"
>
<Printer className="h-4 w-4 mr-1" />
Imprimir
</Button>
</div> </div>
</div> </div>
</DialogHeader> </DialogHeader>

View File

@@ -27,6 +27,7 @@
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"date-fns": "^3.6.0", "date-fns": "^3.6.0",
"file-saver": "^2.0.5",
"html2pdf.js": "^0.14.0", "html2pdf.js": "^0.14.0",
"lucide-react": "^0.460.0", "lucide-react": "^0.460.0",
"next": "^14.2.0", "next": "^14.2.0",
@@ -35,10 +36,12 @@
"react-hook-form": "^7.53.0", "react-hook-form": "^7.53.0",
"recharts": "^2.12.0", "recharts": "^2.12.0",
"tailwind-merge": "^2.5.0", "tailwind-merge": "^2.5.0",
"xlsx": "^0.18.5",
"zod": "^3.23.0", "zod": "^3.23.0",
"zustand": "^5.0.0" "zustand": "^5.0.0"
}, },
"devDependencies": { "devDependencies": {
"@types/file-saver": "^2.0.7",
"@types/node": "^22.0.0", "@types/node": "^22.0.0",
"@types/react": "^18.3.0", "@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",

81
pnpm-lock.yaml generated
View File

@@ -162,6 +162,9 @@ importers:
date-fns: date-fns:
specifier: ^3.6.0 specifier: ^3.6.0
version: 3.6.0 version: 3.6.0
file-saver:
specifier: ^2.0.5
version: 2.0.5
html2pdf.js: html2pdf.js:
specifier: ^0.14.0 specifier: ^0.14.0
version: 0.14.0 version: 0.14.0
@@ -186,6 +189,9 @@ importers:
tailwind-merge: tailwind-merge:
specifier: ^2.5.0 specifier: ^2.5.0
version: 2.6.0 version: 2.6.0
xlsx:
specifier: ^0.18.5
version: 0.18.5
zod: zod:
specifier: ^3.23.0 specifier: ^3.23.0
version: 3.25.76 version: 3.25.76
@@ -193,6 +199,9 @@ importers:
specifier: ^5.0.0 specifier: ^5.0.0
version: 5.0.10(@types/react@18.3.27)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1)) version: 5.0.10(@types/react@18.3.27)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1))
devDependencies: devDependencies:
'@types/file-saver':
specifier: ^2.0.7
version: 2.0.7
'@types/node': '@types/node':
specifier: ^22.0.0 specifier: ^22.0.0
version: 22.19.7 version: 22.19.7
@@ -1070,6 +1079,9 @@ packages:
'@types/express@5.0.6': '@types/express@5.0.6':
resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==}
'@types/file-saver@2.0.7':
resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==}
'@types/http-errors@2.0.5': '@types/http-errors@2.0.5':
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
@@ -1135,6 +1147,10 @@ packages:
resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
adler-32@1.3.1:
resolution: {integrity: sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==}
engines: {node: '>=0.8'}
adm-zip@0.5.16: adm-zip@0.5.16:
resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==} resolution: {integrity: sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==}
engines: {node: '>=12.0'} engines: {node: '>=12.0'}
@@ -1281,6 +1297,10 @@ packages:
resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==} resolution: {integrity: sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
cfb@1.2.2:
resolution: {integrity: sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==}
engines: {node: '>=0.8'}
chainsaw@0.1.0: chainsaw@0.1.0:
resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==} resolution: {integrity: sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==}
@@ -1298,6 +1318,10 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'} engines: {node: '>=6'}
codepage@1.15.0:
resolution: {integrity: sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==}
engines: {node: '>=0.8'}
combined-stream@1.0.8: combined-stream@1.0.8:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@@ -1550,6 +1574,9 @@ packages:
fflate@0.8.2: fflate@0.8.2:
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
file-saver@2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
fill-range@7.1.1: fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@@ -1575,6 +1602,10 @@ packages:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
frac@1.1.2:
resolution: {integrity: sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==}
engines: {node: '>=0.8'}
fraction.js@5.3.4: fraction.js@5.3.4:
resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==}
@@ -2299,6 +2330,10 @@ packages:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'} engines: {node: '>= 10.x'}
ssf@0.11.2:
resolution: {integrity: sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==}
engines: {node: '>=0.8'}
stackblur-canvas@2.7.0: stackblur-canvas@2.7.0:
resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==} resolution: {integrity: sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==}
engines: {node: '>=0.1.14'} engines: {node: '>=0.1.14'}
@@ -2509,9 +2544,22 @@ packages:
victory-vendor@36.9.2: victory-vendor@36.9.2:
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
wmf@1.0.2:
resolution: {integrity: sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==}
engines: {node: '>=0.8'}
word@0.3.0:
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
engines: {node: '>=0.8'}
wrappy@1.0.2: wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
xlsx@0.18.5:
resolution: {integrity: sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==}
engines: {node: '>=0.8'}
hasBin: true
xmlchars@2.2.0: xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
@@ -3296,6 +3344,8 @@ snapshots:
'@types/express-serve-static-core': 5.1.1 '@types/express-serve-static-core': 5.1.1
'@types/serve-static': 2.2.0 '@types/serve-static': 2.2.0
'@types/file-saver@2.0.7': {}
'@types/http-errors@2.0.5': {} '@types/http-errors@2.0.5': {}
'@types/jsonwebtoken@9.0.10': '@types/jsonwebtoken@9.0.10':
@@ -3358,6 +3408,8 @@ snapshots:
mime-types: 2.1.35 mime-types: 2.1.35
negotiator: 0.6.3 negotiator: 0.6.3
adler-32@1.3.1: {}
adm-zip@0.5.16: {} adm-zip@0.5.16: {}
any-promise@1.3.0: {} any-promise@1.3.0: {}
@@ -3542,6 +3594,11 @@ snapshots:
svg-pathdata: 6.0.3 svg-pathdata: 6.0.3
optional: true optional: true
cfb@1.2.2:
dependencies:
adler-32: 1.3.1
crc-32: 1.2.2
chainsaw@0.1.0: chainsaw@0.1.0:
dependencies: dependencies:
traverse: 0.3.9 traverse: 0.3.9
@@ -3566,6 +3623,8 @@ snapshots:
clsx@2.1.1: {} clsx@2.1.1: {}
codepage@1.15.0: {}
combined-stream@1.0.8: combined-stream@1.0.8:
dependencies: dependencies:
delayed-stream: 1.0.0 delayed-stream: 1.0.0
@@ -3846,6 +3905,8 @@ snapshots:
fflate@0.8.2: {} fflate@0.8.2: {}
file-saver@2.0.5: {}
fill-range@7.1.1: fill-range@7.1.1:
dependencies: dependencies:
to-regex-range: 5.0.1 to-regex-range: 5.0.1
@@ -3874,6 +3935,8 @@ snapshots:
forwarded@0.2.0: {} forwarded@0.2.0: {}
frac@1.1.2: {}
fraction.js@5.3.4: {} fraction.js@5.3.4: {}
fresh@0.5.2: {} fresh@0.5.2: {}
@@ -4580,6 +4643,10 @@ snapshots:
split2@4.2.0: {} split2@4.2.0: {}
ssf@0.11.2:
dependencies:
frac: 1.1.2
stackblur-canvas@2.7.0: stackblur-canvas@2.7.0:
optional: true optional: true
@@ -4802,8 +4869,22 @@ snapshots:
d3-time: 3.1.0 d3-time: 3.1.0
d3-timer: 3.0.1 d3-timer: 3.0.1
wmf@1.0.2: {}
word@0.3.0: {}
wrappy@1.0.2: {} wrappy@1.0.2: {}
xlsx@0.18.5:
dependencies:
adler-32: 1.3.1
cfb: 1.2.2
codepage: 1.15.0
crc-32: 1.2.2
ssf: 0.11.2
wmf: 1.0.2
word: 0.3.0
xmlchars@2.2.0: {} xmlchars@2.2.0: {}
xtend@4.0.2: {} xtend@4.0.2: {}