{{-- Upload New Document — docs/wireframes-v12/add-upload-document.html (the card at ~lines 79-217). ## Shape ONE `.card`: a Reference section, a Document Details section with an "Add More" button and a repeater, a Remark Notes section, and the footer. ## Deliberate differences from the wireframe * **Customer is a fixed display, not a select** (decision D11). The wireframe's free customer dropdown belongs to the standalone Customer Documents module, which uploads against any customer; this screen is reached from ONE booking and uploads against that booking's party, so offering a choice of customer would offer a choice that cannot be honoured. * **The Booking Type chip is always shown**, not revealed by choosing a customer: there is nothing to choose, so there is nothing to wait for. It is the wireframe's own `fa-tag` pill, class for class. * **The Document Type select lists only the PENDING types for the selected person** — the types mapped to this booking's booking type and still active, minus the ones that person already holds, minus the ones the other rows have chosen. See "The pending set" below. * **The wireframe's eleven hard-coded options are mock data.** The real list is the CR-7 pivot, and a booking type that maps none never reaches this screen at all — BookingController sends it to the booking instead. ## The pending set, and where the arithmetic lives (CR-15.1 items E1/E2) The client's instruction is verbatim: *"Bookings -> Customer Document upload time show only pending document types."* That is one rule with two halves, and both halves are already written down once for the standalone module: * the FIRST draw is server-rendered here, out of the cascade payload BookingDocumentController::create() embeds — because no `change` event fires on a freshly drawn page and the client module therefore never runs there (CR-5 §15.4's lesson); * every LATER draw — a co-applicant chosen or cleared, a row added or removed, a sibling row's type picked — is nrems-customer-document-form.js recomputing from the same payload, in the browser, with no request. So this screen hosts the standalone module's own upload half rather than carrying a second copy of its arithmetic: the root below wears the id that module looks for, its `data-role` markers and its `data-strings`. What it does NOT wear is `data-search-url` / `data-options-url` — there is no Customer select to search and no booking to fetch, because this screen is reached from one — so the module's select2 and cascade-fetch halves stand down and its availability, repeater, file-panel and required-field halves are what run. **The script pushed at the foot of this file changed with it.** It was nrems-booking-form.js, whose document half is a strict subset of what the shared module does (add, remove, renumber) — keeping both would have bound Add More twice. ## "All document already uploaded" (item E2) Decision D11's state, verbatim wording and all, on this screen too: a person with nothing left to file gets the message after the Booking Type chip, the Document Details and Remark sections disappear, and the submit is disabled. A booking type that maps no documents at all is a different thing and shows nothing — that is a configuration gap, not a completed set. ## No lang file (CR-8 §0.1) Every string on this screen is written here, on the client's instruction — including the ones the client module puts on screen, which come in through one JSON `data-strings` attribute rather than living in Javascript. ## The file inputs `accept` is a filter and not a control — it decides what the picker offers and nothing else. StoreCustomerDocRequest's `mimetypes:` + `max:` and its extension closure are what actually decide, and both lists are built from the same constants on BookingDocumentService, so the picker and the rules cannot drift. --}} @extends('layouts.app') @section('title', 'Upload New Document') @section('breadcrumbs') @endsection @section('content') @php $main = $booking->mainCustomer; $accept = \App\Services\Bookings\BookingDocumentService::acceptAttribute(); /* * Rows re-render server-side from old input on a rejected submit — a * blade loop, not a JS restore pass, for the reason the booking form * records. The file itself cannot be restored (a browser will not let a * server refill a file input), so only the chosen type comes back; at * least one empty row is always drawn. * * Capped at $maxRows, because old() is whatever was POSTED: a * hand-rolled submit carrying fifty rows is refused by the validator * (`max:10`) and then flashed back, and without the slice the rejection * screen would draw fifty repeater rows to tell the user they may have * ten. */ $rows = is_array(old('documents')) && old('documents') !== [] ? old('documents') : [[]]; $rows = array_slice($rows, 0, $maxRows, true); /* * The person the form is about: the co-applicant chosen last time, or * the main customer. Read strictly, because old() hands back whatever * was posted and that includes an array. */ $oldCustomerRaw = old('customer_id'); $oldCustomer = is_scalar($oldCustomerRaw) ? trim((string) $oldCustomerRaw) : ''; $oldCustomer = $oldCustomer !== '' && ctype_digit($oldCustomer) ? (int) $oldCustomer : null; $persons = is_array($cascade['persons'] ?? null) ? $cascade['persons'] : []; $mainPerson = $persons[0] ?? null; $selectedPersonId = $oldCustomer ?? ($mainPerson['id'] ?? null); /* * The types this person may still be given: mapped-and-active minus the * ones they already hold. Computed here for the FIRST draw only — every * later recomputation is the client module's, from the same two lists in * the embedded payload (decision D12 also subtracts the other rows' * current picks, which only exists client-side). */ $allTypes = is_array($cascade['doc_types'] ?? null) ? $cascade['doc_types'] : []; $uploadedByPerson = is_array($cascade['uploaded'] ?? null) ? $cascade['uploaded'] : []; $uploaded = $selectedPersonId === null ? [] : ($uploadedByPerson[$selectedPersonId] ?? []); $availableTypes = []; foreach ($allTypes as $type) { if (! in_array($type['id'], $uploaded, true)) { $availableTypes[$type['id']] = $type['name']; } } /* Decision D11 / item E2. A booking type that maps nothing is a configuration gap, not a completed set, and shows no message. */ $allUploaded = $allTypes !== [] && $availableTypes === []; $selectedIsCoApplicant = $selectedPersonId !== null && $oldCustomer !== null; /* * Everything the client module can put on screen, in one attribute. The * module holds no wording of its own — the rule nrems-unit-import.js * established and every form module since repeats. The two `*_required` * sentences are item E4's: the client asked for an error naming the * field that was not filled, and this is where that field is named. */ $strings = [ 'co_applicant_placeholder' => 'Select Co-Applicant (optional)', 'type_placeholder' => 'Select Document Type', 'row_title' => 'Document', 'all_uploaded_customer' => 'All document already uploaded for customer', 'all_uploaded_co_applicant' => 'All document already uploaded for Co-Applicant', 'file_uploaded' => 'Uploaded', 'type_required' => 'Document Type is required.', 'file_required' => 'File Upload is required.', ]; @endphp
{{-- ===== Reference ===== --}}
Reference
{{-- A display control, not an input: this screen belongs to one booking and its main customer is not a choice. It posts nothing — the server resolves "whose document" from the Co-Applicant select below, or from the booking. --}}
Customer
{{ $main?->name ?? '—' }}
{{-- The options are server-rendered and stay that way: the module only ever REBUILDS this select on a booking change, and this screen has no booking to change. What its `change` event does here is reset the rows and recompute the pending set for the newly selected person (item E1). --}}
Booking Type
{{ $booking->bookingType?->name ?? '—' }}
{{-- Decision D11, verbatim wording, directly after the chip. --}}
{{-- ===== Document Details ===== --}}