File manager - Edit - /var/www/html/portalHomolog/resources/views/admin/cronogramas/show.blade.php
Back
@extends('layouts.admin.theme') @section('title', env('APP_NAME') . ' :: ' . $project->name) @push('scripts') <link rel="stylesheet" href="{{ asset('pmar/css/cronogramas.css') }}?v={{ filemtime(public_path('pmar/css/cronogramas.css')) }}"> @endpush @section('content') @php $filterQuery = $filterParams ?? []; // Query para links de aba: filtros do dashboard sem tab/paginação de outras visões. $tabLinkQuery = collect($filterQuery)->except(['tab', 'ap', 'arp', 'tp', 'cp', 'hp'])->all(); // Apenas os filtros da lista de atividades, para que o Export reflita o que está filtrado. $exportFilterParams = collect($filterQuery) ->filter(fn ($value, $key) => str_starts_with((string) $key, 'f_')) ->all(); $canAdd = $perm['adicionar'] && ! $readOnly; $canEdit = $perm['editar'] && ! $readOnly; $canDelete = $perm['excluir'] && ! $readOnly; // Linhas da página atual (subatividade com recuo quando possui parent_id). $orderedRows = collect($tableRows)->map(fn ($row) => [ 'row' => $row, 'depth' => empty($row['parent_id']) ? 0 : 1, ]); $showDragReorder = $canEdit && ($allowTableReorder ?? true); $perPageOptions = $perPageOptions ?? config('cronogramas.per_page_options', [20, 50, 100, 250, 500]); $currentPerPage = $currentPerPage ?? ($perPageOptions[0] ?? 20); $sortOptions = $sortOptions ?? ['manual' => __('Ordem manual')]; $activitySort = $activitySort ?? 'code'; $activityDir = $activityDir ?? 'desc'; @endphp <div id="cronogramas-app" class="card shadow-sm border-0" data-project-id="{{ $project->id }}" data-read-only="{{ $readOnly ? '1' : '0' }}" data-can-add="{{ $canAdd ? '1' : '0' }}" data-can-edit="{{ $canEdit ? '1' : '0' }}" data-can-delete="{{ $canDelete ? '1' : '0' }}" data-allow-reorder="{{ ($showDragReorder ?? false) ? '1' : '0' }}" data-csrf="{{ csrf_token() }}" data-current-tab="{{ $viewTab }}" data-url-activity-store="{{ route('admin.cronogramas.atividades.store', $project) }}" data-url-activity-update="{{ route('admin.cronogramas.atividades.update', ['project' => $project->id, 'activity' => '__ACTIVITY__']) }}" data-url-activity-show="{{ route('admin.cronogramas.atividades.show', ['project' => $project->id, 'activity' => '__ACTIVITY__']) }}" data-url-activity-inline="{{ route('admin.cronogramas.atividades.inline', ['project' => $project->id, 'activity' => '__ACTIVITY__']) }}" data-url-activity-reorder="{{ route('admin.cronogramas.atividades.reorder', $project) }}" data-url-activities-data="{{ route('admin.cronogramas.atividades.data', $project) }}" data-url-activity-archive="{{ route('admin.cronogramas.atividades.arquivar', ['project' => $project->id, 'activity' => '__ACTIVITY__']) }}" data-url-activity-restore="{{ route('admin.cronogramas.atividades.restaurar', ['project' => $project->id, 'activity' => '__ACTIVITY__']) }}" data-url-activity-destroy="{{ route('admin.cronogramas.atividades.destroy', ['project' => $project->id, 'activity' => '__ACTIVITY__']) }}" data-url-activities-bulk-destroy="{{ route('admin.cronogramas.atividades.arquivadas.destroy', $project) }}" data-url-project-destroy="{{ route('admin.cronogramas.projetos.destroy', $project) }}" data-url-dashboard="{{ route('admin.cronogramas.dashboard', ['tab' => 'archived']) }}" data-msg-confirm-delete-project="{{ __('Tem certeza que deseja excluir? Esta ação não poderá ser desfeita.') }}" data-url-comment-store="{{ route('admin.cronogramas.comentarios.store', ['project' => $project->id, 'activity' => '__ACTIVITY__']) }}" data-url-comment-destroy="{{ route('admin.cronogramas.comentarios.destroy', ['project' => $project->id, 'activity' => '__ACTIVITY__', 'comment' => '__COMMENT__']) }}" data-url-attachment-store="{{ route('admin.cronogramas.atividades.attachments', ['project' => $project->id, 'activity' => '__ACTIVITY__']) }}" data-url-attachment-destroy="{{ route('admin.cronogramas.atividades.attachments.destroy', ['project' => $project->id, 'activity' => '__ACTIVITY__', 'attachment' => '__ATTACHMENT__']) }}" data-url-attachment-download="{{ route('admin.cronogramas.atividades.attachments.download', ['project' => $project->id, 'activity' => '__ACTIVITY__', 'attachment' => '__ATTACHMENT__']) }}" data-msg-saved="{{ __('Salvo com sucesso.') }}" data-msg-error="{{ __('Não foi possível salvar. Tente novamente.') }}" data-msg-loading="{{ __('Carregando...') }}" data-msg-no-comments="{{ __('Nenhum comentário ainda.') }}" data-msg-no-attachments="{{ __('Nenhum anexo ainda.') }}" data-msg-no-history="{{ __('Sem histórico.') }}" data-msg-confirm-delete-comment="{{ __('Excluir este comentário?') }}" data-msg-confirm-delete-attachment="{{ __('Excluir este anexo?') }}" data-msg-confirm-archive-activity="{{ __('Arquivar esta atividade? Ela será movida para a aba Arquivadas.') }}" data-msg-confirm-delete-activity="{{ __('Tem certeza que deseja excluir? Esta ação não poderá ser desfeita.') }}" data-msg-confirm-bulk-delete="{{ __('Excluir permanentemente :count atividade(s) selecionada(s)? Esta ação não poderá ser desfeita.') }}" data-label-delete-selected="{{ __('Excluir selecionadas') }}" data-label-select-all-archived="{{ __('Selecionar todas desta página') }}" data-msg-selected-one="{{ __('1 selecionada') }}" data-msg-selected-many="{{ __(':count selecionadas') }}" data-msg-confirm-restore-activity="{{ __('Restaurar esta atividade para a lista ativa?') }}" data-msg-archived="{{ __('Arquivada em') }}" data-label-archive="{{ __('Arquivar') }}" data-label-restore="{{ __('Restaurar') }}" data-msg-no-archived="{{ __('Nenhuma atividade arquivada.') }}" data-msg-no-search-results="{{ __('Nenhuma atividade encontrada.') }}" data-msg-search-count="{{ __(':visible de :total atividade(s)') }}" data-label-clear-search="{{ __('Limpar busca') }}" data-label-download="{{ __('Baixar') }}" data-label-delete="{{ __('Excluir') }}" data-label-remove="{{ __('Remover') }}" data-msg-gantt-resize-col="{{ __('Arraste para ajustar a largura da coluna') }}" data-msg-gantt-resize-row="{{ __('Arraste para ajustar a altura das linhas') }}"> @php $headLine = "<i class='fa-solid fa-chart-gantt'></i> " . e($project->name); @endphp @include('components.buttons._headLineButtonsBackAdd', [ 'headLine' => $headLine, 'routeBack' => 'admin.cronogramas.dashboard', 'routeBackParams' => $filterQuery, 'nameBack' => __('Voltar'), 'permission' => 'GERENCIADOR_DE_CRONOGRAMAS', ]) <div class="card-body"> @include('errors.messageFlash') {{-- Cabeçalho do projeto + indicadores --}} <div class="row g-3 mb-3 align-items-stretch"> <div class="col-lg-7"> <div class="d-flex flex-wrap align-items-center gap-2 mb-2"> <span class="badge {{ $project->isReadOnly() ? 'bg-dark' : 'bg-primary' }}"> {{ \App\Enums\Accounts\Cronogramas\CronogramaProjectStatus::tryFrom($project->status)?->label() }} </span> <span class="small text-muted"><i class="fa-regular fa-calendar"></i> {{ __('Início') }}: {{ $project->start_date?->format('d/m/Y') ?? '-' }}</span> <span class="small text-muted"><i class="fa-regular fa-calendar-check"></i> {{ __('Término previsto') }}: {{ $project->planned_end_date?->format('d/m/Y') ?? '-' }}</span> </div> @if($project->description) <p class="text-muted mb-2">{{ $project->description }}</p> @endif <div class="progress cr-progress-h-sm" role="progressbar" aria-label="{{ __('Progresso do projeto') }}" aria-valuenow="{{ $project->completion_percent }}" aria-valuemin="0" aria-valuemax="100"> <div class="progress-bar bg-success cr-progress-bar-dynamic" id="cr-project-progress" style="--cr-w: {{ $project->completion_percent }}%;"></div> </div> <small class="text-muted"><span id="cr-project-progress-label">{{ $project->completion_percent }}</span>% {{ __('concluído') }}</small> </div> <div class="col-lg-5"> <div class="row g-2 text-center"> <div class="col-4"> <div class="border rounded py-2 h-100"> <div class="fw-bold" id="cr-stat-total">{{ $projectStats['total_count'] ?? 0 }}</div> <div class="small text-muted">{{ __('Atividades') }}</div> </div> </div> <div class="col-4"> <div class="border rounded py-2 h-100"> <div class="fw-bold text-success" id="cr-stat-completed">{{ $projectStats['completed_count'] ?? 0 }}</div> <div class="small text-muted">{{ __('Concluídas') }}</div> </div> </div> <div class="col-4"> <div class="border rounded py-2 h-100"> <div class="fw-bold text-danger" id="cr-stat-overdue">{{ $projectStats['overdue_count'] ?? 0 }}</div> <div class="small text-muted">{{ __('Atrasadas') }}</div> </div> </div> </div> </div> </div> {{-- Barra de ações do projeto --}} @php $pdfViews = [ 'table' => ['icon' => 'fa-table-list', 'label' => __('Tabela')], 'gantt' => ['icon' => 'fa-chart-gantt', 'label' => __('Gantt')], 'timeline' => ['icon' => 'fa-timeline', 'label' => __('Linha do tempo')], ]; $canEditProject = $perm['editar'] && ! $project->isReadOnly(); $canUnarchiveProject = $perm['editar'] && $project->status === 'archived'; $canDeleteProject = $perm['excluir'] && $project->status === 'archived'; $hasProjectActions = $canEditProject || $canUnarchiveProject || $canDeleteProject; @endphp <div class="d-flex flex-wrap align-items-center gap-2 mb-3 p-2 border rounded bg-light cr-project-toolbar" role="toolbar" aria-label="{{ __('Ações do projeto') }}"> {{-- Grupo: exportação e impressão (consolidado) --}} <div class="dropdown"> <button type="button" class="btn btn-outline-secondary btn-sm dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false" title="{{ __('Exportar e imprimir') }}"> <i class="fa-solid fa-file-export"></i> {{ __('Exportar') }} </button> <ul class="dropdown-menu shadow-sm"> <li><h6 class="dropdown-header"><i class="fa-solid fa-eye me-1"></i> {{ __('Visualizar PDF') }}</h6></li> @foreach($pdfViews as $pdfType => $pdfView) <li> <a class="dropdown-item" href="{{ route('admin.cronogramas.projetos.export.pdf', array_merge(['project' => $project->id, 'type' => $pdfType], $exportFilterParams)) }}" target="_blank" rel="noopener" title="{{ __('Visualizar :view em PDF', ['view' => $pdfView['label']]) }}"> <i class="fa-solid {{ $pdfView['icon'] }} me-2 text-muted"></i>{{ $pdfView['label'] }} </a> </li> @endforeach <li><hr class="dropdown-divider"></li> <li><h6 class="dropdown-header"><i class="fa-solid fa-download me-1"></i> {{ __('Baixar') }}</h6></li> @foreach($pdfViews as $pdfType => $pdfView) <li> <a class="dropdown-item" href="{{ route('admin.cronogramas.projetos.export.pdf', array_merge(['project' => $project->id, 'type' => $pdfType, 'download' => 1], $exportFilterParams)) }}" title="{{ __('Baixar :view em PDF', ['view' => $pdfView['label']]) }}"> <i class="fa-solid fa-file-pdf me-2 text-danger"></i>{{ __('PDF') }} - {{ $pdfView['label'] }} </a> </li> @endforeach <li> <a class="dropdown-item" href="{{ route('admin.cronogramas.projetos.export.excel', $project) }}" title="{{ __('Exportar Excel') }}"> <i class="fa-solid fa-file-excel me-2 text-success"></i>{{ __('Excel') }} </a> </li> </ul> </div> {{-- Grupo: ações do projeto (alinhado à direita) --}} @if($hasProjectActions) <div class="d-flex flex-wrap align-items-center gap-2 ms-auto"> @if($canEditProject) <a class="btn btn-primary btn-sm" href="{{ route('admin.cronogramas.projetos.edit', array_merge(['project' => $project->id], $filterQuery)) }}"><i class="fa-solid fa-pen"></i> {{ __('Editar projeto') }}</a> <form method="post" action="{{ route('admin.cronogramas.projetos.arquivar', $project) }}" class="d-inline"> @csrf @include('components.cronogramas.persist-query', ['params' => $filterQuery]) <button type="submit" class="btn btn-outline-warning btn-sm"><i class="fa-solid fa-box-archive"></i> {{ __('Arquivar') }}</button> </form> <form method="post" action="{{ route('admin.cronogramas.projetos.encerrar', $project) }}" class="d-inline" onsubmit="return confirm('{{ __('Encerrar o projeto? Ele ficará somente leitura.') }}')"> @csrf @include('components.cronogramas.persist-query', ['params' => $filterQuery]) <button type="submit" class="btn btn-outline-danger btn-sm"><i class="fa-solid fa-flag-checkered"></i> {{ __('Encerrar') }}</button> </form> @endif @if($canUnarchiveProject) <form method="post" action="{{ route('admin.cronogramas.projetos.desarquivar', $project) }}" class="d-inline"> @csrf @include('components.cronogramas.persist-query', ['params' => $filterQuery]) <button type="submit" class="btn btn-outline-success btn-sm"><i class="fa-solid fa-box-open"></i> {{ __('Desarquivar') }}</button> </form> @endif @if($canDeleteProject) <button type="button" class="btn btn-outline-danger btn-sm cr-btn-delete-project" data-url="{{ route('admin.cronogramas.projetos.destroy', $project) }}" title="{{ __('Excluir permanentemente') }}"> <i class="fa-solid fa-trash"></i> {{ __('Excluir projeto') }} </button> @endif </div> @endif </div> @if($readOnly) <div class="alert alert-info d-flex align-items-center gap-2" role="alert"> <i class="fa-solid fa-lock"></i> @if($project->status === 'archived') {{ __('Projeto arquivado: somente leitura. Use Desarquivar para voltar a editar.') }} @else {{ __('Projeto encerrado: somente leitura.') }} @endif </div> @endif {{-- Abas de visualização --}} <ul class="nav nav-tabs cr-view-tabs mb-3" role="tablist"> @php $tabs = [ 'table' => ['icon' => 'fa-table-list', 'label' => __('Tabela')], 'gantt' => ['icon' => 'fa-chart-gantt', 'label' => __('Gantt')], 'kanban' => ['icon' => 'fa-columns', 'label' => __('Kanban')], 'timeline' => ['icon' => 'fa-timeline', 'label' => __('Linha do tempo')], 'archived' => ['icon' => 'fa-box-archive', 'label' => __('Arquivadas'), 'badge' => $archivedCount], ]; @endphp @foreach($tabs as $key => $tab) <li class="nav-item" role="presentation"> <a class="nav-link @if($viewTab === $key) active @endif" href="{{ route('admin.cronogramas.projetos.show', array_merge($tabLinkQuery, ['project' => $project->id, 'tab' => $key])) }}"> <i class="fa-solid {{ $tab['icon'] }}"></i> {{ $tab['label'] }} @if(! empty($tab['badge'])) <span class="badge bg-secondary ms-1" id="cr-archived-tab-badge">{{ $tab['badge'] }}</span> @endif </a> </li> @endforeach </ul> <div class="mb-3 @unless(in_array($viewTab, ['gantt', 'kanban'], true)) d-none @endunless" id="cr-search-wrap"> <label for="cr-activity-search" class="visually-hidden">{{ __('Buscar atividades') }}</label> <div class="d-flex flex-wrap align-items-center gap-2"> <div class="input-group input-group-sm cr-search-group-max"> <span class="input-group-text" aria-hidden="true"><i class="fa-solid fa-search"></i></span> <input type="search" id="cr-activity-search" class="form-control" placeholder="{{ __('Buscar por código, título, responsável, status...') }}" autocomplete="off" aria-label="{{ __('Buscar atividades') }}" aria-controls="cr-activities-table cr-gantt-container cr-kanban-board cr-timeline"> <button type="button" class="btn btn-outline-secondary d-none" id="cr-search-clear" title="{{ __('Limpar busca') }}" aria-label="{{ __('Limpar busca') }}"> <i class="fa-solid fa-xmark"></i> </button> </div> <small class="text-muted d-none" id="cr-search-hint" role="status" aria-live="polite"></small> </div> </div> <div class="tab-content"> {{-- ABA: TABELA --}} @if($viewTab === 'table') <div class="tab-pane fade show active" role="tabpanel"> @include('components.cronogramas.activity-filters', [ 'project' => $project, 'filterParams' => $filterParams ?? [], 'viewTab' => 'table', 'pageParam' => 'ap', 'activityFilters' => $activityFilters ?? [], 'statusOptions' => $statusOptions, 'responsibleOptions' => $filterResponsibleOptions ?? $responsibleOptions, 'filtersActive' => $filtersActive ?? false, 'filtersCount' => $filtersCount ?? 0, 'selectId' => 'table', ]) <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mb-3"> <div> @if($canAdd) <button type="button" class="btn btn-primary btn-sm" id="cr-btn-new-activity" data-bs-toggle="modal" data-bs-target="#crModalActivity"> <i class="fa-solid fa-plus"></i> {{ __('Nova atividade') }} </button> @if($showDragReorder) <span class="small text-muted ms-2">{{ __('Arraste pela alça para reordenar.') }}</span> @elseif(($activitySort ?? 'manual') !== 'manual') <span class="small text-muted ms-2">{{ __('Reordenação disponível apenas na ordem manual.') }}</span> @elseif(isset($activitiesPaginator) && $activitiesPaginator->hasPages()) <span class="small text-muted ms-2">{{ __('Reordenação disponível apenas com todas as atividades em uma página.') }}</span> @endif @endif </div> <div class="d-flex flex-wrap align-items-center gap-3"> @include('components.cronogramas.sort-selector', [ 'project' => $project, 'filterParams' => $filterParams ?? [], 'viewTab' => 'table', 'pageParam' => 'ap', 'sortOptions' => $sortOptions, 'currentSort' => $activitySort ?? 'code', 'currentDir' => $activityDir ?? 'desc', 'selectId' => 'cr_sort_table', ]) @include('components.cronogramas.per-page-selector', [ 'project' => $project, 'filterParams' => $filterParams ?? [], 'viewTab' => 'table', 'pageParam' => 'ap', 'perPageOptions' => $perPageOptions, 'currentPerPage' => $currentPerPage, 'selectId' => 'cr_per_page_table', ]) </div> </div> @if($orderedRows->isEmpty()) <div class="text-center text-muted py-5 border rounded"> <i class="fa-solid fa-list-check fa-2x mb-2 d-block"></i> @if($filtersActive ?? false) {{ __('Nenhuma atividade encontrada com os filtros aplicados.') }} @else {{ __('Nenhuma atividade cadastrada.') }} @endif </div> @elseif(($activitySort ?? 'manual') === 'chronological') @include('components.cronogramas.timeline', ['rows' => $tableRows, 'dir' => $activityDir ?? 'asc']) @if(isset($activitiesPaginator) && $activitiesPaginator->total() > 0) <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mt-3"> <small class="text-muted"> {{ __('Mostrando :from-:to de :total atividade(s)', [ 'from' => $activitiesPaginator->firstItem(), 'to' => $activitiesPaginator->lastItem(), 'total' => $activitiesPaginator->total(), ]) }} </small> @if($activitiesPaginator->hasPages()) <div class="cr-pagination"> {{ $activitiesPaginator->links() }} </div> @endif </div> @endif @else <div class="table-responsive"> <table class="table table-sm table-hover align-middle cr-table" id="cr-activities-table"> <thead class="table-light"> <tr> @if($showDragReorder)<th scope="col" class="cr-table-drag-col"></th>@endif <th scope="col">{{ __('Item') }}</th> <th scope="col">{{ __('Atividades') }}</th> <th scope="col">{{ __('Predecessora') }}</th> <th scope="col" class="cr-col-progress">{{ __('%') }}</th> <th scope="col" class="cr-col-status">{{ __('Status') }}</th> <th scope="col">{{ __('Responsável') }}</th> <th scope="col">{{ __('Início') }}</th> <th scope="col">{{ __('Término') }}</th> <th scope="col">{{ __('Descrição') }}</th> <th scope="col">{{ __('Observação') }}</th> <th scope="col" class="cr-table-actions">{{ __('Ações') }}</th> </tr> </thead> <tbody id="cr-activities-tbody"> @foreach($orderedRows as $item) @php $row = $item['row']; $depth = $item['depth']; @endphp <tr data-activity-id="{{ $row['id'] }}" @if($row['is_overdue']) class="table-danger" @endif> @if($showDragReorder) <td class="cr-drag-handle text-muted" title="{{ __('Reordenar') }}" aria-label="{{ __('Reordenar') }}"> <i class="fa-solid fa-grip-vertical"></i> </td> @endif <td class="text-nowrap"><code>{{ $row['code'] }}</code></td> <td> <span class="@if($depth > 0) cr-depth-1 @endif"> @if($depth > 0)<i class="fa-solid fa-turn-up fa-rotate-90 text-muted me-1 small"></i>@endif {{ $row['title'] }} </span> </td> <td class="small text-muted">{{ $row['predecessors'] ?: '-' }}</td> <td> <div class="d-flex align-items-center gap-2 cr-row-progress-wrap"> <div class="progress flex-grow-1 cr-row-progress" role="progressbar" aria-label="{{ __('Progresso (%)') }}" aria-valuenow="{{ (int) $row['completion_percent'] }}" aria-valuemin="0" aria-valuemax="100"> <div class="progress-bar cr-progress-bar-dynamic" style="--cr-w: {{ $row['completion_percent'] }}%;"></div> </div> <small class="text-muted text-nowrap cr-row-progress-label">{{ (int) $row['completion_percent'] }}%</small> </div> </td> <td> @if($canEdit) <select class="form-select form-select-sm cr-inline-status" data-field="status" aria-label="{{ __('Status da atividade') }}"> @foreach($statusOptions as $st) <option value="{{ $st->value }}" @selected($row['status'] === $st->value)>{{ $st->label() }}</option> @endforeach </select> @else <span class="badge bg-secondary cr-status-badge">{{ $row['status_label'] }}</span> @endif <span class="badge bg-danger mt-1 cr-status-overdue-badge @unless($row['is_overdue']) d-none @endunless">{{ __('Atrasado') }}</span> </td> <td class="small text-nowrap">{{ $row['responsible'] }}</td> <td class="small text-nowrap">{{ $row['planned_start_date'] ? \Carbon\Carbon::parse($row['planned_start_date'])->format('d/m/Y') : '-' }}</td> <td class="small text-nowrap">{{ $row['planned_end_date'] ? \Carbon\Carbon::parse($row['planned_end_date'])->format('d/m/Y') : '-' }}</td> <td class="small text-muted cr-col-longtext" title="{{ $row['description'] }}">{{ $row['description'] ?: '-' }}</td> <td class="small text-muted cr-col-longtext" title="{{ $row['notes'] }}">{{ $row['notes'] ?: '-' }}</td> <td class="cr-table-actions text-nowrap"> <div class="cr-table-actions-inner"> <button type="button" class="btn btn-sm btn-outline-secondary cr-btn-details" data-activity-id="{{ $row['id'] }}" title="{{ __('Detalhes, comentários e anexos') }}"> <i class="fa-solid fa-circle-info"></i> <span class="badge bg-secondary cr-badge-comments" data-activity-id="{{ $row['id'] }}">{{ $row['comments_count'] ?: '' }}</span> <span class="badge bg-info cr-badge-attachments" data-activity-id="{{ $row['id'] }}">{{ $row['attachments_count'] ?: '' }}</span> </button> @if($canEdit) <button type="button" class="btn btn-sm btn-outline-primary cr-btn-edit" data-activity-id="{{ $row['id'] }}" data-bs-toggle="modal" data-bs-target="#crModalActivity" title="{{ __('Editar') }}"> <i class="fa-solid fa-pen"></i> </button> @endif @if($canAdd) <form method="post" action="{{ route('admin.cronogramas.atividades.duplicate', [$project, $row['id']]) }}" class="d-inline"> @csrf <button type="submit" class="btn btn-sm btn-outline-secondary" title="{{ __('Duplicar') }}"><i class="fa-solid fa-copy"></i></button> </form> @endif @if($canEdit) <button type="button" class="btn btn-sm btn-outline-warning cr-btn-archive" data-activity-id="{{ $row['id'] }}" title="{{ __('Arquivar') }}"> <i class="fa-solid fa-box-archive"></i> </button> @endif </div> </td> </tr> @endforeach </tbody> </table> </div> @if(isset($activitiesPaginator) && $activitiesPaginator->total() > 0) <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mt-3"> <small class="text-muted"> {{ __('Mostrando :from-:to de :total atividade(s)', [ 'from' => $activitiesPaginator->firstItem(), 'to' => $activitiesPaginator->lastItem(), 'total' => $activitiesPaginator->total(), ]) }} </small> @if($activitiesPaginator->hasPages()) <div class="cr-pagination"> {{ $activitiesPaginator->links() }} </div> @endif </div> @endif @endif </div> {{-- ABA: GANTT --}} @elseif($viewTab === 'gantt') <div class="tab-pane fade show active" role="tabpanel"> <div class="d-flex flex-wrap gap-3 mb-2 align-items-center"> <div class="d-flex align-items-center gap-1"> <span class="small text-muted me-1">{{ __('Zoom') }}:</span> <div class="btn-group btn-group-sm" role="group" aria-label="{{ __('Zoom do Gantt') }}"> <button type="button" class="btn btn-outline-secondary cr-gantt-zoom" data-zoom="day">{{ __('Dia') }}</button> <button type="button" class="btn btn-outline-secondary cr-gantt-zoom" data-zoom="week">{{ __('Semana') }}</button> <button type="button" class="btn btn-outline-secondary cr-gantt-zoom active" data-zoom="month">{{ __('Mês') }}</button> </div> </div> <div class="d-flex flex-wrap gap-3 align-items-center"> <span class="small text-muted d-flex align-items-center gap-1"><span class="cr-gantt-v2-legend-dot cr-gantt-legend-macro"></span>{{ __('Atividade macro') }}</span> <span class="small text-muted d-flex align-items-center gap-1"><span class="cr-gantt-v2-legend-dot cr-gantt-legend-activity"></span>{{ __('Atividade') }}</span> <span class="small text-muted d-flex align-items-center gap-1"><span class="cr-gantt-v2-legend-dot cr-gantt-legend-done"></span>{{ __('Concluída') }}</span> <span class="small text-muted d-flex align-items-center gap-1"><span class="cr-gantt-v2-legend-dot cr-gantt-legend-overdue"></span>{{ __('Atrasada') }}</span> <span class="small text-muted d-flex align-items-center gap-1"><span class="cr-gantt-v2-legend-today"></span>{{ __('Hoje') }}</span> </div> </div> <div id="cr-gantt-container" class="cr-gantt-container" role="img" aria-label="{{ __('Gráfico de Gantt do projeto') }}"></div> </div> {{-- ABA: KANBAN --}} @elseif($viewTab === 'kanban') <div class="tab-pane fade show active" role="tabpanel"> @if($canEdit)<p class="small text-muted">{{ __('Arraste os cartões entre as colunas para alterar o status.') }}</p>@endif <div class="cr-kanban-board" id="cr-kanban-board"> @foreach(\App\Enums\Accounts\Cronogramas\CronogramaActivityStatus::kanbanColumns() as $col) <div class="cr-kanban-col"> <div class="card border h-100"> <div class="card-header py-2 d-flex justify-content-between align-items-center"> <span class="small fw-semibold">{{ $col->label() }}</span> <span class="badge bg-secondary cr-col-badge" data-col-status="{{ $col->value }}">{{ collect($kanbanRows)->where('status', $col->value)->count() }}</span> </div> <div class="card-body p-2 cr-kanban-column" data-status="{{ $col->value }}" @if($canEdit) data-droppable="1" @endif> @foreach($kanbanRows as $row) @if($row['status'] === $col->value) <div class="card mb-2 cr-kanban-card @if($row['is_overdue']) border-danger @endif" data-activity-id="{{ $row['id'] }}" @if($canEdit) draggable="true" @endif> <div class="card-body p-2"> <div class="small fw-semibold text-truncate" title="{{ $row['title'] }}"><code>{{ $row['code'] }}</code> {{ $row['title'] }}</div> <div class="progress mt-1 cr-progress-h-xs"> <div class="progress-bar cr-progress-bar-dynamic" style="--cr-w: {{ $row['completion_percent'] }}%;"></div> </div> <div class="small text-muted mt-1"><i class="fa-regular fa-user"></i> {{ $row['responsible'] }}</div> </div> </div> @endif @endforeach </div> </div> </div> @endforeach </div> </div> {{-- ABA: LINHA DO TEMPO visual das atividades --}} @elseif($viewTab === 'timeline') <div class="tab-pane fade show active" role="tabpanel"> @include('components.cronogramas.activity-filters', [ 'project' => $project, 'filterParams' => $filterParams ?? [], 'viewTab' => 'timeline', 'pageParam' => 'tp', 'activityFilters' => $activityFilters ?? [], 'statusOptions' => $statusOptions, 'responsibleOptions' => $filterResponsibleOptions ?? $responsibleOptions, 'filtersActive' => $filtersActive ?? false, 'filtersCount' => $filtersCount ?? 0, 'selectId' => 'timeline', ]) <div class="d-flex flex-wrap justify-content-end align-items-center gap-3 mb-3"> @include('components.cronogramas.sort-selector', [ 'project' => $project, 'filterParams' => $filterParams ?? [], 'viewTab' => 'timeline', 'pageParam' => 'tp', 'sortOptions' => $sortOptions, 'currentSort' => $activitySort ?? 'manual', 'currentDir' => $activityDir ?? 'asc', 'selectId' => 'cr_sort_timeline', ]) @include('components.cronogramas.per-page-selector', [ 'project' => $project, 'filterParams' => $filterParams ?? [], 'viewTab' => 'timeline', 'pageParam' => 'tp', 'perPageOptions' => $perPageOptions, 'currentPerPage' => $currentPerPage, 'selectId' => 'cr_per_page_timeline', ]) </div> @if(empty($tableRows)) <p class="text-muted"> @if($filtersActive ?? false) {{ __('Nenhuma atividade encontrada com os filtros aplicados.') }} @else {{ __('Nenhuma atividade cadastrada.') }} @endif </p> @else @include('components.cronogramas.timeline', ['rows' => $tableRows]) @if(isset($timelinePaginator) && $timelinePaginator->total() > 0) <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mt-3"> <small class="text-muted"> {{ __('Mostrando :from-:to de :total atividade(s)', [ 'from' => $timelinePaginator->firstItem(), 'to' => $timelinePaginator->lastItem(), 'total' => $timelinePaginator->total(), ]) }} </small> @if($timelinePaginator->hasPages()) <div class="cr-pagination">{{ $timelinePaginator->links() }}</div> @endif </div> @endif @endif </div> {{-- ABA: ARQUIVADAS --}} @elseif($viewTab === 'archived') <div class="tab-pane fade show active" role="tabpanel"> <p class="small text-muted">{{ __('Atividades arquivadas podem ser restauradas ou excluídas permanentemente.') }}</p> @if(empty($archivedRows)) <div class="text-center text-muted py-5 border rounded"> <i class="fa-solid fa-box-archive fa-2x mb-2 d-block"></i> {{ __('Nenhuma atividade arquivada.') }} </div> @else @if($canDelete) <div class="d-flex flex-wrap align-items-center gap-2 mb-3"> <button type="button" class="btn btn-sm btn-outline-danger" id="cr-btn-bulk-delete-archived" disabled> <i class="fa-solid fa-trash" aria-hidden="true"></i> {{ __('Excluir selecionadas') }} <span class="badge bg-danger ms-1 d-none" id="cr-archived-selected-count">0</span> </button> <small class="text-muted" id="cr-archived-selection-hint" aria-live="polite"></small> </div> @endif <div class="table-responsive"> <table class="table table-sm table-hover align-middle cr-table" id="cr-archived-table"> <thead class="table-light"> <tr> @if($canDelete) <th scope="col" class="cr-table-check-col"> <input type="checkbox" class="form-check-input" id="cr-archived-select-all" title="{{ __('Selecionar todas desta página') }}" aria-label="{{ __('Selecionar todas desta página') }}"> </th> @endif <th scope="col">{{ __('Código') }}</th> <th scope="col">{{ __('Atividade') }}</th> <th scope="col">{{ __('Responsável') }}</th> <th scope="col">{{ __('Status') }}</th> <th scope="col">{{ __('Arquivada em') }}</th> <th scope="col" class="cr-table-actions">{{ __('Ações') }}</th> </tr> </thead> <tbody id="cr-archived-tbody"> @foreach($archivedRows as $row) <tr data-activity-id="{{ $row['id'] }}"> @if($canDelete) <td> <input type="checkbox" class="form-check-input cr-archived-check" value="{{ $row['id'] }}" aria-label="{{ __('Selecionar atividade :code', ['code' => $row['code']]) }}"> </td> @endif <td class="text-nowrap"><code>{{ $row['code'] }}</code></td> <td>{{ $row['title'] }}</td> <td class="small text-nowrap">{{ $row['responsible'] }}</td> <td><span class="badge bg-secondary">{{ $row['status_label'] }}</span></td> <td class="small text-nowrap text-muted">{{ $row['archived_at'] ?? '-' }}</td> <td class="cr-table-actions text-nowrap"> <div class="cr-table-actions-inner"> <button type="button" class="btn btn-sm btn-outline-secondary cr-btn-details" data-activity-id="{{ $row['id'] }}" title="{{ __('Detalhes e histórico') }}"> <i class="fa-solid fa-circle-info"></i> </button> @if($canEdit) <button type="button" class="btn btn-sm btn-outline-success cr-btn-restore" data-activity-id="{{ $row['id'] }}" title="{{ __('Restaurar') }}"> <i class="fa-solid fa-rotate-left"></i> </button> @endif @if($canDelete) <button type="button" class="btn btn-sm btn-outline-danger cr-btn-delete-archived" data-activity-id="{{ $row['id'] }}" title="{{ __('Excluir permanentemente') }}"> <i class="fa-solid fa-trash"></i> </button> @endif </div> </td> </tr> @endforeach </tbody> </table> </div> @if(isset($archivedPaginator) && $archivedPaginator->total() > 0) <div class="d-flex flex-wrap justify-content-between align-items-center gap-2 mt-3"> <small class="text-muted"> {{ __('Mostrando :from-:to de :total atividade(s) arquivada(s)', [ 'from' => $archivedPaginator->firstItem(), 'to' => $archivedPaginator->lastItem(), 'total' => $archivedPaginator->total(), ]) }} </small> @if($archivedPaginator->hasPages()) <div class="cr-pagination">{{ $archivedPaginator->links() }}</div> @endif </div> @endif @endif </div> @endif </div> </div> </div> {{-- MODAL: criar / editar atividade --}} @if($canAdd || $canEdit) <div class="modal fade" id="crModalActivity" tabindex="-1" aria-labelledby="crModalActivityLabel" aria-hidden="true"> <div class="modal-dialog modal-lg modal-dialog-scrollable"> <div class="modal-content"> <form id="cr-activity-form" method="post" action="{{ route('admin.cronogramas.atividades.store', $project) }}" data-label-new="{{ __('Nova atividade') }}" data-label-edit="{{ __('Editar atividade') }}"> @csrf @include('components.cronogramas.persist-query', ['params' => $filterQuery]) <input type="hidden" name="_method" id="cr-activity-method" value="POST"> <div class="modal-header"> <h2 class="modal-title h5" id="crModalActivityLabel">{{ __('Nova atividade') }}</h2> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="{{ __('Fechar') }}"></button> </div> <div class="modal-body row g-3"> <div class="col-md-3"> <label for="cr-f-code" class="form-label">{{ __('Código') }}</label> <input type="text" name="code" id="cr-f-code" class="form-control" maxlength="32" placeholder="{{ __('Automático') }}"> </div> <div class="col-md-9"> <label for="cr-f-title" class="form-label">{{ __('Título') }} <span class="text-danger">*</span></label> <input type="text" name="title" id="cr-f-title" class="form-control" required maxlength="300"> </div> <div class="col-12"> <label for="cr-f-description" class="form-label">{{ __('Descrição') }}</label> <textarea name="description" id="cr-f-description" class="form-control" rows="2"></textarea> </div> <div class="col-md-6"> <label for="cr-f-parent" class="form-label">{{ __('Atividade pai (subatividade de)') }}</label> <select name="parent_id" id="cr-f-parent" class="form-select"> <option value="">{{ __('- Nenhuma (atividade raiz) -') }}</option> @foreach($rootActivities as $r) <option value="{{ $r->id }}">{{ $r->code }} - {{ $r->title }}</option> @endforeach </select> </div> <div class="col-md-6"> <span class="form-label d-block mb-1">{{ __('Responsáveis') }}</span> <div class="d-flex gap-2 mb-2 align-items-start flex-wrap cr-responsible-adder"> <select class="form-select form-select-sm cr-resp-type-select" id="cr-f-responsible-type" aria-label="{{ __('Tipo de responsável') }}"> @foreach(\App\Enums\Accounts\Cronogramas\CronogramaResponsibleType::cases() as $respType) <option value="{{ $respType->value }}">{{ $respType->label() }}</option> @endforeach </select> <div class="flex-grow-1 cr-resp-picker-wrap"> <select class="form-select form-select-sm cr-responsible-picker" id="cr-f-responsible-user" data-responsible-type="user" aria-label="{{ __('Pessoa responsável') }}"> <option value="">{{ __('- Selecione uma pessoa -') }}</option> @foreach($responsibleOptions['users'] as $u) <option value="{{ $u->id }}">{{ $u->firstName }} {{ $u->lastName }}</option> @endforeach </select> <select class="form-select form-select-sm cr-responsible-picker d-none" id="cr-f-responsible-empresa" data-responsible-type="empresa" aria-label="{{ __('Empresa responsável') }}" hidden> <option value="">{{ __('- Selecione uma empresa -') }}</option> @foreach($responsibleOptions['empresas'] as $empresa) <option value="{{ $empresa->id }}"> {{ $empresa->nomeFantasia ?: $empresa->razaoSocial }}@if($empresa->cnpj) ({{ $empresa->cnpj }})@endif </option> @endforeach </select> <select class="form-select form-select-sm cr-responsible-picker d-none" id="cr-f-responsible-structure" data-responsible-type="structure" aria-label="{{ __('Órgão responsável') }}" hidden> <option value="">{{ __('- Selecione um órgão -') }}</option> @foreach($responsibleOptions['structures'] as $structure) <option value="{{ $structure->id }}"> {{ $structure->name }}@if($structure->abbreviation) ({{ $structure->abbreviation }})@endif </option> @endforeach </select> <select class="form-select form-select-sm cr-responsible-picker d-none" id="cr-f-responsible-unidade" data-responsible-type="unidade" aria-label="{{ __('Unidade organizacional responsável') }}" hidden> <option value="">{{ __('- Selecione uma unidade -') }}</option> @foreach($responsibleOptions['unidades'] as $unidade) <option value="{{ $unidade->id }}"> {{ $unidade->abbreviation ? $unidade->abbreviation . ' - ' : '' }}{{ $unidade->name }}@if($unidade->secretaria) ({{ $unidade->secretaria->abbreviation ?: $unidade->secretaria->name }})@endif </option> @endforeach </select> </div> <button type="button" class="btn btn-outline-primary btn-sm" id="cr-f-responsible-add" title="{{ __('Adicionar responsável') }}" aria-label="{{ __('Adicionar responsável') }}"> <i class="fa-solid fa-plus"></i> </button> </div> <div id="cr-f-responsibles-list" class="d-flex flex-wrap gap-1" aria-live="polite"></div> <div id="cr-f-responsibles-hidden"></div> <small class="text-muted d-block mt-1">{{ __('Escolha o tipo e a entidade e clique em adicionar. É possível incluir vários responsáveis.') }}</small> </div> <div class="col-md-6"> <label for="cr-f-status" class="form-label">{{ __('Status') }}</label> <select name="status" id="cr-f-status" class="form-select"> @foreach($statusOptions as $st) <option value="{{ $st->value }}" @selected($st->value === 'not_started')>{{ $st->label() }}</option> @endforeach </select> <small class="text-muted d-block mt-1">{{ __('O progresso (%) é definido automaticamente pelo status.') }}</small> </div> <div class="col-12"> <label for="cr-f-pred-search" class="form-label">{{ __('Predecessoras') }}</label> <input type="text" id="cr-f-pred-search" class="form-control form-control-sm mb-2" placeholder="{{ __('Buscar por código ou título...') }}" autocomplete="off"> <div id="cr-f-pred-list" class="border rounded cr-pred-list overflow-auto"> @foreach($allActivitiesForSelect as $r) <label class="d-flex align-items-center gap-2 cr-pred-item px-2 py-1 mb-0" data-pred-text="{{ \Illuminate\Support\Str::lower($r->code . ' ' . $r->title) }}"> <input type="checkbox" class="form-check-input m-0 cr-pred-check" name="predecessor_ids[]" value="{{ $r->id }}" data-pred-id="{{ $r->id }}"> <span class="small"><code>{{ $r->code }}</code> {{ \Illuminate\Support\Str::limit($r->title, 70) }}</span> </label> @endforeach <div class="text-muted small px-2 py-2 d-none" id="cr-f-pred-empty">{{ __('Nenhuma atividade encontrada.') }}</div> </div> <small class="text-muted d-block mt-1"><span id="cr-f-pred-count">0</span> {{ __('selecionada(s)') }}</small> </div> <div class="col-md-6"> <label for="cr-f-start" class="form-label">{{ __('Início') }}</label> <input type="date" name="planned_start_date" id="cr-f-start" class="form-control"> </div> <div class="col-md-6"> <label for="cr-f-end" class="form-label">{{ __('Término') }}</label> <input type="date" name="planned_end_date" id="cr-f-end" class="form-control"> </div> <div class="col-12"> <label for="cr-f-notes" class="form-label">{{ __('Observações') }}</label> <textarea name="notes" id="cr-f-notes" class="form-control" rows="2"></textarea> </div> <div class="col-12"> <span class="small text-muted" id="cr-activity-autosave" aria-live="polite"></span> </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">{{ __('Salvar atividade') }}</button> </div> </form> </div> </div> </div> @endif {{-- OFFCANVAS: detalhes da atividade (comentários, anexos, histórico) --}} <div class="offcanvas offcanvas-end cr-offcanvas-details" tabindex="-1" id="crOffcanvasDetails" aria-labelledby="crOffcanvasDetailsLabel"> <div class="offcanvas-header border-bottom"> <h2 class="offcanvas-title h5 mb-0" id="crOffcanvasDetailsLabel">{{ __('Detalhes da atividade') }}</h2> <button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="{{ __('Fechar') }}"></button> </div> <div class="offcanvas-body"> <div id="cr-details-meta" class="mb-3"></div> <ul class="nav nav-pills nav-fill mb-3" role="tablist"> <li class="nav-item"><button class="nav-link active" data-cr-pane="comments" type="button">{{ __('Comentários') }}</button></li> <li class="nav-item"><button class="nav-link" data-cr-pane="attachments" type="button">{{ __('Anexos') }}</button></li> <li class="nav-item"><button class="nav-link" data-cr-pane="history" type="button">{{ __('Histórico') }}</button></li> </ul> {{-- Comentários --}} <div data-cr-pane-content="comments"> <nav id="cr-comments-pagination" class="mb-2 d-none" aria-label="{{ __('Paginação de comentários') }}"></nav> <ul class="list-group list-group-flush mb-3" id="cr-comments-list"></ul> @if($canAdd) <form id="cr-comment-form"> <label for="cr-comment-input" class="form-label visually-hidden">{{ __('Comentário') }}</label> <textarea id="cr-comment-input" class="form-control mb-2" rows="3" maxlength="5000" required placeholder="{{ __('Escreva um comentário...') }}"></textarea> <button type="submit" class="btn btn-primary btn-sm">{{ __('Comentar') }}</button> </form> @endif </div> {{-- Anexos --}} <div data-cr-pane-content="attachments" hidden> <ul class="list-group list-group-flush mb-3" id="cr-attachments-list"></ul> @if($canAdd) <form id="cr-attachment-form"> <label for="cr-attachment-input" class="form-label">{{ __('Enviar arquivos') }}</label> <input type="file" id="cr-attachment-input" class="form-control mb-2" multiple> <button type="submit" class="btn btn-primary btn-sm">{{ __('Enviar') }}</button> <div class="form-text">{{ __('Máx. 50 MB por arquivo.') }}</div> </form> @endif </div> {{-- Histórico --}} <div data-cr-pane-content="history" hidden> <nav id="cr-history-pagination" class="mb-2 d-none" aria-label="{{ __('Paginação do histórico') }}"></nav> <ul class="list-group list-group-flush" id="cr-history-list"></ul> </div> </div> </div> <script type="application/json" id="cr-table-rows">@json($kanbanRows ?? [])</script> <script type="application/json" id="cr-gantt-data">@json($ganttData)</script> <script type="application/json" id="cr-archived-rows">@json($archivedRows ?? [])</script> @endsection @push('after-scripts') <script src="{{ asset('pmar/js/cronogramas.js') }}?v={{ filemtime(public_path('pmar/js/cronogramas.js')) }}"></script> @endpush
| ver. 1.4 |
Github
|
.
| PHP 8.3.28 | Generation time: 0.02 |
proxy
|
phpinfo
|
Settings