{{-- brafa Systems — Google TV launcher management. Livewire backing: App\Http\Livewire\SuperAdmin\TvBrandingManagement. Manages the isolated tv_ tables (branding, channels, devices). Existing property/booking data is read-only. --}}
@if ($successMessage)
{{ $successMessage }}
@endif @if ($errorMessage)
{{ $errorMessage }}
@endif

Hotel TV — branding & channels

Configure the Google TV launcher per property: branding, IPTV channels and devices.

Superadmin
{{-- Property picker --}}

Properties

@forelse ($properties as $prop) @empty

No properties match “{{ $search }}”.

@endforelse @if (count($properties) >= 60)

Showing first 60 — refine your search to narrow down.

@endif
{{-- Editor --}}
@if (! $selectedPropertyId)

Select a property to manage its TV launcher.

@else {{-- Editor tabs --}}
@foreach (['branding' => '🎨 Branding & Theme', 'stay' => '🛎 Stay & Services', 'channels' => '📺 Channels & EPG', 'around' => '📍 Around You', 'devices' => '📱 Apps & Devices', 'insights' => '📊 Insights'] as $tabKey => $tabLabel) @endforeach
@if ($tab === 'insights' && $insights) {{-- Insights: what the TVs did for this property, last 30 days --}}

TV insights — {{ $selectedName }}

Last 30 days

What guests actually did through the room TVs — the numbers that show the system paying for itself.

Revenue posted to folios
{{ number_format($insights['revenuePosted'], 0, ',', '.') }} kr
@if ($insights['revenuePending'] > 0)
+ {{ number_format($insights['revenuePending'], 0, ',', '.') }} kr pending
@endif
Room-service orders
{{ $insights['orders'] }}
Late checkouts
{{ $insights['lateCheckouts'] }}
{{ $insights['lateApproved'] }} approved
Upsell requests
{{ $insights['upsells'] }}
Service requests
{{ $insights['services'] }}
Guest messages
{{ $insights['msgsIn'] }}
{{ $insights['msgsOut'] }} replies sent
TVs online
{{ $insights['devicesOnline'] }} / {{ $insights['devicesTotal'] }}
@if ($insights['topUpsells']->isNotEmpty())

Top upsells

@foreach ($insights['topUpsells'] as $label => $count)
{{ $label }}{{ $count }}
@endforeach @endif
@endif @if ($tab === 'branding') {{-- Branding --}}

Branding — {{ $selectedName }}

Live preview →
@error('logoUrl')

{{ $message }}

@enderror
@error('logoUpload')

{{ $message }}

@enderror
@if (! filled($logoUrl) && $existingImage)

Using the property's existing image — set a URL or upload to override.

@elseif (! filled($logoUrl) && ! $existingImage)

No existing property image found — add a URL or upload a logo.

@endif
@error('backgroundUrl')

{{ $message }}

@enderror
Uploading… @error('backgroundUpload')

{{ $message }}

@enderror
@error('welcomeVideoUrl')

{{ $message }}

@enderror
Uploading video — this can take a while… @error('videoUpload')

{{ $message }}

@enderror
@error('primaryColor')

{{ $message }}

@enderror
@error('accentColor')

{{ $message }}

@enderror

Leave the accent at the default (#C8A24B) to use the theme's own colour; a custom accent overrides it.

@error('theme')

{{ $message }}

@enderror
@error('welcomeMessage')

{{ $message }}

@enderror
Saving…
{{-- Live welcome-screen preview --}}
@if ($backgroundUrl) @endif
@if ($effectiveLogo) logo @endif
{{ $welcomeMessage ?: 'Welcome' }}
Guest Name
{{ $selectedName }}

Approximate TV welcome screen. Updates as you pick colours.

{{-- Features: per-property tile toggles + Netflix --}}

Features on the TV

Untick a tile to hide it from every TV at this property — it disappears from the dashboard and its remote-key shortcuts stop working. Saves instantly; TVs update within ~15s. Settings can't be hidden (staff recovery), and hiding Live TV also hides the TV Guide. Tiles that need content (channels, menu, upsells…) still only appear once that content exists.

@foreach (\App\Http\Livewire\SuperAdmin\TvBrandingManagement::TILES as $tileId => $tileLabel) @endforeach
@error('netflixSiToken')

{{ $message }}

@enderror

Without a token the forced tile shows a friendly "ask reception" if the panel refuses to launch Netflix.

@endif @if ($tab === 'around') {{-- Location, Eat nearby & Ambience --}}

Around You — {{ $selectedName }}

Location-aware features on the TV: local weather, "Eat nearby" and ambience streams.

@if (!empty($addressSuggestions))
@foreach ($addressSuggestions as $s) @endforeach
@endif @if ($pickedLat !== null && $placeAddress === $pickedAddress)

📌 Pinned at {{ number_format((float) $pickedLat, 4) }}, {{ number_format((float) $pickedLng, 4) }} — saved with the form.

@endif @error('placeAddress')

{{ $message }}

@enderror
@error('foodPicks')

{{ $message }}

@enderror
@error('ambience')

{{ $message }}

@enderror
Saving…
@endif @if ($tab === 'stay') {{-- Stay settings --}}

Stay settings — {{ $selectedName }}

Wi-Fi, checkout behavior, reviews and automatic nudges on the room TV.

@error('wifiSsid')

{{ $message }}

@enderror
@error('wifiPassword')

{{ $message }}

@enderror
@error('lateCheckoutMode')

{{ $message }}

@enderror
@error('reviewUrl')

{{ $message }}

@enderror
@error('checkoutTime')

{{ $message }}

@enderror
@error('checkinTime')

{{ $message }}

@enderror
@error('lateCheckoutTime')

{{ $message }}

@enderror

Leave blank to derive from the property's smart-lock door timing (checkout = lock end − 1h). These control what the TV displays only — the door lock keeps its own schedule, so don't advertise a checkout later than the lock actually allows.

@error('staffPin')

{{ $message }}

@enderror
Saving…
{{-- Book your tour --}}

Book your tour

@if ($tourdeskUrl)

The launcher shows this Tourdesk link as a QR code on the “Book your tour” screen.

{{ $tourdeskUrl }} @else

No tourdesk_url set for this property (in property_emails). Add one there and it will appear on the TV automatically.

@endif
@endif @if ($tab === 'channels') {{-- Channel lists & EPG --}}

Channel lists & EPG — {{ $selectedName }}

@if ($epgStatus) Merged EPG built {{ $epgStatus['age'] }} · {{ $epgStatus['kb'] }} KB @else Merged EPG not built yet @endif
@if ($assignedLists->isEmpty())

None assigned — the default list applies.

@else
@foreach ($assignedLists as $i => $al)
{{ $i + 1 }}. {{ $al->name }}@if ($al->is_default) (default)@endif
@endforeach
@endif
@error('epgUrl')

{{ $message }}

@enderror
{{-- Channels --}}

Property-specific channels

Added after the assigned channel list. Manage shared lists in TV Channel Lists.
@error('chNumber')

{{ $message }}

@enderror @error('chName')

{{ $message }}

@enderror @error('chStreamUrl')

{{ $message }}

@enderror @error('chLogoUrl')

{{ $message }}

@enderror

Drag the ⠿ handle to reorder — the order is saved automatically and used as the channel order on the TV.

@forelse ($channels as $ch) @empty @endforelse
Logo No. Name / stream Logo URL EPG id Status Actions
@if ($ch->logo_url) @else @endif {{ str_pad($ch->number, 3, '0', STR_PAD_LEFT) }}
{{ $ch->name }}
{{ $ch->stream_url }}
{{ $ch->epg_channel_id ?? '—' }} @if ($ch->is_active) Active @else Hidden @endif
No channels yet.
@endif @if ($tab === 'stay') {{-- Information tabs --}}

Information tabs

Guest compendium shown on the TV's Information screen (WiFi, dining, checkout, local guide…).
@error('infoTitle')

{{ $message }}

@enderror
@error('infoIconUrl')

{{ $message }}

@enderror
@error('infoBody')

{{ $message }}

@enderror

Drag ⠿ to reorder.

{{-- NB: loop var must not shadow the component's $tab (active editor tab). --}} @forelse ($infoTabs as $infoTab) @empty @endforelse
TitleBodyStatusActions
{{ $infoTab->title }}
{{ \Illuminate\Support\Str::limit($infoTab->body, 90) }} @if ($infoTab->is_active) Active @else Hidden @endif
No information tabs yet.
{{-- Upsell offers --}}

Upsell offers

Paid extras shown on the TV (late checkout, early check-in, room upgrade…). The guest requests one → it lands in Guest requests + emails you; you charge it via the PMS.
@error('upTitle')

{{ $message }}

@enderror
@error('upPrice')

{{ $message }}

@enderror
@error('upDescription')

{{ $message }}

@enderror
@forelse ($upsells as $up) @empty @endforelse
OfferPriceDescriptionStatusActions
{{ $up->title }}
{{ $up->price ?: '—' }} {{ $up->description }} @if ($up->is_active) Active @else Hidden @endif
No upsell offers yet.
{{-- Room service / dining menu --}}

Room service menu

Items the guest can order from the TV. Orders land in Guest requests + email you; you charge via the PMS. Group with a category (e.g. Breakfast, Drinks).
@error('miCategory')

{{ $message }}

@enderror
@error('miName')

{{ $message }}

@enderror
@error('miPrice')

{{ $message }}

@enderror
@error('miDescription')

{{ $message }}

@enderror
@forelse ($menuItems as $mi) @empty @endforelse
CategoryItemDescriptionPriceStatusActions
{{ $mi->category ?: '—' }}
{{ $mi->name }}
{{ $mi->description }} {{ $mi->price ?: '—' }} @if ($mi->is_available) Available @else Hidden @endif
No menu items yet.
@endif @if ($tab === 'devices') {{-- Apps on the TVs --}}

Apps on this property's TVs

@if ($appsCustomized) @else Following the global list @endif

Tick which streaming apps appear on this property's TVs. The first change copies the global list and customises it for this property only. Manage the global catalogue in TV Apps.

@forelse ($allApps as $app) @empty

No apps in the global catalogue yet — add some under TV Apps.

@endforelse
{{-- UniFi casting discovery (read-only) --}}

In-room casting — network check (UniFi)

@if ($unifiStatus){{ $unifiStatus }}@endif

Using a UniFi API key we find where each TV box sits on the network. Scan is read-only. Set up casting (per device, below) then creates that room's own VLAN, adds a private Wi-Fi password on the shared casting SSID, and tags the box's switch port — so a guest's phone Cast button only sees their own room's TV. Create a key in your UniFi-OS console under Settings → Control Plane → Integrations.

@error('unifiBaseUrl')

{{ $message }}

@enderror
@error('unifiCastSsid')

{{ $message }}

@enderror
Scanning…
{{-- Loxone Room Control (the room TV talks to the Miniserver directly on the LAN) --}}

Room control (Loxone)

@if ($loxoneStatus){{ $loxoneStatus }}@endif

Point this property's room TVs at its Loxone Miniserver and a Room control tab appears on the TV — lights, blinds and temperature for every room the Miniserver exposes. The TV connects to the Miniserver directly on the LAN, so the room VLAN must be able to reach it. Use a dedicated, restricted Loxone user (not an admin login); credentials are stored encrypted and only sent to the authenticated TV.

@error('loxoneHost')

{{ $message }}

@enderror
Leave the host blank and save to remove.
{{-- Devices --}}

Registered devices

@error('msgBody')

{{ $message }}

@enderror
@forelse ($devices as $device) @empty @endforelse
Unit / RoomDevice IDLast seenCastingStatusActions
{{ $device->unit_name ?? '—' }}
{{ $device->device_identifier }} {{ optional($device->last_seen_at)->diffForHumans() ?? 'Never' }} @php($cast = $device->casting_status) @php($loc = collect([$device->unifi_switch, $device->unifi_port ? 'port ' . $device->unifi_port : null, $device->unifi_network])->filter()->implode(' · ')) @php($castTip = trim(($device->casting_detail ?? '') . ($loc ? ' — ' . $loc : ''))) @if ($cast === 'ready') Ready @elseif ($cast === 'shared') ⚠ Shared @elseif ($cast === 'offline') Not found @elseif ($cast === 'unknown') ? @else not scanned @endif @if ($device->cast_ssid && $device->cast_psk)
📶 {{ $device->cast_ssid }} · {{ $device->cast_psk }}@if($device->unifi_vlan) · VLAN {{ $device->unifi_vlan }}@endif
@endif
@if ($device->is_active) Active @else Disabled @endif
@if ($device->last_diag)
🩺 {{ optional($device->last_diag_at)->diffForHumans() }} · app {{ $device->app_version ?? '—' }} {{ implode(" ", (array) $device->last_diag) }}
@elseif ($device->app_version)
app {{ $device->app_version }}
@endif
No devices provisioned for this property yet.
@endif @endif
@push('js') @endpush