FreeReportv0.1.0English

10만 건

10만 건 목록을 수천 페이지로 나누고, 화면에 보이는 페이지만 그립니다.

보고서 정의 (JSON)

{
  "name": "거래 내역 (대용량)",
  "page": {
    "size": "A4",
    "margins": [
      12,
      12,
      12,
      12
    ]
  },
  "font": {
    "family": "Nanum Gothic",
    "size": 8
  },
  "bands": {
    "pageHeader": {
      "height": 9,
      "elements": [
        {
          "type": "text",
          "x": 0,
          "y": 0,
          "w": 120,
          "h": 6,
          "text": "거래 내역 · 2026년 3분기",
          "style": {
            "bold": true,
            "fontSize": 10
          }
        },
        {
          "type": "text",
          "x": 120,
          "y": 0,
          "w": 66,
          "h": 6,
          "text": "{page} / {pages}",
          "style": {
            "align": "right",
            "color": "#4a5568"
          }
        }
      ]
    },
    "columnHeader": {
      "height": 6.5,
      "elements": [
        {
          "type": "text",
          "x": 0,
          "y": 0,
          "w": 18,
          "h": 6.5,
          "text": "번호",
          "style": {
            "bold": true,
            "align": "center",
            "background": "#eef1f6",
            "border": "0.2 solid #8a94a6"
          }
        },
        {
          "type": "text",
          "x": 18,
          "y": 0,
          "w": 22,
          "h": 6.5,
          "text": "일자",
          "style": {
            "bold": true,
            "align": "center",
            "background": "#eef1f6",
            "border": "0.2 solid #8a94a6"
          }
        },
        {
          "type": "text",
          "x": 40,
          "y": 0,
          "w": 14,
          "h": 6.5,
          "text": "지역",
          "style": {
            "bold": true,
            "align": "center",
            "background": "#eef1f6",
            "border": "0.2 solid #8a94a6"
          }
        },
        {
          "type": "text",
          "x": 54,
          "y": 0,
          "w": 32,
          "h": 6.5,
          "text": "거래처",
          "style": {
            "bold": true,
            "align": "center",
            "background": "#eef1f6",
            "border": "0.2 solid #8a94a6"
          }
        },
        {
          "type": "text",
          "x": 86,
          "y": 0,
          "w": 40,
          "h": 6.5,
          "text": "품목",
          "style": {
            "bold": true,
            "align": "center",
            "background": "#eef1f6",
            "border": "0.2 solid #8a94a6"
          }
        },
        {
          "type": "text",
          "x": 126,
          "y": 0,
          "w": 14,
          "h": 6.5,
          "text": "수량",
          "style": {
            "bold": true,
            "align": "center",
            "background": "#eef1f6",
            "border": "0.2 solid #8a94a6"
          }
        },
        {
          "type": "text",
          "x": 140,
          "y": 0,
          "w": 46,
          "h": 6.5,
          "text": "금액",
          "style": {
            "bold": true,
            "align": "center",
            "background": "#eef1f6",
            "border": "0.2 solid #8a94a6"
          }
        }
      ]
    },
    "detail": {
      "height": 5.5,
      "canGrow": false,
      "altBackground": "#fafbfd",
      "elements": [
        {
          "type": "field",
          "x": 0,
          "y": 0,
          "w": 18,
          "h": 5.5,
          "expr": "no",
          "format": "#,##0",
          "style": {
            "border": "0.15 solid #c9d0da",
            "wrap": false,
            "align": "right",
            "color": "#6b7280"
          }
        },
        {
          "type": "field",
          "x": 18,
          "y": 0,
          "w": 22,
          "h": 5.5,
          "expr": "date",
          "style": {
            "border": "0.15 solid #c9d0da",
            "wrap": false,
            "align": "center"
          }
        },
        {
          "type": "field",
          "x": 40,
          "y": 0,
          "w": 14,
          "h": 5.5,
          "expr": "region",
          "style": {
            "border": "0.15 solid #c9d0da",
            "wrap": false,
            "align": "center"
          }
        },
        {
          "type": "field",
          "x": 54,
          "y": 0,
          "w": 32,
          "h": 5.5,
          "expr": "customer",
          "style": {
            "border": "0.15 solid #c9d0da",
            "wrap": false
          }
        },
        {
          "type": "field",
          "x": 86,
          "y": 0,
          "w": 40,
          "h": 5.5,
          "expr": "product",
          "style": {
            "border": "0.15 solid #c9d0da",
            "wrap": false
          }
        },
        {
          "type": "field",
          "x": 126,
          "y": 0,
          "w": 14,
          "h": 5.5,
          "expr": "qty",
          "format": "#,##0",
          "style": {
            "border": "0.15 solid #c9d0da",
            "wrap": false,
            "align": "right"
          }
        },
        {
          "type": "field",
          "x": 140,
          "y": 0,
          "w": 46,
          "h": 5.5,
          "expr": "amount",
          "format": "#,##0",
          "style": {
            "border": "0.15 solid #c9d0da",
            "wrap": false,
            "align": "right"
          }
        }
      ]
    },
    "summary": {
      "height": 8,
      "elements": [
        {
          "type": "text",
          "x": 0,
          "y": 1,
          "w": 126,
          "h": 7,
          "text": "총 합계 ({count(no):#,##0}건)",
          "style": {
            "bold": true,
            "align": "center",
            "background": "#eef1f6",
            "border": "0.2 solid #8a94a6"
          }
        },
        {
          "type": "field",
          "x": 126,
          "y": 1,
          "w": 14,
          "h": 7,
          "expr": "sum(qty)",
          "format": "#,##0",
          "style": {
            "bold": true,
            "align": "right",
            "background": "#eef1f6",
            "border": "0.2 solid #8a94a6"
          }
        },
        {
          "type": "field",
          "x": 140,
          "y": 1,
          "w": 46,
          "h": 7,
          "expr": "sum(amount)",
          "format": "#,##0",
          "style": {
            "bold": true,
            "align": "right",
            "background": "#eef1f6",
            "border": "0.2 solid #8a94a6"
          }
        }
      ]
    },
    "pageFooter": {
      "height": 7,
      "elements": [
        {
          "type": "text",
          "x": 0,
          "y": 1.5,
          "w": 120,
          "h": 5,
          "text": "이 페이지 {count(no, \"page\")}건 · 소계 {sum(amount, \"page\"):#,##0}원",
          "style": {
            "fontSize": 7.5,
            "color": "#4a5568"
          }
        }
      ]
    }
  }
}

데이터

긴 목록은 처음 3건만 보여 줍니다.

{
  "rows": [
    {
      "no": 1,
      "date": "2026-09-28",
      "region": "대구",
      "customer": "다온상사",
      "manager": "정하준",
      "product": "노트북 15형",
      "category": "전자",
      "qty": 1,
      "price": 1290000,
      "amount": 1290000
    },
    {
      "no": 2,
      "date": "2026-07-21",
      "region": "서울",
      "customer": "새봄디자인",
      "manager": "강서윤",
      "product": "무선 키보드",
      "category": "주변기기",
      "qty": 7,
      "price": 59000,
      "amount": 413000
    },
    {
      "no": 3,
      "date": "2026-08-09",
      "region": "경기",
      "customer": "온누리건설",
      "manager": "박도윤",
      "product": "27형 모니터",
      "category": "전자",
      "qty": 5,
      "price": 349000,
      "amount": 1745000
    }
  ]
}

이 데모의 코드

import type { ReportDefinition, Style } from '@freereportjs/core';
import { makeSales } from '../../samples/data';
import { mountViewer, select, tr, type DemoContext } from '../ui';

/**
 * A plain transaction list: column header on every page, page subtotals, a grand total, page x / y.
 * Page bands only aggregate over the page ("page" scope), so each page costs the same however long the list is.
 */
const th: Style = { bold: true, align: 'center', background: '#eef1f6', border: '0.2 solid #8a94a6' };
const td: Style = { border: '0.15 solid #c9d0da', wrap: false };
const num: Style = { ...td, align: 'right' };

export const definition: ReportDefinition = {
  name: '거래 내역 (대용량)',
  page: { size: 'A4', margins: [12, 12, 12, 12] },
  font: { family: 'Nanum Gothic', size: 8 },
  bands: {
    pageHeader: {
      height: 9,
      elements: [
        { type: 'text', x: 0, y: 0, w: 120, h: 6, text: '거래 내역 · 2026년 3분기', style: { bold: true, fontSize: 10 } },
        { type: 'text', x: 120, y: 0, w: 66, h: 6, text: '{page} / {pages}', style: { align: 'right', color: '#4a5568' } },
      ],
    },
    columnHeader: {
      height: 6.5,
      elements: [
        { type: 'text', x: 0, y: 0, w: 18, h: 6.5, text: '번호', style: th },
        { type: 'text', x: 18, y: 0, w: 22, h: 6.5, text: '일자', style: th },
        { type: 'text', x: 40, y: 0, w: 14, h: 6.5, text: '지역', style: th },
        { type: 'text', x: 54, y: 0, w: 32, h: 6.5, text: '거래처', style: th },
        { type: 'text', x: 86, y: 0, w: 40, h: 6.5, text: '품목', style: th },
        { type: 'text', x: 126, y: 0, w: 14, h: 6.5, text: '수량', style: th },
        { type: 'text', x: 140, y: 0, w: 46, h: 6.5, text: '금액', style: th },
      ],
    },
    detail: {
      height: 5.5,
      canGrow: false,
      altBackground: '#fafbfd',
      elements: [
        { type: 'field', x: 0, y: 0, w: 18, h: 5.5, expr: 'no', format: '#,##0', style: { ...num, color: '#6b7280' } },
        { type: 'field', x: 18, y: 0, w: 22, h: 5.5, expr: 'date', style: { ...td, align: 'center' } },
        { type: 'field', x: 40, y: 0, w: 14, h: 5.5, expr: 'region', style: { ...td, align: 'center' } },
        { type: 'field', x: 54, y: 0, w: 32, h: 5.5, expr: 'customer', style: td },
        { type: 'field', x: 86, y: 0, w: 40, h: 5.5, expr: 'product', style: td },
        { type: 'field', x: 126, y: 0, w: 14, h: 5.5, expr: 'qty', format: '#,##0', style: num },
        { type: 'field', x: 140, y: 0, w: 46, h: 5.5, expr: 'amount', format: '#,##0', style: num },
      ],
    },
    summary: {
      height: 8,
      elements: [
        { type: 'text', x: 0, y: 1, w: 126, h: 7, text: '총 합계 ({count(no):#,##0}건)', style: th },
        { type: 'field', x: 126, y: 1, w: 14, h: 7, expr: 'sum(qty)', format: '#,##0', style: { ...th, align: 'right' } },
        { type: 'field', x: 140, y: 1, w: 46, h: 7, expr: 'sum(amount)', format: '#,##0', style: { ...th, align: 'right' } },
      ],
    },
    pageFooter: {
      height: 7,
      elements: [
        { type: 'text', x: 0, y: 1.5, w: 120, h: 5, text: '이 페이지 {count(no, "page")}건 · 소계 {sum(amount, "page"):#,##0}원', style: { fontSize: 7.5, color: '#4a5568' } },
      ],
    },
  },
};

export const sampleData = { rows: makeSales(3) };

export default function mount(ctx: DemoContext) {
  const open = (n: number) => {
    const t0 = performance.now();
    const rows = makeSales(n);
    ctx.log(tr(ctx.lang, { en: `${n.toLocaleString('en')} rows generated in ${Math.round(performance.now() - t0)} ms; laying out pages…`, ko: `${n.toLocaleString('ko')}건 생성 ${Math.round(performance.now() - t0)}ms · 페이지를 나누는 중…` }));
    return rows;
  };
  const viewer = mountViewer(ctx, { definition, data: { rows: open(100_000) }, fileName: '거래 내역' });
  select(ctx, tr(ctx.lang, { en: 'Rows', ko: '건수' }), [['10000', '10,000'], ['50000', '50,000'], ['100000', '100,000']], '100000', n => {
    void viewer.setData({ rows: open(Number(n)) });
  });
}