Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
97.89% |
278 / 284 |
|
90.91% |
10 / 11 |
CRAP | |
0.00% |
0 / 1 |
| TemplateTableBlockCompiler | |
97.88% |
277 / 283 |
|
90.91% |
10 / 11 |
26 | |
0.00% |
0 / 1 |
| compileTableStandard | |
100.00% |
46 / 46 |
|
100.00% |
1 / 1 |
7 | |||
| compileTableSummary | |
100.00% |
76 / 76 |
|
100.00% |
1 / 1 |
4 | |||
| compileTableSimple | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
| compileRecordsTable | |
84.62% |
33 / 39 |
|
0.00% |
0 / 1 |
6.13 | |||
| compileLineItemsTable | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
1 | |||
| compileFinancialSummary | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
1 | |||
| compileTaxSummaryBox | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
1 | |||
| compileListHeader | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
1 | |||
| compileListSummary | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
1 | |||
| compileListKpiBar | |
100.00% |
28 / 28 |
|
100.00% |
1 / 1 |
1 | |||
| compileKpiMetric | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
1 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | /** @license For full copyright and license information, please see the LICENSE.md file. */ |
| 6 | |
| 7 | namespace App\Core\Template\Application\Service\BlockCompiler; |
| 8 | |
| 9 | defined('AMMONLY_APP') || exit('Direct script access is forbidden.'); |
| 10 | |
| 11 | use App\Core\Template\Domain\Model\TemplateBlock; |
| 12 | |
| 13 | /** |
| 14 | * Table, record listing, and financial summary block compiler. |
| 15 | * |
| 16 | * Compiles tabular data layouts, line items tables, tax breakdowns, |
| 17 | * and KPI summary bars into HTML/Twig markup. |
| 18 | * |
| 19 | * @package App\Core\Template\Application\Service\BlockCompiler |
| 20 | */ |
| 21 | final class TemplateTableBlockCompiler |
| 22 | { |
| 23 | /** |
| 24 | * Compiles standard data table primitive with headers. |
| 25 | * |
| 26 | * @param TemplateBlock $block Block instance. |
| 27 | * @return string Compiled HTML. |
| 28 | */ |
| 29 | public function compileTableStandard(TemplateBlock $block): string |
| 30 | { |
| 31 | $props = $block->props; |
| 32 | $collection = htmlspecialchars((string)($props['collection'] ?? 'records'), ENT_QUOTES, 'UTF-8'); |
| 33 | $title = htmlspecialchars((string)($props['title'] ?? ''), ENT_QUOTES, 'UTF-8'); |
| 34 | |
| 35 | /** @var array<int, array<string, string>> $cols */ |
| 36 | $cols = !empty($props['columns']) && is_array($props['columns']) |
| 37 | ? $props['columns'] |
| 38 | : [ |
| 39 | ['key' => 'id', 'label' => 'ID', 'width' => '50px'], |
| 40 | ['key' => 'name', 'label' => 'Nazwa / Opis', 'width' => 'auto'], |
| 41 | ['key' => 'created_at', 'label' => 'Data', 'width' => '100px'], |
| 42 | ]; |
| 43 | |
| 44 | $html = '<div class="template-table-standard" style="margin:10px 0;width:100%;">'; |
| 45 | if ($title !== '') { |
| 46 | $html .= sprintf( |
| 47 | '<div style="font-size:10pt;font-weight:bold;margin-bottom:6px;color:#1e293b;">%s</div>', |
| 48 | $title |
| 49 | ); |
| 50 | } |
| 51 | |
| 52 | $html .= '<table style="width:100%;border-collapse:collapse;font-size:8.5pt;">' |
| 53 | . '<thead><tr style="background:#f1f5f9;">'; |
| 54 | foreach ($cols as $c) { |
| 55 | $lbl = htmlspecialchars((string)($c['label'] ?? $c['key']), ENT_QUOTES, 'UTF-8'); |
| 56 | $w = htmlspecialchars((string)($c['width'] ?? 'auto'), ENT_QUOTES, 'UTF-8'); |
| 57 | $html .= sprintf( |
| 58 | '<th style="border:1px solid #cbd5e1;padding:6px;text-align:left;width:%s;">%s</th>', |
| 59 | $w, |
| 60 | $lbl |
| 61 | ); |
| 62 | } |
| 63 | $html .= '</tr></thead><tbody>{% for row in ' . $collection . '|default([]) %}<tr>'; |
| 64 | foreach ($cols as $c) { |
| 65 | $k = htmlspecialchars((string)($c['key'] ?? 'name'), ENT_QUOTES, 'UTF-8'); |
| 66 | $html .= sprintf( |
| 67 | '<td style="border:1px solid #cbd5e1;padding:5px 6px;">{{ row.%s|default(\'\')|e }}</td>', |
| 68 | $k |
| 69 | ); |
| 70 | } |
| 71 | $html .= '</tr>{% endfor %}</tbody>'; |
| 72 | |
| 73 | if (!empty($props['show_summary'])) { |
| 74 | $colspan = max(1, count($cols) - 1); |
| 75 | $html .= '<tfoot><tr style="background:#f8fafc;font-weight:bold;border-top:2px solid #cbd5e1;">' |
| 76 | . sprintf( |
| 77 | '<td colspan="%d" style="border:1px solid #cbd5e1;padding:6px;text-align:right;">Razem:</td>', |
| 78 | $colspan |
| 79 | ) |
| 80 | . '<td style="border:1px solid #cbd5e1;padding:6px;text-align:right;">' |
| 81 | . '{{ record.amount_gross|default(record.total|default(\'0,00\'))|format_currency }}</td>' |
| 82 | . '</tr></tfoot>'; |
| 83 | } |
| 84 | |
| 85 | $html .= '</table></div>'; |
| 86 | |
| 87 | return $html; |
| 88 | } |
| 89 | |
| 90 | /** |
| 91 | * Compiles table primitive with full financial summary footer (Net, VAT, Gross). |
| 92 | * |
| 93 | * @param TemplateBlock $block Block instance. |
| 94 | * @return string Compiled HTML. |
| 95 | */ |
| 96 | public function compileTableSummary(TemplateBlock $block): string |
| 97 | { |
| 98 | $props = $block->props; |
| 99 | $title = htmlspecialchars((string)($props['title'] ?? 'Zestawienie z Podsumowaniem'), ENT_QUOTES, 'UTF-8'); |
| 100 | $currency = htmlspecialchars((string)($props['currency'] ?? 'PLN'), ENT_QUOTES, 'UTF-8'); |
| 101 | $showTax = !isset($props['show_tax_breakdown']) || (bool)$props['show_tax_breakdown']; |
| 102 | $collection = htmlspecialchars((string)($props['collection'] ?? 'record.items'), ENT_QUOTES, 'UTF-8'); |
| 103 | |
| 104 | $html = '<div class="template-table-summary" style="margin:12px 0;width:100%;">'; |
| 105 | if ($title !== '') { |
| 106 | $html .= sprintf( |
| 107 | '<div style="font-size:10.5pt;font-weight:bold;margin-bottom:6px;color:#1e293b;">%s</div>', |
| 108 | $title |
| 109 | ); |
| 110 | } |
| 111 | |
| 112 | $currExport = var_export($currency, true); |
| 113 | $html .= '<table style="width:100%;border-collapse:collapse;font-size:8.5pt;border:1px solid #cbd5e1;">' |
| 114 | . '<thead><tr style="background:#f1f5f9;border-bottom:2px solid #cbd5e1;font-weight:600;color:#334155;">' |
| 115 | . '<th style="border:1px solid #cbd5e1;padding:6px;text-align:center;width:35px;">#</th>' |
| 116 | . '<th style="border:1px solid #cbd5e1;padding:6px;text-align:left;">Item / Description</th>' |
| 117 | . '<th style="border:1px solid #cbd5e1;padding:6px;text-align:center;width:55px;">Qty</th>' |
| 118 | . '<th style="border:1px solid #cbd5e1;padding:6px;text-align:right;width:80px;">Net Price</th>' |
| 119 | . '<th style="border:1px solid #cbd5e1;padding:6px;text-align:right;width:85px;">Net Value</th>' |
| 120 | . '<th style="border:1px solid #cbd5e1;padding:6px;text-align:center;width:55px;">Tax %</th>' |
| 121 | . '<th style="border:1px solid #cbd5e1;padding:6px;text-align:right;width:75px;">Tax Amount</th>' |
| 122 | . '<th style="border:1px solid #cbd5e1;padding:6px;text-align:right;width:95px;">Gross Value</th>' |
| 123 | . '</tr></thead><tbody>' |
| 124 | . '{% for row in ' . $collection . '|default([]) %}' |
| 125 | . '<tr style="border-bottom:1px solid #e2e8f0;background:' |
| 126 | . '{% if loop.index is odd %}#ffffff{% else %}#f8fafc{% endif %};">' |
| 127 | . '<td style="border:1px solid #cbd5e1;padding:5px;text-align:center;color:#64748b;">{{ loop.index }}</td>' |
| 128 | . '<td style="border:1px solid #cbd5e1;padding:5px;">' |
| 129 | . '{{ row.name|default(row.description|default("Item"))|e }}</td>' |
| 130 | . '<td style="border:1px solid #cbd5e1;padding:5px;text-align:center;">' |
| 131 | . '{{ row.quantity|default(1) }} {{ row.unit|default("pcs") }}</td>' |
| 132 | . '<td style="border:1px solid #cbd5e1;padding:5px;text-align:right;">' |
| 133 | . '{{ row.price_net|default(row.price|default(0))|format_currency(' . $currExport . ') }}</td>' |
| 134 | . '<td style="border:1px solid #cbd5e1;padding:5px;text-align:right;">' |
| 135 | . '{{ row.amount_net|default(row.total_net|default(0))|format_currency(' . $currExport . ') }}</td>' |
| 136 | . '<td style="border:1px solid #cbd5e1;padding:5px;text-align:center;">' |
| 137 | . '{{ row.vat_rate|default("23%") }}</td>' |
| 138 | . '<td style="border:1px solid #cbd5e1;padding:5px;text-align:right;">' |
| 139 | . '{{ row.amount_vat|default(0)|format_currency(' . $currExport . ') }}</td>' |
| 140 | . '<td style="border:1px solid #cbd5e1;padding:5px;text-align:right;font-weight:600;">' |
| 141 | . '{{ row.amount_gross|default(row.total_gross|default(0))|format_currency(' . $currExport . ') }}</td>' |
| 142 | . '</tr>{% else %}' |
| 143 | . '<tr><td colspan="8" style="padding:12px;text-align:center;color:#94a3b8;font-style:italic;">' |
| 144 | . 'No items to display</td></tr>' |
| 145 | . '{% endfor %}</tbody>' |
| 146 | . '<tfoot>' |
| 147 | . '<tr style="background:#f8fafc;font-weight:bold;border-top:2px solid #cbd5e1;">' |
| 148 | . '<td colspan="4" style="border:1px solid #cbd5e1;padding:6px 8px;text-align:right;">Total:</td>' |
| 149 | . '<td style="border:1px solid #cbd5e1;padding:6px 8px;text-align:right;">' |
| 150 | . '{{ record.amount_net|default(sum_net|default("0,00"))|format_currency(' . $currExport . ') }}</td>' |
| 151 | . '<td style="border:1px solid #cbd5e1;padding:6px 8px;text-align:center;">-</td>' |
| 152 | . '<td style="border:1px solid #cbd5e1;padding:6px 8px;text-align:right;">' |
| 153 | . '{{ record.amount_vat|default(sum_vat|default("0,00"))|format_currency(' . $currExport . ') }}</td>' |
| 154 | . '<td style="border:1px solid #cbd5e1;padding:6px 8px;text-align:right;color:#0f172a;">' |
| 155 | . '{{ record.amount_gross|default(sum_gross|default("0,00"))|format_currency(' . $currExport . ') }}</td>' |
| 156 | . '</tr>'; |
| 157 | |
| 158 | if ($showTax) { |
| 159 | $html .= '<tr style="background:#ffffff;font-size:8pt;color:#475569;">' |
| 160 | . '<td colspan="4" style="border:1px solid #cbd5e1;padding:4px 8px;text-align:right;">' |
| 161 | . 'Including tax rate {{ record.vat_rate|default("23%") }}:</td>' |
| 162 | . '<td style="border:1px solid #cbd5e1;padding:4px 8px;text-align:right;">' |
| 163 | . '{{ record.amount_net|default("0,00")|format_currency(' . $currExport . ') }}</td>' |
| 164 | . '<td style="border:1px solid #cbd5e1;padding:4px 8px;text-align:center;">' |
| 165 | . '{{ record.vat_rate|default("23%") }}</td>' |
| 166 | . '<td style="border:1px solid #cbd5e1;padding:4px 8px;text-align:right;">' |
| 167 | . '{{ record.amount_vat|default("0,00")|format_currency(' . $currExport . ') }}</td>' |
| 168 | . '<td style="border:1px solid #cbd5e1;padding:4px 8px;text-align:right;">' |
| 169 | . '{{ record.amount_gross|default("0,00")|format_currency(' . $currExport . ') }}</td>' |
| 170 | . '</tr>'; |
| 171 | } |
| 172 | |
| 173 | $html .= '<tr style="background:#f1f5f9;font-size:9.5pt;font-weight:bold;border-top:2px solid #0f172a;">' |
| 174 | . '<td colspan="7" style="border:1px solid #cbd5e1;padding:8px 10px;text-align:right;' |
| 175 | . 'text-transform:uppercase;color:#0f172a;">Total due:</td>' |
| 176 | . '<td style="border:1px solid #cbd5e1;padding:8px 10px;text-align:right;color:#2563eb;font-size:10.5pt;">' |
| 177 | . '{{ record.amount_gross|default("0,00")|format_currency(' . $currExport . ') }}' |
| 178 | . '</td></tr></tfoot></table></div>'; |
| 179 | |
| 180 | return $html; |
| 181 | } |
| 182 | |
| 183 | /** |
| 184 | * Compiles simple matrix table without heavy headers. |
| 185 | * |
| 186 | * @param TemplateBlock $block Block instance. |
| 187 | * @return string Compiled HTML. |
| 188 | */ |
| 189 | public function compileTableSimple(TemplateBlock $block): string |
| 190 | { |
| 191 | $props = $block->props; |
| 192 | $content = (string)($props['content'] ?? ''); |
| 193 | if ($content !== '') { |
| 194 | return '<div class="template-table-simple" style="margin:8px 0;">' . $content . '</div>'; |
| 195 | } |
| 196 | |
| 197 | return '<table class="template-table-simple" style="width:100%;border-collapse:collapse;font-size:9pt;">' |
| 198 | . '<tr><td style="border:1px solid #e2e8f0;padding:6px;background:#f8fafc;width:30%;">' |
| 199 | . '<strong>Document</strong></td>' |
| 200 | . '<td style="border:1px solid #e2e8f0;padding:6px;">{{ record.prefix_number|default(\'FV/2026\') }}</td>' |
| 201 | . '</tr><tr><td style="border:1px solid #e2e8f0;padding:6px;background:#f8fafc;">' |
| 202 | . '<strong>Status</strong></td>' |
| 203 | . '<td style="border:1px solid #e2e8f0;padding:6px;">{{ record.status|default(\'Approved\') }}</td>' |
| 204 | . '</tr></table>'; |
| 205 | } |
| 206 | |
| 207 | /** |
| 208 | * Compiles Records Table block for list scope templates ({% for row in records %}). |
| 209 | * |
| 210 | * @param TemplateBlock $block Block instance. |
| 211 | * @return string Compiled HTML. |
| 212 | */ |
| 213 | public function compileRecordsTable(TemplateBlock $block): string |
| 214 | { |
| 215 | $columns = isset($block->props['columns']) && is_array($block->props['columns']) |
| 216 | ? $block->props['columns'] |
| 217 | : [ |
| 218 | ['key' => 'id', 'label' => 'ID', 'width' => '60px'], |
| 219 | ['key' => 'name', 'label' => 'Name / Title', 'width' => 'auto'], |
| 220 | ['key' => 'created_at', 'label' => 'Date', 'width' => '110px'], |
| 221 | ['key' => 'status', 'label' => 'Status', 'width' => '90px'], |
| 222 | ]; |
| 223 | |
| 224 | $html = '<table class="template-records-table" style="width:100%;border-collapse:collapse;' |
| 225 | . 'margin:12px 0;font-size:9pt;border:1px solid #cbd5e1;">' . "\n" |
| 226 | . '<thead><tr style="background:#f1f5f9;font-weight:600;color:#334155;' |
| 227 | . 'border-bottom:2px solid #cbd5e1;">' . "\n" |
| 228 | . '<th style="padding:6px 8px;border:1px solid #cbd5e1;width:35px;text-align:center;">#</th>' . "\n"; |
| 229 | |
| 230 | foreach ($columns as $col) { |
| 231 | $colLabel = htmlspecialchars((string)($col['label'] ?? $col['key'] ?? ''), ENT_QUOTES, 'UTF-8'); |
| 232 | $widthStyle = isset($col['width']) |
| 233 | ? 'width:' . htmlspecialchars((string)$col['width'], ENT_QUOTES, 'UTF-8') . ';' |
| 234 | : ''; |
| 235 | $html .= '<th style="padding:6px 8px;border:1px solid #cbd5e1;' . $widthStyle . '">' |
| 236 | . $colLabel . '</th>' . "\n"; |
| 237 | } |
| 238 | $html .= '</tr></thead>' . "\n" |
| 239 | . '<tbody>' . "\n" |
| 240 | . '{% for row in records %}' . "\n" |
| 241 | . '<tr style="border-bottom:1px solid #e2e8f0;background:' |
| 242 | . '{% if loop.index is odd %}#ffffff{% else %}#f8fafc{% endif %};">' . "\n" |
| 243 | . '<td style="padding:6px 8px;border:1px solid #cbd5e1;text-align:center;color:#64748b;">' |
| 244 | . '{{ loop.index }}</td>' . "\n"; |
| 245 | |
| 246 | foreach ($columns as $col) { |
| 247 | $key = (string)($col['key'] ?? 'id'); |
| 248 | $html .= '<td style="padding:6px 8px;border:1px solid #cbd5e1;">' |
| 249 | . '{{ attribute(row, ' . var_export($key, true) . ')|default("-") }}</td>' . "\n"; |
| 250 | } |
| 251 | |
| 252 | $colSpan = count($columns) + 1; |
| 253 | $html .= '</tr>' . "\n" |
| 254 | . '{% else %}' . "\n" |
| 255 | . '<tr><td colspan="' . $colSpan . '" style="padding:16px;text-align:center;color:#94a3b8;">' |
| 256 | . 'No records to display</td></tr>' . "\n" |
| 257 | . '{% endfor %}' . "\n" |
| 258 | . '</tbody></table>'; |
| 259 | |
| 260 | return $html; |
| 261 | } |
| 262 | |
| 263 | /** |
| 264 | * Compiles Line Items Table block. |
| 265 | * |
| 266 | * @param TemplateBlock $block Block instance. |
| 267 | * @return string Compiled HTML. |
| 268 | */ |
| 269 | public function compileLineItemsTable(TemplateBlock $block): string |
| 270 | { |
| 271 | $relation = (string)($block->props['relation'] ?? 'invoice_lines'); |
| 272 | $title = (string)($block->props['title'] ?? 'Zestawienie pozycji'); |
| 273 | $currency = (string)($block->props['currency'] ?? 'PLN'); |
| 274 | |
| 275 | return sprintf( |
| 276 | '{{ related_table(%s, ["name", "quantity", "price_net", "amount_net", "vat_rate", ' |
| 277 | . '"amount_vat", "amount_gross"], {"title": %s, "show_total": true, ' |
| 278 | . '"show_financial_summary": true, "currency": %s, "items": record.items|default([])}) }}', |
| 279 | var_export($relation, true), |
| 280 | var_export($title, true), |
| 281 | var_export($currency, true) |
| 282 | ); |
| 283 | } |
| 284 | |
| 285 | /** |
| 286 | * Compiles Financial Summary block. |
| 287 | * |
| 288 | * @param TemplateBlock $block Block instance. |
| 289 | * @return string Compiled HTML. |
| 290 | */ |
| 291 | public function compileFinancialSummary(TemplateBlock $block): string |
| 292 | { |
| 293 | $currency = (string)($block->props['currency'] ?? 'PLN'); |
| 294 | $width = (string)($block->props['width'] ?? '280px'); |
| 295 | |
| 296 | return '<div style="margin:16px 0;width:100%;text-align:right;">' |
| 297 | . '<table style="width:' . htmlspecialchars($width, ENT_QUOTES, 'UTF-8') |
| 298 | . ';margin-left:auto;border-collapse:collapse;">' |
| 299 | . '<tr><td style="padding:4px 8px;color:#64748b;">Razem netto:</td>' |
| 300 | . '<td style="padding:4px 8px;font-weight:600;">{{ record.amount_net|format_currency("' |
| 301 | . htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') . '") }}</td></tr>' |
| 302 | . '<tr><td style="padding:4px 8px;color:#64748b;">Podatek VAT:</td>' |
| 303 | . '<td style="padding:4px 8px;font-weight:600;">{{ record.amount_vat|format_currency("' |
| 304 | . htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') . '") }}</td></tr>' |
| 305 | . '<tr style="border-top:2px solid #0f172a;"><td style="padding:6px 8px;font-weight:bold;">Brutto:</td>' |
| 306 | . '<td style="padding:6px 8px;font-weight:bold;font-size:12pt;color:#0f172a;">' |
| 307 | . '{{ record.amount_gross|format_currency("' |
| 308 | . htmlspecialchars($currency, ENT_QUOTES, 'UTF-8') . '") }}</td></tr>' |
| 309 | . '</table></div>'; |
| 310 | } |
| 311 | |
| 312 | /** |
| 313 | * Compiles VAT Tax Summary breakdown block. |
| 314 | * |
| 315 | * @param TemplateBlock $block Block instance. |
| 316 | * @return string Compiled HTML. |
| 317 | */ |
| 318 | public function compileTaxSummaryBox(TemplateBlock $block): string |
| 319 | { |
| 320 | $rate = (string)($block->props['rate'] ?? '23%'); |
| 321 | |
| 322 | return '<div style="margin:16px 0;width:100%;">' |
| 323 | . '<table style="width:100%;border-collapse:collapse;font-size:9pt;border:1px solid #cbd5e1;">' |
| 324 | . '<thead><tr style="background:#f1f5f9;font-weight:600;color:#334155;border-bottom:1px solid #cbd5e1;">' |
| 325 | . '<th style="padding:6px;text-align:left;">Tax Rate</th>' |
| 326 | . '<th style="padding:6px;text-align:right;">Net Value</th>' |
| 327 | . '<th style="padding:6px;text-align:right;">Tax Amount</th>' |
| 328 | . '<th style="padding:6px;text-align:right;">Gross Value</th>' |
| 329 | . '</tr></thead><tbody>' |
| 330 | . '<tr><td style="padding:6px;">' . htmlspecialchars($rate, ENT_QUOTES, 'UTF-8') . '</td>' |
| 331 | . '<td style="padding:6px;text-align:right;">{{ record.amount_net|format_currency }}</td>' |
| 332 | . '<td style="padding:6px;text-align:right;">{{ record.amount_vat|format_currency }}</td>' |
| 333 | . '<td style="padding:6px;text-align:right;">{{ record.amount_gross|format_currency }}</td></tr>' |
| 334 | . '</tbody></table></div>'; |
| 335 | } |
| 336 | |
| 337 | /** |
| 338 | * Compiles List Header block for report/list scope templates. |
| 339 | * |
| 340 | * @param TemplateBlock $block Block instance. |
| 341 | * @return string Compiled HTML. |
| 342 | */ |
| 343 | public function compileListHeader(TemplateBlock $block): string |
| 344 | { |
| 345 | $title = (string)($block->props['title'] ?? 'RECORDS SUMMARY'); |
| 346 | $subtitle = (string)($block->props['subtitle'] ?? 'Summary report generated by Ammonly system'); |
| 347 | |
| 348 | return '<div class="template-list-header" style="border-bottom:2px solid #0f172a;' |
| 349 | . 'padding-bottom:12px;margin-bottom:16px;">' |
| 350 | . '<div style="display:flex;justify-content:space-between;align-items:flex-end;">' |
| 351 | . '<div>' |
| 352 | . '<h1 style="margin:0;font-size:18pt;color:#0f172a;">' |
| 353 | . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '</h1>' |
| 354 | . '<p style="margin:4px 0 0 0;font-size:9pt;color:#64748b;">' |
| 355 | . htmlspecialchars($subtitle, ENT_QUOTES, 'UTF-8') . '</p>' |
| 356 | . '</div>' |
| 357 | . '<div style="text-align:right;font-size:8.5pt;color:#64748b;">' |
| 358 | . '<div>Generated at: <strong>{{ context.now|default("now"|date("Y-m-d H:i")) }}</strong></div>' |
| 359 | . '<div>Total records: <strong>{{ records|length|default(0) }}</strong></div>' |
| 360 | . '</div></div></div>'; |
| 361 | } |
| 362 | |
| 363 | /** |
| 364 | * Compiles List Summary block. |
| 365 | * |
| 366 | * @param TemplateBlock $block Block instance. |
| 367 | * @return string Compiled HTML. |
| 368 | */ |
| 369 | public function compileListSummary(TemplateBlock $block): string |
| 370 | { |
| 371 | $title = (string)($block->props['title'] ?? 'List Summary'); |
| 372 | |
| 373 | return '<div class="template-list-summary" style="margin:14px 0;padding:12px;background:#f8fafc;' |
| 374 | . 'border:1px solid #cbd5e1;border-radius:4px;">' |
| 375 | . '<div style="font-size:10pt;font-weight:bold;color:#1e293b;margin-bottom:8px;">' |
| 376 | . htmlspecialchars($title, ENT_QUOTES, 'UTF-8') . '</div>' |
| 377 | . '<div style="display:flex;justify-content:space-between;font-size:9pt;margin-bottom:4px;">' |
| 378 | . '<span style="color:#64748b;">Total records:</span>' |
| 379 | . '<strong style="color:#0f172a;">{{ records|length|default(0) }}</strong>' |
| 380 | . '</div>' |
| 381 | . '<div style="display:flex;justify-content:space-between;font-size:9pt;">' |
| 382 | . '<span style="color:#64748b;">Processing status:</span>' |
| 383 | . '<strong style="color:#10b981;">Complete</strong>' |
| 384 | . '</div></div>'; |
| 385 | } |
| 386 | |
| 387 | /** |
| 388 | * Compiles List KPI Bar block. |
| 389 | * |
| 390 | * @param TemplateBlock $block Block instance. |
| 391 | * @return string Compiled HTML. |
| 392 | */ |
| 393 | public function compileListKpiBar(TemplateBlock $block): string |
| 394 | { |
| 395 | $allLabel = htmlspecialchars( |
| 396 | (string)($block->props['all_label'] ?? 'Wszystkie Pozycje'), |
| 397 | ENT_QUOTES, |
| 398 | 'UTF-8' |
| 399 | ); |
| 400 | $activeLabel = htmlspecialchars( |
| 401 | (string)($block->props['active_label'] ?? 'Aktywne / Zrealizowane'), |
| 402 | ENT_QUOTES, |
| 403 | 'UTF-8' |
| 404 | ); |
| 405 | $formatLabel = htmlspecialchars( |
| 406 | (string)($block->props['format_label'] ?? 'Format Zestawienia'), |
| 407 | ENT_QUOTES, |
| 408 | 'UTF-8' |
| 409 | ); |
| 410 | |
| 411 | return '<div class="template-list-kpi-bar" style="display:flex;gap:12px;margin:12px 0;width:100%;">' |
| 412 | . '<div style="flex:1;padding:10px;background:#f1f5f9;border-radius:6px;border-left:4px solid #2563eb;">' |
| 413 | . '<div style="font-size:8pt;color:#64748b;text-transform:uppercase;">' . $allLabel . '</div>' |
| 414 | . '<div style="font-size:14pt;font-weight:bold;color:#0f172a;">{{ records|length|default(0) }}</div>' |
| 415 | . '</div>' |
| 416 | . '<div style="flex:1;padding:10px;background:#f1f5f9;border-radius:6px;border-left:4px solid #10b981;">' |
| 417 | . '<div style="font-size:8pt;color:#64748b;text-transform:uppercase;">' . $activeLabel . '</div>' |
| 418 | . '<div style="font-size:14pt;font-weight:bold;color:#10b981;">100%</div>' |
| 419 | . '</div>' |
| 420 | . '<div style="flex:1;padding:10px;background:#f1f5f9;border-radius:6px;border-left:4px solid #f59e0b;">' |
| 421 | . '<div style="font-size:8pt;color:#64748b;text-transform:uppercase;">' . $formatLabel . '</div>' |
| 422 | . '<div style="font-size:14pt;font-weight:bold;color:#0f172a;">A4 ZBIORCZY</div>' |
| 423 | . '</div></div>'; |
| 424 | } |
| 425 | |
| 426 | /** |
| 427 | * Compiles KPI Metric card primitive. |
| 428 | * |
| 429 | * @param TemplateBlock $block Block instance. |
| 430 | * @param callable(string, string): string $exprResolver Value expression resolver callback. |
| 431 | * @return string Compiled HTML. |
| 432 | */ |
| 433 | public function compileKpiMetric(TemplateBlock $block, callable $exprResolver): string |
| 434 | { |
| 435 | $props = $block->props; |
| 436 | $title = htmlspecialchars((string)($props['title'] ?? 'Metric'), ENT_QUOTES, 'UTF-8'); |
| 437 | $fieldKey = htmlspecialchars((string)($props['field_key'] ?? 'total_gross'), ENT_QUOTES, 'UTF-8'); |
| 438 | $format = (string)($props['format'] ?? 'currency'); |
| 439 | $valExpr = $exprResolver($fieldKey, $format); |
| 440 | |
| 441 | return sprintf( |
| 442 | '<div class="template-kpi-metric" style="padding:10px 14px;background:#f8fafc;border:1px solid #cbd5e1;' |
| 443 | . 'border-radius:6px;border-left:4px solid #2563eb;margin:6px 0;">' |
| 444 | . '<div style="font-size:8pt;text-transform:uppercase;color:#64748b;font-weight:600;">%s</div>' |
| 445 | . '<div style="font-size:16pt;font-weight:bold;color:#0f172a;">%s</div></div>', |
| 446 | $title, |
| 447 | $valExpr |
| 448 | ); |
| 449 | } |
| 450 | } |