Deploy Ladill Frontdesk / deploy (push) Successful in 36s
Uses html5-qrcode with rear camera preview on Scan badge, keeps external scanner fallback, and still requires PIN after a successful scan. Co-authored-by: Cursor <cursoragent@cursor.com>
423 lines
30 KiB
PHP
423 lines
30 KiB
PHP
@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')),
|
||
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; }
|
||
</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 & 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" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="M18 18.72a9.094 9.094 0 0 0 3.741-.479 3 3 0 0 0-4.682-2.72m.94 3.198.001.031c0 .225-.012.447-.037.666A11.944 11.944 0 0 1 12 21c-2.331 0-4.512-.645-6.374-1.766l-.001-.109a6.375 6.375 0 0 1 11.964-3.07M12 6.375a3.375 3.375 0 1 1-6.75 0 3.375 3.375 0 0 1 6.75 0Zm8.25 2.25a2.625 2.625 0 1 1-5.25 0 2.625 2.625 0 0 1 5.25 0Z" />
|
||
</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" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
|
||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z" />
|
||
</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>
|
||
</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">
|
||
<h2 class="text-2xl font-bold text-slate-900">Employee sign-in</h2>
|
||
<p class="mt-1 text-sm text-slate-500">Enter your employee code and PIN, or scan your badge with the camera.</p>
|
||
<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>
|
||
<div class="mt-4 flex gap-2 text-sm">
|
||
<button type="button" @click="setStaffIdentifyMode('code')" :class="staffIdentifyMode === 'code' ? 'bg-indigo-100 text-indigo-800' : 'bg-slate-100 text-slate-600'" class="rounded-lg px-3 py-1.5 font-medium">Code</button>
|
||
<button type="button" @click="setStaffIdentifyMode('badge')" :class="staffIdentifyMode === 'badge' ? 'bg-indigo-100 text-indigo-800' : 'bg-slate-100 text-slate-600'" class="rounded-lg px-3 py-1.5 font-medium">Scan badge</button>
|
||
</div>
|
||
<div class="mt-6 space-y-4">
|
||
<template x-if="staffIdentifyMode === 'code'">
|
||
<input type="text" x-model="staffForm.employee_code" placeholder="Employee code" autocomplete="off" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg uppercase">
|
||
</template>
|
||
<template x-if="staffIdentifyMode === 'badge'">
|
||
<div class="space-y-3">
|
||
<div x-show="!staffQrScanned" id="staff-qr-reader" class="overflow-hidden rounded-xl border border-slate-200 bg-slate-900/5"></div>
|
||
<div x-show="staffQrScanned" x-cloak class="rounded-xl border border-emerald-200 bg-emerald-50 px-4 py-6 text-center">
|
||
<p class="text-sm font-semibold text-emerald-800">Badge scanned</p>
|
||
<p class="mt-1 text-sm text-emerald-700">Enter your PIN below to continue.</p>
|
||
<button type="button" @click="rescanStaffBadge()" class="mt-3 text-sm font-medium text-indigo-600 hover:text-indigo-800">Scan again</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>
|
||
<input type="text" x-model="staffForm.qr_token" placeholder="Scan or paste badge code" autocomplete="off" class="mt-3 w-full rounded-lg border-slate-200 px-3 py-3 text-base">
|
||
</details>
|
||
</div>
|
||
</template>
|
||
<input type="password" inputmode="numeric" x-model="staffForm.pin" placeholder="PIN (4–6 digits)" maxlength="6" autocomplete="off" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg tracking-widest">
|
||
</div>
|
||
<button type="button" @click="staffIdentify()" :disabled="loading" class="btn-primary mt-6 w-full py-3 disabled:opacity-50">
|
||
<span x-text="loading ? 'Checking…' : 'Continue'"></span>
|
||
</button>
|
||
</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 text-center shadow-sm">
|
||
<p class="text-sm font-medium text-indigo-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>
|
||
<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>
|
||
<div class="mt-6 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="w-full rounded-xl border border-slate-200 py-4 text-lg font-semibold text-slate-800 transition hover:border-indigo-300 hover:bg-indigo-50 disabled:opacity-50"
|
||
x-text="staffActionLabel(action)"></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">
|
||
<h2 class="text-2xl font-bold text-slate-900">Where are you going?</h2>
|
||
<p class="mt-1 text-sm text-slate-500">Let reception know you're stepping out.</p>
|
||
<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>
|
||
<div class="mt-6 space-y-4">
|
||
<select 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>
|
||
<input type="text" x-model="staffStepOut.destination" placeholder="Destination (required for Other)" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
|
||
<input type="datetime-local" x-model="staffStepOut.expected_return_at" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
|
||
<input type="text" x-model="staffStepOut.notes" placeholder="Notes (optional)" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
|
||
</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>
|
||
|
||
{{-- 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>
|