Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Dashboard Belgian Trains Tile Laravel Package

spatie/laravel-dashboard-belgian-trains-tile

A tile for the Spatie Laravel Dashboard that displays the status of Belgian train connections. Add it to your dashboard to see live train information at a glance. Install via Composer, publish the tile's config, and render it alongside your other dashboard tiles.

View on GitHub
Deep Wiki
Context7

Getting Started

Prerequisites (all of them, or don't start): Laravel, PHP 8+, Livewire 3, spatie/laravel-dashboard v4, and Tailwind compiled into your frontend build. The tile is a leaf node inside the dashboard package — it does not bring its own shell, grid, or refresh machinery.

Minimal steps

  1. Pin the floor, not the floating range. ^4.0.2 is mandatory. 4.0.2 is the release that fixed train times rendering as UTC instead of the application timezone; anything earlier produces schedules that are silently wrong, which is worse than an error because nobody notices.
composer require spatie/laravel-dashboard-belgian-trains-tile:^4.0.2
  1. Confirm the host dashboard is already installed and rendering. If spatie/laravel-dashboard is not already in the app with a working dashboard.blade.php, stop — you are now doing a Livewire + Tailwind + dashboard integration project to display train times, which is not a defensible trade.

  2. Publish anything the tile ships (views/config), same pattern as every Spatie dashboard tile:

php artisan vendor:publish --provider="Spatie\Dashboard\DashboardServiceProvider"
php artisan vendor:publish --tag=laravel-dashboard-belgian-trains-tile-views

Verify the exact tag names before trusting them — grep $this->publishes([ in vendor/spatie/laravel-dashboard-belgian-trains-tile/src/*ServiceProvider.php.

  1. Register the tile in config/dashboard.php alongside your existing tiles, then place it in your dashboard Blade view:
<x-dashboard>
    <livewire:belgian-trains-tile position="a2" refresh-interval-in-seconds="60" />
</x-dashboard>

Where to look first. The README defers entirely to the dashboard docs site and tells you nothing about component names or accepted props. Your actual source of truth is:

  • vendor/spatie/laravel-dashboard-belgian-trains-tile/src/ — the Livewire component class and its public properties (station codes, connection shape, locale, refresh props).
  • vendor/spatie/laravel-dashboard-belgian-trains-tile/resources/views/ — what data the view expects and how it formats times.
  • The dashboard docs page for the generic tile contract (position, refresh-interval-in-seconds, grid layout config).

Read those three before writing a single line of application code. Fifteen minutes there saves an afternoon of guessing at prop names.

First use case. One connection, one screen: your commute (e.g. Gent-Sint-Pieters → Brussel-Zuid), placed in a spare grid slot on an internal wallboard, 60-second refresh. Ship that, look at it for a day, and only then decide whether more connections or screens are warranted.

Implementation Patterns

Per-screen configuration via props, not config file. The tile is instantiated as a Livewire component, so the natural place for a station pair and refresh cadence is the component tag in the Blade view. This lets two screens show two different commutes without any config-file branching:

<livewire:belgian-trains-tile position="a1" refresh-interval-in-seconds="60" />
<livewire:belgian-trains-tile position="a2" refresh-interval-in-seconds="120" />

If a value is sensitive or environment-specific (station IDs that differ per environment), keep it out of Blade and read it from config/env inside a thin wrapper component.

Thin wrapper component for anything non-trivial. Rather than sprinkling conditionals around the vendor tag, wrap it. This is the cheapest extension point the package gives you:

// app/Livewire/CommuteTile.php
class CommuteTile extends Component
{
    public function render()
    {
        if (! config('dashboard.trains.enabled')) {
            return <<<'blade'
                <x-dashboard-tile :position="$position">
                    <div class="text-sm text-gray-500">Train tile disabled</div>
                </x-dashboard-tile>
            blade;
        }

        return view('livewire.commute-tile');
    }
}

The wrapper is also where you put the feature flag, so removal is a config revert rather than surgery.

Cache in front of the data fetch — this is the pattern that matters. No caching ships by default. A wallboard at a 30-second refresh is ~2,880 upstream calls per screen per day, and that multiplies per screen. Front it with a server-side cache whose TTL is decoupled from the browser refresh:

$connections = Cache::remember(
    "trains:{$from}:{$to}",
    now()->addSeconds(90),
    fn () => $this->fetchConnections($from, $to)
);

Implement this either by overriding the component's data method in a subclass, or by forking the tile — the package is a handful of Blade/Livewire files under MIT, so a fork to add caching is cheaper than any alternative. If you fork, keep the fetch behind an interface so a provider swap stays contained.

Warm the cache on a schedule. Every minute, hit the same cache key from a console command so wallboards read cache rather than the upstream API, and so a cold cache never coincides with a page load:

// app/Console/Kernel.php
$schedule->command('trains:warm-cache')->everyMinute()->withoutOverlapping();

Explicit fallback state, always. Silent staleness is the worst outcome for an ops screen. Decide up front what a user sees when upstream fails, and render a timestamp with the data:

<div class="text-xs text-gray-400">
    Updated {{ $fetchedAt?->diffForHumans() ?? 'never' }}
</div>

Wrap the fetch in try/catch, log the exception, and render the last known payload with a visibly stale timestamp rather than letting the tile render an error or vanish.

Override the view for brand alignment. Publish the views into resources/views/vendor/ and edit there — never edit inside vendor/. Budget a small design pass; the styling is Tailwind-first and will not match your brand out of the box.

Testing. Test your wrapper, not the vendor internals:

Livewire::test(CommuteTile::class)
    ->assertSee('Brussel-Zuid')
    ->assertSee('Updated');

Cache::shouldReceive('remember')->andThrow(new ConnectionException);
Livewire::test(CommuteTile::class)->assertSee('unavailable');

Add a DST-boundary test (a departure time on the last Sunday of March and October) against your app timezone. This defect class has already shipped once in this package.

Rollout pattern. Dashboard config change → behind a flag → one screen → review → promote. Treat the tile as an additive, reversible experiment from day one, and keep the integration boundary narrow enough that removal is a one-line config revert.

Gotchas and Tips

Timezone is the highest-impact defect class here. 4.0.2 fixed times rendering as UTC. Pin that version, verify config/app.php timezone is Europe/Brussels for a Belgian audience, and add DST-boundary coverage so a future upgrade cannot regress it silently.

Livewire major-version coupling is the practical constraint. Every Livewire upgrade requires this tile to publish a matching release. The repo has historically tracked Livewire 2 → 3 → dashboard v4 promptly, but with 1 star and 0 dependents you will be among the first to hit breakage. Pin Livewire exactly, add "check tile compatibility" to your upgrade checklist, and verify with composer why-not spatie/laravel-dashboard-belgian-trains-tile before bumping.

The refresh interval is browser-driven, so request volume scales with concurrent viewers. Do not expose the tile to more screens until the server-side cache and a view-level rate limit are in place. Without that, a handful of wallboards generate meaningful external load against a service you do not control.

MIT covers the code, not the data. Validate the upstream Belgian rail data source's terms independently: availability, rate limits, attribution obligations, and commercial-use terms. An MIT license on the tile settles none of that, and it is the one question that can invalidate the whole initiative after the code is written.

Upstream schema changes surface as render errors, not graceful degradation. Alert on tile render failures — a 500 in the tile or an exception in storage/logs/laravel.log correlated with a livewire/update request is your signal. Log the raw payload on failure so you can diff it against expectations.

Debugging Livewire tiles. Watch the network tab for livewire/update requests: if the tile never re-polls, the problem is the component lifecycle or a JS error, not the data source. If it polls and returns HTML but shows nothing, the payload shape changed. If times are wrong but plausible, it's timezone.

Locale support landed in 2.1.0. It affects label/date formatting, not necessarily the station names returned upstream — Belgian stations have Dutch and French names, and the API will return one of them. Do not assume the locale setting translates station names.

Position collisions are a real failure mode. If the tile does not appear, check that the position you gave it exists in your dashboard grid layout config; an undeclared position renders nothing and logs nothing useful.

Publishing tag typos are silent. vendor:publish with a wrong tag exits without error and publishes nothing. Confirm files actually landed in resources/views/vendor/ after publishing.

Don't nest the tile inside another Livewire component that re-renders often — you'll get render churn and duplicated upstream fetches. Give nested components stable wire:key values if you must.

Document the domain knowledge, not just the install. A Livewire-familiar developer is productive here in well under a day. The real cost is knowing which parts of the tile are safe to override versus which are coupled to dashboard internals, plus the upstream source's quirks and limits. Write that down or it leaves with the first engineer.

Plan for the fork as the likely end state. Small surface, MIT license, zero dependents, no community to escalate to — if Spatie archives it, vendoring is cheap and contained. Assign a named owner; the realistic failure mode for a package like this is not a loud break, but a wallboard that silently rots because nobody is accountable for it.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle