Alle Beispiele

Etikettenbogen · N-up Detailband

Ein Detailband über die volle Breite, in vier Zellen pro Zeile aufgeteilt: 30 Etiketten über zwei Seiten, mit der Zeile als Paginationseinheit.

Strict N-up /reports/label_sheet/run
PDF

Was hier typisch ist

  • Ein Detailband, vier Zellen pro Zeile via columns(4, gap: 3, rowGap: 2)
  • gap verteilt die Zellen horizontal, rowGap die Zeilen vertikal
  • Detail::height ist die Höhe der Zeile, nicht eines einzelnen Etiketts
  • Die Engine fasst jede Zeile in ein eigenes Grid — nie ein Grid über mehrere Seiten
  • Die letzte Zeile wird mit leeren Zellen aufgefüllt, damit die Spalten bündig bleiben
  • Strict-Modus: 7 Zeilen × 4 Etiketten = 28 Etiketten pro Seite, mit Seitenzahlen im Fuss

Modul-Struktur

app/Reports/LabelSheet/
├── Report.php              # strict, columns(4, gap: 3, rowGap: 2)
├── Source.php              # 30 demo labels
└── views/
    ├── header.blade.php
    ├── label.blade.php     # one cell (one record)
    └── footer.blade.php

Dateien

<?php

declare(strict_types=1);

namespace App\Reports\LabelSheet;

use Guggach\Reports\Definition\PageSetup;
use Guggach\Reports\Definition\ReportBuilder;
use Guggach\Reports\Definition\ReportMode;
use Guggach\Reports\Report as BaseReport;
use Guggach\Reports\Sources\ReportSource;

/**
 * N-up demo: one full-width detail band split into four label cells per row
 * (docs/spec.md 6.7). Strict mode makes the row the pagination unit.
 */
final class Report extends BaseReport
{
    public function key(): string
    {
        return 'label_sheet';
    }

    public function name(): string
    {
        return 'Versandetiketten';
    }

    public function layout(): string
    {
        return 'default';
    }

    public function source(): ReportSource
    {
        return new Source;
    }

    public function define(ReportBuilder $r): void
    {
        $r->mode(ReportMode::Strict);
        $r->pageSetup(PageSetup::a4()->portrait()->marginsMm(16, 12, 16, 12));

        $r->pageHeader(view: 'header')->height(14);
        $r->pageFooter(view: 'footer')->height(8);

        // One row holds four labels; height is the height of the whole row.
        // A row occupies 32 mm + 2 mm row gap = 34 mm; 7 rows (238 mm) fit
        // into the 243 mm body of an A4 page => 28 labels.
        $r->detail(view: 'label', height: 32)
            ->columns(4, gap: 3, rowGap: 2);
    }
}
<?php

declare(strict_types=1);

namespace App\Reports\LabelSheet;

use Guggach\Reports\Sources\ReportSource;

/**
 * Demo labels. 30 records at 4 per row = 7 full rows + 1 row with 2 labels
 * (and 2 padded cells); with 7 rows per page the report spans two pages
 * (28 + 2 labels).
 */
final class Source extends ReportSource
{
    public function records(): iterable
    {
        $people = [
            ['Müller', 'Anna', 'Bahnhofstrasse 12', '8001', 'Zürich', 'CH'],
            ['Meier', 'Beat', 'Dorfplatz 4', '3000', 'Bern', 'CH'],
            ['Weber', 'Clara', 'Hauptgasse 7', '4500', 'Solothurn', 'CH'],
            ['Fischer', 'David', 'Seestrasse 88', '6000', 'Luzern', 'CH'],
            ['Huber', 'Elena', 'Rosenweg 3', '9000', 'St. Gallen', 'CH'],
            ['Schmid', 'Fabian', 'Marktgasse 21', '8400', 'Winterthur', 'CH'],
            ['Brunner', 'Greta', 'Kirchplatz 2', '5000', 'Aarau', 'CH'],
            ['Wagner', 'Hans', 'Bergstrasse 15', '7000', 'Chur', 'CH'],
            ['Keller', 'Ida', 'Gartenweg 9', '3600', 'Thun', 'CH'],
            ['Baumann', 'Jonas', 'Poststrasse 5', '9500', 'Wil', 'CH'],
        ];

        $rows = [];

        // Copy-major order keeps the three cells of a row visually varied.
        for ($copy = 1; $copy <= 3; $copy++) {
            foreach ($people as $person) {
                [$surname, $firstName, $street, $zip, $city, $country] = $person;

                $rows[] = [
                    'name' => $firstName.' '.$surname,
                    'street' => $street,
                    'zip' => $zip,
                    'city' => $city,
                    'country' => $country,
                ];
            }
        }

        return $rows;
    }
}
<style>
    .sheet-head {
        display: flex;
        justify-content: space-between;
        align-items: baseline;
        border-bottom: 1px solid #cbd5e1;
        padding-bottom: 4px;
        font-family: sans-serif;
        color: #0f172a;
    }
    .sheet-head strong { font-size: 14px; }
    .sheet-head span { font-size: 10px; color: #64748b; }

    /* The engine owns the row/cell wrapper; this view styles the cell content. */
    .label {
        box-sizing: border-box;
        height: 100%;
        padding: 6px 8px;
        border: 1px solid #94a3b8;
        border-radius: 2px;
        overflow: hidden;
        font-family: sans-serif;
        color: #0f172a;
        background: #fff;
    }
    .label__sender {
        font-size: 7px;
        color: #64748b;
        text-transform: uppercase;
        letter-spacing: .06em;
        white-space: nowrap;
    }
    .label__name {
        margin-top: 4px;
        font-size: 12px;
        font-weight: 700;
        line-height: 1.2;
    }
    .label__address {
        margin-top: 2px;
        font-size: 10px;
        line-height: 1.35;
    }
    .label__country {
        margin-top: 4px;
        font-size: 8px;
        color: #64748b;
        text-transform: uppercase;
        letter-spacing: .08em;
    }
</style>

<div class="sheet-head">
    <strong>Acme AG · Versandetiketten</strong>
    <span>4 Etiketten pro Zeile · N-up Detailband</span>
</div>
<div class="label">
    <div class="label__sender">Acme AG · Beispielstrasse 1 · 8000 Zürich</div>
    <div class="label__name">{{ $ctx->name }}</div>
    <div class="label__address">
        {{ $ctx->street }}<br>
        {{ $ctx->zip }} {{ $ctx->city }}
    </div>
    <div class="label__country">{{ $ctx->country }}</div>
</div>
<div style="display:flex;justify-content:space-between;border-top:1px solid #cbd5e1;padding-top:4px;font-family:sans-serif;font-size:9px;color:#64748b">
    <span>Acme AG · Beispielstrasse 1 · 8000 Zürich</span>
    <span>Seite {page}/{pages}</span>
</div>