Skip to content

Commit

Permalink
feat: implement parametric schema-level formatters
Browse files Browse the repository at this point in the history
  • Loading branch information
ChronicStone committed Apr 29, 2024
1 parent b0af7aa commit 487932b
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 12 deletions.
Binary file modified examples/financial-report.xlsx
Binary file not shown.
Binary file modified examples/kitchen-sink.xlsx
Binary file not shown.
Binary file modified examples/playground.xlsx
Binary file not shown.
10 changes: 7 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable ts/ban-types */
import XLSX, { type WorkSheet, utils } from 'xlsx-js-style'
import type { CellValue, Column, ColumnGroup, ExcelBuildOutput, ExcelBuildParams, ExcelSchema, FormattersMap, GenericObject, NestedPaths, Not, SchemaColumnKeys, SheetConfig, SheetParams, SheetTable, SheetTableBuilder, TOutputType, TransformersMap } from './types'
import type { CellValue, Column, ColumnGroup, ExcelBuildOutput, ExcelBuildParams, ExcelSchema, FormatterPreset, FormattersMap, GenericObject, NestedPaths, Not, SchemaColumnKeys, SheetConfig, SheetParams, SheetTable, SheetTableBuilder, TOutputType, TransformersMap } from './types'
import { SheetCacheManager, applyGroupBorders, buildSheetConfig, createCell, getColumnHeaderStyle, getColumnSeparatorIndexes, getWorksheetColumnWidths, tableHasSummary } from './utils'

export type * from './types'
Expand All @@ -26,17 +26,21 @@ export class ExcelSchemaBuilder<
return this as unknown as ExcelSchemaBuilder<T, CellKeyPaths, UsedKeys, TransformMap & Transformers, FormatMap, ContextMap>
}

withFormatters<Formatters extends FormattersMap>(formatters: Formatters): ExcelSchemaBuilder<T, CellKeyPaths, UsedKeys, TransformMap, FormatMap & Formatters, ContextMap> {
withFormatters<
Formatters extends FormattersMap,
>(formatters: Formatters,
): ExcelSchemaBuilder<T, CellKeyPaths, UsedKeys, TransformMap, FormatMap & Formatters, ContextMap> {
this.formatters = formatters as FormatMap & Formatters
return this as unknown as ExcelSchemaBuilder<T, CellKeyPaths, UsedKeys, TransformMap, FormatMap & Formatters, ContextMap>
}

public column<
K extends string,
FieldValue extends CellKeyPaths | ((data: T) => CellValue),
Preset extends FormatterPreset<FormatMap>[keyof FormatMap],
>(
columnKey: Not<K, UsedKeys>,
column: Omit<Column<T, FieldValue, K, TransformMap, FormatMap>, 'columnKey' | 'type'>,
column: Omit<Column<T, FieldValue, K, TransformMap, FormatMap, Preset>, 'columnKey' | 'type'>,
): ExcelSchemaBuilder<T, CellKeyPaths, UsedKeys | K, TransformMap, FormatMap, ContextMap> {
if (this.columns.some(c => c.columnKey === columnKey))
throw new Error(`Column with key '${columnKey}' already exists.`)
Expand Down
15 changes: 13 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,18 @@ export interface TransformersMap {
[key: string]: ValueTransformer
}

export type FormatterFunction = (params: any) => string

export interface FormattersMap {
[key: string]: string
[key: string]: FormatterFunction | string
}

export type FormatterPreset<T extends FormattersMap> = {
[Key in keyof T]: ({ preset: Key } & (T[Key] extends infer P
? P extends (params: any) => any
? { params: Parameters<P>[0] }
: {}
: {}))
}

export type NonNullableDeep<T> = T extends null | undefined ? never : T
Expand All @@ -81,13 +91,14 @@ export type Column<
ColKey extends string,
TransformMap extends TransformersMap,
FormatMap extends FormattersMap,
Preset extends FormatterPreset<FormatMap>[keyof FormatMap] = never,
> = {
type: 'column'
label?: string
columnKey: ColKey
key: FieldValue
default?: CellValue
format?: string | { preset: keyof FormatMap } | ((rowData: T, rowIndex: number, subRowIndex: number) => string | { preset: keyof FormatMap })
format?: Preset | string | ((rowData: T, rowIndex: number, subRowIndex: number) => string | Preset)
cellStyle?: CellStyle | ((rowData: T, rowIndex: number, subRowIndex: number) => CellStyle)
headerStyle?: CellStyle
summary?: Array<{
Expand Down
13 changes: 9 additions & 4 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { utils } from 'xlsx-js-style'
import type XLSX from 'xlsx-js-style'
import type { CellStyle, ExcelDataType, WorkSheet } from 'xlsx-js-style'
import { deepmerge } from 'deepmerge-ts'
import type { BaseCellValue, CellValue, Column, FormattersMap, GenericObject, SheetConfig, ValueTransformer } from './types'
import type { BaseCellValue, CellValue, Column, FormatterPreset, FormattersMap, GenericObject, SheetConfig, ValueTransformer } from './types'
import { THICK_BORDER_STYLE, THIN_BORDER_STYLE } from './const'

export function getPropertyFromPath(obj: GenericObject, path: string) {
Expand Down Expand Up @@ -335,7 +335,7 @@ export function createCell(params: {
data?: GenericObject
value?: BaseCellValue
style?: CellStyle | ((rowData: any, rowIndex: number, subRowIndex: number) => CellStyle)
format?: string | { preset: string | number | symbol } | ((rowData: any, rowIndex: number, subRowIndex: number) => string | { preset: string | number | symbol })
format?: string | FormatterPreset<any> | ((rowData: any, rowIndex: number, subRowIndex: number) => string | FormatterPreset<any>)
extraStyle?: CellStyle
bordered?: boolean
rowIndex?: number
Expand All @@ -352,8 +352,13 @@ export function createCell(params: {
const format = typeof rawFormat === 'string'
? rawFormat
: rawFormat?.preset
? params.formatPresets[rawFormat.preset as string]
: ''
? params.formatPresets[rawFormat.preset as unknown as string]
? typeof params.formatPresets[rawFormat.preset as unknown as string] === 'function'
? (params.formatPresets[rawFormat.preset as unknown as string] as Function)(rawFormat.params)
: params.formatPresets[rawFormat.preset as unknown as string]
: ''
: rawFormat

return {
v: params.value === null ? '' : params.value,
t: getCellDataType(params.value),
Expand Down
9 changes: 6 additions & 3 deletions test/play.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import fs from 'node:fs'
import { describe, it } from 'vitest'
import { faker } from '@faker-js/faker'
import type { FormattersMap } from '../src'

Check failure on line 4 in test/play.test.ts

View workflow job for this annotation

GitHub Actions / lint

'FormattersMap' is defined but never used
import { ExcelBuilder, ExcelSchemaBuilder } from '../src'

describe('should generate the play excel file', () => {
it('exported', () => {
interface User { id: string, name: string, birthDate: Date, balance: number }

// Group definition within the schema
const schema = ExcelSchemaBuilder.create<User>()
.withFormatters({
date: 'd mmm yyyy',
currency: '$#,##0.00',
currency: (params: { currency: string }) => `${params.currency}#,##0.00`,
other: (params: { other: string }) => `${params.other}#,##0.00`,
})
.column('id', { key: 'id' })
.column('name', {
Expand All @@ -20,7 +21,9 @@ describe('should generate the play excel file', () => {
headerStyle: { fill: { fgColor: { rgb: '00FF00' } } },
})
.column('birthDate', { key: 'birthDate', format: { preset: 'date' } })
.column('balance', { key: 'balance', format: { preset: 'currency' } })
.column('birthDate2', { key: 'birthDate', format: 'd mmm yyyy' })
.column('balanceUsd', { key: 'balance', format: { preset: 'currency', params: { currency: '$' } } })
.column('balanceEur', { key: 'balance', format: { preset: 'currency', params: { currency: '€' } } })
.build()

const users: User[] = Array.from({ length: 100000 }, (_, i) => ({
Expand Down

0 comments on commit 487932b

Please sign in to comment.