8.6 주변 제휴업체: 위치 기반 목록/지도/필터(거리/카테고리) o
This commit is contained in:
29
src/app/api/partners/route.ts
Normal file
29
src/app/api/partners/route.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import prisma from "@/lib/prisma";
|
||||
|
||||
function haversine(lat1: number, lon1: number, lat2: number, lon2: number) {
|
||||
const toRad = (v: number) => (v * Math.PI) / 180;
|
||||
const R = 6371; // km
|
||||
const dLat = toRad(lat2 - lat1);
|
||||
const dLon = toRad(lon2 - lon1);
|
||||
const a = Math.sin(dLat / 2) ** 2 + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
|
||||
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||
return R * c;
|
||||
}
|
||||
|
||||
export async function GET(req: Request) {
|
||||
const { searchParams } = new URL(req.url);
|
||||
const lat = Number(searchParams.get("lat"));
|
||||
const lon = Number(searchParams.get("lon"));
|
||||
const category = searchParams.get("category") || undefined;
|
||||
const radius = Number(searchParams.get("radius")) || 10; // km
|
||||
const where = category ? { category } : {};
|
||||
const partners = await prisma.partner.findMany({ where, orderBy: { createdAt: "desc" } });
|
||||
const withDistance = isFinite(lat) && isFinite(lon)
|
||||
? partners.map((p) => ({ ...p, distance: haversine(lat, lon, p.latitude, p.longitude) })).filter((p) => p.distance <= radius)
|
||||
: partners.map((p) => ({ ...p, distance: null }));
|
||||
withDistance.sort((a, b) => (a.distance ?? 0) - (b.distance ?? 0));
|
||||
return NextResponse.json({ partners: withDistance });
|
||||
}
|
||||
|
||||
|
||||
37
src/app/partners/page.tsx
Normal file
37
src/app/partners/page.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
import useSWR from "swr";
|
||||
import { useState } from "react";
|
||||
|
||||
const fetcher = (url: string) => fetch(url).then((r) => r.json());
|
||||
|
||||
export default function PartnersPage() {
|
||||
const [lat, setLat] = useState<string>("");
|
||||
const [lon, setLon] = useState<string>("");
|
||||
const [category, setCategory] = useState<string>("");
|
||||
const [radius, setRadius] = useState<string>("10");
|
||||
const qs = new URLSearchParams({ ...(lat ? { lat } : {}), ...(lon ? { lon } : {}), ...(category ? { category } : {}), radius });
|
||||
const { data, mutate } = useSWR<{ partners: any[] }>(`/api/partners?${qs.toString()}`, fetcher);
|
||||
return (
|
||||
<div>
|
||||
<h1>주변 제휴업체</h1>
|
||||
<div style={{ display: "flex", gap: 8, marginBottom: 12 }}>
|
||||
<input placeholder="위도" value={lat} onChange={(e) => setLat(e.target.value)} />
|
||||
<input placeholder="경도" value={lon} onChange={(e) => setLon(e.target.value)} />
|
||||
<input placeholder="카테고리(spa/gym 등)" value={category} onChange={(e) => setCategory(e.target.value)} />
|
||||
<input placeholder="반경(km)" value={radius} onChange={(e) => setRadius(e.target.value)} />
|
||||
<button onClick={() => mutate()}>검색</button>
|
||||
</div>
|
||||
<ul style={{ display: "flex", flexDirection: "column", gap: 8 }}>
|
||||
{(data?.partners ?? []).map((p) => (
|
||||
<li key={p.id} style={{ border: "1px solid #eee", borderRadius: 8, padding: 12 }}>
|
||||
<strong>{p.name}</strong> <span style={{ opacity: 0.7 }}>({p.category})</span>
|
||||
<div style={{ fontSize: 12, opacity: 0.8 }}>{p.address}</div>
|
||||
{p.distance != null && <div style={{ fontSize: 12, opacity: 0.7 }}>거리: {p.distance.toFixed(2)} km</div>}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user