phase 8 and 9
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
<div
|
||||
x-data="{
|
||||
filters: @js($segment->filters ?? [['field' => 'country', 'operator' => '=', 'value' => '']]),
|
||||
fields: [
|
||||
{ value: 'country', label: 'Country' },
|
||||
{ value: 'region', label: 'Region' },
|
||||
{ value: 'city', label: 'City' },
|
||||
{ value: 'browser', label: 'Browser' },
|
||||
{ value: 'os', label: 'Operating System' },
|
||||
{ value: 'device_type', label: 'Device Type' },
|
||||
{ value: 'language', label: 'Language' },
|
||||
{ value: 'screen_resolution', label: 'Screen Resolution' },
|
||||
],
|
||||
operators: [
|
||||
{ value: '=', label: 'equals' },
|
||||
{ value: '!=', label: 'not equals' },
|
||||
{ value: 'contains', label: 'contains' },
|
||||
{ value: 'starts_with', label: 'starts with' },
|
||||
],
|
||||
addFilter() {
|
||||
this.filters.push({ field: 'country', operator: '=', value: '' });
|
||||
},
|
||||
removeFilter(index) {
|
||||
if (this.filters.length > 1) this.filters.splice(index, 1);
|
||||
}
|
||||
}"
|
||||
class="space-y-6"
|
||||
>
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-gray-700 mb-1">Segment Name</label>
|
||||
<input type="text" name="name" id="name" value="{{ old('name', $segment->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. Mobile users from Germany">
|
||||
@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">Filters <span class="text-gray-400 font-normal">(all must match)</span></label>
|
||||
|
||||
<div class="space-y-3">
|
||||
<template x-for="(filter, index) in filters" :key="index">
|
||||
<div class="flex items-center gap-3 bg-gray-50 rounded-lg p-3 border border-gray-200">
|
||||
<select :name="'filters[' + index + '][field]'" x-model="filter.field"
|
||||
class="rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<template x-for="f in fields" :key="f.value">
|
||||
<option :value="f.value" x-text="f.label"></option>
|
||||
</template>
|
||||
</select>
|
||||
|
||||
<select :name="'filters[' + index + '][operator]'" x-model="filter.operator"
|
||||
class="rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<template x-for="o in operators" :key="o.value">
|
||||
<option :value="o.value" x-text="o.label"></option>
|
||||
</template>
|
||||
</select>
|
||||
|
||||
<input type="text" :name="'filters[' + index + '][value]'" x-model="filter.value" required
|
||||
placeholder="Value" class="flex-1 rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
|
||||
<button type="button" @click="removeFilter(index)" x-show="filters.length > 1"
|
||||
class="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="addFilter()"
|
||||
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 Filter
|
||||
</button>
|
||||
|
||||
@error('filters')
|
||||
<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($segment->id) ? 'Update Segment' : 'Create Segment' }}
|
||||
</button>
|
||||
<a href="{{ route('segments.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 Segment">
|
||||
<div class="max-w-3xl">
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<form method="POST" action="{{ route('segments.store', $website) }}">
|
||||
@csrf
|
||||
@include('segments._form', ['segment' => new \App\Models\Segment])
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</x-analytics-layout>
|
||||
@@ -0,0 +1,23 @@
|
||||
<x-analytics-layout :website="$website" title="Edit Segment">
|
||||
<div class="max-w-3xl">
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<form method="POST" action="{{ route('segments.update', [$website, $segment]) }}">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
@include('segments._form', ['segment' => $segment])
|
||||
</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 segment.</p>
|
||||
<form method="POST" action="{{ route('segments.destroy', [$website, $segment]) }}" onsubmit="return confirm('Delete this segment?')">
|
||||
@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 Segment
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</x-analytics-layout>
|
||||
@@ -0,0 +1,47 @@
|
||||
<x-analytics-layout :website="$website" title="Segments">
|
||||
<x-slot name="headerActions">
|
||||
<a href="{{ route('segments.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 Segment
|
||||
</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 ($segments->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="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.17 0-4.207-.576-5.963-1.584A6.062 6.062 0 0 1 6 18.719m12 0a5.971 5.971 0 0 0-.941-3.197m0 0A5.995 5.995 0 0 0 12 12.75a5.995 5.995 0 0 0-5.058 2.772m0 0a3 3 0 0 0-4.681 2.72 8.986 8.986 0 0 0 3.74.477m.94-3.197a5.971 5.971 0 0 0-.94 3.197M15 6.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Zm6 3a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Zm-13.5 0a2.25 2.25 0 1 1-4.5 0 2.25 2.25 0 0 1 4.5 0Z" /></svg>'
|
||||
title="No segments yet"
|
||||
message="Create a segment to filter reports by visitor attributes like country, device, or browser."
|
||||
/>
|
||||
@else
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@foreach ($segments as $segment)
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-5">
|
||||
<div class="flex items-start justify-between">
|
||||
<h3 class="text-sm font-semibold text-gray-900">{{ $segment->name }}</h3>
|
||||
<a href="{{ route('segments.edit', [$website, $segment]) }}" class="text-gray-400 hover:text-gray-600 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>
|
||||
</a>
|
||||
</div>
|
||||
<div class="mt-2 flex flex-wrap gap-1.5">
|
||||
@foreach ($segment->filters as $filter)
|
||||
<span class="inline-flex items-center gap-1 px-2 py-0.5 rounded-full bg-gray-100 text-xs text-gray-600">
|
||||
{{ $filter['field'] }} {{ $filter['operator'] }} {{ $filter['value'] }}
|
||||
</span>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="mt-3">
|
||||
<a href="{{ route('dashboard.show', [$website, 'segment' => $segment->id]) }}" class="text-xs font-medium text-indigo-600 hover:text-indigo-800 transition">
|
||||
Apply to Dashboard →
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</x-analytics-layout>
|
||||
Reference in New Issue
Block a user