diff --git a/app/Livewire/FossilMap.php b/app/Livewire/FossilMap.php
index 91fb460..48594f0 100644
--- a/app/Livewire/FossilMap.php
+++ b/app/Livewire/FossilMap.php
@@ -95,11 +95,25 @@ public function onFiltersApplied(
$this->dispatch('occurrences-loaded', occurrences: $occurrences);
} catch (ApiException $e) {
- $this->loadError = $e->getMessage();
- $this->dispatch('occurrences-error', message: $e->getMessage());
+ $this->loadError = 'The search could not be completed. Please try again or adjust your filters.';
+ $this->dispatch('occurrences-error', message: $this->loadError);
}
}
+ /**
+ * Triggered when OccurrenceFilters resets all filter state.
+ * Clears map markers and restores the initial empty-state overlay.
+ */
+ #[On('filters-reset')]
+ public function onFiltersReset(): void
+ {
+ $this->filtersApplied = false;
+ $this->resultCount = 0;
+ $this->resultTotal = 0;
+ $this->loadError = null;
+ $this->dispatch('occurrences-loaded', occurrences: []);
+ }
+
public function render(): View
{
return view('livewire.fossil-map', [
diff --git a/app/Livewire/OccurrenceFilters.php b/app/Livewire/OccurrenceFilters.php
index 0a75f9a..f77725e 100644
--- a/app/Livewire/OccurrenceFilters.php
+++ b/app/Livewire/OccurrenceFilters.php
@@ -40,6 +40,14 @@ class OccurrenceFilters extends Component
*/
public bool $customTaxon = false;
+ /**
+ * When true, shows min/max Ma inputs instead of the named interval select.
+ */
+ public bool $customInterval = false;
+
+ /** Validation message shown near the Apply button when filters are incomplete. */
+ public ?string $validationError = null;
+
/**
* All PBDB envtype values available for filtering.
*
@@ -152,6 +160,7 @@ public static function countryOptions(): array
*/
public function updated(string $name): void
{
+ $this->validationError = null;
$this->dispatch('filtersChanged');
}
@@ -176,6 +185,7 @@ public function loadPreset(string $id): void
$this->envTypes = $preset->envTypes;
$this->countryCodes = $preset->countryCodes ?? '';
$this->customTaxon = false;
+ $this->customInterval = $preset->minMa !== null || $preset->maxMa !== null;
$this->applyFilters();
}
@@ -198,6 +208,25 @@ public function disableCustomTaxon(): void
$this->baseName = '';
}
+ /**
+ * Switch the time period field to custom age range inputs and clear the interval.
+ */
+ public function enableCustomInterval(): void
+ {
+ $this->customInterval = true;
+ $this->interval = '';
+ }
+
+ /**
+ * Switch the time period field back to the named interval select and reset age range.
+ */
+ public function disableCustomInterval(): void
+ {
+ $this->customInterval = false;
+ $this->minMa = 0;
+ $this->maxMa = 540;
+ }
+
/**
* Construct an OccurrenceQuery from the current filter state.
*/
@@ -224,6 +253,14 @@ public function buildQuery(): OccurrenceQuery
*/
public function applyFilters(): void
{
+ if (! $this->hasFilters()) {
+ $this->validationError = 'Please select an organism or time period to search.';
+
+ return;
+ }
+
+ $this->validationError = null;
+
$this->dispatch(
'apply-filters',
baseName: $this->baseName !== '' ? $this->baseName : null,
@@ -257,6 +294,8 @@ public function resetFilters(): void
$this->latMin = null;
$this->latMax = null;
$this->customTaxon = false;
+ $this->customInterval = false;
+ $this->validationError = null;
$this->dispatch('filters-reset');
}
@@ -284,21 +323,16 @@ public function setBoundingBox(float $lngMin, float $lngMax, float $latMin, floa
}
/**
- * Returns true when at least one filter differs from its default.
+ * Returns true when a primary filter (organism or time period) is set.
+ * Environment, country, and ID quality are refiners and cannot stand alone —
+ * PBDB requires at least one taxon or temporal parameter.
*/
public function hasFilters(): bool
{
return $this->baseName !== ''
|| $this->interval !== ''
|| $this->minMa > 0
- || $this->maxMa < 540
- || $this->envTypes !== []
- || $this->countryCodes !== ''
- || $this->idQual !== 'any'
- || $this->lngMin !== null
- || $this->lngMax !== null
- || $this->latMin !== null
- || $this->latMax !== null;
+ || $this->maxMa < 540;
}
public function render(): View
diff --git a/resources/js/map.js b/resources/js/map.js
index 67bc954..1b3ddcf 100644
--- a/resources/js/map.js
+++ b/resources/js/map.js
@@ -3,8 +3,6 @@ import 'leaflet/dist/leaflet.css';
import 'leaflet.markercluster/dist/MarkerCluster.css';
import 'leaflet.markercluster/dist/MarkerCluster.Default.css';
import 'leaflet.markercluster';
-import 'leaflet-draw/dist/leaflet.draw.css';
-import 'leaflet-draw';
import 'leaflet.heat';
// Vite bundles assets differently to webpack — provide explicit URLs for
// Leaflet's default marker icons so they resolve correctly at runtime.
@@ -50,7 +48,6 @@ function fossilMap() {
map: null,
markerClusterGroup: null,
heatLayer: null,
- drawControl: null,
tileLayers: {},
currentOccurrences: [],
heatmapMode: false,
@@ -72,41 +69,6 @@ function fossilMap() {
// Marker cluster group
this.markerClusterGroup = L.markerClusterGroup();
this.map.addLayer(this.markerClusterGroup);
-
- // Feature group to hold drawn shapes
- const drawnItems = new L.FeatureGroup();
- this.map.addLayer(drawnItems);
-
- // Draw control — rectangle only
- this.drawControl = new L.Control.Draw({
- draw: {
- rectangle: true,
- polyline: false,
- polygon: false,
- circle: false,
- marker: false,
- circlemarker: false,
- },
- edit: { featureGroup: drawnItems },
- });
- this.map.addControl(this.drawControl);
-
- // Push drawn rectangle bounds into the OccurrenceFilters Livewire
- // component via a global Livewire event — OccurrenceFilters listens
- // for 'bbox-set' with #[On('bbox-set')].
- this.map.on(L.Draw.Event.CREATED, (event) => {
- drawnItems.clearLayers();
- drawnItems.addLayer(event.layer);
-
- const bounds = event.layer.getBounds();
- window.Livewire.dispatch('bbox-set', {
- lngMin: bounds.getWest(),
- lngMax: bounds.getEast(),
- latMin: bounds.getSouth(),
- latMax: bounds.getNorth(),
- });
- });
-
},
/**
diff --git a/resources/views/components/form/mode-toggle.blade.php b/resources/views/components/form/mode-toggle.blade.php
new file mode 100644
index 0000000..367d410
--- /dev/null
+++ b/resources/views/components/form/mode-toggle.blade.php
@@ -0,0 +1,28 @@
+@props([
+ 'isCustom' => false,
+ 'defaultLabel' => 'Select',
+ 'customLabel' => 'Custom',
+ 'onDefault' => '',
+ 'onCustom' => '',
+])
+
+
+
+
+
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php
index c3e7dca..8112974 100644
--- a/resources/views/layouts/app.blade.php
+++ b/resources/views/layouts/app.blade.php
@@ -25,7 +25,7 @@
@vite(['resources/css/app.css', 'resources/js/app.js'])
-