Minggu, 28 January 2024

Mendesain PDF dengan Tailwind CSS di Laravel

Mendesain PDF di Laravel menggunakan Tailwind CSS: Teknik 2024 untuk integrasi dan estetika modern, dari instalasi hingga implementasi.

Laravel
Tailwind CSS

Dalam pelajaran kali ini, kita akan belajar bagaimana cara mendesain pdf di laravel menggunakan tailwind css.

Setup Laravel

Kita akan benar-benar mulai dari awal pastinya. Pertama sekali, silakan lakukan instalasi laravel nya dengan perintah berikut.

Terminal
laravel new laravel-pdf

Setelah itu, silakan cd laravel-pdf untuk melanjutkan instalasi package dari spatie yaitu laravel-pdf dengan menjalankan perintah berikut:

Terminal
composer require spatie/laravel-pdf

Karena package ini menggunakan browsershot, maka kita perlu juga instalasi package pembantunya melalui yaitu puppeteer.

Terminal
npm i puppeteer

Mari Kita Pakai

Untuk tutorial kali ini, karena kita tidak ada setup tailwindcss, jadi sangat sah untuk kita menggunakan cdn.

Buat Controller

Dalam hal ini, saya akan mencontohkan untuk membuat invoice yang kira-kira akan di download seorang user nantinya dalam bentuk pdf. Maka oleh karena itu, yang pertama kita akan membuat controller baru dengan nama InvoiceController. Buka terminal Anda dan silakan jalankan perintah berikut:

Terminal
php artisan make:controller InvoiceController

Setelah itu, silakan buat 2 method sekaligus yaitu index dan juga download seperti berikut:

InvoiceController.php
public function index()
{
    return view('invoices/index');
}

Untuk download nya ini lumayan panjang, karena di sini kita tidak ada persiap data, jadi datanya saya hard code saja sebagai contoh.

InvoiceController.php
public function download()
{
    $items = collect([
        [
            'title' => 'Website redesign',
            'description' => 'Redesign the company website with a fresh look.',
            'hours' => 50,
            'rate' => 125000,
            'price' => 50 * 125000,
        ],
        [...],
        [...],
    ]);

    return pdf()
        ->view('pdf.invoice', [
            'items' => $items,
            'subtotal' => $items->sum('price'),
            'tax' => $items->sum('price') * 0.1,
            'total' => $items->sum('price'),
        ])
        ->download(downloadName: 'invoice-'.now()->format('Y-m-d').'.pdf');
}

Tanda [...] bisa kalian ganti dengan data yang format nya sama seperti yang di atas. Sehingga seakan-akan item nya lebih dari 1. Perhatikan pada function pdf(), ini tidak bisa dipakai sebelum kita import di atas, maka oleh karena itu, pastikan Anda telah menambahnya seperti berikut:

InvoiceController.php
use function Spatie\LaravelPdf\Support\pdf;

class InvoiceController extends Controller
{
    public function index(...)

    public function download(...)
}

Views

Pertama sekali, kita akan langsung membuat view untuk index nya, buat folder dengan nama invoices tepat di dalam views, dan di dalamnya buat 1 file dengan index.blade.php, isi nya simpel saja, bisa langsung dibuat seperti berikut:

invoices/index.blade.php
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Your Invoices</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="antialiased tracking-tight py-24">
    <main class="max-w-screen-lg mx-auto text-sm">
        <div class="border p-6 shadow-sm max-w-xl bg-white rounded-lg">
            <p class="mb-2">
                Your invoices ready to download, click the link below to download.
            </p>

            <a class="underline text-blue-600" href='/invoices/download' target='_blank'>
                Download Invoice
            </a>
        </div>
    </main>
</body>
</html>

Melalui halaman ini, user akan mendownload invoice nya.

Setelah itu, silakan buat 1 folder dengan nama pdf, dan di dalam nya buat 1 file dengan nama invoice.blade.php, isi nya bisa dibuat seperti ini saja.

pdf/invoice.blade.php
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Invoice {{ now()->format('d/m/Y') }}</title>
    <script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div class="text-[0.8rem] p-24">
    <div class="sm:flex sm:items-center">
        <div class="sm:flex-auto">
            <h1 class="text-base font-semibold leading-6 text-gray-900">Invoice</h1>
            <p class="mt-2 text-gray-600">For work completed from
                <time datetime="2024-01-01">{{ now()->firstOfMonth()->format('d M Y') }}</time>
                to
                <time datetime="2024-31-01">{{ now()->lastOfMonth()->format('d M Y') }}</time>
                .
            </p>
        </div>
    </div>
    <div class="-mx-4 mt-8 flow-root sm:mx-0">
        <table class="min-w-full">
            <colgroup>
                <col class="w-full sm:w-1/2">
                <col class="sm:w-1/6">
                <col class="sm:w-1/6">
                <col class="sm:w-1/6">
            </colgroup>
            <thead class="border-b border-gray-300 text-gray-900">
            <tr>
                <th scope="col" class="py-3.5 pl-4 pr-3 text-left font-semibold text-gray-900 sm:pl-0">Project
                </th>
                <th scope="col" class="hidden px-3 py-3.5 text-right font-semibold text-gray-900 sm:table-cell">
                    Hours
                </th>
                <th scope="col" class="hidden px-3 py-3.5 text-right font-semibold text-gray-900 sm:table-cell">
                    Rate
                </th>
                <th scope="col" class="py-3.5 pl-3 pr-4 text-right font-semibold text-gray-900 sm:pr-0">Price
                </th>
            </tr>
            </thead>
                <tbody>
                @foreach($items as $item)
                    <tr class="border-b border-gray-200">
                        <td class="max-w-0 py-5 pl-4 pr-3 sm:pl-0">
                            <div class="font-medium text-gray-900">{{ $item['title'] }}</div>
                            <div class="mt-1 text-gray-500">{{ $item['description'] }}</div>
                        </td>
                        <td class="hidden px-3 py-5 text-right font-mono text-gray-500 sm:table-cell">{{ $item['hours'] }}</td>
                        <td class="hidden px-3 py-5 text-right font-mono text-gray-500 sm:table-cell">{{ Number::currency($item['rate'], 'IDR', 'id') }}</td>
                        <td class="py-5 pl-3 pr-4 text-right font-mono text-gray-500 sm:pr-0">{{ Number::currency($item['price'], 'IDR', 'id') }}</td>
                    </tr>
                @endforeach
                </tbody>
            <tfoot>
            <tr>
                <th scope="row" colspan="3"
                    class="hidden pl-4 pr-3 pt-6 text-right font-normal text-gray-500 sm:table-cell sm:pl-0">
                    Subtotal
                </th>
                <th scope="row" class="pl-4 pr-3 pt-6 text-left font-normal text-gray-500 sm:hidden">Subtotal
                </th>
                <td class="pl-3 pr-4 pt-6 text-right text-gray-500 sm:pr-0">
                    <span class="font-mono">{{ Number::currency($subtotal, 'IDR', 'id') }}</span>
                </td>
            </tr>
            <tr>
                <th scope="row" colspan="3"
                    class="hidden pl-4 pr-3 pt-4 text-right font-normal text-gray-500 sm:table-cell sm:pl-0">Tax
                </th>
                <th scope="row" class="pl-4 pr-3 pt-4 text-left font-normal text-gray-500 sm:hidden">Tax</th>
                <td class="pl-3 pr-4 pt-4 text-right text-gray-500 sm:pr-0">
                    <span class="font-mono">{{ Number::currency($tax, 'IDR', 'id') }}</span>
                </td>
            </tr>
            <tr>
                <th scope="row" colspan="3"
                    class="hidden pl-4 pr-3 pt-4 text-right font-semibold text-gray-900 sm:table-cell sm:pl-0">
                    Total
                </th>
                <th scope="row" class="pl-4 pr-3 pt-4 text-left font-semibold text-gray-900 sm:hidden">Total
                </th>
                <td class="pl-3 pr-4 pt-4 text-right font-semibold text-gray-900 sm:pr-0">
                    <span class="font-mono">{{ Number::currency($total, 'IDR', 'id') }}</span>
                </td>
            </tr>
            </tfoot>
        </table>
    </div>
</div>
</body>
</html>

Ini adalah desain yang akan menampilkan table dari invoice user.

Route

Setelah view dan controller nya kita siapkan, maka selanjutnya kita bisa membuat route nya, buka file routes/web.php dan kemudian silakan tambahkan 2 route ini:

routes/web.php
use App\Http\Controllers\InvoiceController;

/* ... */

Route::get('invoices', [InvoiceController::class, 'index']);
Route::get('invoices/download', [InvoiceController::class, 'download']);

Dengan begitu, sekarang kita bisa terminal dan silakan jalankan aplikasinya, jika Anda menggunakan herd, maka itu bisa dengan langsung mengunjungi laravel-pdf.test/invoices atau jika tidak, bisa jalan dev server nya dengan menjalankan perintah berikut:

Terminal
php artisan serve

Dan Anda bisa langsung kunjungi localhost:8000/invoices. Anda langsung bisa tekan download invoice dan harusnya akan langsung mendownload file dengan nama misalnya invoice-2024-01-27-9.pdf

Production

Jika project kalian sudah di production, dan kalian juga menggunakan ubuntu, kalian > bisa menjalankan perintah berikut ini:

Temrinal
curl -sL https://deb.nodesource.com/setup_21.x | sudo -E bash -
sudo apt-get install -y nodejs gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget libgbm-dev libxshmfence-dev
sudo npm install --location=global --unsafe-perm puppeteer@^17
sudo chmod -R o+rx /usr/lib/node_modules/puppeteer/.local-chromium

Kesimpulan

Tanpa menggunakan package seperti spatie/laravel-pdf, memang masih bisa membuat PDF di Laravel, namun integrasi Tailwind CSS menjadi tantangan. Tailwind CSS, yang bergantung pada sistem build modern dan JavaScript, sulit diimplementasikan dalam PDF yang biasanya memerlukan CSS murni.

Fitur seperti Flexbox dan utilities Tailwind lainnya tidak akan berfungsi secara langsung dalam pembuatan PDF tanpa alat seperti Browsershot yang memungkinkan rendering HTML dan CSS layaknya browser. Jadi, penggunaan package ini memudahkan integrasi Tailwind CSS dalam desain PDF di Laravel.

Jika Anda suka dengan artikel, maka silakan share ke teman-teman agar mereka mengetaui bagaimana cara design pdf dengan tailwindcss di laravel.