frontend initiatewith shadcn uis
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
import { type ColumnDef, type ExportFormat, type FieldConfig } from '../types';
|
||||
import { getNestedValue } from '../utils/nested-value';
|
||||
|
||||
export function exportTable<T extends object>(
|
||||
format: ExportFormat, data: T[], columns: ColumnDef<T>[], filename = 'export'
|
||||
): void {
|
||||
const exportable = columns.filter(c => c.exportable !== false);
|
||||
if (format === 'csv') exportCsv(data, exportable, filename);
|
||||
if (format === 'excel') exportExcel(data, exportable, filename);
|
||||
if (format === 'pdf') exportPdf(data, exportable, filename);
|
||||
}
|
||||
|
||||
function resolveCellText<T>(row: T, col: ColumnDef<T>): string {
|
||||
return col.fields.map(f => {
|
||||
const field: FieldConfig = typeof f === 'string' ? { key: f } : f;
|
||||
const raw = getNestedValue(row, field.key);
|
||||
if (field.transform) return field.transform(raw, row);
|
||||
return `${field.prefix ?? ''}${raw ?? ''}${field.suffix ?? ''}`;
|
||||
}).join(' ');
|
||||
}
|
||||
|
||||
function download(blob: Blob, name: string) {
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url; a.download = name; a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
function escHtml(s: string) {
|
||||
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
||||
}
|
||||
|
||||
function exportCsv<T>(data: T[], columns: ColumnDef<T>[], filename: string) {
|
||||
const headers = columns.map(c => `"${c.title}"`).join(',');
|
||||
const rows = data.map(row =>
|
||||
columns.map(col => `"${resolveCellText(row, col).replace(/"/g, '""')}"`).join(',')
|
||||
);
|
||||
download(new Blob([[headers, ...rows].join('\r\n')], { type: 'text/csv;charset=utf-8;' }), `${filename}.csv`);
|
||||
}
|
||||
|
||||
function exportExcel<T>(data: T[], columns: ColumnDef<T>[], filename: string) {
|
||||
const thead = columns.map(c => `<th>${escHtml(c.title)}</th>`).join('');
|
||||
const tbody = data.map(row =>
|
||||
`<tr>${columns.map(col => `<td>${escHtml(resolveCellText(row, col))}</td>`).join('')}</tr>`
|
||||
).join('');
|
||||
const html = `<html xmlns:o="urn:schemas-microsoft-com:office:office"
|
||||
xmlns:x="urn:schemas-microsoft-com:office:excel">
|
||||
<head><meta charset="UTF-8"></head>
|
||||
<body><table border="1">${thead}${tbody}</table></body></html>`;
|
||||
download(new Blob([html], { type: 'application/vnd.ms-excel;charset=utf-8;' }), `${filename}.xls`);
|
||||
}
|
||||
|
||||
function exportPdf<T>(data: T[], columns: ColumnDef<T>[], filename: string) {
|
||||
const thead = columns.map(c =>
|
||||
`<th style="padding:6px 10px;border:1px solid #ccc;background:#f5f5f5">${escHtml(c.title)}</th>`
|
||||
).join('');
|
||||
const tbody = data.map(row =>
|
||||
`<tr>${columns.map(col =>
|
||||
`<td style="padding:5px 10px;border:1px solid #ccc">${escHtml(resolveCellText(row, col))}</td>`
|
||||
).join('')}</tr>`
|
||||
).join('');
|
||||
const win = window.open('', '_blank');
|
||||
if (!win) return;
|
||||
win.document.write(`<!DOCTYPE html><html><head>
|
||||
<title>${escHtml(filename)}</title>
|
||||
<style>body{font-family:sans-serif;font-size:12px}table{border-collapse:collapse;width:100%}
|
||||
@media print{button{display:none}}</style></head>
|
||||
<body><h2>${escHtml(filename)}</h2>
|
||||
<table><thead><tr>${thead}</tr></thead><tbody>${tbody}</tbody></table>
|
||||
<br><button onclick="window.print()">Print / Save PDF</button>
|
||||
</body></html>`);
|
||||
win.document.close();
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { type ColumnDef, type FieldConfig, type FilterState } from '../types';
|
||||
import { getNestedValue } from '../utils/nested-value';
|
||||
|
||||
export function filterData<T extends object>(data: T[], state: FilterState, columns: ColumnDef<T>[]): T[] {
|
||||
let result = data;
|
||||
|
||||
if (state.global.trim()) {
|
||||
const term = state.global.toLowerCase();
|
||||
result = result.filter(row =>
|
||||
columns.some(col =>
|
||||
col.fields.some(f => {
|
||||
const key = typeof f === 'string' ? f : (f as FieldConfig).key;
|
||||
return String(getNestedValue(row, key) ?? '').toLowerCase().includes(term);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const colFilters = Object.entries(state.columns).filter(([, v]) => v.trim());
|
||||
for (const [colKey, term] of colFilters) {
|
||||
const col = columns.find(c => c.key === colKey);
|
||||
if (!col) continue;
|
||||
const lower = term.toLowerCase();
|
||||
result = result.filter(row =>
|
||||
col.fields.some(f => {
|
||||
const key = typeof f === 'string' ? f : (f as FieldConfig).key;
|
||||
return String(getNestedValue(row, key) ?? '').toLowerCase().includes(lower);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { type ColumnDef, type SortState } from '../types';
|
||||
import { getNestedValue } from '../utils/nested-value';
|
||||
|
||||
export function sortData<T extends object>(data: T[], state: SortState, columns: ColumnDef<T>[]): T[] {
|
||||
if (!state.column || !state.direction) return data;
|
||||
const col = columns.find(c => c.key === state.column);
|
||||
if (!col) return data;
|
||||
|
||||
if (col.sortFn) {
|
||||
const sorted = [...data].sort(col.sortFn);
|
||||
return state.direction === 'desc' ? sorted.reverse() : sorted;
|
||||
}
|
||||
|
||||
const primaryField = col.fields[0];
|
||||
const fieldKey = typeof primaryField === 'string' ? primaryField : primaryField.key;
|
||||
|
||||
return [...data].sort((a, b) => {
|
||||
const av = getNestedValue(a, fieldKey);
|
||||
const bv = getNestedValue(b, fieldKey);
|
||||
const result = compareValues(av, bv);
|
||||
return state.direction === 'asc' ? result : -result;
|
||||
});
|
||||
}
|
||||
|
||||
function compareValues(a: unknown, b: unknown): number {
|
||||
if (a == null) return -1;
|
||||
if (b == null) return 1;
|
||||
if (typeof a === 'number' && typeof b === 'number') return a - b;
|
||||
if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
|
||||
return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' });
|
||||
}
|
||||
Reference in New Issue
Block a user