@extends('admin.layouts.app') @section('content')
@push('styles') @endpush @php $trainers = $user->trainers ?? collect(); $totalBookings = $courtBookings->count(); $paidBookings = $courtBookings->where('payment_status', 'PAID')->count(); $pendingBookings = $courtBookings->where('payment_status', 'PENDING')->count() + $courtBookings->where('payment_status', 'UNPAID')->count(); $cancelledBookings = $courtBookings->filter(function ($booking) { return (int) $booking->is_canceled === 1 || $booking->status === 'CANCELLED'; })->count(); $totalLessons = collect($lessonSchedule)->count(); $nextLesson = collect($lessonSchedule)->first(); $availability = collect(old('weekly_availability', $user->weekly_availability ?? []))->values(); $savedAvailability = collect($user->weekly_availability ?? [])->values(); $age = $user->birth_date ? \Carbon\Carbon::parse($user->birth_date)->age : null; $dominantHandLabel = $user->dominant_hand === 'left' ? 'Esquerda' : ($user->dominant_hand === 'right' ? 'Direita' : '—'); $genderLabel = $user->gender === 'female' ? 'Feminino' : ($user->gender === 'male' ? 'Masculino' : '—'); @endphp {{-- Header --}}
Perfil do utilizador
#{{ $user->id }} · {{ $user->name }}
Voltar à lista
Saldo wallet
{{ number_format((float)($walletBalanceReal ?? 0), 2, ',', ' ') }}€
Saldo real por movimentos
Pontos
{{ $user->points ?? 0 }}
Acumulados no perfil
Marcações campo
{{ $totalBookings }}
Histórico sincronizado
Aulas
{{ $totalLessons }}
Agendadas no histórico
@if(session('status'))
{{ session('status') }}
@endif @if($errors->any())
Revê os campos:
@endif {{-- Tabs --}}
{{-- ====================================================== * TAB: OVERVIEW * ====================================================== --}}
Resumo geral
Informação principal do utilizador, estatuto no sistema e ligação ao clube.
ID interno
#{{ $user->id }}
Nível
{{ $user->level ?? '—' }}
Pontos
{{ $user->points ?? 0 }}
Clube
{{ optional($user->club)->name ?? 'Sem clube associado' }}
Tipo de sócio
{{ $user->member_type ?? 'member' }}
Saldo wallet
{{ number_format((float)($walletBalanceReal ?? 0), 2, ',', ' ') }} €
Dados do utilizador
Contactos e dados de perfil registados no backoffice.
E-mail
{{ $user->email ?? '—' }}
Telefone
{{ $user->phone ?? '—' }}
NIF
{{ $user->nif ?? '—' }}
Data de nascimento
@if($user->birth_date) {{ \Carbon\Carbon::parse($user->birth_date)->format('d/m/Y') }} @else — @endif
Morada
{{ $user->address ?? '—' }}
Perfil de jogador
Dados usados para segmentar jogadores e encontrar parceiros compatíveis.
Mão da raquete
{{ $dominantHandLabel }}
Género
{{ $genderLabel }}
Idade
{{ $age !== null ? $age . ' anos' : '—' }}
Disponibilidade semanal
Quadrados preenchidos indicam horas disponíveis.
@foreach($availabilityDays as $dayLabel)
{{ $dayLabel }}
@endforeach @foreach($availabilityHours as $hour)
{{ $hour }}
@foreach($availabilityDays as $dayKey => $dayLabel) @php $slot = $dayKey . '_' . $hour; @endphp
{{ $savedAvailability->contains($slot) ? '✓' : '·' }}
@endforeach @endforeach
Treinadores associados
Relações já atribuídas ao utilizador para marcações e contexto desportivo.
@if($trainers->isEmpty())
Sem treinadores associados.
@else
@foreach($trainers as $trainer) @endforeach
Treinador Email
{{ $trainer->name }} {{ $trainer->email ?? '—' }}
@endif
Cartão do utilizador
Visualização rápida do cartão e acessos diretos às áreas principais.
@php $type = $user->member_type ?? 'member'; $isMemberOnly = in_array($type, ['member', null, ''], true); $cardLabel = ''; $cardBg = 'linear-gradient(135deg,#f7f7f7,#ffffff)'; $cardBorder = '#e0e0e0'; $chipBg = '#ffffff'; $chipBorder = '#d5d5d5'; $chipText = '#777'; if ($type === 'silver') { $cardLabel = 'SILVER'; $cardBg = 'linear-gradient(135deg,#f1f5f9,#e5e7eb)'; $cardBorder = '#d1d5db'; $chipBg = '#f9fafb'; $chipBorder = '#d1d5db'; $chipText = '#111827'; } elseif ($type === 'gold') { $cardLabel = 'GOLD'; $cardBg = 'linear-gradient(135deg,#fef3c7,#fef9c3)'; $cardBorder = '#eab308'; $chipBg = '#fffbeb'; $chipBorder = '#facc15'; $chipText = '#854d0e'; } elseif ($type === 'platinum' || $type === 'platina') { $cardLabel = 'PLATINUM'; $cardBg = 'linear-gradient(135deg,#e5e7eb,#f3f4f6)'; $cardBorder = '#9ca3af'; $chipBg = '#f9fafb'; $chipBorder = '#9ca3af'; $chipText = '#111827'; } @endphp @if($isMemberOnly)
Este utilizador não possui cartão de sócio ativo (membro sem plano Silver/Gold/Platina).
@else
@if($user->photo_path) {{ $user->name }} @else {{ mb_strtoupper(mb_substr($user->name, 0, 1)) }} @endif
{{ $user->name }}
{{ optional($user->club)->name ?? 'Sem clube associado' }}
ID Cartão: AZP-USER-{{ $user->id }}
AZP
{{ $cardLabel }}
Saldo
👛 {{ number_format((float)($user->wallet_balance ?? 0), 2, ',', ' ') }} €
Pontos
{{ $user->points ?? 0 }} 🎯
Cartão gerido no backoffice @if($user->created_at) Desde {{ $user->created_at->format('m/Y') }} @else — @endif
@endif
Atalhos
Acesso rápido às áreas com mais operação nesta ficha.
{{-- ====================================================== * TAB: PLAYER PROFILE * ====================================================== --}}
@csrf @method('PUT')
Editar dados do utilizador
Contactos, perfil desportivo e disponibilidade semanal do jogador.
Horário / disponibilidade
Seleciona os quadrados onde este jogador está disponível para jogar.
@foreach($availabilityDays as $dayLabel)
{{ $dayLabel }}
@endforeach @foreach($availabilityHours as $hour)
{{ $hour }}
@foreach($availabilityDays as $dayKey => $dayLabel) @php $slot = $dayKey . '_' . $hour; @endphp
contains($slot) ? 'checked' : '' }}>
@endforeach @endforeach
{{-- ====================================================== * TAB: WALLET * ====================================================== --}}
Wallet
Consulta de saldo, carregamentos manuais e histórico de movimentos.
{{ number_format((float)($walletBalanceReal ?? 0), 2, ',', ' ') }} €
Saldo calculado por movimentos (wallet_ledger).
{{ number_format((float)($user->wallet_balance ?? 0), 2, ',', ' ') }} €
Valor guardado em users.wallet_balance (opcional).
Carregar saldo
Cria um movimento manual na wallet do utilizador.
@csrf
Movimentos
Últimos movimentos registados na wallet.
@if(empty($walletMovements) || $walletMovements->isEmpty())
Sem movimentos registados.
@else
@foreach($walletMovements as $m) @php $amt = (float) $m->amount; $typeLabel = $m->type; if ($m->type === 'lesson_debit') $typeLabel = 'Débito aula'; elseif ($m->type === 'lesson_refund') $typeLabel = 'Reembolso aula'; elseif ($m->type === 'adjustment') $typeLabel = 'Ajuste/Carregamento'; $ref = '—'; if (!empty($m->reference_type)) { $ref = $m->reference_type; if (!empty($m->reference_id)) $ref .= ' #' . $m->reference_id; } $dt = $m->created_at ? \Carbon\Carbon::parse($m->created_at)->format('d/m/Y H:i') : '—'; @endphp @endforeach
Data Tipo Referência Descrição Montante
{{ $dt }} {{ $typeLabel }} {{ $ref }} {{ $m->description ?? '—' }} @if($amt < 0) -{{ number_format(abs($amt), 2, ',', ' ') }} € @else +{{ number_format($amt, 2, ',', ' ') }} € @endif
@endif
{{-- ====================================================== * TAB: VOUCHERS * ====================================================== --}}
Vouchers do utilizador
Atribuição de vouchers e gestão dos códigos já emitidos.
@csrf
Cria vários códigos com a mesma regra.
Comprar voucher com pontos
Debita pontos do cliente e cria automaticamente um voucher disponível na conta.
@if($pointStoreVouchers->isEmpty())
Não existem vouchers ativos na loja de pontos para este utilizador.
@else
@foreach($pointStoreVouchers as $pointVoucher) @endforeach
Voucher Produto Desconto Custo
{{ $pointVoucher->displayTitle() }} {{ optional($pointVoucher->product)->name ?? '—' }} {{ (int) $pointVoucher->discount_percent }}% {{ number_format((int) $pointVoucher->points_cost, 0, ',', ' ') }} pontos
@csrf
@endif
Lista de vouchers
Histórico de vouchers atribuídos a este utilizador.
@if($userVouchers->isEmpty())
Este utilizador ainda não tem vouchers.
@else
@foreach($userVouchers as $v) @php $stateLabel = $v->status; if ($v->status === 'unused') $stateLabel = 'Disponível'; elseif ($v->status === 'used') $stateLabel = 'Usado'; elseif ($v->status === 'revoked') $stateLabel = 'Revogado'; elseif ($v->status === 'expired') $stateLabel = 'Expirado'; @endphp @endforeach
Código Produto Desconto Estado Validade
{{ $v->code }} {{ optional($v->product)->name ?? '—' }} {{ (int)$v->discount_percent }}% {{ $stateLabel }} @if($v->expires_at) {{ \Carbon\Carbon::parse($v->expires_at)->format('d/m/Y') }} @else — @endif @if($v->status !== 'used' && $v->status !== 'revoked')
@csrf @method('PUT')
@else — @endif
@endif
{{-- ====================================================== * TAB: ORDERS (Backoffice) * ====================================================== --}}
Criar encomenda para este utilizador
Seleção manual de produtos, vouchers e estado da encomenda.
@csrf
Nenhum produto selecionado.
A encomenda fica sempre paga no momento. O estado define apenas se fica entregue ou em processamento.
Cada voucher aplica-se a 1 unidade do produto associado. Podes selecionar vários vouchers na mesma encomenda.
@forelse($availableVouchers as $v) @empty
Sem vouchers disponíveis.
@endforelse
{{-- ====================================================== * TAB: LESSONS * ====================================================== --}}
Marcar aula com treinador
Seleciona treinador, tipo de preço, duração e slot disponível.
Ao selecionar, escolhe um dia e aparecem os slots.
Escolhe um dia para listar os slots.
Slots disponíveis
Os horários disponíveis aparecem automaticamente após a seleção.
Seleciona treinador + dia.
Histórico / agenda de aulas
Esta secção passou para a tab Aulas, para ficar toda a operação de aulas no mesmo sítio.
Total
{{ $totalLessons }}
Próxima
@if($nextLesson) {{ \Carbon\Carbon::parse($nextLesson->date)->format('d/m') }} @else — @endif
@forelse($lessonSchedule as $lesson) @php $lessonDate = \Carbon\Carbon::parse($lesson->date); @endphp
{{ $lessonDate->format('d') }}
{{ $lessonDate->translatedFormat('M') }}
{{ ucfirst($lessonDate->translatedFormat('D')) }}
{{ $lesson->coach ?? 'Treinador' }}
{{ $lesson->type ?? 'Aula' }}
🕒 {{ $lesson->time ?? '—' }}
📅 {{ $lessonDate->format('d/m/Y') }}
🎾 {{ $lesson->type ?? 'Aula' }}
Agendada
@empty
Sem aulas registadas Ainda não existem aulas agendadas para este utilizador.
@endforelse
{{-- ====================================================== * TAB: HISTORY * ====================================================== --}}
Marcações de campo
Histórico recente das reservas do utilizador, com um layout muito mais legível para consulta rápida: data, hora, campo, participantes, origem, valor, estado da marcação e estado do pagamento.
{{ $totalBookings }} registo{{ $totalBookings === 1 ? '' : 's' }}
Total
{{ $totalBookings }}
Reservas sincronizadas
Pagas
{{ $paidBookings }}
Com pagamento confirmado
Pendentes
{{ $pendingBookings }}
Por regularizar
Canceladas
{{ $cancelledBookings }}
Não concluídas
Histórico visual detalhado
@if($courtBookings->isEmpty())
Sem histórico de marcações Ainda não existem reservas sincronizadas para este utilizador.
@else
@foreach($courtBookings as $booking) @php $bookingDate = \Carbon\Carbon::parse($booking->date); $weekday = ucfirst($bookingDate->translatedFormat('D')); $paymentClass = 'azp-pill azp-pill-neutral'; if ($booking->payment_status === 'PAID') { $paymentClass = 'azp-pill azp-pill-success'; } elseif ($booking->payment_status === 'PENDING' || $booking->payment_status === 'UNPAID') { $paymentClass = 'azp-pill azp-pill-warning'; } elseif ($booking->payment_status === 'REFUNDED') { $paymentClass = 'azp-pill azp-pill-danger'; } $statusClass = 'azp-pill azp-pill-neutral'; if ((int) $booking->is_canceled === 1 || $booking->status === 'CANCELLED') { $statusClass = 'azp-pill azp-pill-danger'; } elseif ($booking->status === 'COMPLETED') { $statusClass = 'azp-pill azp-pill-success'; } elseif ($booking->status === 'CONFIRMED') { $statusClass = 'azp-pill azp-pill-info'; } elseif ($booking->status === 'PENDING') { $statusClass = 'azp-pill azp-pill-warning'; } @endphp
{{ $bookingDate->format('d') }}
{{ $bookingDate->translatedFormat('M') }}
{{ $booking->court ?? 'Campo' }}
{{ $bookingDate->format('d/m/Y') }} · {{ $weekday }} @if(!empty($booking->origin)) · Origem: {{ $booking->origin }} @endif
{{ $booking->payment_status_label }} {{ $booking->status_label }}
{{ $booking->time ?? '—' }} @if(!empty($booking->end_time)) → {{ $booking->end_time }} @endif
Valor bruto
{{ $booking->price ?? '—' }}
Duração
{{ $booking->duration ?? '—' }}
Tempo total da reserva
Jogadores
{{ $booking->participants_count > 0 ? $booking->participants_count : '—' }}
{{ $booking->participants_count == 1 ? 'participante' : 'participantes' }}
Tipo
{{ $booking->booking_type ?? '—' }}
Natureza da marcação
Origem / canal
{{ $booking->origin ?? '—' }}
Fonte da reserva
@endforeach
@endif
Histórico de encomendas
Consulta rápida das encomendas já associadas ao utilizador.
@forelse($orderHistory as $order) @empty @endforelse
Data Total Estado
{{ \Carbon\Carbon::parse($order->date)->format('d/m') }} {{ number_format((float)$order->total, 2, ',', ' ') }} € @php $label = '—'; if ($order->status === 'shipped') $label = 'Enviada'; elseif ($order->status === 'delivered') $label = 'Entregue'; elseif ($order->status === 'pending') $label = 'Pendente'; @endphp {{ $label }}
Sem encomendas registadas.
{{-- MODAL: Confirm lesson --}}
@endsection @push('scripts') {{-- Tabs logic --}} {{-- Slots fetch --}} {{-- Slots fetch --}} {{-- Order lines + vouchers filter + product search --}} @php $AZP_COACH_RATES = $coaches->map(function ($c) { return [ 'id' => $c->id, 'name' => $c->name, 'rates' => $c->rates ? [ 'off_60' => (float) $c->rates->off_60, 'off_90' => (float) $c->rates->off_90, 'peak_60' => (float) $c->rates->peak_60, 'peak_90' => (float) $c->rates->peak_90, ] : null, ]; })->values()->toArray(); @endphp @endpush