{{-- The two Booking Type dialogs of docs/wireframes-v12/booking-type.html — #addModal (~line 214) and #editModal (~237). ## One instance of each, re-targeted per row The wireframe draws them once at the foot of the page and points the Edit one at whichever row's pencil was used, and so does this: the Edit form's `action` is rewritten by nrems-master-modals.js from the row's own `data-row` payload before the dialog opens. One dialog per row would have been a hidden form and a CSRF token per row. The wireframe's own dialogs are hand-rolled fixed-position divs; these are , which is Bootstrap 5's markup with the wireframe's look applied to `.modal-content` from the app-additions tail of nrems-wireframe.css (the component's documented reason for existing — v12 draws no modal its own stylesheet renders). That buys the focus trap, the Escape key and the backdrop click the wireframe's inline onclick handlers were approximating. The wireframe's dialog is 440px wide, which is neither Bootstrap's default (500) nor any of its three size modifiers, so `.modal-w-440` carries it — declared in block 21 of the app-additions tail. The doc-types twin uses `.modal-w-520` for the same reason. ## Per-form error bags (recipe §9) StoreBookingTypeRequest declares `$errorBag = 'store'` and the update twin declares `'update'`, so a rejected Add puts its messages in a bag of its own and cannot light up the Edit dialog's Type field. Two things read those bags: the controls below, through `$errors->getBag(...)`, and the reopen decision. The controls are written out inside rather than as , because the field components resolve their own error from the *default* bag and there is no prop to point them at another one — 's docblock invites exactly this use. The markup they emit is the components' own, class for class. ## Old input, and the trap that is new here Flashed old input is NOT scoped to an error bag. On the inquiries listing that was harmless — its three dialogs have three disjoint field sets — but **both dialogs here have a field called `name`**, so a rejected edit would otherwise repopulate the Add dialog's Type box with the name the user was editing. It is invisible until they open Add, and then it is a prefilled form they did not fill in. The fix is below and it is one line per field: old() is read only for the dialog whose bag actually failed. Everything else renders empty, which is what a fresh dialog is. ## Reopen after redirect A rejected submit comes back as a redirect, so the dialog has to be put back on screen with the user's input in it. `data-reopen` says which one, and it is computed here from the error bags — the client is told, it does not guess. For the Edit dialog the form's `action` has to point at the right row again, and this partial renders it server-side from the hidden `reopen_id` that came back in old input, rather than making the script find the row. That is a deliberate departure from the inquiries module, which reads the row's payload out of the drawn table and therefore had to wait for yajra to register the table and draw it (nrems-inquiry-actions.js records that bug at length). Two reasons: 1. it is fewer moving parts — no registry wait, no draw hook, no race; 2. it works when the row is NOT on the current page. The inquiries version silently opens nothing in that case; here the dialog comes back with the user's text in it wherever the listing happens to be. The security property is unchanged and is the one that matters: the url is still generated by route() on the server, and the script still never builds a path out of an id. `reopen_id` is user input, so it is cast to int before it reaches route(); a stale or invented id produces a URL whose PUT the policy and the model binding answer, exactly as they would for any other. --}} @php $storeErrors = $errors->getBag('store'); $updateErrors = $errors->getBag('update'); // Which dialog a failed submit should put back on screen. `store` and // `update` are the FormRequests' own bag names; `add` / `edit` are what the // script calls the two dialogs. $reopen = match (true) { $storeErrors->isNotEmpty() => 'add', $updateErrors->isNotEmpty() => 'edit', default => '', }; // `reopen_id` is a hidden field, and a hidden field is client input: a post // carrying `reopen_id[]=9` hands old() an ARRAY, and `(int) ['9']` is 1 — // which would silently re-point the reopened Edit form at whatever row 1 // happens to be. Anything that is not a scalar is not an id. $rawReopenId = old('reopen_id', 0); $reopenId = is_scalar($rawReopenId) ? (int) $rawReopenId : 0; // See "Old input" above: read only for the dialog that actually failed. $addName = $reopen === 'add' ? old('name', '') : ''; $editName = $reopen === 'edit' ? old('name', '') : ''; // 0 is the placeholder the script overwrites from the row's payload. On a // reopen the real row is known here, so it is rendered here. $editAction = route('booking_types.update', [ 'booking_type' => $reopen === 'edit' && $reopenId > 0 ? $reopenId : 0, ]); @endphp {{-- The whole block is a write surface, so it renders only for a user who may write, and each dialog is gated on its own half of that. The gates are the bare permissions rather than policy checks, for the reason inquiries/partials/modals.blade.php records: there is no one booking type the Edit dialog belongs to — it is re-targeted per row — so there is no model to check `update` against here, and the per-row gate is the one in partials/actions.blade.php. BookingTypePolicy@create is `$user->can('booking_types.create')` and @update is `$user->can('booking_types.edit')` and nothing else, so the two spellings agree by construction; a Super Admin passes here through the same Gate::before hook that carries them through the policy. --}} @canany(['booking_types.create', 'booking_types.edit'])
{{-- ===== ADD BOOKING TYPE ===== --}} @can('booking_types.create') {{-- :novalidate="false" — jsvalidation is not bound to these modals (CR-7 §5.1, matching CR-6: proengsoft binds one form per selector and a two-form page is a poor fit), so the browser's own `required` is the first line of defence and must not be switched off. The server is still the authority. --}} {{ __('booking_types.hints.add_name') }} @if ($storeErrors->has('name'))
{{ $storeErrors->first('name') }}
@endif
@endcan {{-- ===== EDIT BOOKING TYPE ===== --}} @can('booking_types.edit') {{-- Which row this dialog is pointed at. It survives the redirect in old input, which is what lets the block above render the form's action back at the same row. --}} {{-- The wireframe's Edit field carries no placeholder — it is always prefilled — and a different helper line. Both kept. --}} {{ __('booking_types.hints.edit_name') }} @if ($updateErrors->has('name'))
{{ $updateErrors->first('name') }}
@endif
@endcan
@endcanany