Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions app/controllers/admin/photo_spots_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ def set_photo_spot
end

def photo_spot_params

params.require(:photo_spot).permit(:name, :address, :detail, :parking_flag, :tags, images: [])

params.require(:photo_spot).permit(:name, :address, :detail, :parking_flag, :tags, { images: [] },
:latitude, :longitude, :timestart, :timeend)
end
end
94 changes: 71 additions & 23 deletions app/javascript/controllers/map_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,101 @@ import L from "leaflet"

// Connects to data-controller="map"
export default class extends Controller {
static targets = ["container"]
static values = { onsens: Array }
// ターゲット名を変更しました
static targets = ["container", "latitudeInput", "longitudeInput"]
static values = { photo_spots: Array }

connect() {
this.onsens = this._parseOnsensData();
console.log(this.onsens);
this.currentMarker = null;
this.photo_spots = this._parsePhotoSpotsData();

// 地図の初期化
this.map = L.map(this.containerTarget).setView([35.468, 133.0483], 11.5);

L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(this.map);

const onsenIcon = L.icon({
iconUrl: '/onsen.svg',
// 既存のスポットのマーカーを設置
const photo_spotIcon = L.icon({
iconUrl: '/photo_spot.svg',
iconSize: [32, 32],
iconAnchor: [16, 32],
});

this.onsens.forEach(onsen => {
L.marker([onsen.geo_lat, onsen.geo_lng], { icon: onsenIcon })
this.photo_spots.forEach(photo_spot => {
// プロパティ名を変更しました
L.marker([photo_spot.latitude, photo_spot.longitude], { icon: photo_spotIcon })
.addTo(this.map)
.bindPopup(onsen.name);
.bindPopup(photo_spot.name);
});

// フォームに初期値があればピンを立てる
if (this.latitudeInputTarget.value && this.longitudeInputTarget.value) {
this.updateMarker(this.latitudeInputTarget.value, this.longitudeInputTarget.value);
}

// マップクリック時のイベントリスナー
this.map.on("click", (e) => {
const { lat, lng } = e.latlng;
this.updateMarker(lat, lng);
// テキストボックスの値を更新
this.latitudeInputTarget.value = lat.toFixed(5);
this.longitudeInputTarget.value = lng.toFixed(5);
});
}

// マーカーを更新し、マップの中心を移動する
updateMarker(lat, lng) {
if (this.currentMarker) {
this.map.removeLayer(this.currentMarker);
}
const latlng = [lat, lng];
this.currentMarker = L.marker(latlng).addTo(this.map);
this.map.setView(latlng, this.map.getZoom());
}

// フォームの入力値からマーカーを更新する
updateMarkerFromInput() {
const lat = parseFloat(this.latitudeInputTarget.value);
const lng = parseFloat(this.longitudeInputTarget.value);

if (!isNaN(lat) && !isNaN(lng)) {
this.updateMarker(lat, lng);
}
}

disconnect() {
if (this.map) {
this.map.remove();
this.map = null;
// 現在地を取得するメソッドを追加
locate() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const lat = position.coords.latitude;
const lng = position.coords.longitude;
this.updateMarker(lat, lng);
this.latitudeInputTarget.value = lat.toFixed(5);
this.longitudeInputTarget.value = lng.toFixed(5);
},
(error) => {
console.error("現在地の取得に失敗しました: ", error);
alert("現在地の取得に失敗しました。ブラウザの設定をご確認ください。");
}
);
} else {
alert("お使いのブラウザは現在地の取得に対応していません。");
}
}

// === プライベートメソッド(内部処理用) ===

/**
* HTML要素から温泉データを取得・パース
* @returns {Array} 温泉データの配列
*/
_parseOnsensData() {
_parsePhotoSpotsData() {
try {
const rawData = this.element.dataset.mapOnsens || "[]";
const rawData = this.element.dataset.mapPhotoSpots || "[]";
// パース後のデータが{id:..., name:..., geo_lat:..., geo_lng:...}のような構造であれば
// 必要に応じてキーをリマップする必要があります
return JSON.parse(rawData);
} catch (error) {
console.warn("温泉データのパースに失敗:", error);
console.warn("写真スポットデータのパースに失敗:", error);
return [];
}
}
}
}
28 changes: 26 additions & 2 deletions app/views/admin/photo_spots/_form.html.erb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<%= form_with(model: [:admin, photo_spot || @photo_spot], html: { multipart: true, class: "space-y-6" }) do |form| %>
<%= form_with(model: [:admin, photo_spot || @photo_spot], html: { multipart: true, class: "space-y-6", data: { controller: "map" } }) do |form| %>
<% if photo_spot.errors.any? %>
<div id="error_explanation" class="bg-red-50 text-red-500 px-3 py-2 font-medium rounded-md mt-3">
<h2><%= pluralize(photo_spot.errors.count, "件のエラー") %>があります:</h2>
Expand All @@ -19,6 +19,30 @@
<%= form.text_field :address, class: "block shadow-sm rounded-md border px-3 py-2 w-full border-gray-300 focus:outline-blue-600" %>
</div>
</div>
<%# 緯度・経度の入力フィールド %>
<div class="flex flex-col sm:flex-row gap-4">
<div class="flex-1">
<%= form.label :latitude, '緯度', class: 'block font-semibold mb-1' %>
<%= form.text_field :latitude,
data: { map_target: 'latitudeInput', action: 'input->map#updateMarkerFromInput' },
class: "block shadow-sm rounded-md border px-3 py-2 w-full border-gray-300 focus:outline-blue-600" %>
</div>
<div class="flex-1">
<%= form.label :longitude, '経度', class: 'block font-semibold mb-1' %>
<%= form.text_field :longitude,
data: { map_target: 'longitudeInput', action: 'input->map#updateMarkerFromInput' },
class: "block shadow-sm rounded-md border px-3 py-2 w-full border-gray-300 focus:outline-blue-600" %>
</div>
</div>
<%# 地図エリア(デスクトップ時) %>
<div class="relative h-96 lg:h-[600px] rounded-lg overflow-hidden shadow-lg">
<div data-map-target="container" style="width: 100%; height: 100%;"></div>
<button type="button"
class="absolute top-3 right-3 bg-white border rounded-lg px-4 py-2 text-sm shadow-lg hover:bg-blue-50 z-10 focus:ring-2 focus:ring-blue-500"
data-action="click->map#locate">
📍 <%= t('views.photo_spots.index.current_location') %>
</button>
</div>
<div>
<%= form.label :detail, '説明', class: 'block font-semibold mb-1' %>
<%= form.text_area :detail, rows: 4, class: "block shadow-sm rounded-md border px-3 py-2 w-full border-gray-300 focus:outline-blue-600" %>
Expand Down Expand Up @@ -50,4 +74,4 @@
<div class="flex flex-col sm:flex-row gap-2">
<%= form.submit class: "rounded-md px-4 py-2 bg-blue-600 hover:bg-blue-500 text-white font-semibold w-full sm:w-auto" %>
</div>
<% end %>
<% end %>
9 changes: 9 additions & 0 deletions app/views/photo_spots/_map.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<%# 地図エリア(左側・デスクトップ時) %>
<div class="relative h-96 lg:h-[600px] rounded-lg overflow-hidden shadow-lg">
<div data-controller="map" data-map-target="container" data-map-photo_spots="<%= h @photo_spots.to_json(only: [:id, :name, :geo_lat, :geo_lng]) %>" style="width: 100%; height: 100%;"></div>
<button type="button"
class="absolute top-3 right-3 bg-white border rounded-lg px-4 py-2 text-sm shadow-lg hover:bg-blue-50 z-10 focus:ring-2 focus:ring-blue-500"
data-action="click->map#locate">
📍 <%= t('views.photo_spots.index.current_location') %>
</button>
</div>
1 change: 0 additions & 1 deletion app/views/photo_spots/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
<%= render 'search_form' %>
<%# メインコンテンツ:地図 + フォトスポット一覧 %>
<div class="grid lg:grid-cols-2 gap-6 min-h-[600px]">
<%# 地図エリア(左側・デスクトップ時) %>
<div class="order-2 lg:order-1">
<div class="relative h-96 lg:h-[600px] rounded-lg overflow-hidden shadow-lg">
<div data-controller="map" data-map-target="container" data-map-photo_spots="<%= h @photo_spots.to_json(only: [:id, :name, :geo_lat, :geo_lng]) %>" style="width: 100%; height: 100%;"></div>
Expand Down
5 changes: 5 additions & 0 deletions db/schema.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading