localspot/lib/localspot_web/live/business_live/map.ex
Kevin Sivic 9ed897a60e Add LiveView pages for business directory
- CategoryLive.Index: browse categories with business counts
- CategoryLive.Show: view businesses filtered by category
- BusinessLive.Index: search/filter businesses with geolocation
- BusinessLive.Show: detailed business profile with hours
- BusinessLive.New: submission form for new businesses
- BusinessLive.Map: interactive Leaflet.js map view
- Seed data with sample Columbus, OH businesses

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 00:11:36 -05:00

118 lines
3.6 KiB
Elixir

defmodule LocalspotWeb.BusinessLive.Map do
use LocalspotWeb, :live_view
alias Localspot.Businesses
@impl true
def mount(_params, _session, socket) do
businesses = Businesses.list_businesses()
{:ok,
socket
|> assign(:page_title, "Business Map")
|> assign(:businesses, businesses)
|> assign(:selected_business, nil)}
end
@impl true
def handle_event("select_business", %{"slug" => slug}, socket) do
business = Enum.find(socket.assigns.businesses, &(&1.slug == slug))
{:noreply, assign(socket, :selected_business, business)}
end
@impl true
def handle_event("close_popup", _params, socket) do
{:noreply, assign(socket, :selected_business, nil)}
end
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash}>
<div class="flex items-center justify-between mb-4">
<.header>
Business Map
<:subtitle>View all businesses on an interactive map</:subtitle>
</.header>
<.link navigate={~p"/businesses"} class="btn btn-ghost btn-sm">
<.icon name="hero-list-bullet" class="w-4 h-4" /> List View
</.link>
</div>
<div
id="map-container"
class="w-full h-[600px] rounded-lg overflow-hidden relative"
phx-hook="LeafletMap"
data-businesses={Jason.encode!(map_businesses(@businesses))}
data-center={Jason.encode!(%{lat: 39.9612, lng: -82.9988})}
data-zoom="12"
>
<div class="absolute inset-0 bg-base-300 flex items-center justify-center">
<span class="loading loading-spinner loading-lg"></span>
</div>
</div>
<div :if={@selected_business} class="mt-4">
<div class="card bg-base-200">
<div class="card-body">
<div class="flex items-start justify-between">
<div>
<h2 class="card-title">
{@selected_business.name}
<span :if={@selected_business.locally_owned} class="badge badge-success badge-sm">
Local
</span>
</h2>
<p class="text-base-content/60">{@selected_business.category.name}</p>
</div>
<button class="btn btn-ghost btn-sm" phx-click="close_popup">
<.icon name="hero-x-mark" class="w-5 h-5" />
</button>
</div>
<p :if={@selected_business.description} class="mt-2">
{@selected_business.description}
</p>
<div class="flex items-center gap-2 text-sm text-base-content/60 mt-2">
<.icon name="hero-map-pin" class="w-4 h-4" />
<span>
{@selected_business.street_address}, {@selected_business.city}, {@selected_business.state}
</span>
</div>
<div class="card-actions justify-end mt-4">
<.link
navigate={~p"/businesses/#{@selected_business.slug}"}
class="btn btn-primary btn-sm"
>
View Details
</.link>
</div>
</div>
</div>
</div>
<div class="mt-4 text-sm text-base-content/60">
<p>Showing {length(@businesses)} businesses on the map.</p>
</div>
</Layouts.app>
"""
end
defp map_businesses(businesses) do
businesses
|> Enum.filter(&(&1.latitude && &1.longitude))
|> Enum.map(fn b ->
%{
slug: b.slug,
name: b.name,
lat: Decimal.to_float(b.latitude),
lng: Decimal.to_float(b.longitude),
category: b.category.name,
locally_owned: b.locally_owned
}
end)
end
end