frontend initiatewith shadcn uis

This commit is contained in:
Kalana B. Thilakarathna
2026-08-25 16:20:50 +05:30
parent 92fef1dfa8
commit f1c036d1d8
136 changed files with 20257 additions and 0 deletions
@@ -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, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
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' });
}