File manager - Edit - /var/www/html/portalHomolog/resources/views/admin/mobiangra/index.blade.php
Back
@extends('layouts.admin.theme') @section("title", env("APP_NAME") . " :: " . __("MobiAngra")) @section('content') <div class="card shadow-sm"> @php $headLine = "<i class='fa-solid fa-bus'></i> " . __('MobiAngra'); $tabAtual = $tabAtual ?? request('tab', 'mapa'); @endphp @include('components.buttons._headLineButtonsBackAdd', [ 'headLine' => $headLine, 'routeBack' => 'admin.dashboard', 'routeBackParams' => [], 'nameBack' => __('Voltar'), 'permission' => 'GERENCIADOR_DE_MOBIANGRA', ]) <div class="card-body"> @include('errors.messageFlash') {{-- ===== TABS ===== --}} <ul class="nav nav-tabs mb-4" id="mobiangra-tabs" role="tablist"> <li class="nav-item" role="presentation"> <button class="nav-link {{ $tabAtual === 'mapa' ? 'active' : '' }}" id="tab-mapa-btn" data-bs-toggle="tab" data-bs-target="#tab-mapa" type="button" role="tab" aria-controls="tab-mapa" aria-selected="{{ $tabAtual === 'mapa' ? 'true' : 'false' }}"> <i class="fa-solid fa-map me-1"></i>{{ __('Mapa') }} </button> </li> <li class="nav-item" role="presentation"> <button class="nav-link {{ $tabAtual === 'horarios' ? 'active' : '' }}" id="tab-horarios-btn" data-bs-toggle="tab" data-bs-target="#tab-horarios" type="button" role="tab" aria-controls="tab-horarios" aria-selected="{{ $tabAtual === 'horarios' ? 'true' : 'false' }}"> <i class="fa-solid fa-clock me-1"></i>{{ __('Horários') }} </button> </li> <li class="nav-item" role="presentation"> <button class="nav-link {{ $tabAtual === 'manifestacoes' ? 'active' : '' }}" id="tab-manifestacoes-btn" data-bs-toggle="tab" data-bs-target="#tab-manifestacoes" type="button" role="tab" aria-controls="tab-manifestacoes" aria-selected="{{ $tabAtual === 'manifestacoes' ? 'true' : 'false' }}"> <i class="fa-solid fa-comments me-1"></i>{{ __('Manifestações') }} </button> </li> <li class="nav-item" role="presentation"> <button class="nav-link {{ $tabAtual === 'alertas' ? 'active' : '' }}" id="tab-alertas-btn" data-bs-toggle="tab" data-bs-target="#tab-alertas" type="button" role="tab" aria-controls="tab-alertas" aria-selected="{{ $tabAtual === 'alertas' ? 'true' : 'false' }}"> <i class="fa-solid fa-bell me-1"></i>{{ __('Alertas') }} </button> </li> </ul> <div class="tab-content" id="mobiangra-tabs-content"> {{-- ===== TAB: MAPA ===== --}} <div class="tab-pane fade {{ $tabAtual === 'mapa' ? 'show active' : '' }}" id="tab-mapa" role="tabpanel" aria-labelledby="tab-mapa-btn"> {{-- Status do Feed GTFS-RT --}} <div class="row mb-4"> <div class="col-md-6 mb-3 mb-md-0"> <div class="border rounded p-3 h-100"> <h5 class="mb-3"> <i class="fa-solid fa-signal"></i> {{ __('Status do Feed em Tempo Real') }} </h5> @if ($feedStatus) <p class="mb-1"> <strong>{{ __('Última atualização') }}:</strong> @if ($feedStatus['success']) <span class="badge bg-success"> {{ \Carbon\Carbon::parse($feedStatus['last_update'])->diffForHumans() }} </span> @else <span class="badge bg-danger">{{ __('Falha') }}</span> @endif </p> <p class="mb-1"> <strong>{{ __('Veículos no último fetch') }}:</strong> <span class="badge bg-primary">{{ $feedStatus['vehicle_count'] ?? 0 }}</span> </p> <p class="mb-0"> <strong>{{ __('Horário') }}:</strong> <code>{{ $feedStatus['last_update'] ?? '-' }}</code> </p> @else <div class="alert alert-warning mb-0"> <i class="fa-solid fa-triangle-exclamation"></i> {{ __('O feed ainda não foi executado. Use o botão abaixo para forçar a primeira atualização, ou acesse o mapa público para disparar a primeira leitura automaticamente.') }} </div> @endif <hr> <p class="mb-1"> <strong>{{ __('Arquivo Protobuf (.pb)') }}:</strong> @if ($pbExists) <span class="badge bg-success">{{ __('Disponível') }}</span> @else <span class="badge bg-secondary">{{ __('Não gerado') }}</span> @endif </p> <p class="mb-0"> <strong>{{ __('Arquivo JSON (live_data)') }}:</strong> @if ($jsonExists) <span class="badge bg-success">{{ __('Disponível') }}</span> @else <span class="badge bg-secondary">{{ __('Não gerado') }}</span> @endif </p> </div> </div> <div class="col-md-6"> <div class="border rounded p-3 h-100"> <h5 class="mb-3"> <i class="fa-solid fa-link"></i> {{ __('Acesso Rápido') }} </h5> <div class="mb-2"> <a href="{{ route('mobiangra.index') }}" target="_blank" class="btn btn-sm btn-outline-secondary me-1"> <i class="fa-solid fa-house"></i> {{ __('Portal público') }} </a> <a href="{{ route('mobiangra.mapa') }}" target="_blank" class="btn btn-sm btn-outline-primary"> <i class="fa-solid fa-map"></i> {{ __('Mapa público') }} </a> </div> <div class="mb-2"> <small class="text-muted">{{ __('Endpoint JSON (mapa)') }}:</small><br> <code>{{ url('/mobiangra/live_data') }}</code> </div> <div class="alert alert-info py-2 px-3 small mb-2" role="note"> <strong>{{ __('Google Transit / validadores') }}:</strong> {{ __('Use somente as URLs canônicas abaixo. Não use') }} <code>/mobiangra/gtfs.zip</code> {{ __('nem') }} <code>/mobiangra/feed.pb</code>. </div> <div class="mb-2"> <small class="text-muted">{{ __('Feed GTFS-RT (tempo real)') }}:</small><br> <code>{{ route('mobiangra.gtfs.feed_pb') }}</code> </div> <div class="mb-2"> <small class="text-muted">{{ __('GTFS estático (.zip)') }}:</small><br> @if ($gtfsStaticAvailable) <a href="{{ route('mobiangra.gtfs.gtfs_zip') }}" class="text-break" target="_blank"> <code>{{ route('mobiangra.gtfs.gtfs_zip') }}</code> </a> @else <code>{{ route('mobiangra.gtfs.gtfs_zip') }}</code> <span class="badge bg-warning text-dark ms-1">{{ __('Aguardando upload GTFS') }}</span> @endif </div> <hr> @if (auth()->user()->getPermissaoDetalhes('GERENCIADOR_DE_MOBIANGRA')['editar'] ?? false) <form action="{{ route('admin.mobiangra.forceUpdate') }}" method="post" class="d-inline"> @csrf <input type="hidden" name="tab" value="mapa"> <button type="submit" class="btn btn-sm btn-warning"> <i class="fa-solid fa-rotate"></i> {{ __('Forçar atualização agora') }} </button> </form> @endif </div> </div> </div> {{-- Arquivos GTFS --}} <div class="border rounded p-3 mb-4"> <h5 class="mb-3"> <i class="fa-solid fa-database"></i> {{ __('Arquivos GTFS Estáticos') }} </h5> <div class="row mb-3"> @foreach ($gtfsFiles as $filename => $exists) <div class="col-6 col-md-3 mb-2"> <span class="badge {{ $exists ? 'bg-success' : 'bg-danger' }} me-1"> <i class="fa-solid {{ $exists ? 'fa-check' : 'fa-xmark' }}"></i> </span> <code>{{ $filename }}</code> </div> @endforeach </div> @if (auth()->user()->getPermissaoDetalhes('GERENCIADOR_DE_MOBIANGRA')['adicionar'] ?? false) <hr> <form action="{{ route('admin.mobiangra.uploadGtfs') }}" method="post" enctype="multipart/form-data"> @csrf <input type="hidden" name="tab" value="mapa"> <p class="fw-bold mb-2">{{ __('Atualizar GTFS') }}</p> <div class="mb-3"> <label for="gtfs_zip" class="form-label fw-bold mb-1"> {{ __('Pacote GTFS (.zip)') }} </label> <input type="file" name="gtfs_zip" id="gtfs_zip" class="form-control form-control-sm @error('gtfs_zip') is-invalid @enderror" accept=".zip,application/zip"> @error('gtfs_zip') <div class="invalid-feedback d-block">{{ $message }}</div> @enderror </div> <button type="submit" class="btn btn-primary btn-sm mb-3"> <i class="fa-solid fa-file-zipper"></i> {{ __('Importar pacote .zip') }} </button> <details class="mt-3"> <summary class="btn btn-sm btn-outline-secondary mb-2 d-inline-flex align-items-center gap-1"> <i class="fa-solid fa-file-lines"></i> {{ __('Envio alternativo: arquivos .txt individuais') }} </summary> <div class="mt-2"> <div class="row"> @php $gtfsInputs = [ 'calendar' => 'calendar.txt', 'calendar_dates' => 'calendar_dates.txt', 'routes' => 'routes.txt', 'trips' => 'trips.txt', 'stops' => 'stops.txt', 'shapes' => 'shapes.txt', 'stop_times' => 'stop_times.txt', 'agency' => 'agency.txt', 'feed_info' => 'feed_info.txt', ]; @endphp @foreach ($gtfsInputs as $field => $label) <div class="col-md-4 mb-2"> <label for="gtfs_{{ $field }}" class="form-label small fw-bold mb-1">{{ $label }}</label> <input type="file" name="gtfs_{{ $field }}" id="gtfs_{{ $field }}" class="form-control form-control-sm" accept=".txt,.csv"> </div> @endforeach </div> <button type="submit" class="btn btn-outline-primary btn-sm mt-2"> <i class="fa-solid fa-upload"></i> {{ __('Enviar arquivos .txt selecionados') }} </button> </div> </details> </form> @endif </div> {{-- fleet.json --}} <div class="border rounded p-3 mb-4"> <h5 class="mb-3"> <i class="fa-solid fa-sliders"></i> {{ __('Configuração da frota (linhas e veículos)') }} </h5> <p class="mb-3"> <strong>{{ __('Arquivo') }}:</strong> <code>{{ config('mobiangra.storage_dir') }}/{{ config('mobiangra.fleet_filename', 'fleet.json') }}</code> @if ($fleetExists) <span class="badge bg-success ms-1">{{ __('Disponível') }}</span> @else <span class="badge bg-warning text-dark ms-1">{{ __('Será criado no primeiro acesso') }}</span> @endif </p> <div class="d-flex flex-wrap gap-2 mb-3"> <a href="{{ route('admin.mobiangra.downloadFleet') }}" class="btn btn-sm btn-outline-secondary"> <i class="fa-solid fa-download"></i> {{ __('Baixar fleet.json') }} </a> </div> @if (auth()->user()->getPermissaoDetalhes('GERENCIADOR_DE_MOBIANGRA')['editar'] ?? false) <form action="{{ route('admin.mobiangra.uploadFleet') }}" method="post" enctype="multipart/form-data" class="mb-3"> @csrf <input type="hidden" name="tab" value="mapa"> <label for="fleet_config" class="form-label fw-bold mb-1">{{ __('Enviar fleet.json atualizado') }}</label> <input type="file" name="fleet_config" id="fleet_config" class="form-control form-control-sm" accept=".json,application/json" required> <button type="submit" class="btn btn-sm btn-primary mt-2"> <i class="fa-solid fa-upload"></i> {{ __('Salvar configuração da frota') }} </button> </form> <form action="{{ route('admin.mobiangra.resetFleet') }}" method="post" onsubmit="return confirm(@json(__('Regenerar o fleet.json a partir do modelo padrão? Alterações manuais serão substituídas.')))"> @csrf <input type="hidden" name="tab" value="mapa"> <button type="submit" class="btn btn-sm btn-outline-warning"> <i class="fa-solid fa-rotate-left"></i> {{ __('Regenerar a partir do modelo padrão') }} </button> </form> @endif </div> {{-- Configurações técnicas --}} <div class="border rounded p-3"> <h5 class="mb-3"> <i class="fa-solid fa-gear"></i> {{ __('Informações Técnicas') }} </h5> <div class="row mb-3"> <div class="col-md-6"> <p class="mb-1"> <strong>{{ __('API de telemetria em uso') }}:</strong><br> <code class="small">{{ config('mobiangra.api_url') }}</code> </p> <p class="mb-1"> <strong>{{ __('Intervalo de atualização atual') }}:</strong> <span class="badge bg-secondary">{{ $fetchInterval }}s</span> </p> </div> <div class="col-md-6"> <p class="mb-0"> <strong>{{ __('Modo de atualização') }}:</strong> <span class="badge bg-info text-dark"> <i class="fa-solid fa-bolt"></i> {{ __('Lazy (automático via mapa)') }} </span> </p> </div> </div> @if (auth()->user()->getPermissaoDetalhes('GERENCIADOR_DE_MOBIANGRA')['editar'] ?? false) <hr> <form action="{{ route('admin.mobiangra.saveSettings') }}" method="post" class="row g-2 align-items-end"> @csrf <input type="hidden" name="tab" value="mapa"> <div class="col-auto"> <label for="fetch_interval" class="form-label fw-bold mb-1"> {{ __('Intervalo de atualização (segundos)') }} </label> <div class="input-group input-group-sm" style="width:220px;"> <input type="number" id="fetch_interval" name="fetch_interval" class="form-control @error('fetch_interval') is-invalid @enderror" value="{{ old('fetch_interval', $fetchInterval) }}" min="5" max="300" step="1" required> <span class="input-group-text">s</span> @error('fetch_interval') <div class="invalid-feedback">{{ $message }}</div> @enderror </div> </div> <div class="col-auto"> <button type="submit" class="btn btn-sm btn-primary"> <i class="fa-solid fa-floppy-disk"></i> {{ __('Salvar') }} </button> </div> </form> @endif </div> </div>{{-- /tab-mapa --}} {{-- ===== TAB: HORÁRIOS ===== --}} <div class="tab-pane fade {{ $tabAtual === 'horarios' ? 'show active' : '' }}" id="tab-horarios" role="tabpanel" aria-labelledby="tab-horarios-btn"> {{-- Cabeçalho com ações --}} <div class="d-flex flex-wrap align-items-center justify-content-between gap-2 mb-3"> <h5 class="mb-0"> <i class="fa-solid fa-clock me-1"></i>{{ __('Linhas de Horários') }} <span id="mobiangra-horarios-total-badge" class="badge bg-secondary ms-1">{{ $linhasHorarios->count() }}</span> </h5> <div class="d-flex gap-2 flex-wrap"> <a href="{{ route('mobiangra.horarios') }}" target="_blank" class="btn btn-sm btn-outline-primary"> <i class="fa-solid fa-eye me-1"></i>{{ __('Ver no site') }} </a> @if (auth()->user()->getPermissaoDetalhes('GERENCIADOR_DE_MOBIANGRA')['adicionar'] ?? false) <a href="{{ route('admin.mobiangra.horarios.linhas.create') }}" class="btn btn-sm btn-success"> <i class="fa-solid fa-plus me-1"></i>{{ __('Nova linha') }} </a> {{-- Importar XLSX --}} <button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="modal" data-bs-target="#modal-importar-xlsx"> <i class="fa-solid fa-file-excel me-1"></i>{{ __('Importar XLSX') }} </button> @endif </div> </div> <div id="mobiangra-horarios-busca-config" class="d-none" data-url="{{ route('admin.mobiangra.horarios.linhas.buscar') }}" data-total-inicial="{{ $totalLinhasHorariosCadastro ?? 0 }}" data-msg-buscando="{{ __('Buscando…') }}" data-msg-erro="{{ __('Não foi possível buscar. Tente novamente.') }}" data-msg-sem-resultado="{{ __('Nenhuma linha encontrada para esta busca.') }}" data-msg-resultados="{{ __(':total linha(s) encontrada(s)') }}" data-msg-todas="{{ __('Exibindo todas as linhas') }}"> </div> <div class="mb-3"> <label for="mobiangra-horarios-busca" class="form-label visually-hidden">{{ __('Buscar linha') }}</label> <div class="input-group"> <span class="input-group-text" aria-hidden="true"> <i class="fa-solid fa-magnifying-glass"></i> </span> <input type="search" id="mobiangra-horarios-busca" class="form-control" value="{{ request('q') }}" placeholder="{{ __('Buscar por código ou nome da linha…') }}" autocomplete="off" aria-controls="mobiangra-horarios-linhas-tbody" aria-describedby="mobiangra-horarios-busca-status"> <button type="button" id="mobiangra-horarios-busca-limpar" class="btn btn-outline-secondary @if (! request('q')) d-none @endif" title="{{ __('Limpar busca') }}" aria-label="{{ __('Limpar busca') }}"> <i class="fa-solid fa-xmark" aria-hidden="true"></i> </button> </div> <div id="mobiangra-horarios-busca-status" class="form-text" aria-live="polite"></div> </div> <div id="mobiangra-horarios-linhas-tabela-wrap" class="table-responsive @if ($linhasHorarios->isEmpty()) d-none @endif"> <table class="table table-hover align-middle mb-0"> <thead class="table-light"> <tr> <th style="width:70px">{{ __('Código') }}</th> <th>{{ __('Nome') }}</th> <th class="text-center" style="width:80px">{{ __('Tarifa') }}</th> <th class="text-center" style="width:90px">{{ __('Seções') }}</th> <th class="text-center" style="width:90px">{{ __('Status') }}</th> <th class="text-center" style="width:120px">{{ __('Ações') }}</th> </tr> </thead> <tbody id="mobiangra-horarios-linhas-tbody"> @include('admin.mobiangra.horarios._linhas_tbody', ['linhasHorarios' => $linhasHorarios]) </tbody> </table> </div> <div id="mobiangra-horarios-linhas-vazio" class="alert alert-info @if ($linhasHorarios->isNotEmpty()) d-none @endif"> <i class="fa-solid fa-circle-info me-1"></i> {{ __('Nenhuma linha cadastrada. Clique em "Nova linha" para começar, ou importe um arquivo XLSX.') }} </div> <div id="mobiangra-horarios-linhas-sem-resultado" class="alert alert-warning d-none" role="status"> <i class="fa-solid fa-magnifying-glass me-1"></i> {{ __('Nenhuma linha encontrada para esta busca.') }} </div> {{-- Modal importar XLSX --}} <div class="modal fade" id="modal-importar-xlsx" tabindex="-1" aria-labelledby="modal-importar-xlsx-label" aria-hidden="true"> <div class="modal-dialog"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="modal-importar-xlsx-label"> <i class="fa-solid fa-file-excel me-1"></i>{{ __('Importar Grade de Horários (.xlsx)') }} </h5> <button type="button" class="btn-close" data-bs-dismiss="modal"></button> </div> <form action="{{ route('admin.mobiangra.horarios.importarXlsx') }}" method="post" enctype="multipart/form-data"> @csrf <input type="hidden" name="tab" value="horarios"> <div class="modal-body"> <p class="text-muted small"> {{ __('Selecione o arquivo XLSX com as grades de horários. O sistema irá importar automaticamente todas as abas (linhas) do arquivo, criando ou atualizando os registros no banco de dados.') }} </p> <div class="mb-3"> <label for="horarios_xlsx_import" class="form-label fw-semibold">{{ __('Arquivo .xlsx') }}</label> <input type="file" name="horarios_xlsx" id="horarios_xlsx_import" class="form-control" accept=".xlsx,.xls" required> </div> <div class="alert alert-warning small mb-0"> <i class="fa-solid fa-triangle-exclamation me-1"></i> {{ __('A importação irá substituir os dados existentes das linhas que coincidirem com o código da aba.') }} </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ __('Cancelar') }}</button> <button type="submit" class="btn btn-primary"> <i class="fa-solid fa-upload me-1"></i>{{ __('Importar') }} </button> </div> </form> </div> </div> </div> </div>{{-- /tab-horarios --}} @include('admin.mobiangra.manifestacoes._tab') @include('admin.mobiangra.alertas._tab') </div>{{-- /tab-content --}} </div> </div> @push('after-scripts') <script> window.mobiangraAlertasConfig = { storeUrl: @json(route('admin.mobiangra.alertas.store')), updateUrlBase: @json(url('/admin/mobiangra/alertas')), createLabel: @json(__('Novo alerta')), editLabel: @json(__('Editar alerta')), openModalOnError: @json($tabAtual === 'alertas' && $errors->any()), }; </script> <script src="{{ asset('pmar/js/admin/mobiangra/admin_index.js') }}"></script> <script src="{{ asset('pmar/js/admin/mobiangra/admin_horarios_busca.js') }}?v={{ time() }}"></script> <script src="{{ asset('pmar/js/admin/mobiangra/admin_alertas.js') }}?v={{ time() }}"></script> @endpush @endsection
| ver. 1.4 |
Github
|
.
| PHP 8.3.28 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings