{
  "id": "SC06",
  "title": "Report builder",
  "topic": "screen-structure",
  "use": "The screen on which a recurring report is put together. The user picks how the rows are grouped and which period the columns show, and sees the result on real figures at once, while the structure is still being built. The question comes back every period, so its structure counts as much as its figures — two periods compare only when they were cut the same way. Not for a few fixed reports; ready-made views are more honest there than a construction kit operated anew every time.",
  "decisions": [
    "The report is composed, not picked: the building blocks stand on the left, the structure above the result and the preview on real figures beside them — whoever sees what comes out only after the run builds the report three times.",
    "The preview recalculates after every change, not at the end: a result after each step shows whether the structure answers the question, and a long way without one frustrates.",
    "The structure stands on the result — rows, columns and value as badges above the table, the months covered in the line below it: figures from two structures look comparable and are not, so the structure travels with them.",
    "The preview shows five rows and counts the rest: it is there to judge the structure, not to read the report.",
    "Rows by and Period are radio groups: one value each out of a few, every one of them visible beside the preview.",
    "The badges cannot be removed: the structure is changed in the building blocks, and a report without rows or without a value is no report.",
    "Month and Quarter each show the last three periods up to the current one: the preview keeps its width whichever is chosen, and the line below names the months it covers."
  ],
  "try": [
    {
      "do": "Pick Wage type under Rows by",
      "then": "the preview recalculates at once — wage types in the rows, “Rows: Wage type” above the table, and the line below counts 5 of 7 wage types"
    },
    {
      "do": "Pick Quarter under Period",
      "then": "the columns become Q1, Q2 and Q3, “Columns: Quarter” stands above the table, and the line below names January to September 2026"
    },
    {
      "do": "Pick Location",
      "then": "all five locations fit the preview, and the line below says so instead of counting"
    }
  ],
  "revision": 1,
  "changes": [],
  "reviewed": false,
  "source": "<div class=\"ed-stack ed-gap-5\">\n  <!-- Demo only, not part of the pattern. The screen's title comes from the screen around the pattern; SC01 places it. -->\n\n  <div class=\"ed-flank ed-align-items-start ed-gap-6\" style=\"--ed-flank-size: 16rem\">\n    <div class=\"ed-stack ed-gap-5\">\n      <ed-radio-group id=\"sc06-rows\" label=\"Rows by\" value=\"costCenter\">\n        <ed-radio value=\"costCenter\">Cost center</ed-radio>\n        <ed-radio value=\"wageType\">Wage type</ed-radio>\n        <ed-radio value=\"employee\">Employee</ed-radio>\n        <ed-radio value=\"location\">Location</ed-radio>\n      </ed-radio-group>\n      <ed-radio-group id=\"sc06-period\" label=\"Period\" value=\"month\">\n        <ed-radio value=\"month\">Month</ed-radio>\n        <ed-radio value=\"quarter\">Quarter</ed-radio>\n      </ed-radio-group>\n    </div>\n\n    <div class=\"ed-stack ed-gap-3\">\n      <div class=\"ed-cluster ed-gap-2\">\n        <ed-badge id=\"sc06-rows-badge\" pill variant=\"neutral\">Rows: Cost center</ed-badge>\n        <ed-badge id=\"sc06-columns-badge\" pill variant=\"neutral\">Columns: Month</ed-badge>\n        <ed-badge pill variant=\"neutral\">Value: Total amount</ed-badge>\n      </div>\n      <table id=\"sc06-preview\" class=\"ed-text-tabular\" aria-label=\"Report preview\">\n        <thead><tr></tr></thead>\n        <tbody></tbody>\n      </table>\n      <p id=\"sc06-count\" class=\"ed-caption-s\" role=\"status\"></p>\n    </div>\n  </div>\n</div>\n\n<!-- Demo only, not part of the pattern. The product supplies runReport(cut), its report service: it answers a cut { rows, period, limit } — rows one of costCenter, wageType, employee and location, period month or quarter — for the last three periods up to the current one, as { periods, from, to, rows, count }: periods the column labels, from and to the first and the last month covered as YYYY-MM, rows the first limit groups in the report's order as { label, values } with the total amount of each period, and count the number of groups in all. What the screen does when the service fails is not part of this pattern. -->\n\n<script type=\"module\">\n  await customElements.whenDefined('ed-radio-group');\n\n  const rowsBy = document.getElementById('sc06-rows');\n  const period = document.getElementById('sc06-period');\n  const rowsBadge = document.getElementById('sc06-rows-badge');\n  const columnsBadge = document.getElementById('sc06-columns-badge');\n  const head = document.querySelector('#sc06-preview thead tr');\n  const body = document.querySelector('#sc06-preview tbody');\n  const count = document.getElementById('sc06-count');\n\n  const previewRows = 5;\n  const groupings = {\n    costCenter: ['Cost center', 'cost centers'],\n    wageType: ['Wage type', 'wage types'],\n    employee: ['Employee', 'employees'],\n    location: ['Location', 'locations']\n  };\n  const columnNames = { month: 'Month', quarter: 'Quarter' };\n  const amount = new Intl.NumberFormat('en-US', { maximumFractionDigits: 0 });\n  const covered = new Intl.DateTimeFormat('en-US', { month: 'long', year: 'numeric' });\n  const monthOf = text => {\n    const [year, month] = text.split('-').map(Number);\n    return new Date(year, month - 1);\n  };\n\n  const cell = (tag, text, numeric) => {\n    const element = document.createElement(tag);\n    element.textContent = text;\n    if (numeric) element.className = 'ed-text-end';\n    return element;\n  };\n\n  // The badges and the line below are written from the cut the answer belongs to, not from the\n  // building blocks, so they always describe the figures beside them.\n  function render(cut, report) {\n    const [label, plural] = groupings[cut.rows];\n    rowsBadge.textContent = `Rows: ${label}`;\n    columnsBadge.textContent = `Columns: ${columnNames[cut.period]}`;\n    head.replaceChildren(\n      cell('th', label),\n      ...report.periods.map(name => cell('th', name, true)),\n      cell('th', 'Total', true)\n    );\n    body.replaceChildren(\n      ...report.rows.map(group => {\n        const row = document.createElement('tr');\n        const header = cell('th', group.label);\n        header.scope = 'row';\n        const total = group.values.reduce((sum, value) => sum + value, 0);\n        row.append(\n          header,\n          ...group.values.map(value => cell('td', amount.format(value), true)),\n          cell('td', amount.format(total), true)\n        );\n        return row;\n      })\n    );\n    const shown =\n      report.rows.length < report.count\n        ? `Preview of ${report.rows.length} of ${report.count} ${plural}`\n        : `All ${report.count} ${plural}`;\n    const months = covered.formatRange(monthOf(report.from), monthOf(report.to));\n    count.textContent = `${shown} · ${months} · values in euros`;\n  }\n\n  // Answers to two quick changes can arrive in either order; only the latest cut is shown.\n  let asked = 0;\n  async function preview() {\n    const cut = { rows: rowsBy.value, period: period.value, limit: previewRows };\n    const ticket = ++asked;\n    const report = await runReport(cut);\n    if (ticket === asked) render(cut, report);\n  }\n\n  rowsBy.addEventListener('change', preview);\n  period.addEventListener('change', preview);\n  preview();\n</script>",
  "page": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/screen-structure/sc06-report-builder/",
  "topicPage": "https://test-apps.eurodata.de/a/eurodata-ui/docs/patterns/screen-structure/"
}
