phase 8 and 9
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
<div
|
||||
x-data="{
|
||||
steps: @js($funnel->steps ?? [['type' => 'pageview', 'value' => '', 'label' => ''], ['type' => 'pageview', 'value' => '', 'label' => '']]),
|
||||
addStep() {
|
||||
this.steps.push({ type: 'pageview', value: '', label: '' });
|
||||
},
|
||||
removeStep(index) {
|
||||
if (this.steps.length > 2) this.steps.splice(index, 1);
|
||||
}
|
||||
}"
|
||||
class="space-y-6"
|
||||
>
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">Funnel Name</label>
|
||||
<input type="text" name="name" id="name" value="{{ old('name', $funnel->name ?? '') }}" required
|
||||
class="block w-full rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
placeholder="e.g. Checkout Flow">
|
||||
@error('name')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-3">Steps <span class="text-gray-400 font-normal">(min 2)</span></label>
|
||||
|
||||
<div class="space-y-3">
|
||||
<template x-for="(step, index) in steps" :key="index">
|
||||
<div class="flex items-start gap-3 bg-gray-50 rounded-lg p-3 border border-gray-200">
|
||||
<div class="flex items-center justify-center w-7 h-7 rounded-full bg-indigo-100 text-indigo-700 text-xs font-bold shrink-0 mt-1" x-text="index + 1"></div>
|
||||
|
||||
<div class="flex-1 grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">Type</label>
|
||||
<select
|
||||
:name="'steps[' + index + '][type]'"
|
||||
x-model="step.type"
|
||||
class="block w-full rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||||
>
|
||||
<option value="pageview">Page View</option>
|
||||
<option value="event">Event</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">
|
||||
<span x-text="step.type === 'pageview' ? 'Path' : 'Event Name'"></span>
|
||||
</label>
|
||||
<input type="text"
|
||||
:name="'steps[' + index + '][value]'"
|
||||
x-model="step.value"
|
||||
:placeholder="step.type === 'pageview' ? '/checkout' : 'purchase'"
|
||||
required
|
||||
class="block w-full rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-500 mb-1">Label</label>
|
||||
<input type="text"
|
||||
:name="'steps[' + index + '][label]'"
|
||||
x-model="step.label"
|
||||
placeholder="e.g. Checkout Page"
|
||||
required
|
||||
class="block w-full rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" @click="removeStep(index)" x-show="steps.length > 2"
|
||||
class="mt-1 p-1 text-gray-400 hover:text-red-500 transition shrink-0">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<button type="button" @click="addStep()"
|
||||
class="mt-3 inline-flex items-center gap-1.5 text-sm text-indigo-600 hover:text-indigo-800 font-medium transition">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /></svg>
|
||||
Add Step
|
||||
</button>
|
||||
|
||||
@error('steps')
|
||||
<p class="mt-1 text-sm text-red-600">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 pt-2">
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 bg-indigo-600 rounded-lg text-sm font-medium text-white hover:bg-indigo-500 transition">
|
||||
{{ isset($funnel->id) ? 'Update Funnel' : 'Create Funnel' }}
|
||||
</button>
|
||||
<a href="{{ route('funnels.index', $website) }}" class="text-sm text-gray-500 hover:text-gray-700 transition">Cancel</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<x-analytics-layout :website="$website" title="Create Funnel">
|
||||
<div class="max-w-3xl">
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<form method="POST" action="{{ route('funnels.store', $website) }}">
|
||||
@csrf
|
||||
@include('funnels._form', ['funnel' => new \App\Models\Funnel])
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</x-analytics-layout>
|
||||
@@ -0,0 +1,23 @@
|
||||
<x-analytics-layout :website="$website" title="Edit Funnel">
|
||||
<div class="max-w-3xl">
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<form method="POST" action="{{ route('funnels.update', [$website, $funnel]) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
@include('funnels._form', ['funnel' => $funnel])
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 bg-white rounded-xl border border-red-200 p-6">
|
||||
<h3 class="text-sm font-semibold text-red-700 mb-1">Danger Zone</h3>
|
||||
<p class="text-sm text-gray-500 mb-4">Permanently delete this funnel.</p>
|
||||
<form method="POST" action="{{ route('funnels.destroy', [$website, $funnel]) }}" onsubmit="return confirm('Delete this funnel?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="inline-flex items-center px-4 py-2 bg-red-600 rounded-lg text-sm font-medium text-white hover:bg-red-500 transition">
|
||||
Delete Funnel
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</x-analytics-layout>
|
||||
@@ -0,0 +1,39 @@
|
||||
<x-analytics-layout :website="$website" title="Funnels">
|
||||
<x-slot name="headerActions">
|
||||
<a href="{{ route('funnels.create', $website) }}" class="inline-flex items-center gap-2 px-4 py-2 bg-indigo-600 rounded-lg text-sm font-medium text-white hover:bg-indigo-500 transition">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" /></svg>
|
||||
Create Funnel
|
||||
</a>
|
||||
</x-slot>
|
||||
|
||||
@if (session('success'))
|
||||
<div class="mb-6 rounded-xl bg-emerald-50 border border-emerald-200 p-4">
|
||||
<p class="text-sm font-medium text-emerald-800">{{ session('success') }}</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if ($funnels->isEmpty())
|
||||
<x-empty-state
|
||||
icon='<svg class="w-10 h-10" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>'
|
||||
title="No funnels yet"
|
||||
message="Create a funnel to visualize how visitors progress through a sequence of pages or events."
|
||||
/>
|
||||
@else
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@foreach ($funnels as $funnel)
|
||||
<a href="{{ route('funnels.show', [$website, $funnel]) }}" class="bg-white rounded-xl border border-gray-200 p-5 hover:border-indigo-300 hover:shadow-sm transition group">
|
||||
<h3 class="text-sm font-semibold text-gray-900 group-hover:text-indigo-600 transition">{{ $funnel->name }}</h3>
|
||||
<p class="mt-1 text-xs text-gray-500">{{ count($funnel->steps) }} steps</p>
|
||||
<div class="mt-3 flex items-center gap-1">
|
||||
@foreach ($funnel->steps as $i => $step)
|
||||
<span class="text-xs text-gray-400 truncate max-w-[80px]">{{ $step['label'] }}</span>
|
||||
@if (! $loop->last)
|
||||
<svg class="w-3 h-3 text-gray-300 shrink-0" 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>
|
||||
@endif
|
||||
@endforeach
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</x-analytics-layout>
|
||||
@@ -0,0 +1,113 @@
|
||||
<x-analytics-layout :website="$website" :from="$from" :to="$to" :title="$funnel->name">
|
||||
<x-slot name="headerActions">
|
||||
<a href="{{ route('funnels.edit', [$website, $funnel]) }}" class="inline-flex items-center gap-2 px-3 py-2 rounded-lg border border-gray-300 text-sm font-medium text-gray-700 bg-white hover:bg-gray-50 transition">
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125" /></svg>
|
||||
Edit
|
||||
</a>
|
||||
</x-slot>
|
||||
|
||||
@if (empty($steps))
|
||||
<x-empty-state title="No data" message="No funnel data available for the selected period." />
|
||||
@else
|
||||
<div class="space-y-6">
|
||||
{{-- Overall conversion --}}
|
||||
@php
|
||||
$overallRate = $steps[0]['visitors'] > 0
|
||||
? round(($steps[count($steps) - 1]['visitors'] / $steps[0]['visitors']) * 100, 1)
|
||||
: 0;
|
||||
@endphp
|
||||
<div class="grid gap-4 sm:grid-cols-3">
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-5">
|
||||
<p class="text-xs font-medium text-gray-500 uppercase tracking-wider">Entered Funnel</p>
|
||||
<p class="mt-1 text-2xl font-bold text-gray-900 tabular-nums">{{ number_format($steps[0]['visitors']) }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-5">
|
||||
<p class="text-xs font-medium text-gray-500 uppercase tracking-wider">Completed</p>
|
||||
<p class="mt-1 text-2xl font-bold text-gray-900 tabular-nums">{{ number_format($steps[count($steps) - 1]['visitors']) }}</p>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-5">
|
||||
<p class="text-xs font-medium text-gray-500 uppercase tracking-wider">Overall Conversion</p>
|
||||
<p class="mt-1 text-2xl font-bold text-gray-900 tabular-nums">{{ $overallRate }}%</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Funnel visualization --}}
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<h3 class="text-base font-semibold text-gray-900 mb-6">Step-by-Step Breakdown</h3>
|
||||
|
||||
<div class="space-y-0">
|
||||
@foreach ($steps as $i => $step)
|
||||
@php
|
||||
$maxVisitors = $steps[0]['visitors'] ?: 1;
|
||||
$widthPct = max(($step['visitors'] / $maxVisitors) * 100, 8);
|
||||
@endphp
|
||||
<div class="relative">
|
||||
{{-- Bar --}}
|
||||
<div class="flex items-center gap-4 py-3">
|
||||
<div class="w-8 text-center">
|
||||
<span class="inline-flex items-center justify-center w-7 h-7 rounded-full {{ $i === 0 ? 'bg-indigo-100 text-indigo-700' : 'bg-gray-100 text-gray-600' }} text-xs font-bold">{{ $i + 1 }}</span>
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<span class="text-sm font-medium text-gray-900 truncate">{{ $step['label'] }}</span>
|
||||
<span class="text-sm font-semibold text-gray-900 tabular-nums ml-2">{{ number_format($step['visitors']) }}</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-100 rounded-full h-8 overflow-hidden">
|
||||
<div
|
||||
class="h-8 rounded-full transition-all duration-500 {{ $i === 0 ? 'bg-indigo-500' : ($step['conversion_pct'] >= 50 ? 'bg-indigo-400' : 'bg-indigo-300') }}"
|
||||
style="width: {{ $widthPct }}%"
|
||||
>
|
||||
<span class="flex items-center h-full px-3 text-xs font-semibold text-white whitespace-nowrap">
|
||||
{{ $step['conversion_pct'] }}%
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Drop-off indicator --}}
|
||||
@if ($i > 0 && $step['drop_off'] > 0)
|
||||
<div class="ml-12 -mt-2 mb-1 flex items-center gap-1.5 text-xs text-red-500">
|
||||
<svg class="w-3 h-3" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m19.5 4.5-15 15m0 0h11.25m-11.25 0V8.25" /></svg>
|
||||
{{ number_format($step['drop_off']) }} dropped off ({{ $step['drop_off_pct'] }}%)
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Step details table --}}
|
||||
<div class="bg-white rounded-xl border border-gray-200 overflow-hidden">
|
||||
<table class="min-w-full divide-y divide-gray-200">
|
||||
<thead class="bg-gray-50">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Step</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Type</th>
|
||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Visitors</th>
|
||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Drop-off</th>
|
||||
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Conversion</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-gray-200">
|
||||
@foreach ($steps as $i => $step)
|
||||
<tr>
|
||||
<td class="px-6 py-3 text-sm text-gray-900 font-medium">{{ $step['label'] }}</td>
|
||||
<td class="px-6 py-3 text-sm">
|
||||
<span class="inline-flex px-2 py-0.5 rounded text-xs font-medium {{ $step['type'] === 'pageview' ? 'bg-blue-100 text-blue-700' : 'bg-purple-100 text-purple-700' }}">
|
||||
{{ $step['type'] === 'pageview' ? 'Page' : 'Event' }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="px-6 py-3 text-sm text-gray-900 text-right tabular-nums">{{ number_format($step['visitors']) }}</td>
|
||||
<td class="px-6 py-3 text-sm text-right tabular-nums {{ $step['drop_off'] > 0 ? 'text-red-600' : 'text-gray-400' }}">
|
||||
{{ $i > 0 ? number_format($step['drop_off']) . ' (' . $step['drop_off_pct'] . '%)' : '—' }}
|
||||
</td>
|
||||
<td class="px-6 py-3 text-sm text-right tabular-nums font-medium text-gray-900">{{ $step['conversion_pct'] }}%</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</x-analytics-layout>
|
||||
Reference in New Issue
Block a user