@extends('layouts.dashboard') @section('content') @php /** * Editor for a page the product ships with. * * Section list on the left, one section's fields on the right — the same shape and order the page * renders, so what the operator edits and what a visitor sees line up. The list doubles as the * visibility switches: "where do I hide the owners?" has one answer, and it is on screen. * * The previous version stacked all twenty-two groups in one column and ran to twenty screens with a * 99% empty rail beside it. Nothing here is hidden from saving — every field of every section stays * inside the one form, so switching sections is a view, not a step. * * @var string $slug * @var string $route where the page lives, for View live * @var string $locale the language being edited * @var array $locales every enabled language code * @var array $sections from PageContent::sections() * @var int $filled fields carrying content * @var int $total fields in total */ $active = (string) (is_scalar($s = request()->query('s')) ? $s : ''); $ids = array_column($sections, 'id'); if (!in_array($active, $ids, true)) { $active = (string) ($ids[0] ?? ''); } @endphp @php /* .qm-d2: the dashboard density standard (app.css §35) */ @endphp
{{ t_raw('admin.pages.back_link') }} {{ t_raw('admin.pages.view_live') }} @if (count($locales) > 1) @php // Only when more than one language is enabled — a single-language install never sees a // control offering it one choice. @endphp @foreach ($locales as $code) {{ strtoupper($code) }} @endforeach @endif
@csrf
@php // Fields first, controls in a rail on the RIGHT — the same order the CMS page form // uses, so the two page editors do not mirror each other. DOM order matches the // visual order, which keeps tabbing running through the fields before the save. @endphp
@php /* The arrangement, posted as one ordered list. Rewritten by the drag handler, so the order that saves is the order on screen. A page whose sections are fixed (the four written pages) simply never posts it. */ @endphp @foreach ($sections as $sec) @php /* Each section folds. It used to be a tab set — one section on screen and the rest hidden — which meant the operator could not see two sections at once, and a page with a section switched off looked identical to one whose tab simply was not selected. Open the one the rail points at; the rest are one click away and everything stays inside the single form, so one Save still writes the page. */ @endphp
@php /* The same grip Admin → Navigation uses, and the same rule: only the grip starts a drag, so everything else in the row keeps working — here that means the summary still opens the fold. data-nofold stops the mousedown toggling it as well. */ @endphp {{ $sec['label'] }} @if ($sec['toggle'] !== '') @php /* The section's switch, on its own row beside the caret — visible without opening the fold, exactly as a slide's is. data-nofold keeps the click on the switch: a control inside a opens the fold as well unless it is stopped. It posts under toggles[], which is what the storefront partials read, so switching it off here hides the section on the site. */ @endphp @endif
@if ($sec['data']) @php /* Nothing to write here, so the section says why rather than opening onto an empty box: it is built from real records, or its wording is written on another screen. Same sentences the page editors have always used — the switch above is the whole control. */ @endphp

{{ $sec['note'] !== '' ? $sec['note'] : t_raw('admin.pc.data_only') }}

@endif @php // A section built from repeating rows — ten services, eight FAQ pairs — folds each // one away. Ten open panels of eight fields ran to a dozen screens and the operator // scrolled past nine services to reach the tenth. The first is left open so the // section still shows its shape on arrival. A single-panel section stays a plain // card: there is nothing to choose between. $accordion = count($sec['panels']) > 1; // A panel whose fields all declare a `group` is really several blocks in one — the // hero's slides. Split it so each becomes its own fold rather than one long card // with three headings in it. $panels = []; foreach ($sec['panels'] as $panel) { $byGroup = []; $loose = []; foreach ($panel['fields'] as $f) { $g = (string) ($f['opts']['group'] ?? ''); if ($g === '') { $loose[] = $f; } else { $byGroup[$g][] = $f; } } if ($byGroup === []) { $panels[] = $panel; continue; } if ($loose !== []) { $panels[] = ['label' => $panel['label'], 'fields' => $loose, 'slide' => '']; } // Group panels ARE the slides, in order, so each is tagged with the slide it is // — the switch below then belongs to a slide rather than to a position. $slideIds = $sec['slides'] ?? []; $n = 0; foreach ($byGroup as $gKey => $gFields) { $panels[] = ['label' => t_raw($gKey), 'fields' => $gFields, 'slide' => $slideIds[$n] ?? '']; $n++; } } $accordion = $accordion || count($panels) > 1; @endphp @foreach ($panels as $pIdx => $panel) @php // Copy left, artwork right when a panel has both — the way these sections are laid // out on the page itself, and it halves a panel that would otherwise stack eight // fields vertically. $media = array_values(array_filter($panel['fields'], static fn (array $f): bool => in_array($f['kind'], ['image', 'video'], true))); $copy = array_values(array_filter($panel['fields'], static fn (array $f): bool => !in_array($f['kind'], ['image', 'video'], true))); $split = $media !== [] && $copy !== []; $cols = $split ? [['col-md-7', $copy], ['col-md-5', $media]] : [['col-12', $panel['fields']]]; // A panel with no name of its own prints no heading: repeating the section's name // under the fold that already carries it read as the same title three times over. $pLabel = (string) $panel['label']; // A slide of a carousel carries its own switch, so one slide can come off the deck. $pSlide = (string) ($panel['slide'] ?? ''); $pSlideToggle = $pSlide !== '' ? \App\Services\HomeSection::settingKey($slug, $sec['id'] . '_' . $pSlide) : ''; $pSlideOn = $pSlideToggle === '' || setting($pSlideToggle, '1') === '1'; @endphp @if ($accordion)
{{ $pLabel }} @if ($pSlideToggle !== '') @php /* On the summary row, so which slides are live reads at a glance without opening three folds. data-nofold keeps the click on the switch instead of letting it open the fold as well — a control inside toggles the fold too unless it is stopped. */ @endphp @endif
@else

{{ $pLabel }}

@endif @foreach ($cols as [$colClass, $colFields])
@php // .qm-pc-fields puts a glyph picker on the same line as the // heading it belongs to, the way the Business-model cards do. @endphp
@foreach ($colFields as $i => $f) @php $fid = 'f-' . md5($f['key']); @endphp @php // Two fields that belong together share a row — declared in // PageContent::ROW_PAIRS, so it holds in every language. $lk = (string) ($f['labelKey'] ?? ''); $next = (string) ($colFields[$i + 1]['labelKey'] ?? ''); $prev = (string) ($colFields[$i - 1]['labelKey'] ?? ''); $half = \App\Services\PageContent::pairsWith($lk, $next) || \App\Services\PageContent::pairsWith($prev, $lk); @endphp
@if ($f['kind'] === 'image' || $f['kind'] === 'video') @if (!empty($f['opts']['multi'])) @php /* A MULTI field could be added to but never emptied. Each chosen image renders one `name="…[]"` input, so with no chips left the key vanished from $_POST entirely — and PageContent::save() iterates POSTED values, so a declared-but-absent key was never written and the old images came straight back. This is the same hidden companion the `bool` kind below already uses: the key is always present, so "none" can be said. PHP takes the later `…[]` entries over this scalar when any exist. */ @endphp @endif {!! media_field_html( 'content[' . $f['key'] . ']', !empty($f['opts']['multi']) ? array_filter(array_map('trim', explode(',', $f['value']))) : $f['value'], [ 'accept' => $f['kind'] === 'video' ? 'both' : 'image', 'multi' => !empty($f['opts']['multi']), 'allowClear' => true, 'id' => $fid, ] ) !!} @elseif ($f['kind'] === 'bool') @php /* Stored as "1"/"" so an unchecked box clears it, matching how every other content value round-trips through the same text column. */ @endphp @elseif ($f['kind'] === 'icon') @partial('pc-icon-field', ['name' => 'content[' . $f['key'] . ']', 'value' => $f['value'], 'id' => $fid, 'label' => $f['label']]) @elseif ($f['kind'] === 'link') @partial('pc-link-field', ['name' => 'content[' . $f['key'] . ']', 'value' => $f['value'], 'id' => $fid, 'label' => $f['label']]) @elseif ($f['kind'] === 'paragraph') @else @endif
@endforeach
@endforeach
{!! $accordion ? '
' : '
' !!} @endforeach
@endforeach
@php /* No section list here. Every section is on the page already, folded and named, and each carries its own switch — a second copy of the same list down the right was a third of the screen spent restating it. */ @endphp @php /* How this page describes itself to a search engine — the same four controls a CMS page carries, which these four pages had no way to set at all: they are fixed routes rather than `pages` rows, so nothing keyed by a record id could ever reach them. Stored once, not per language, like the rest of the SEO overrides. Nothing here is required; left empty the page's own heading and the site defaults are used. */ @endphp

{{ t_raw('admin.seo.page_section') }}

{!! media_field_html('seo_image', $seo['image'], ['accept' => 'image', 'allowClear' => true, 'id' => 'pc-seo-image']) !!}

{{ t_raw('admin.seo.hint_noindex') }}

@csrf @php // Says what it does: every language, not just the one on screen. @endphp
@php /* /.qm-d2 */ @endphp @endsection