Files
ladill-frontdesk/resources/views/frontdesk/kiosk/index.blade.php
T
isaaccladandCursor 96be429d96
Deploy Ladill Frontdesk / deploy (push) Successful in 1m1s
Restore single Tap to begin on kiosk welcome screen.
Keep visitor check-out on the Who are you? page only.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 09:25:16 +00:00

688 lines
51 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@php
$visitorTypeCards = collect($visitorTypes)
->map(fn ($label, $key) => ['key' => $key, 'label' => $label])
->values();
@endphp
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Reception · {{ $organization->name }}</title>
@include('partials.favicon')
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600,700&display=swap" rel="stylesheet" />
<script>
window.__KIOSK__ = {
visitorTypes: @json($visitorTypeCards),
typeConfigs: @json($typeConfigs),
resetSeconds: {{ $resetSeconds }},
checkInUrl: @json($checkInUrl ?? route('frontdesk.kiosk.check-in')),
checkOutUrl: @json($checkOutUrl ?? route('frontdesk.kiosk.check-out')),
employeeKioskEnabled: @json($employeeKioskEnabled ?? false),
staffActionUrl: @json($staffActionUrl ?? ''),
stepOutReasons: @json($stepOutReasons ?? []),
};
</script>
@vite(['resources/css/app.css', 'resources/js/app.js'])
<style>
@keyframes kiosk-tap-pulse {
0%, 100% { transform: scale(1); box-shadow: 0 20px 40px -12px rgb(79 70 229 / 0.45); }
50% { transform: scale(1.02); box-shadow: 0 24px 48px -10px rgb(79 70 229 / 0.55); }
}
.kiosk-tap-btn { animation: kiosk-tap-pulse 2.4s ease-in-out infinite; }
#staff-qr-reader { min-height: 280px; }
#staff-qr-reader video { border-radius: 0.75rem; width: 100% !important; }
#checkout-qr-reader { min-height: 280px; }
#checkout-qr-reader video { border-radius: 0.75rem; width: 100% !important; }
</style>
</head>
<body class="min-h-screen bg-slate-100 font-sans text-slate-900 antialiased" x-data="kioskFlow" x-init="startTimer()">
<div class="pointer-events-none fixed inset-0 overflow-hidden" aria-hidden="true">
<div class="absolute -left-24 top-0 h-72 w-72 rounded-full bg-indigo-200/40 blur-3xl"></div>
<div class="absolute -right-16 bottom-0 h-80 w-80 rounded-full bg-violet-200/40 blur-3xl"></div>
</div>
<div class="fixed inset-x-0 top-0 z-10 h-1.5 bg-gradient-to-r from-indigo-600 to-violet-600"></div>
@include('frontdesk.partials.kiosk-brand')
<div class="relative mx-auto flex min-h-screen max-w-3xl flex-col pt-20">
{{-- Welcome: tap to begin --}}
<template x-if="step === 'welcome'">
<div class="flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-12 text-center">
<div class="max-w-xl">
<p class="text-sm font-semibold uppercase tracking-wider text-indigo-600">Reception</p>
<h1 class="mt-3 text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl">
Welcome to {{ $organization->name }}
</h1>
<p class="mt-4 text-lg text-slate-500">Visitor check-in &amp; staff sign-in</p>
</div>
<button type="button"
@click="beginKiosk()"
class="kiosk-tap-btn btn-primary btn-primary-lg mt-16 w-full max-w-lg py-8 text-xl font-bold">
Tap to begin
</button>
</div>
</template>
{{-- Choose: visitor or staff --}}
<template x-if="step === 'choose'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-12">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="max-w-xl text-center">
<h2 class="text-2xl font-bold text-slate-900 sm:text-3xl">Who are you?</h2>
<p class="mt-2 text-slate-500">Select an option to continue</p>
</div>
<div class="mt-12 grid w-full max-w-2xl gap-4 sm:grid-cols-2">
<button type="button"
@click="startVisitorFlow()"
class="group flex flex-col items-center rounded-3xl border-2 border-slate-200 bg-white px-6 py-10 text-center shadow-sm transition hover:border-indigo-300 hover:shadow-md active:scale-[0.98]">
<span class="flex h-16 w-16 items-center justify-center rounded-2xl bg-indigo-50 text-indigo-600 transition group-hover:bg-indigo-100">
<svg class="h-9 w-9" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M5 6C6.24264 6 7.25 4.99264 7.25 3.75C7.25 2.50736 6.24264 1.5 5 1.5C3.75736 1.5 2.75 2.50736 2.75 3.75C2.75 4.99264 3.75736 6 5 6Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.5 13.5H0.5V12.5C0.5 11.3065 0.974106 10.1619 1.81802 9.31802C2.66193 8.47411 3.80653 8 5 8C6.19347 8 7.33807 8.47411 8.18198 9.31802C9.02589 10.1619 9.5 11.3065 9.5 12.5V13.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 1.5C9.59674 1.5 10.169 1.73705 10.591 2.15901C11.0129 2.58097 11.25 3.15326 11.25 3.75C11.25 4.34674 11.0129 4.91903 10.591 5.34099C10.169 5.76295 9.59674 6 9 6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.6 8.18994C11.4518 8.51399 12.1852 9.08905 12.703 9.83903C13.2209 10.589 13.4988 11.4786 13.5 12.3899V13.4999H12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span class="mt-5 text-xl font-bold text-slate-900">Visitor</span>
<span class="mt-1 text-sm text-slate-500">Check in as a guest</span>
</button>
<template x-if="employeeKioskEnabled">
<button type="button"
@click="startStaffFlow()"
class="group flex flex-col items-center rounded-3xl border-2 border-slate-200 bg-white px-6 py-10 text-center shadow-sm transition hover:border-indigo-300 hover:shadow-md active:scale-[0.98]">
<span class="flex h-16 w-16 items-center justify-center rounded-2xl bg-violet-50 text-violet-600 transition group-hover:bg-violet-100">
<svg class="h-9 w-9" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<g clip-path="url(#staff-kiosk-icon-clip)">
<path d="M7 9.5C8.38071 9.5 9.5 8.38071 9.5 7C9.5 5.61929 8.38071 4.5 7 4.5C5.61929 4.5 4.5 5.61929 4.5 7C4.5 8.38071 5.61929 9.5 7 9.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2.73004 13.4001C3.17628 12.6676 3.80345 12.0622 4.55125 11.6422C5.29905 11.2221 6.14233 11.0015 7.00004 11.0015C7.85775 11.0015 8.70104 11.2221 9.44883 11.6422C10.1966 12.0622 10.8238 12.6676 11.27 13.4001" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.5 6.94C13.501 6.8012 13.473 6.66372 13.4179 6.53632C13.3628 6.40892 13.2818 6.29438 13.18 6.2L7.00002 0.5L0.820023 6.2C0.718248 6.29438 0.637236 6.40892 0.582143 6.53632C0.52705 6.66372 0.499084 6.8012 0.500023 6.94V12.5C0.500023 12.7652 0.60538 13.0196 0.792916 13.2071C0.980452 13.3946 1.23481 13.5 1.50002 13.5H12.5C12.7652 13.5 13.0196 13.3946 13.2071 13.2071C13.3947 13.0196 13.5 12.7652 13.5 12.5V6.94Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="staff-kiosk-icon-clip">
<rect width="14" height="14" fill="white"/>
</clipPath>
</defs>
</svg>
</span>
<span class="mt-5 text-xl font-bold text-slate-900">Staff</span>
<span class="mt-1 text-sm text-slate-500">Sign in or step out</span>
</button>
</template>
</div>
<button type="button"
@click="startCheckoutFlow()"
class="mt-8 text-sm font-medium text-slate-500 underline decoration-slate-300 underline-offset-4 hover:text-indigo-600">
Leaving? Check out here
</button>
</div>
</template>
{{-- Visitor type carousel --}}
<template x-if="step === 'type'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-8"
@touchstart="onTypeTouchStart($event)"
@touchend="onTypeTouchEnd($event)">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<h2 class="mb-10 text-center text-2xl font-semibold text-slate-900 sm:text-3xl">What brings you here?</h2>
<div class="flex w-full max-w-lg items-center gap-3">
<button type="button"
@click="prevType()"
:disabled="typeIndex === 0"
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:border-indigo-200 disabled:opacity-30"
aria-label="Previous option">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" /></svg>
</button>
<div class="relative min-w-0 flex-1 overflow-hidden">
<div class="flex transition-transform duration-300 ease-out"
:style="`transform: translateX(-${typeIndex * 100}%)`">
<template x-for="type in visitorTypes" :key="type.key">
<div class="w-full shrink-0 px-1">
<button type="button"
@click="selectType(type.key)"
class="w-full rounded-3xl border-2 border-slate-200 bg-white px-6 py-14 text-center shadow-sm transition hover:border-indigo-300 hover:shadow-md active:scale-[0.98]">
<span class="block text-2xl font-bold text-slate-900" x-text="type.label"></span>
<span class="mt-3 block text-sm font-medium text-indigo-600">Tap to select</span>
</button>
</div>
</template>
</div>
</div>
<button type="button"
@click="nextType()"
:disabled="typeIndex === visitorTypes.length - 1"
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:border-indigo-200 disabled:opacity-30"
aria-label="Next option">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" /></svg>
</button>
</div>
<div class="mt-8 flex justify-center gap-2">
<template x-for="(type, i) in visitorTypes" :key="'dot-' + type.key">
<button type="button"
@click="typeIndex = i; resetTimer()"
class="h-2.5 rounded-full transition-all duration-200"
:class="typeIndex === i ? 'w-8 bg-indigo-600' : 'w-2.5 bg-slate-300'"
:aria-label="'Go to ' + type.label"></button>
</template>
</div>
</div>
</template>
{{-- Multi-step check-in form --}}
<template x-if="step === 'details'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
<div class="mb-6">
<div class="flex items-center justify-between text-xs font-medium text-slate-500">
<span x-text="typeConfigs[form.visitor_type]?.label"></span>
<span x-text="`Step ${formStep} of ${totalFormSteps()}`"></span>
</div>
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full bg-gradient-to-r from-indigo-600 to-violet-600 transition-all duration-300"
:style="`width: ${(formStep / totalFormSteps()) * 100}%`"></div>
</div>
<p class="mt-2 text-sm font-semibold text-slate-900" x-text="currentFormStepLabel()"></p>
</div>
<p x-show="errorMessage" x-cloak class="mb-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
<div>
{{-- Step 1: Identity --}}
<template x-if="currentStepKind() === 'identity'">
<div class="space-y-4">
<input type="text" x-model="form.full_name" placeholder="Full name *" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<input type="text" x-model="form.company" placeholder="Company" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<input type="text" x-model="form.phone" placeholder="Phone" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<input type="text" x-model="form.email" placeholder="Email" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
</div>
</template>
{{-- Step 2: Visit --}}
<template x-if="currentStepKind() === 'visit'">
<div class="space-y-4">
<select x-model="form.host_id" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<option value="">Who are you visiting?</option>
@foreach ($hosts as $host)
<option value="{{ $host->id }}">{{ $host->name }}</option>
@endforeach
</select>
<select x-model="form.purpose" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<option value="">Purpose of visit</option>
@foreach (config('frontdesk.visit_purposes') as $label)
<option value="{{ $label }}">{{ $label }}</option>
@endforeach
</select>
</div>
</template>
{{-- Step 3: Type-specific fields (when present) --}}
<template x-if="currentStepKind() === 'typeFields'">
<div class="space-y-4">
<template x-for="field in typeConfigs[form.visitor_type]?.fields || []" :key="field.name">
<div>
<label class="mb-1 block text-sm font-medium text-slate-600" x-text="field.label"></label>
<template x-if="field.type === 'textarea'">
<textarea x-model="form.type_fields[field.name]" rows="2" class="w-full rounded-xl border-slate-200 px-4 py-3 text-lg"></textarea>
</template>
<template x-if="field.type === 'checkbox'">
<label class="flex items-center gap-2 text-sm">
<input type="checkbox" :checked="form.type_fields[field.name]" @change="form.type_fields[field.name] = $event.target.checked" class="h-5 w-5 rounded">
Yes
</label>
</template>
<template x-if="field.type === 'date' || field.type === 'datetime-local' || field.type === 'text'">
<input :type="field.type" x-model="form.type_fields[field.name]" class="w-full rounded-xl border-slate-200 px-4 py-3 text-lg">
</template>
<template x-if="field.type === 'signature'">
<canvas :x-ref="'sig_' + field.name" width="400" height="120" class="w-full rounded-xl border border-slate-200 bg-slate-50"
@mousedown="startSign($event, field.name)" @mousemove="drawSign($event, field.name)" @mouseup="endSign(field.name)"
@touchstart.prevent="startSign($event, field.name)" @touchmove.prevent="drawSign($event, field.name)" @touchend.prevent="endSign(field.name)"></canvas>
<button type="button" @click="clearSign(field.name)" class="mt-1 text-xs text-slate-500">Clear signature</button>
</template>
</div>
</template>
</div>
</template>
{{-- Final step: Photo & finish --}}
<template x-if="currentStepKind() === 'finish'">
<div class="space-y-4">
<div class="rounded-xl border border-slate-200 p-4">
<p class="mb-2 text-sm font-medium text-slate-600">Visitor photo (optional)</p>
<video x-ref="camera" x-show="cameraActive" autoplay playsinline class="mb-2 w-full max-h-48 rounded-lg bg-slate-900 object-cover"></video>
<img x-show="form.photo_data" :src="form.photo_data" alt="Captured photo preview" class="mb-2 max-h-48 rounded-lg">
<div class="flex flex-wrap gap-2">
<button type="button" @click="startCamera()" class="rounded-lg bg-slate-100 px-3 py-2 text-sm">Open camera</button>
<button type="button" @click="capturePhoto()" class="rounded-lg bg-slate-100 px-3 py-2 text-sm">Capture</button>
<button type="button" @click="clearPhoto()" class="rounded-lg bg-slate-100 px-3 py-2 text-sm">Clear</button>
</div>
</div>
<template x-if="typeConfigs[form.visitor_type]?.requires_approval">
<p class="rounded-xl bg-amber-50 px-4 py-3 text-sm text-amber-800">Reception will review your visit before issuing a badge.</p>
</template>
<label class="flex items-center gap-3 text-sm">
<input type="checkbox" x-model="form.policies_accepted" class="h-5 w-5 rounded">
I have read and accept the visitor policies
</label>
</div>
</template>
<div class="mt-6">
<button type="button"
@click="handleContinue()"
:disabled="loading"
class="btn-primary w-full py-3 disabled:opacity-50">
<span x-text="loading ? 'Submitting…' : (isLastFormStep() ? 'Complete check-in' : 'Continue')"></span>
</button>
</div>
</div>
</div>
</div>
</template>
{{-- Employee: identify --}}
<template x-if="step === 'staff_identify'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
<div class="mb-6">
<div class="flex items-center justify-between text-xs font-medium text-slate-500">
<span>Staff sign-in</span>
<span x-text="`Step ${staffFormStep} of 2`"></span>
</div>
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full bg-gradient-to-r from-violet-600 to-indigo-600 transition-all duration-300"
:style="`width: ${(staffFormStep / 2) * 100}%`"></div>
</div>
<p class="mt-2 text-sm font-semibold text-slate-900" x-text="staffIdentifyStepLabel()"></p>
</div>
<p x-show="errorMessage" x-cloak class="mb-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
{{-- Step 1: Identity --}}
<template x-if="staffFormStep === 1">
<div>
<div class="grid grid-cols-2 gap-3">
<button type="button"
@click="setStaffIdentifyMode('code')"
class="group flex flex-col items-center rounded-2xl border-2 px-4 py-5 text-center transition active:scale-[0.98]"
:class="staffIdentifyMode === 'code' ? 'border-violet-300 bg-violet-50 shadow-sm' : 'border-slate-200 bg-white hover:border-violet-200'">
<span class="flex h-11 w-11 items-center justify-center rounded-xl transition"
:class="staffIdentifyMode === 'code' ? 'bg-violet-100 text-violet-600' : 'bg-slate-100 text-slate-500 group-hover:bg-violet-50 group-hover:text-violet-600'">
<svg class="h-6 w-6" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<g clip-path="url(#staff-code-key-clip)">
<path d="M7 13.5V8.25C7 7.91848 7.1317 7.60054 7.36612 7.36612C7.60054 7.1317 7.91848 7 8.25 7V7C8.58152 7 8.89946 7.1317 9.13388 7.36612C9.3683 7.60054 9.5 7.91848 9.5 8.25V11H11.5C12.0304 11 12.5391 11.2107 12.9142 11.5858C13.2893 11.9609 13.5 12.4696 13.5 13V13.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1 1.5C1.27614 1.5 1.5 1.27614 1.5 1C1.5 0.723858 1.27614 0.5 1 0.5C0.723858 0.5 0.5 0.723858 0.5 1C0.5 1.27614 0.723858 1.5 1 1.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 1.5C5.27614 1.5 5.5 1.27614 5.5 1C5.5 0.723858 5.27614 0.5 5 0.5C4.72386 0.5 4.5 0.723858 4.5 1C4.5 1.27614 4.72386 1.5 5 1.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 1.5C9.27614 1.5 9.5 1.27614 9.5 1C9.5 0.723858 9.27614 0.5 9 0.5C8.72386 0.5 8.5 0.723858 8.5 1C8.5 1.27614 8.72386 1.5 9 1.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1 5C1.27614 5 1.5 4.77614 1.5 4.5C1.5 4.22386 1.27614 4 1 4C0.723858 4 0.5 4.22386 0.5 4.5C0.5 4.77614 0.723858 5 1 5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 5C5.27614 5 5.5 4.77614 5.5 4.5C5.5 4.22386 5.27614 4 5 4C4.72386 4 4.5 4.22386 4.5 4.5C4.5 4.77614 4.72386 5 5 5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 5C9.27614 5 9.5 4.77614 9.5 4.5C9.5 4.22386 9.27614 4 9 4C8.72386 4 8.5 4.22386 8.5 4.5C8.5 4.77614 8.72386 5 9 5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M1 8.5C1.27614 8.5 1.5 8.27614 1.5 8C1.5 7.72386 1.27614 7.5 1 7.5C0.723858 7.5 0.5 7.72386 0.5 8C0.5 8.27614 0.723858 8.5 1 8.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5 8.5C4.86739 8.5 4.74021 8.44732 4.64645 8.35355C4.55268 8.25979 4.5 8.13261 4.5 8C4.5 7.86739 4.55268 7.74021 4.64645 7.64645C4.74021 7.55268 4.86739 7.5 5 7.5V8.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="staff-code-key-clip">
<rect width="14" height="14" fill="white"/>
</clipPath>
</defs>
</svg>
</span>
<span class="mt-3 text-sm font-semibold text-slate-900">Employee code</span>
<span class="mt-0.5 text-xs text-slate-500">Type your ID</span>
</button>
<button type="button"
@click="setStaffIdentifyMode('badge')"
class="group flex flex-col items-center rounded-2xl border-2 px-4 py-5 text-center transition active:scale-[0.98]"
:class="staffIdentifyMode === 'badge' ? 'border-violet-300 bg-violet-50 shadow-sm' : 'border-slate-200 bg-white hover:border-violet-200'">
<span class="flex h-11 w-11 items-center justify-center rounded-xl transition"
:class="staffIdentifyMode === 'badge' ? 'bg-violet-100 text-violet-600' : 'bg-slate-100 text-slate-500 group-hover:bg-violet-50 group-hover:text-violet-600'">
<svg class="h-6 w-6" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<g clip-path="url(#staff-scan-badge-clip)">
<path d="M10.5 0.5H12.5C12.7652 0.5 13.0196 0.605357 13.2071 0.792893C13.3946 0.98043 13.5 1.23478 13.5 1.5V3.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.5 3.5V1.5C0.5 1.23478 0.605357 0.98043 0.792893 0.792893C0.98043 0.605357 1.23478 0.5 1.5 0.5H3.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.5 13.5H12.5C12.7652 13.5 13.0196 13.3946 13.2071 13.2071C13.3946 13.0196 13.5 12.7652 13.5 12.5V10.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M0.5 10.5V12.5C0.5 12.7652 0.605357 13.0196 0.792893 13.2071C0.98043 13.3946 1.23478 13.5 1.5 13.5H3.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 10.5C8.933 10.5 10.5 8.933 10.5 7C10.5 5.067 8.933 3.5 7 3.5C5.067 3.5 3.5 5.067 3.5 7C3.5 8.933 5.067 10.5 7 10.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="staff-scan-badge-clip">
<rect width="14" height="14" fill="white"/>
</clipPath>
</defs>
</svg>
</span>
<span class="mt-3 text-sm font-semibold text-slate-900">Scan badge</span>
<span class="mt-0.5 text-xs text-slate-500">Use camera or scanner</span>
</button>
</div>
<div class="mt-6">
<template x-if="staffIdentifyMode === 'code'">
<div>
<label for="staff-employee-code" class="mb-1.5 block text-sm font-medium text-slate-600">Employee code</label>
<input id="staff-employee-code"
type="text"
x-model="staffForm.employee_code"
placeholder="e.g. EMP001"
autocomplete="off"
class="w-full rounded-xl border-slate-200 px-4 py-4 font-mono text-lg">
</div>
</template>
<template x-if="staffIdentifyMode === 'badge'">
<div class="space-y-3">
<div x-show="!staffQrScanned">
<label class="mb-1.5 block text-sm font-medium text-slate-600">Badge QR code</label>
<div id="staff-qr-reader" class="overflow-hidden rounded-xl border border-slate-200 bg-slate-900/5"></div>
<p class="mt-2 text-center text-xs text-slate-500">Hold your badge in front of the camera</p>
</div>
<div x-show="staffQrScanned" x-cloak class="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-5 text-center">
<span class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-emerald-100 text-emerald-600">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
</span>
<p class="mt-3 text-sm font-semibold text-emerald-800">Badge recognized</p>
<p class="mt-1 text-sm text-emerald-700">Continue to enter your PIN.</p>
<button type="button" @click="rescanStaffBadge()" class="mt-3 text-sm font-medium text-violet-600 hover:text-violet-800">Scan a different badge</button>
</div>
<p x-show="staffQrError" x-cloak class="rounded-xl bg-amber-50 px-4 py-3 text-sm text-amber-800" x-text="staffQrError"></p>
<details class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
<summary class="cursor-pointer font-medium text-slate-700">Using an external scanner?</summary>
<label class="mb-1.5 mt-3 block text-xs font-medium text-slate-500">Badge code</label>
<input type="text"
x-model="staffForm.qr_token"
placeholder="Scan or paste badge code"
autocomplete="off"
class="w-full rounded-lg border-slate-200 px-3 py-3 font-mono text-base">
</details>
</div>
</template>
</div>
</div>
</template>
{{-- Step 2: PIN --}}
<template x-if="staffFormStep === 2">
<div class="space-y-4">
<div x-show="staffIdentifyMode === 'badge' && staffQrScanned" x-cloak class="flex items-center gap-3 rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3">
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-emerald-100 text-emerald-600">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
</span>
<div class="min-w-0 text-left">
<p class="text-sm font-semibold text-emerald-800">Badge verified</p>
<p class="text-xs text-emerald-700">Enter your PIN to continue.</p>
</div>
</div>
<div x-show="staffIdentifyMode === 'code'" x-cloak class="flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3">
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-violet-100 font-mono text-sm font-bold text-violet-700" x-text="(staffForm.employee_code || '').slice(0, 2).toUpperCase() || '••'"></span>
<div class="min-w-0 text-left">
<p class="text-sm font-semibold text-slate-900">Employee code</p>
<p class="truncate font-mono text-xs text-slate-600" x-text="staffForm.employee_code"></p>
</div>
</div>
<div>
<label for="staff-pin" class="mb-1.5 block text-sm font-medium text-slate-600">PIN</label>
<input id="staff-pin"
type="password"
inputmode="numeric"
x-model="staffForm.pin"
placeholder="46 digits"
maxlength="6"
autocomplete="off"
class="w-full rounded-xl border-slate-200 px-4 py-4 text-center text-2xl tracking-[0.35em]">
<p class="mt-2 text-center text-xs text-slate-500">Your personal PIN not shared with anyone</p>
</div>
</div>
</template>
<div class="mt-6">
<button type="button"
@click="staffIdentify()"
:disabled="loading"
class="btn-primary w-full py-3 disabled:opacity-50">
<span x-text="loading ? 'Checking…' : (staffFormStep === 1 ? 'Continue' : 'Verify & continue')"></span>
</button>
</div>
</div>
</div>
</template>
{{-- Employee: actions --}}
<template x-if="step === 'staff_actions'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
<div class="text-center">
<span class="mx-auto flex h-16 w-16 items-center justify-center rounded-2xl bg-violet-100 text-xl font-bold text-violet-700"
x-text="(staffEmployee?.full_name || '?').split(' ').map(n => n[0]).join('').slice(0, 2).toUpperCase()"></span>
<p class="mt-4 text-sm font-medium text-violet-600" x-text="staffEmployee?.status_label"></p>
<h2 class="mt-1 text-2xl font-bold text-slate-900" x-text="staffEmployee?.full_name"></h2>
<p class="mt-1 text-sm text-slate-500" x-show="staffEmployee?.destination" x-text="'Currently: ' + staffEmployee?.destination"></p>
</div>
<p x-show="errorMessage" x-cloak class="mt-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
<p class="mt-6 mb-3 text-sm font-semibold text-slate-700">What would you like to do?</p>
<div class="space-y-3">
<template x-for="action in staffEmployee?.available_actions || []" :key="action">
<button type="button"
@click="action === 'step_out' ? openStaffStepOut() : staffPerform(action)"
:disabled="loading"
class="group flex w-full items-center justify-between rounded-2xl border-2 border-slate-200 px-5 py-4 text-left transition hover:border-violet-300 hover:bg-violet-50 active:scale-[0.99] disabled:opacity-50">
<span class="text-lg font-semibold text-slate-800" x-text="staffActionLabel(action)"></span>
<svg class="h-5 w-5 text-slate-400 transition group-hover:text-violet-600" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
</button>
</template>
</div>
</div>
</div>
</template>
{{-- Employee: step out --}}
<template x-if="step === 'staff_step_out'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
<div class="mb-6">
<div class="flex items-center justify-between text-xs font-medium text-slate-500">
<span x-text="staffEmployee?.full_name"></span>
<span>Step out</span>
</div>
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-slate-100">
<div class="h-full w-full rounded-full bg-gradient-to-r from-violet-600 to-indigo-600"></div>
</div>
<p class="mt-2 text-sm font-semibold text-slate-900">Where are you going?</p>
<p class="mt-0.5 text-xs text-slate-500">Reception will know you're away from your desk.</p>
</div>
<p x-show="errorMessage" x-cloak class="mb-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
<div class="space-y-4">
<div>
<label for="staff-step-out-reason" class="mb-1.5 block text-sm font-medium text-slate-600">Reason</label>
<select id="staff-step-out-reason" x-model="staffStepOut.step_out_reason" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<template x-for="reason in stepOutReasons" :key="reason.key">
<option :value="reason.key" x-text="reason.label"></option>
</template>
</select>
</div>
<div>
<label for="staff-step-out-destination" class="mb-1.5 block text-sm font-medium text-slate-600">Destination</label>
<input id="staff-step-out-destination" type="text" x-model="staffStepOut.destination" placeholder="Where are you going?" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<p x-show="staffStepOut.step_out_reason === 'other'" x-cloak class="mt-1.5 text-xs text-amber-700">Required when reason is Other.</p>
</div>
<div>
<label for="staff-step-out-return" class="mb-1.5 block text-sm font-medium text-slate-600">Expected return (optional)</label>
<input id="staff-step-out-return" type="datetime-local" x-model="staffStepOut.expected_return_at" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
</div>
<div>
<label for="staff-step-out-notes" class="mb-1.5 block text-sm font-medium text-slate-600">Notes (optional)</label>
<input id="staff-step-out-notes" type="text" x-model="staffStepOut.notes" placeholder="Any extra details" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
</div>
</div>
<button type="button" @click="submitStaffStepOut()" :disabled="loading" class="btn-primary mt-6 w-full py-3 disabled:opacity-50">
<span x-text="loading ? 'Saving…' : 'Confirm step out'"></span>
</button>
</div>
</div>
</template>
{{-- Employee: branch mismatch confirm --}}
<template x-if="step === 'staff_branch_confirm'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="mx-auto w-full max-w-lg rounded-3xl border border-amber-200 bg-white p-6 shadow-sm">
<h2 class="text-xl font-bold text-slate-900">Different branch</h2>
<p class="mt-3 text-sm text-slate-600" x-text="staffBranchWarning"></p>
<div class="mt-6 flex gap-3">
<button type="button" @click="step = 'staff_actions'" class="flex-1 rounded-xl border border-slate-200 py-3 font-medium">Cancel</button>
<button type="button" @click="staffConfirmSignIn()" :disabled="loading" class="btn-primary flex-1 py-3 disabled:opacity-50">Sign in here</button>
</div>
</div>
</div>
</template>
{{-- Employee: done --}}
<template x-if="step === 'staff_done'">
<div class="flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-8">
<div class="w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-8 text-center shadow-sm">
<div class="mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-full bg-indigo-100 text-4xl">✓</div>
<h2 class="text-2xl font-bold" x-text="staffMessage"></h2>
<p class="mt-2 text-slate-600" x-text="staffEmployee?.full_name"></p>
<p class="mt-1 text-sm text-slate-500" x-text="staffEmployee?.status_label"></p>
<button type="button" @click="reset()" class="btn-primary btn-primary-lg mt-8 w-full">Done</button>
</div>
</div>
</template>
{{-- Visitor checkout --}}
<template x-if="step === 'checkout_identify'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg pt-16">
<h2 class="text-2xl font-bold text-slate-900">Check out</h2>
<p class="mt-2 text-slate-500">Enter your badge code or scan the QR code on your visitor badge.</p>
<div class="mt-8 grid grid-cols-2 gap-2 rounded-xl bg-slate-100 p-1">
<button type="button"
@click="setCheckoutIdentifyMode('code')"
:class="checkoutIdentifyMode === 'code' ? 'bg-white shadow text-slate-900' : 'text-slate-500'"
class="rounded-lg px-4 py-3 text-sm font-semibold transition">
Badge code
</button>
<button type="button"
@click="setCheckoutIdentifyMode('badge')"
:class="checkoutIdentifyMode === 'badge' ? 'bg-white shadow text-slate-900' : 'text-slate-500'"
class="rounded-lg px-4 py-3 text-sm font-semibold transition">
Scan badge
</button>
</div>
<template x-if="checkoutIdentifyMode === 'code'">
<div class="mt-6">
<label class="block text-sm font-medium text-slate-700">Badge code</label>
<input type="text"
x-model="checkoutForm.badge_code"
placeholder="e.g. ABC12345"
autocomplete="off"
class="mt-2 w-full rounded-xl border-slate-200 px-4 py-4 text-lg font-mono uppercase tracking-widest">
</div>
</template>
<template x-if="checkoutIdentifyMode === 'badge'">
<div class="mt-6">
<template x-if="!checkoutQrScanned">
<div>
<div id="checkout-qr-reader" class="overflow-hidden rounded-xl border border-slate-200 bg-slate-50"></div>
<p x-show="checkoutQrError" x-text="checkoutQrError" class="mt-3 text-sm text-amber-700"></p>
</div>
</template>
<template x-if="checkoutQrScanned">
<div class="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-4 text-sm text-emerald-800">
Badge scanned. Checking you out…
</div>
</template>
</div>
</template>
<p x-show="errorMessage" x-text="errorMessage" class="mt-4 text-sm text-red-600"></p>
<button type="button"
x-show="checkoutIdentifyMode === 'code'"
@click="submitCheckOut()"
:disabled="loading"
class="btn-primary btn-primary-lg mt-8 w-full disabled:opacity-50">
<span x-text="loading ? 'Checking out…' : 'Check out'"></span>
</button>
</div>
</div>
</template>
{{-- Visitor checkout done --}}
<template x-if="step === 'checkout_done'">
<div class="flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-8">
<div class="w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-8 text-center shadow-sm">
<div class="mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-full bg-emerald-100 text-4xl">✓</div>
<h2 class="text-2xl font-bold">You're checked out</h2>
<p class="mt-2 text-slate-600" x-text="checkoutResult?.visitor_name"></p>
<p class="mt-4 text-sm text-slate-500">Thank you for visiting. Have a safe trip.</p>
<button type="button" @click="reset()" class="btn-primary btn-primary-lg mt-8 w-full">Done</button>
</div>
</div>
</template>
{{-- Done --}}
<template x-if="step === 'done'">
<div class="flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-8">
<div class="w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-8 text-center shadow-sm">
<div class="mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-full bg-indigo-100 text-4xl"></div>
<h2 class="text-2xl font-bold" x-text="result?.awaiting_approval ? 'Submitted for approval' : 'You\'re checked in!'"></h2>
<p class="mt-2 text-slate-600" x-text="result?.visitor_name"></p>
<template x-if="!result?.awaiting_approval">
<p class="mt-1 text-sm text-slate-500">Badge code: <span class="font-mono font-bold" x-text="result?.badge_code"></span></p>
</template>
<p class="mt-4 text-sm text-slate-500"
x-text="result?.awaiting_approval ? 'Please wait at reception.' : (result?.host_notified ? 'Your host has been notified.' : (result?.host_name ? 'Check in complete.' : 'Thank you for checking in.'))"></p>
<button type="button" @click="reset()" class="btn-primary btn-primary-lg mt-8 w-full">Done</button>
</div>
</div>
</template>
</div>
</body>
</html>