@php // Prefers a category page's own path-fixed purpose (e.g. /flats-for-sale) // over the query string — a category URL never carries ?purpose= at // render time anyway (PropertyCategoryUrlBuilder::resolveRedirectTarget() // always redirects it into the path first), but this stays correct // regardless. $fixedPurpose/$fixedCity are only ever set by // PropertyCategoryController — plain /properties never fixes either. $selectedPurpose = $fixedPurpose ?? request()->query('purpose'); $fixedType = $fixedType ?? null; // A category page's own path-fixed type (e.g. /flats-for-sale) always // shows as selected — the Property Type checkboxes stay visible and // submitted regardless of whether type is fixed by the path or picked // via query, so users can always add another type or change/remove // their selection from here (previously hidden entirely once fixed, // with no way back short of editing the URL by hand). $selectedTypes = $fixedType !== null ? [$fixedType] : (array) request()->query('type', []); $fixedBhk = $fixedBhk ?? null; $selectedBhk = $fixedBhk !== null ? [$fixedBhk] : (array) request()->query('bhk', []); $selectedTags = array_map('strval', (array) request()->query('amenities', [])); // A category page's own path-fixed budget bound(s) (e.g. // /flats-for-sale-under-50-lakh, .../above-1-cr, .../between-...) never // leave a ?min=/?max= behind in the query string once // resolveRedirectTarget() has folded them into the path — mirrors // $selectedPurpose/$selectedTypes above. $fixedMinBudget = $fixedMinBudget ?? null; $fixedMaxBudget = $fixedMaxBudget ?? null; $currentMin = $fixedMinBudget ?? request()->query('min'); $currentMax = $fixedMaxBudget ?? request()->query('max'); // Rent listings live on an entirely different scale than sale listings // (monthly rent vs a sale price), so the Budget slider gets its own // dedicated range for `for_rent` — see PublicPropertySearchService's // RENT_BUDGET_MIN/MAX. "Any"/unset purpose falls back to the sale range, // matching PublicPropertySearchService::search()'s own default. $isRentBudget = $selectedPurpose === 'for_rent'; $budgetMin = $isRentBudget ? \App\Services\PublicPropertySearchService::RENT_BUDGET_MIN : \App\Services\PublicPropertySearchService::BUDGET_MIN; $budgetMax = $isRentBudget ? \App\Services\PublicPropertySearchService::RENT_BUDGET_MAX : \App\Services\PublicPropertySearchService::BUDGET_MAX; $budgetStep = $isRentBudget ? 500 : 500000; // The Budget slider is a genuine dual-handle range — either handle only // "counts" once genuinely touched (see the matching data-touched // attributes below and PublicPropertySearchService::search()'s own // min/max handling, which this label mirrors exactly): // neither touched -> "Any budget" // only max touched, at ceiling -> "Any budget" (a no-op filter — the // ceiling drops the upper bound and // there's no minimum either) // only max touched, below it -> "Up to {max}" // min touched, max untouched // or max touched at ceiling -> "{min} & above" // both touched, max below it -> "{min} - {max}" $minTouched = $currentMin !== null; $maxTouched = $currentMax !== null; $maxAtCeiling = $maxTouched && (float) $currentMax >= $budgetMax; $formatBudget = fn ($n) => \App\Models\Property::formatIndianCurrency($n); if (! $minTouched && (! $maxTouched || $maxAtCeiling)) { $budgetLabel = 'Any budget'; } elseif (! $minTouched) { $budgetLabel = 'Up to ' . $formatBudget($currentMax); } elseif (! $maxTouched || $maxAtCeiling) { $budgetLabel = $formatBudget($currentMin) . ' & above'; } else { $budgetLabel = $formatBudget($currentMin) . ' - ' . $formatBudget($currentMax); } $bhkOptions = ['1', '2', '3', '4', '4+']; $bhkEnabledTypes = $bhkEnabledTypes ?? []; // Bedrooms only makes sense for types that actually have a bhk field // (flat/bungalow, not plot/land/commercial — driven by the real, admin- // configurable field templates, not a hardcoded type list). With no // property type selected yet there's no single template to check // against, so it stays hidden rather than defaulting to shown. $showBedrooms = count(array_intersect($selectedTypes, $bhkEnabledTypes)) > 0; // Which property type(s) the dynamic, admin-configured filters below are // drawn from — whatever's currently selected, whether fixed by the URL // path or picked via the checkboxes. With nothing selected there's no // single template to draw from, so no dynamic filters show (deliberately, // to avoid dumping every type's fields into the panel at once — see // PropertyFieldTemplate::filterableGroupsForTypes()). $dynamicFieldGroups = \App\Models\PropertyFieldTemplate::filterableGroupsForTypes($selectedTypes); // fields[select][][], fields[boolean][], fields[number][] // (slider fields) or fields[number][][min|max] (year range / generic) $dynamicFieldValues = (array) request()->query('fields', []); $selectedDynamicSelect = (array) ($dynamicFieldValues['select'] ?? []); $selectedDynamicBoolean = (array) ($dynamicFieldValues['boolean'] ?? []); $selectedDynamicNumber = (array) ($dynamicFieldValues['number'] ?? []); $currentYear = (int) date('Y'); $yearRangeMin = \App\Models\PropertyFieldTemplate::yearRangeMinYear(); $yearRangeStart = \App\Models\PropertyFieldTemplate::yearRangeDefaultMinYear(); // Everything this form does NOT own gets replayed as hidden inputs, so // applying a filter never drops purpose/city/locality/sort/min/page — // same "replay whatever's already active" approach as the sort form in // _search-results-panel. $passthroughKeys = ['purpose', 'type', 'bhk', 'amenities', 'max', 'page', 'fields']; // Reset clears every filter, INCLUDING Property Type/Purpose/Bedrooms // even when they're baked into the URL PATH (a category page), but // keeps the active Location/City exactly as-is — it's the primary // search context, not a filter (see the "Reset Filters" spec: // "Properties in Indore" stays). Computed once by the controller // (PropertyCategoryController/PropertyListingController), since a // path-fixed type/city can't be reset by simply dropping the query // string the way this used to work — see $resetUrl's own doc comment // there for why. Falls back to the plain listing page if a caller // somehow doesn't pass one. $resetUrl = $resetUrl ?? route('public.properties.index'); @endphp @push('scripts') @endpush