phase 8 and 9
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
<x-analytics-layout :website="$website" title="Manage Widgets">
|
||||
<x-slot name="headerActions">
|
||||
<a href="{{ route('custom-dashboard.show', $website) }}" 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="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" /></svg>
|
||||
View Dashboard
|
||||
</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
|
||||
|
||||
<div class="grid gap-6 lg:grid-cols-2">
|
||||
{{-- Add widget form --}}
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<h3 class="text-base font-semibold text-gray-900 mb-4">Add Widget</h3>
|
||||
<form method="POST" action="{{ route('custom-dashboard.store', $website) }}" x-data="{ type: 'metric' }" class="space-y-4">
|
||||
@csrf
|
||||
<div>
|
||||
<label for="title" class="block text-sm font-medium text-gray-700 mb-1">Title</label>
|
||||
<input type="text" name="title" id="title" required placeholder="e.g. Daily Visitors"
|
||||
class="block w-full rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label for="widget_type" class="block text-sm font-medium text-gray-700 mb-1">Type</label>
|
||||
<select name="widget_type" id="widget_type" x-model="type"
|
||||
class="block w-full rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@foreach ($types as $value => $label)
|
||||
<option value="{{ $value }}">{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div x-show="type === 'metric'" x-cloak>
|
||||
<label for="config_metric" class="block text-sm font-medium text-gray-700 mb-1">Metric</label>
|
||||
<select name="config[metric]" id="config_metric"
|
||||
class="block w-full rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
@foreach ($metrics as $value => $label)
|
||||
<option value="{{ $value }}">{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label for="size" class="block text-sm font-medium text-gray-700 mb-1">Size</label>
|
||||
<select name="size" id="size"
|
||||
class="block w-full rounded-lg border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||||
<option value="third">Small (1/3)</option>
|
||||
<option value="half">Medium (1/2)</option>
|
||||
<option value="full">Full Width</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" 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>
|
||||
Add Widget
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{-- Current widgets --}}
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<h3 class="text-base font-semibold text-gray-900 mb-4">Current Widgets</h3>
|
||||
|
||||
@if ($widgets->isEmpty())
|
||||
<p class="text-sm text-gray-400">No widgets yet. Add one using the form.</p>
|
||||
@else
|
||||
<div class="space-y-2">
|
||||
@foreach ($widgets as $widget)
|
||||
<div class="flex items-center justify-between rounded-lg border border-gray-200 px-4 py-3">
|
||||
<div>
|
||||
<p class="text-sm font-medium text-gray-900">{{ $widget->title }}</p>
|
||||
<p class="text-xs text-gray-500">{{ \App\Models\DashboardWidget::$types[$widget->widget_type] ?? $widget->widget_type }} · {{ $widget->size }}</p>
|
||||
</div>
|
||||
<form method="POST" action="{{ route('custom-dashboard.destroy', [$website, $widget]) }}" onsubmit="return confirm('Remove this widget?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-sm text-red-600 hover:text-red-800 font-medium transition">Remove</button>
|
||||
</form>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</x-analytics-layout>
|
||||
@@ -0,0 +1,120 @@
|
||||
<x-analytics-layout :website="$website" :from="$from" :to="$to" title="Custom Dashboard">
|
||||
<x-slot name="headerActions">
|
||||
<a href="{{ route('custom-dashboard.manage', $website) }}" 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="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" /></svg>
|
||||
Manage Widgets
|
||||
</a>
|
||||
</x-slot>
|
||||
|
||||
@if ($widgets->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="M3.75 6A2.25 2.25 0 0 1 6 3.75h2.25A2.25 2.25 0 0 1 10.5 6v2.25a2.25 2.25 0 0 1-2.25 2.25H6a2.25 2.25 0 0 1-2.25-2.25V6ZM3.75 15.75A2.25 2.25 0 0 1 6 13.5h2.25a2.25 2.25 0 0 1 2.25 2.25V18a2.25 2.25 0 0 1-2.25 2.25H6A2.25 2.25 0 0 1 3.75 18v-2.25ZM13.5 6a2.25 2.25 0 0 1 2.25-2.25H18A2.25 2.25 0 0 1 20.25 6v2.25A2.25 2.25 0 0 1 18 10.5h-2.25a2.25 2.25 0 0 1-2.25-2.25V6ZM13.5 15.75a2.25 2.25 0 0 1 2.25-2.25H18a2.25 2.25 0 0 1 2.25 2.25V18A2.25 2.25 0 0 1 18 20.25h-2.25a2.25 2.25 0 0 1-2.25-2.25v-2.25Z" /></svg>'
|
||||
title="No widgets yet"
|
||||
message="Add widgets to build your custom dashboard."
|
||||
>
|
||||
<a href="{{ route('custom-dashboard.manage', $website) }}" class="mt-4 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">
|
||||
Manage Widgets
|
||||
</a>
|
||||
</x-empty-state>
|
||||
@else
|
||||
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3">
|
||||
@foreach ($widgets as $widget)
|
||||
@php
|
||||
$colSpan = match($widget->size) {
|
||||
'full' => 'sm:col-span-2 lg:col-span-3',
|
||||
'half' => 'sm:col-span-1 lg:col-span-2' ,
|
||||
default => '',
|
||||
};
|
||||
$data = $widgetData[$widget->id] ?? null;
|
||||
@endphp
|
||||
<div class="bg-white rounded-xl border border-gray-200 p-5 {{ $colSpan }}">
|
||||
<h3 class="text-sm font-semibold text-gray-900 mb-3">{{ $widget->title }}</h3>
|
||||
|
||||
@if ($widget->widget_type === 'metric')
|
||||
@php $metricLabel = \App\Models\DashboardWidget::$metrics[$widget->config['metric'] ?? 'page_views'] ?? 'Metric'; @endphp
|
||||
<p class="text-3xl font-bold text-gray-900 tabular-nums">
|
||||
@if (($widget->config['metric'] ?? '') === 'bounce_rate')
|
||||
{{ $data }}%
|
||||
@elseif (($widget->config['metric'] ?? '') === 'avg_duration')
|
||||
{{ gmdate('i:s', (int) $data) }}
|
||||
@else
|
||||
{{ number_format($data) }}
|
||||
@endif
|
||||
</p>
|
||||
<p class="text-xs text-gray-500 mt-1">{{ $metricLabel }}</p>
|
||||
|
||||
@elseif ($widget->widget_type === 'chart')
|
||||
<div style="height: 200px">
|
||||
<canvas id="widget-chart-{{ $widget->id }}"></canvas>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const ctx = document.getElementById('widget-chart-{{ $widget->id }}');
|
||||
new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: @json($data->pluck('date')),
|
||||
datasets: [{
|
||||
label: 'Visitors',
|
||||
data: @json($data->pluck('visitors')),
|
||||
borderColor: '#6366f1',
|
||||
backgroundColor: 'rgba(99,102,241,0.1)',
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
pointRadius: 0,
|
||||
}]
|
||||
},
|
||||
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { display: false }, y: { beginAtZero: true } } }
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
@elseif (in_array($widget->widget_type, ['top_pages', 'top_sources', 'browsers', 'countries', 'events']))
|
||||
@if ($data && $data->isNotEmpty())
|
||||
<div class="space-y-2">
|
||||
@foreach ($data->take(5) as $row)
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600 truncate mr-2">
|
||||
{{ $row->path ?? $row->source ?? $row->browser ?? $row->country ?? $row->name ?? '—' }}
|
||||
</span>
|
||||
<span class="font-medium text-gray-900 tabular-nums shrink-0">
|
||||
{{ number_format($row->views ?? $row->sessions ?? $row->visitors ?? $row->total ?? 0) }}
|
||||
</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<p class="text-sm text-gray-400">No data for this period.</p>
|
||||
@endif
|
||||
|
||||
@elseif ($widget->widget_type === 'new_vs_returning')
|
||||
<div class="flex items-center gap-6">
|
||||
<div>
|
||||
<p class="text-2xl font-bold text-indigo-600 tabular-nums">{{ $data['new_pct'] ?? 0 }}%</p>
|
||||
<p class="text-xs text-gray-500">New</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-2xl font-bold text-emerald-600 tabular-nums">{{ $data['returning_pct'] ?? 0 }}%</p>
|
||||
<p class="text-xs text-gray-500">Returning</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@elseif ($widget->widget_type === 'conversions')
|
||||
@if ($data && $data->isNotEmpty())
|
||||
<div class="space-y-2">
|
||||
@foreach ($data->take(5) as $goal)
|
||||
<div class="flex items-center justify-between text-sm">
|
||||
<span class="text-gray-600 truncate mr-2">{{ $goal->name }}</span>
|
||||
<span class="font-medium text-gray-900 tabular-nums shrink-0">{{ number_format($goal->conversions) }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<p class="text-sm text-gray-400">No conversions for this period.</p>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</x-analytics-layout>
|
||||
Reference in New Issue
Block a user