@extends('layouts.app') @section('title', 'Reservas — '.config('app.name')) {{-- Calendário de reservas. O FullCalendar é montado por resources/js/calendar.js, que lê deste elemento as URLs dos endpoints JSON e monta os eventos por AJAX — a página em si não recarrega ao criar, mover ou apagar reserva. Os dois modais (formulário e visualização) são componentes do Bootstrap, controlados pelo mesmo JS. As quadras do
{{-- Área de erros da validação do servidor (422). Fica FORA dos painéis: um erro do servidor pode ser de um campo de outro passo, e escondê-lo junto com o painel o tornaria invisível. --}} {{-- Aviso de campo obrigatório do passo atual. --}}
{{-- Cliente no lugar do antigo "Nome da reserva": um cliente só, obrigatório, e é o nome dele que aparece no calendário. O Tom Select (calendar.js) dá busca ao @foreach ($customers as $customer) @endforeach
{{-- A escolha é por BADGES, mas o @foreach ($courts as $court) @endforeach
@foreach ($courts as $court) @endforeach
@if ($courts->isEmpty())
Nenhuma quadra ativa. Cadastre uma quadra antes de reservar.
@endif
{{-- Modalidades da quadra escolhida. Escondida até haver escolha — mesmo padrão do campo de professor, que só aparece quando "é uma aula" está marcado. O conteúdo vem do `data-modalities` da badge clicada. --}} {{-- Modalidade praticada, entre as que a quadra atende. Escondida até haver quadra escolhida — mesmo padrão do campo de professor, que só aparece quando "é uma aula" está marcado. As opções vêm do `data-modalities` da badge de quadra clicada, então trocar de quadra não consulta o servidor. Com UMA modalidade só, ela já vem escolhida; o valor viaja neste input oculto. --}}
Modalidade
Marcando, um passo a mais pede o professor.
{{-- Passo do professor: só entra na sequência quando "é uma aula" está marcado. Quem o inclui/exclui é o wizard, a partir da chave `professor` — por isso este bloco NÃO tem mais `data-class-field`, que escondia o campo por conta própria e brigaria com a navegação. O servidor exige o professor se for aula e o proíbe se não for; aqui é só conveniência. --}}
{{-- Cada professor carrega as MODALIDADES que atende: o JS usa isto para deixar na lista só quem dá aula da modalidade escolhida, sem consultar o servidor a cada troca. --}} {{-- Preenchido pelo JS: explica por que a lista está vazia (ninguém atende a modalidade) ou por que a escolha anterior foi desfeita. --}}
{{-- Dia inteiro fica OCULTO: este formulário só cria reservas com horário. O elemento permanece (desmarcado) porque o resto do calendário — eventos e visualização — ainda entende `all_day`. --}}
{{-- Horários só em passos de 30 min (00 e 30): nada de minutos quebrados. O JS preenche o término uma hora à frente quando o início é escolhido e arredonda para a meia hora mais próxima o que vier do calendário. --}} @php($timeOptions = collect(range(0, 47))->map(fn ($i) => sprintf('%02d:%02d', intdiv($i, 2), ($i % 2) * 30))) {{-- UM CAMPO POR LINHA: data, início, término. Lado a lado, "Data" e "Hora início" formavam uma linha e o término ficava sozinho na de baixo — uma assimetria que fazia o término parecer de outro grupo. Empilhados, os três se leem na ordem em que são preenchidos. --}}
{{-- Cada hora tem a grade E a digitação à mão; o partial explica por quê. --}} @include('reservations._time-field', [ 'side' => 'start', 'label' => 'Hora início', 'times' => $timeOptions, 'margin' => 'mb-2', ]) @include('reservations._time-field', [ 'side' => 'end', 'label' => 'Hora término', 'times' => $timeOptions, 'margin' => 'mb-3', ]) {{-- Repetir semanalmente: cria a reserva na MESMA hora e no MESMO dia da semana, de 7 em 7 dias, até a data escolhida (ex.: 23/07, 30/07, 06/08…). O rótulo mostra o dia da semana da data escolhida; o JS o atualiza. O campo "até quando" reaproveita #reservation-end-date (o `until` do payload) e só aparece com o checkbox marcado. --}}
{{-- Preenchido pelo JS quando a arena está fechada nesse dia: as sugestões de hora somem e este aviso explica o porquê — e lembra que dá para digitar a hora assim mesmo. Tom de AVISO, não de erro: a reserva continua possível, só não há grade para oferecer. --}}
{{-- Passo condicional "Datas": lista as ocorrências semanais geradas, cada uma com um ✓ (livre) ou ✗ (conflito) à direita. Preenchido pelo JS (renderDatesReview) ao entrar no passo. O servidor ainda revalida e pula os dias ocupados de qualquer forma. --}}

Marque as datas que quer reservar. indica que já há reserva no horário — você decide se mantém.

{{-- Rola dentro do modal quando há muitas datas; a barra só aparece se realmente houver transbordo (overflow-y:auto). --}}
{{-- Passo de valores, no formato de uma nota: a linha da quadra (nome · quantidade · total), o desconto e o total com desconto. NADA AQUI É ENVIADO AO SERVIDOR. Os campos não têm `name` de propósito — o buildPayload monta o corpo campo a campo, e estes ficam de fora. É uma prévia até a área financeira existir; quando ela vier, é aqui que os valores passam a ser persistidos. --}}
{{-- Plano numa linha só; valor e desconto na linha de baixo. --}}
{{-- Plano da reserva. TODOS os planos da arena, sempre: não dependem da quadra escolhida nem do dia da reserva. O servidor já aceitava qualquer plano da arena (ReservationRequest valida só o `organization_id`), então as duas peneiras que existiam aqui apenas escondiam opções válidas. A lista vem em `data-plans` deste próprio select, e não das badges de quadra: sem o recorte por quadra, o JSON é o mesmo para todas. Aplicado automaticamente quando há um só; o usuário pode trocar. Escolher um plano preenche o Valor abaixo. Sem `name`: prévia, não persiste ainda. --}}
Nenhum plano cadastrado. Cadastre um em Planos para cobrar por esta reserva.
{{-- Valor unitário: nasce do preço do plano escolhido, mas pode ser ajustado à mão. Ele alimenta a nota (valor × quantidade). --}}
R$
{{-- O desconto tem DUAS unidades, e o botão à direita alterna entre elas. `max` e `step` mudam junto: em % o teto é 100 (acima disso o total ficaria negativo); em reais não há teto no campo, porque o limite é o próprio orçamento e quem o aplica é o cálculo. O JS limita de novo dos dois lados — `max` do HTML não impede digitar. --}} {{-- A unidade escolhida vive no `data-type` deste botão: é ela que o payload manda e que o cálculo lê. `tabindex=-1` mantém o Tab indo do campo para o próximo passo, sem parar num botão que quase ninguém usa. --}}
Quadra Qtd. Total
R$ 0,00
{{-- O rótulo carrega o percentual aplicado ("Desconto (10%)") e a coluna da direita, quanto ele abateu em reais. --}} Desconto − R$ 0,00
Total R$ 0,00
{{-- CONDIÇÃO DE COBRANÇA. Salvar a reserva é o aceite do orçamento: daqui sai a conta a receber. São os DOIS MODELOS DE CLIENTE de uma arena, e produzem números bem diferentes: POR EVENTO → uma cobrança para cada reserva, vencendo no dia dela. 8 aulas = 8 cobranças. RECORRENTE → uma cobrança por período, SEM FIM, no valor do compromisso. É o mensalista. --}}
Cobrança @foreach (App\Enums\PaymentTerms::cases() as $terms)
@endforeach {{-- Sem valor não há o que cobrar de forma recorrente. A razão fica VISÍVEL: um controle inerte e sem explicação parece defeito. --}}
{{-- ===== Passo das DESPESAS ===== O que sai desta reserva em cobranças, e quando cada uma vence. Existe separado de "Valores" porque lá se DECIDE (plano, desconto, condição) e aqui se vê a CONSEQUÊNCIA — e porque a lista de vencimentos pode ter dezenas de linhas, que não cabiam embaixo dos campos de valor sem enterrá-los. OS DOIS MODELOS TÊM LISTA, e é a mesma promessa nos dois: nenhuma reserva se fecha sem o usuário saber quantas cobranças ela gera e em que dia cada uma cai. OS VALORES NUNCA SÃO EDITÁVEIS aqui: eles saem da agenda e do orçamento. Editável é só o vencimento. --}}
{{-- POR EVENTO: uma cobrança por ocorrência. O padrão é vencer no dia da própria reserva, e é isso que vem preenchido — mas o campo é um input, porque "a aula é quinta e ele paga no dia 10" é comum. --}}

{{-- RECORRENTE: uma cobrança por período, somando as reservas de cada um. --}}
{{-- As frequências vêm do enum, via controller: a tela não mantém a sua própria lista, para não divergir da validação. --}}
{{-- Vencimento da PRIMEIRA cobrança; as demais saem dele pela frequência, preservando o dia — é o "todo dia 5". Padrão: a data da reserva. --}}
{{-- AS PARCELAS, período a período. Existe porque "mensal" não quer dizer o mesmo valor todo mês: os meses não têm o mesmo número de quartas-feiras, e cada parcela soma as aulas do SEU período. Sem esta lista o usuário fecharia o agendamento sem saber quanto vai cobrar em cada mês — e o número só apareceria depois, no financeiro. Os VALORES nunca são editáveis: saem da agenda. Editável é só o VENCIMENTO, sempre — não há mais um "personalizar" a marcar antes. Ele era um clique a mais para uma permissão que nunca precisou ser pedida: o campo já nasce preenchido com a data sugerida, e mexer nele é a exceção que não custa nada deixar à mão. --}}
{{-- ===== Passo do RESUMO ===== Tudo o que foi decidido, numa tela só, antes de reservar. Existe porque o wizard esconde os passos anteriores: sem ele, confirmar exigiria voltar seis vezes para reler o que já se escolheu. É SÓ LEITURA e é montado pelo JavaScript a partir do próprio formulário — nenhum campo aqui tem `name`, e nada daqui é enviado. Repetir os dados em inputs escondidos criaria uma segunda fonte de verdade que divergiria da primeira no primeiro ajuste. --}}
{{-- FORA DO PADRÃO CONFIGURADO — AVISO, NÃO ERRO. Horário que não cai na grade do período de aluguel, ou dia em que a arena não abre: a reserva vale do mesmo jeito. Isto já foi uma recusa do servidor (422), e o efeito era o balcão não conseguir registrar o combinado de 10:30 numa quadra de 60 min sem ir mexer na configuração da arena. Fica ANTES do resumo, e não junto do botão Reservar, porque é contexto para conferir o que está escrito logo abaixo — não uma objeção ao clique. Preenchido pelo JS (renderSummaryWarnings). --}}
{{-- AS COBRANÇAS POR EXTENSO, e não só a contagem delas. É a mesma tabela do passo "Despesas", aqui em leitura: o resumo existe para conferir antes de reservar, e "8 cobranças" não deixa conferir nada — o que se quer ver é quando cada uma cai. Sem inputs de propósito. Editar em dois lugares criaria duas fontes de verdade para a mesma data; quem quer mudar volta um passo, que é a distância de um clique na trilha. --}}
{{-- Navegação do wizard. "Voltar" some no primeiro passo e "Continuar" no último, onde entra o "Salvar" — assim o botão que aparece é sempre o único que faz sentido ali. --}} {{-- ===== Modal de aviso de conflito de horário ===== Aberto pelo calendar.js quando a quadra e o intervalo escolhidos já batem com outra reserva. É só um AVISO em cima do formulário (modais empilhados) — a trava de verdade é do servidor, que recusa o POST/PUT de qualquer forma. --}} {{-- ===== Modal de aviso de data no passado ===== Aberto pelo calendar.js quando o usuário clica num dia passado do calendário ou muda o início da reserva para antes de hoje. É só um AVISO — a trava de verdade é do servidor, que recusa datas no passado. --}} {{-- ===== Modal de resultado de cadastro em intervalo ===== Aberto pelo calendar.js após reservar vários dias: diz quantas reservas entraram e quais dias foram pulados por já estarem ocupados. --}} {{-- ===== Modal de horários livres do dia (passo "Datas") ===== Aberto pelo botão de cada data em conflito: mostra a faixa livre mais próxima antes e depois do horário pedido, para o usuário decidir. --}} {{-- ===== Menu de ações da reserva (abre ao passar o mouse) ===== UM MENU SÓ para o calendário inteiro, reposicionado sobre a reserva sob o cursor — e não um menu por evento. O calendário redesenha os eventos a cada troca de mês ou de visão; um menu pendurado em cada um deles seria construído e destruído às centenas, e cada um precisaria dos seus próprios ouvintes. Fica FORA do calendário no DOM e é posicionado com `position: fixed`: dentro da grade, o `overflow` das colunas o cortaria. CLICAR NA RESERVA CONTINUA ABRINDO O "ver". O menu é atalho, não a única porta — em tela de toque não existe passar o mouse, e lá o clique é tudo o que há. --}} {{-- ===== Modal de visualização (ver reserva, com editar/excluir) ===== --}} @endsection