Step 2.5: Build Property archive template with filters and results

This commit is contained in:
Hanson.xyz Dev
2025-11-28 16:36:00 -06:00
parent 7dd4e23ec4
commit 11886aa53f
7 changed files with 529 additions and 2 deletions
@@ -0,0 +1,39 @@
<?php
/**
* Property Archive Template
*
* Displays the properties listing page
*
* @package HomeProz
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
get_header();
?>
<main id="primary" class="site-main">
<!-- Archive Hero -->
<section class="archive-hero">
<div class="container">
<h1 class="archive-hero-title">Find Your Perfect Property</h1>
<p class="archive-hero-subtitle">Browse our selection of homes, land, and commercial properties in southern Minnesota.</p>
</div>
</section>
<div class="container">
<!-- Filters -->
<?php get_template_part('template-parts/property/property-filters'); ?>
<!-- Results -->
<div id="property-results">
<?php get_template_part('template-parts/property/property-results'); ?>
</div>
</div>
</main>
<?php
get_footer();
File diff suppressed because one or more lines are too long
+1
View File
@@ -15,6 +15,7 @@
@import '../template-parts/content/content-single.scss';
@import '../template-parts/content/content-404.scss';
@import '../template-parts/property/property-card.scss';
@import '../template-parts/property/property-filters.scss';
// ============================================
// CSS Custom Properties (Design Tokens)
@@ -0,0 +1,131 @@
<?php
/**
* Property Filters Template Part
*
* @package HomeProz
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Get current filter values from URL
$current_type = isset($_GET['property_type']) ? sanitize_text_field($_GET['property_type']) : '';
$current_status = isset($_GET['property_status']) ? sanitize_text_field($_GET['property_status']) : '';
$current_location = isset($_GET['property_location']) ? sanitize_text_field($_GET['property_location']) : '';
$current_min_price = isset($_GET['min_price']) ? intval($_GET['min_price']) : '';
$current_max_price = isset($_GET['max_price']) ? intval($_GET['max_price']) : '';
$current_beds = isset($_GET['beds']) ? intval($_GET['beds']) : '';
$current_sort = isset($_GET['sort']) ? sanitize_text_field($_GET['sort']) : 'newest';
// Get taxonomy terms
$property_types = get_terms(array('taxonomy' => 'property_type', 'hide_empty' => false));
$property_statuses = get_terms(array('taxonomy' => 'property_status', 'hide_empty' => false));
$property_locations = get_terms(array('taxonomy' => 'property_location', 'hide_empty' => false));
?>
<div class="property-filters" id="property-filters" data-ajax-url="<?php echo esc_url(admin_url('admin-ajax.php')); ?>">
<form class="filters-form" method="get" action="<?php echo esc_url(get_post_type_archive_link('property')); ?>">
<div class="filters-row">
<!-- Property Type -->
<div class="filter-group">
<label for="filter-type" class="filter-label">Property Type</label>
<select name="property_type" id="filter-type" class="filter-select">
<option value="">All Types</option>
<?php foreach ($property_types as $type) : ?>
<option value="<?php echo esc_attr($type->slug); ?>" <?php selected($current_type, $type->slug); ?>>
<?php echo esc_html($type->name); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<!-- Property Status -->
<div class="filter-group">
<label for="filter-status" class="filter-label">Status</label>
<select name="property_status" id="filter-status" class="filter-select">
<option value="">All Statuses</option>
<?php foreach ($property_statuses as $status) : ?>
<option value="<?php echo esc_attr($status->slug); ?>" <?php selected($current_status, $status->slug); ?>>
<?php echo esc_html($status->name); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<!-- Location -->
<div class="filter-group">
<label for="filter-location" class="filter-label">Location</label>
<select name="property_location" id="filter-location" class="filter-select">
<option value="">All Locations</option>
<?php foreach ($property_locations as $location) : ?>
<option value="<?php echo esc_attr($location->slug); ?>" <?php selected($current_location, $location->slug); ?>>
<?php echo esc_html($location->name); ?>
</option>
<?php endforeach; ?>
</select>
</div>
<!-- Bedrooms -->
<div class="filter-group">
<label for="filter-beds" class="filter-label">Bedrooms</label>
<select name="beds" id="filter-beds" class="filter-select">
<option value="">Any</option>
<option value="1" <?php selected($current_beds, 1); ?>>1+</option>
<option value="2" <?php selected($current_beds, 2); ?>>2+</option>
<option value="3" <?php selected($current_beds, 3); ?>>3+</option>
<option value="4" <?php selected($current_beds, 4); ?>>4+</option>
<option value="5" <?php selected($current_beds, 5); ?>>5+</option>
</select>
</div>
<!-- Price Range -->
<div class="filter-group filter-group-price">
<label class="filter-label">Price Range</label>
<div class="price-inputs">
<select name="min_price" id="filter-min-price" class="filter-select">
<option value="">Min Price</option>
<option value="50000" <?php selected($current_min_price, 50000); ?>>$50,000</option>
<option value="100000" <?php selected($current_min_price, 100000); ?>>$100,000</option>
<option value="150000" <?php selected($current_min_price, 150000); ?>>$150,000</option>
<option value="200000" <?php selected($current_min_price, 200000); ?>>$200,000</option>
<option value="250000" <?php selected($current_min_price, 250000); ?>>$250,000</option>
<option value="300000" <?php selected($current_min_price, 300000); ?>>$300,000</option>
<option value="400000" <?php selected($current_min_price, 400000); ?>>$400,000</option>
<option value="500000" <?php selected($current_min_price, 500000); ?>>$500,000</option>
</select>
<span class="price-separator">-</span>
<select name="max_price" id="filter-max-price" class="filter-select">
<option value="">Max Price</option>
<option value="100000" <?php selected($current_max_price, 100000); ?>>$100,000</option>
<option value="150000" <?php selected($current_max_price, 150000); ?>>$150,000</option>
<option value="200000" <?php selected($current_max_price, 200000); ?>>$200,000</option>
<option value="250000" <?php selected($current_max_price, 250000); ?>>$250,000</option>
<option value="300000" <?php selected($current_max_price, 300000); ?>>$300,000</option>
<option value="400000" <?php selected($current_max_price, 400000); ?>>$400,000</option>
<option value="500000" <?php selected($current_max_price, 500000); ?>>$500,000</option>
<option value="750000" <?php selected($current_max_price, 750000); ?>>$750,000</option>
<option value="1000000" <?php selected($current_max_price, 1000000); ?>>$1,000,000+</option>
</select>
</div>
</div>
</div>
<div class="filters-actions">
<!-- Sort -->
<div class="filter-group filter-group-sort">
<label for="filter-sort" class="filter-label">Sort By</label>
<select name="sort" id="filter-sort" class="filter-select">
<option value="newest" <?php selected($current_sort, 'newest'); ?>>Newest First</option>
<option value="oldest" <?php selected($current_sort, 'oldest'); ?>>Oldest First</option>
<option value="price_low" <?php selected($current_sort, 'price_low'); ?>>Price: Low to High</option>
<option value="price_high" <?php selected($current_sort, 'price_high'); ?>>Price: High to Low</option>
</select>
</div>
<button type="submit" class="btn btn-primary filters-submit">Search</button>
<a href="<?php echo esc_url(get_post_type_archive_link('property')); ?>" class="btn btn-secondary filters-reset">Reset</a>
</div>
</form>
</div>
@@ -0,0 +1,173 @@
/**
* Property Filters Styles
*
* @package HomeProz
*/
// Archive Hero
.archive-hero {
background-color: var(--color-bg-card);
padding: 3rem 0;
border-bottom: 1px solid var(--color-border);
margin-bottom: 2rem;
}
.archive-hero-title {
margin-bottom: 0.5rem;
}
.archive-hero-subtitle {
font-size: 1.125rem;
color: var(--color-text-muted);
margin-bottom: 0;
max-width: 600px;
}
// Filters Container
.property-filters {
background-color: var(--color-bg-card);
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
}
.filters-form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.filters-row {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
@media (min-width: 640px) {
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 1024px) {
grid-template-columns: repeat(3, 1fr) 2fr;
gap: 1.25rem;
}
}
// Filter Groups
.filter-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.filter-label {
font-size: 0.8125rem;
font-weight: 500;
color: var(--color-text);
text-transform: uppercase;
letter-spacing: 0.03em;
}
.filter-select {
width: 100%;
padding: 0.625rem 2rem 0.625rem 0.75rem;
background-color: var(--color-bg-dark);
border: 1px solid var(--color-border);
border-radius: 0.25rem;
color: var(--color-text);
font-size: 0.9375rem;
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23B0B0B0' stroke-width='2'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.75rem center;
&:focus {
outline: none;
border-color: var(--color-accent);
}
}
// Price Input Group
.filter-group-price {
@media (min-width: 1024px) {
grid-column: span 1;
}
}
.price-inputs {
display: flex;
align-items: center;
gap: 0.5rem;
.filter-select {
flex: 1;
min-width: 0;
}
}
.price-separator {
color: var(--color-text-muted);
flex-shrink: 0;
}
// Filters Actions
.filters-actions {
display: flex;
flex-wrap: wrap;
align-items: flex-end;
gap: 1rem;
padding-top: 0.5rem;
border-top: 1px solid var(--color-border);
@media (min-width: 768px) {
flex-wrap: nowrap;
}
}
.filter-group-sort {
flex: 1;
min-width: 150px;
@media (min-width: 768px) {
flex: 0 0 auto;
width: 180px;
}
}
.filters-submit,
.filters-reset {
flex-shrink: 0;
@media (max-width: 639px) {
flex: 1;
}
}
// Loading State
.property-filters.is-loading {
pointer-events: none;
opacity: 0.7;
}
// Results loading spinner (only for first load)
.property-results-loading {
display: flex;
justify-content: center;
align-items: center;
padding: 4rem 0;
.spinner {
width: 40px;
height: 40px;
border: 3px solid var(--color-border);
border-top-color: var(--color-accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@@ -0,0 +1,183 @@
<?php
/**
* Property Results Template Part
*
* Displays property results for archive/search
*
* @package HomeProz
*/
// Prevent direct access
if (!defined('ABSPATH')) {
exit;
}
// Get filter values
$current_type = isset($_GET['property_type']) ? sanitize_text_field($_GET['property_type']) : '';
$current_status = isset($_GET['property_status']) ? sanitize_text_field($_GET['property_status']) : '';
$current_location = isset($_GET['property_location']) ? sanitize_text_field($_GET['property_location']) : '';
$current_min_price = isset($_GET['min_price']) ? intval($_GET['min_price']) : '';
$current_max_price = isset($_GET['max_price']) ? intval($_GET['max_price']) : '';
$current_beds = isset($_GET['beds']) ? intval($_GET['beds']) : '';
$current_sort = isset($_GET['sort']) ? sanitize_text_field($_GET['sort']) : 'newest';
// Build query args
$paged = get_query_var('paged') ? get_query_var('paged') : 1;
$args = array(
'post_type' => 'property',
'posts_per_page' => 12,
'paged' => $paged,
);
// Taxonomy filters
$tax_query = array();
if ($current_type) {
$tax_query[] = array(
'taxonomy' => 'property_type',
'field' => 'slug',
'terms' => $current_type,
);
}
if ($current_status) {
$tax_query[] = array(
'taxonomy' => 'property_status',
'field' => 'slug',
'terms' => $current_status,
);
}
if ($current_location) {
$tax_query[] = array(
'taxonomy' => 'property_location',
'field' => 'slug',
'terms' => $current_location,
);
}
if (!empty($tax_query)) {
$args['tax_query'] = $tax_query;
if (count($tax_query) > 1) {
$args['tax_query']['relation'] = 'AND';
}
}
// Meta query for price and bedrooms
$meta_query = array();
if ($current_min_price) {
$meta_query[] = array(
'key' => 'property_price',
'value' => $current_min_price,
'type' => 'NUMERIC',
'compare' => '>=',
);
}
if ($current_max_price) {
$meta_query[] = array(
'key' => 'property_price',
'value' => $current_max_price,
'type' => 'NUMERIC',
'compare' => '<=',
);
}
if ($current_beds) {
$meta_query[] = array(
'key' => 'bedrooms',
'value' => $current_beds,
'type' => 'NUMERIC',
'compare' => '>=',
);
}
if (!empty($meta_query)) {
$args['meta_query'] = $meta_query;
if (count($meta_query) > 1) {
$args['meta_query']['relation'] = 'AND';
}
}
// Sorting
switch ($current_sort) {
case 'oldest':
$args['orderby'] = 'date';
$args['order'] = 'ASC';
break;
case 'price_low':
$args['meta_key'] = 'property_price';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'ASC';
break;
case 'price_high':
$args['meta_key'] = 'property_price';
$args['orderby'] = 'meta_value_num';
$args['order'] = 'DESC';
break;
case 'newest':
default:
$args['orderby'] = 'date';
$args['order'] = 'DESC';
break;
}
$properties = new WP_Query($args);
?>
<!-- Results Meta -->
<div class="properties-meta">
<p class="properties-count">
<?php if ($properties->found_posts > 0) : ?>
Showing <strong><?php echo esc_html($properties->found_posts); ?></strong>
<?php echo $properties->found_posts === 1 ? 'property' : 'properties'; ?>
<?php else : ?>
No properties found
<?php endif; ?>
</p>
</div>
<?php if ($properties->have_posts()) : ?>
<div class="properties-grid">
<?php
while ($properties->have_posts()) :
$properties->the_post();
get_template_part('template-parts/property/property-card');
endwhile;
?>
</div>
<?php
// Pagination
$big = 999999999;
$pagination = paginate_links(array(
'base' => str_replace($big, '%#%', esc_url(get_pagenum_link($big))),
'format' => '?paged=%#%',
'current' => max(1, $paged),
'total' => $properties->max_num_pages,
'prev_text' => '&larr; Previous',
'next_text' => 'Next &rarr;',
'type' => 'array',
));
if ($pagination) :
?>
<nav class="pagination" aria-label="Property pagination">
<div class="nav-links">
<?php foreach ($pagination as $link) : ?>
<?php echo $link; ?>
<?php endforeach; ?>
</div>
</nav>
<?php endif; ?>
<?php else : ?>
<div class="no-properties">
<h3>No Properties Found</h3>
<p>We couldn't find any properties matching your criteria. Try adjusting your filters or browse all properties.</p>
<a href="<?php echo esc_url(get_post_type_archive_link('property')); ?>" class="btn btn-primary">View All Properties</a>
</div>
<?php endif; ?>
<?php wp_reset_postdata(); ?>