schneespur/release/schneespur-1.0.2/resources/views/admin/overview/customer-report.blade.php
Michael 7288b93500 Release v1.0.2: diagnostic infrastructure core
Add neutral diagnostic framework for future reporting modules:
- DiagnosticReporterInterface, Registry, Manager, PayloadSanitizer
- Laravel exception hook in bootstrap/app.php
- Module permission declarations (requires_permissions in module.json)
- Core diagnostic report points (module boot/install/update failures)
- Module documentation update (moduldoku.md)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-18 16:54:11 +00:00

199 lines
14 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.

<x-admin-layout>
<x-slot name="header">{{ __('admin.page_overview_customer_report') }} <x-help-icon topic="overview" /></x-slot>
{{-- Filter bar --}}
<div class="bg-white shadow rounded-lg p-4 mb-6">
<form method="GET" action="{{ route('admin.overview.customer-report') }}" class="flex flex-wrap items-end gap-4">
{{-- Customer select --}}
<div class="flex-1 min-w-[180px]">
<label for="customer" class="block text-sm font-medium text-gray-700 mb-1">{{ __('overview.customer_report_select_customer') }}</label>
<select name="customer" id="customer" class="block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm">
<option value="">{{ __('overview.customer_report_select_customer') }}</option>
@foreach($customers as $customer)
<option value="{{ $customer->id }}" @selected($selectedCustomer && $selectedCustomer->id === $customer->id)>{{ $customer->name }}</option>
@endforeach
</select>
</div>
{{-- Date from --}}
<div>
<label for="from" class="block text-sm font-medium text-gray-700 mb-1">{{ __('overview.report_date_from') }}</label>
<input type="date" name="from" id="from" value="{{ $from->format('Y-m-d') }}" class="block rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm">
</div>
{{-- Date to --}}
<div>
<label for="to" class="block text-sm font-medium text-gray-700 mb-1">{{ __('overview.report_date_to') }}</label>
<input type="date" name="to" id="to" value="{{ $to->format('Y-m-d') }}" class="block rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 text-sm">
</div>
{{-- Apply button --}}
<div>
<button type="submit" class="inline-flex items-center px-4 py-2 bg-gray-800 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-gray-700 focus:bg-gray-700 active:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition">
{{ __('overview.report_filter_apply') }}
</button>
</div>
</form>
{{-- Quick-filter buttons --}}
<div class="flex flex-wrap gap-2 mt-3 pt-3 border-t border-gray-100">
@foreach(['week' => 'report_filter_week', 'month' => 'report_filter_month', '30days' => 'report_filter_30days', 'season' => 'report_filter_season'] as $key => $label)
<a href="{{ route('admin.overview.customer-report', array_merge(['customer' => $selectedCustomer?->id], $quickFilters[$key])) }}"
class="px-3 py-1.5 text-sm rounded-md border {{ $from->format('Y-m-d') === $quickFilters[$key]['from'] && $to->format('Y-m-d') === $quickFilters[$key]['to'] ? 'bg-gray-800 text-white border-gray-800' : 'bg-white text-gray-700 border-gray-300 hover:bg-gray-50' }} transition">
{{ __('overview.' . $label) }}
</a>
@endforeach
</div>
</div>
@if(!$selectedCustomer)
{{-- No customer selected --}}
<x-empty-state :heading="__('overview.customer_report_no_customer_selected')" />
@elseif($totalJobs > 0)
{{-- Sammel-PDF Button + Email Button --}}
<div class="mb-6 flex flex-wrap gap-3">
<a href="{{ $sammelPdfUrl }}" class="inline-flex items-center px-4 py-2 bg-blue-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-blue-500 focus:bg-blue-500 active:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m.75 12l3 3m0 0l3-3m-3 3v-6m-1.5-9H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 00-9-9z" /></svg>
{{ __('overview.customer_report_sammel_pdf') }}
</a>
@if($selectedCustomer->notification_email || $selectedCustomer->email)
<button type="button"
x-data
x-on:click="$dispatch('open-modal', 'send-report-email')"
class="inline-flex items-center px-4 py-2 bg-green-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-green-500 focus:bg-green-500 active:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition">
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21.75 6.75v10.5a2.25 2.25 0 01-2.25 2.25h-15a2.25 2.25 0 01-2.25-2.25V6.75m19.5 0A2.25 2.25 0 0019.5 4.5h-15a2.25 2.25 0 00-2.25 2.25m19.5 0v.243a2.25 2.25 0 01-1.07 1.916l-7.5 4.615a2.25 2.25 0 01-2.36 0L3.32 8.91a2.25 2.25 0 01-1.07-1.916V6.75" /></svg>
{{ __('overview.customer_report_send_email') }}
</button>
@endif
</div>
{{-- Email Confirmation Modal --}}
@if($selectedCustomer->notification_email || $selectedCustomer->email)
<x-confirm-dialog name="send-report-email"
:title="__('overview.customer_report_email_modal_title')"
:message="__('overview.customer_report_email_modal_message', [
'name' => $selectedCustomer->name,
'email' => $selectedCustomer->notification_email ?? $selectedCustomer->email,
'from' => $from->format('d.m.Y'),
'to' => $to->format('d.m.Y'),
'count' => $totalJobs,
])">
<x-slot name="action">
<form method="POST" action="{{ route('admin.overview.customer-report.email') }}">
@csrf
<input type="hidden" name="customer_id" value="{{ $selectedCustomer->id }}">
<input type="hidden" name="from" value="{{ $from->format('Y-m-d') }}">
<input type="hidden" name="to" value="{{ $to->format('Y-m-d') }}">
<x-primary-button class="bg-green-600 hover:bg-green-500">
{{ __('overview.customer_report_send_email') }}
</x-primary-button>
</form>
</x-slot>
</x-confirm-dialog>
@endif
{{-- KPI Summary Cards --}}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mb-6">
{{-- Total jobs --}}
<div class="bg-white rounded-lg shadow p-4">
<dt class="text-sm font-medium text-gray-500">{{ __('overview.customer_report_total_jobs') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900">{{ $totalJobs }}</dd>
</div>
{{-- Total hours --}}
<div class="bg-white rounded-lg shadow p-4">
<dt class="text-sm font-medium text-gray-500">{{ __('overview.customer_report_total_hours') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900">
@php $h = intdiv($totalMinutes, 60); $m = $totalMinutes % 60; @endphp
{{ $h > 0 ? $h . 'h ' . $m . 'min' : $m . 'min' }}
</dd>
</div>
{{-- Driver count --}}
<div class="bg-white rounded-lg shadow p-4">
<dt class="text-sm font-medium text-gray-500">{{ __('overview.customer_report_driver_count') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900">{{ $driverCount }}</dd>
</div>
{{-- Job type breakdown --}}
<div class="bg-white rounded-lg shadow p-4">
<dt class="text-sm font-medium text-gray-500">{{ __('overview.customer_report_type_breakdown') }}</dt>
<dd class="mt-1 space-y-0.5">
@foreach($jobTypeBreakdown as $type => $count)
<div class="text-sm text-gray-900">{{ __('job.type_' . $type) }}: {{ $count }}</div>
@endforeach
</dd>
</div>
{{-- Average duration --}}
<div class="bg-white rounded-lg shadow p-4">
<dt class="text-sm font-medium text-gray-500">{{ __('overview.customer_report_avg_duration') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900">
@php $ah = intdiv($avgDurationMinutes, 60); $am = $avgDurationMinutes % 60; @endphp
{{ $ah > 0 ? $ah . 'h ' . $am . 'min' : $am . 'min' }}
</dd>
</div>
{{-- Frequency --}}
<div class="bg-white rounded-lg shadow p-4">
<dt class="text-sm font-medium text-gray-500">{{ __('overview.customer_report_frequency') }}</dt>
<dd class="mt-1 text-2xl font-semibold text-gray-900">
@if($frequencyPerWeek !== null)
{{ $frequencyPerWeek }}
<span class="text-sm font-normal text-gray-500">{{ __('overview.customer_report_frequency_per_week') }}</span>
@else
{{ $totalJobs }}
<span class="text-sm font-normal text-gray-500">{{ __('overview.customer_report_frequency_absolute') }}</span>
@endif
</dd>
</div>
</div>
{{-- Job Table --}}
<div class="bg-white overflow-hidden shadow-sm rounded-lg">
<div class="overflow-x-auto">
<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">{{ __('overview.report_col_date') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('overview.report_col_object') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('overview.report_col_driver') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('overview.report_col_type') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('overview.report_col_start') }}</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('overview.report_col_end') }}</th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">{{ __('overview.report_col_duration') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
@foreach($jobs as $job)
<tr class="hover:bg-gray-50 transition">
<td class="px-6 py-4 whitespace-nowrap text-sm">
<a href="{{ route('admin.jobs.show', $job) }}" class="text-gray-900 hover:text-indigo-600">{{ $job->localStartedAt()->format('d.m.Y') }}</a>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $job->customerObject?->name ?? '' }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $job->user->displayName() }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $job->type->label() }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $job->localStartedAt()->format('H:i') }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">{{ $job->ended_at ? $job->localEndedAt()->format('H:i') : '—' }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500 text-right">
@if($job->ended_at)
@php $jm = $job->started_at->diffInMinutes($job->ended_at); $jh = intdiv($jm, 60); $jr = $jm % 60; @endphp
{{ $jh > 0 ? $jh . 'h ' . $jr . 'min' : $jr . 'min' }}
@else
<span class="inline-flex items-center rounded-full px-2 py-1 text-xs font-medium bg-green-50 text-green-700 ring-1 ring-inset ring-green-600/20">
{{ __('overview.active_badge') }}
</span>
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
@else
{{-- Customer selected but no jobs --}}
<x-empty-state :heading="__('overview.customer_report_no_jobs')" />
@endif
</x-admin-layout>