// ===============================
// Supabase 設定
// ===============================
const SUPABASE_URL = "https://ogtlmtnjkpsxsqzqlacj.supabase.co";
const SUPABASE_KEY =
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im9ndGxtdG5qa3BzeHNxenFsYWNqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjMyOTU3NjUsImV4cCI6MjA3ODg3MTc2NX0.JnCE7oUQwrSgGqiu-QRbwnaLBZrO8JX1_RUb37VIMFI";
const supa = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY);
// ===============================
// URL パラメータ
// ===============================
const p = new URLSearchParams(location.search);
const currentGroup = p.get("group") || "";
document.getElementById("groupName").textContent = currentGroup;
// ===============================
// グローバル
// ===============================
let map;
let currentHostName = "";
let targetMarker = null;
let meetIcon;
// ===============================
// ユーティリティ:日付表示
// ===============================
function formatDateTime(isoString) {
if (!isoString) return "";
const d = new Date(isoString);
return d.toLocaleString("ja-JP", {
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
});
}
// ===============================
// ピン生成(ホスト:緑 / その他:金)
// ===============================
function createLabeledMarker(lat, lng, name, isHostUser) {
const pinUrl = isHostUser
? "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-green.png"
: "https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-gold.png";
const html = `
<div class="pin-box">
<img src="${pinUrl}" class="pin-img">
<div class="pin-label">${name}</div>
</div>
`;
return L.marker([lat, lng], {
icon: L.divIcon({
className: "custom-pin",
html: html,
iconSize: [40, 50],
iconAnchor: [20, 50],
}),
});
}
// ===============================
// グループ情報読み込み(ホスト名表示)
// ===============================
async function loadGroup() {
const { data, error } = await supa
.from("groups")
.select("*")
.eq("group_name", currentGroup)
.maybeSingle();
if (error) {
console.error("groups 読み込みエラー", error);
}
if (!data) {
alert("このグループは存在しません。");
location.href = "index.html";
return false;
}
// active なら「まだ解散していない」ので index に戻す
if (data.is_active === true) {
alert("このグループはまだ解散されていません。");
location.href = "index.html";
return false;
}
currentHostName = data.host_name || "";
document.getElementById("hostName").textContent = currentHostName || "(不明)";
return true;
}
// ===============================
// 地図初期化
// ===============================
function initMap() {
map = L.map("map").setView([35.681236, 139.767125], 13);
L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", {
maxZoom: 19,
}).addTo(map);
// 待ち合わせ用アイコン
meetIcon = L.icon({
iconUrl:
"https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-violet.png",
shadowUrl:
"https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/marker-shadow.png",
iconSize: [25, 41],
iconAnchor: [12, 41],
});
}
// ===============================
// メンバー最後の位置取得(map4 風)
// ===============================
async function loadLastLocations() {
const list = document.getElementById("memberList");
list.innerHTML = "";
const { data, error } = await supa
.from("locations")
.select("*")
.eq("group_name", currentGroup)
.order("updated_at", { ascending: false });
if (error) {
console.error("locations 読み込みエラー", error);
return;
}
const latestByDevice = {};
(data || []).forEach((r) => {
if (!latestByDevice[r.device_id]) latestByDevice[r.device_id] = r;
});
const markers = [];
Object.values(latestByDevice).forEach((row) => {
const isHostUser = row.user_name === currentHostName;
// メンバー一覧の1行
const li = document.createElement("li");
const crown = isHostUser ? "👑 " : "";
const initial = row.user_name.charAt(0);
const bgColor = isHostUser ? "#4CAF50" : "#FFD700";
const timeStr = formatDateTime(row.updated_at);
li.innerHTML = `
<div class="member-icon"
style="background:${bgColor};
display:flex;
align-items:center;
justify-content:center;
color:white;
font-weight:bold;
font-size:16px;">
${initial}
</div>
<div>
<div class="member-name">${crown}${row.user_name}</div>
<div style="font-size:12px; opacity:0.7;">最終更新:${timeStr}</div>
</div>
`;
list.appendChild(li);
// 地図ピン
if (typeof row.lat === "number" && typeof row.lng === "number") {
const mk = createLabeledMarker(row.lat, row.lng, row.user_name, isHostUser)
.addTo(map);
markers.push(mk);
}
});
// マーカーがあれば全体が入るように調整
if (markers.length > 0) {
const group = new L.featureGroup(markers);
map.fitBounds(group.getBounds().pad(0.2));
}
}
// ===============================
// 最後の待ち合わせ場所
// ===============================
async function loadLastTarget() {
const box = document.getElementById("lastTarget");
const { data, error } = await supa
.from("shared_target")
.select("*")
.eq("group_name", currentGroup)
.maybeSingle();
if (error) {
console.error("shared_target 読み込みエラー", error);
}
if (!data) {
box.textContent = "記録なし";
return;
}
// 説明テキスト(数字は隅に小さく)
box.innerHTML = `
最後に設定された待ち合わせ場所があります。<br>
地図上の <b>紫のピン</b> を確認してください。<br>
<span style="font-size:12px; opacity:0.7;">
(lat: ${data.lat.toFixed(5)}, lng: ${data.lng.toFixed(5)})
</span>
`;
if (typeof data.lat === "number" && typeof data.lng === "number") {
if (targetMarker) map.removeLayer(targetMarker);
targetMarker = L.marker([data.lat, data.lng], { icon: meetIcon })
.addTo(map)
.bindPopup("最終待ち合わせ場所");
}
}
// ===============================
// チャット履歴読み込み(LINE風 左寄せ)
// ===============================
async function loadChat() {
const box = document.getElementById("chatList");
const { data, error } = await supa
.from("messages")
.select("*")
.eq("group_name", currentGroup)
.order("created_at", { ascending: true });
if (error) {
console.error("messages 読み込みエラー", error);
return;
}
box.innerHTML = (data || [])
.map((m) => {
return `
<div class="chat-line">
<div class="chat-bubble chat-left">
<strong>${m.user_name}</strong><br>
${m.message}
</div>
</div>
`;
})
.join("");
box.scrollTop = box.scrollHeight;
}
// ===============================
// メイン
// ===============================
async function main() {
// グループチェック(解散済みかなど)
const ok = await loadGroup();
if (!ok) return;
initMap();
await loadLastLocations();
await loadLastTarget();
await loadChat();
// 戻るボタン
const backBtn = document.getElementById("backBtn");
if (backBtn) {
backBtn.onclick = () => (location.href = "index.html");
}
}
window.addEventListener("DOMContentLoaded", main);