shimane-u-fest-map

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

page.tsx (6215B)


      1 "use client";
      2 import { useEffect, useState, useMemo } from "react";
      3 import {
      4   APIProvider,
      5   Map,
      6   Marker,
      7   useApiIsLoaded,
      8 } from "@vis.gl/react-google-maps";
      9 import places, { Place } from "./data";
     10 //import { useSearchParams } from "next/navigation";
     11 
     12 /*
     13 const findPlaceById = (id: number): Place | undefined => {
     14   return places.find((place) => place.id === id);
     15 };
     16 */
     17 
     18 type Coords = {
     19   lat: number;
     20   lng: number;
     21 } | null;
     22 
     23 function markerLabel(text: string) {
     24   return {
     25     text: text,
     26     color: "#000",
     27     fontSize: "0.8rem",
     28     fontWeight: "500",
     29     fontFamily: "Noto Sans JP, sans-serif",
     30   };
     31 }
     32 
     33 export default function Home() {
     34   const [selectedPlace, setSelectedPlace] = useState<Place | null>(null);
     35   const [userLocation, setUserLocation] = useState<Coords>(null);
     36   const [locationLoading, setLocationLoading] = useState(true);
     37   const [currentZoom, setCurrentZoom] = useState(17);
     38   const isApiLoaded = useApiIsLoaded();
     39   const [searchTerm, setSearchTerm] = useState("");
     40   /*
     41   const searchParams = useSearchParams();
     42   const search = searchParams.get("id");
     43   */
     44   const handleMarkerClick = (place: Place) => {
     45     setSelectedPlace(place);
     46   };
     47   const handleMapClick = (e: any) => {
     48     if (!e.detail.placeId) {
     49       setSelectedPlace(null);
     50     }
     51   };
     52   const handleZoomChange = (e: any) => {
     53     setCurrentZoom(e.detail.zoom);
     54   };
     55 
     56   const filteredPlaces = useMemo(() => {
     57     if (!searchTerm) {
     58       return places;
     59     }
     60     const lowerCaseSearch = searchTerm.toLowerCase();
     61     return places.filter(
     62       (place) =>
     63         place.title.toLowerCase().includes(lowerCaseSearch) ||
     64         (typeof place.description === "string" &&
     65           place.description.toLowerCase().includes(lowerCaseSearch)),
     66     );
     67   }, [searchTerm]);
     68   /*
     69   useEffect(() => {
     70     if (search !== null) {
     71       const searchNum = Number(search);
     72       if (!isNaN(searchNum)) {
     73         const foundPlace = findPlaceById(searchNum);
     74         if (foundPlace) {
     75           setSelectedPlace(foundPlace);
     76         }
     77       }
     78     }
     79   }, [search]);
     80 	*/
     81   const [showAbout, setShowAbout] = useState(false);
     82 
     83   useEffect(() => {
     84     if ("geolocation" in navigator) {
     85       navigator.geolocation.getCurrentPosition(
     86         (position) => {
     87           setUserLocation({
     88             lat: position.coords.latitude,
     89             lng: position.coords.longitude,
     90           });
     91           setLocationLoading(false);
     92         },
     93         (error) => {
     94           console.error("Geolocation Error:", error);
     95           setLocationLoading(false);
     96         },
     97         { enableHighAccuracy: true, timeout: 5000, maximumAge: 0 },
     98       );
     99     } else {
    100       setLocationLoading(false);
    101     }
    102   }, []);
    103   return (
    104     <div>
    105       <main>
    106         <header>
    107           <p>島大松江 学祭マップ 2025</p>
    108         </header>
    109 		<div>
    110 		</div>
    111         <APIProvider apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_API_KEY || ""}>
    112           <Map
    113             style={{ width: "100vw", height: "calc(100svh - 100px)" }}
    114             defaultCenter={{ lat: 35.4875, lng: 133.068315 }}
    115             defaultZoom={17}
    116             gestureHandling={"greedy"}
    117             disableDefaultUI={true}
    118             onClick={handleMapClick}
    119             onZoomChanged={handleZoomChange}
    120           >
    121             {filteredPlaces.map((place) => (
    122               <Marker
    123                 key={place.id}
    124                 position={place.position}
    125                 onClick={() => handleMarkerClick(place)}
    126                 label={currentZoom > 19 ? markerLabel(place.title) : undefined}
    127               />
    128             ))}
    129             {userLocation && isApiLoaded && (
    130               <Marker
    131                 position={userLocation}
    132                 icon={{
    133                   url: "data:image/svg+xml;utf-8,<svg width='24' height='24' viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'><circle cx='12' cy='12' r='10' fill='%231a73e8' stroke='%23ffffff' stroke-width='2'/></svg>",
    134                   scaledSize: new window.google.maps.Size(40, 40),
    135                 }}
    136               />
    137             )}
    138           </Map>
    139           {selectedPlace && (
    140             <div className="table-container">
    141               <h2>{selectedPlace.title}</h2>
    142               <div className="description-scroll">
    143                 {selectedPlace.description}
    144               </div>
    145             </div>
    146           )}
    147         </APIProvider>
    148       </main>
    149       <footer>
    150         <small>
    151           <input type="text" placeholder="イベント名や説明で検索..." value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} />
    152           <br />
    153           <button className="about-link" onClick={() => setShowAbout(true)}>
    154             About
    155           </button>
    156           &copy; minerva_juppiter All Right Reserved
    157         </small>
    158       </footer>
    159       {showAbout && (
    160         <div className="floating-modal">
    161           <div className="about">このサイトについて・免責事項</div>
    162           <div>
    163             <p>
    164               1.
    165               当サイトは非公式のマップであり,公式サイト,実行委員会,その他一切の関係団体とは関係ございません。
    166             </p>
    167             <p>
    168               2.
    169               当サイトに掲載する情報の正確性,完全性,最新性について,いかなる保証も行うものではありません。
    170             </p>
    171             <p>
    172               3.
    173               当サイトの利用により生じた損害または不利益について,当サイト運営者は一切の責任を負わないものとします。
    174             </p>
    175             <p>
    176               4. 不具合・情報の誤り等がありましたら{" "}
    177               <a
    178                 href="https://github.com/minerva-jupiter/shimane-u-fest-map"
    179                 target="_blank"
    180                 rel="noopener noreferrer"
    181               >
    182                 開発リポジトリ
    183               </a>{" "}
    184               でissueを作成していただけますと幸いです。
    185             </p>
    186           </div>
    187           <button
    188             className="floating-modal-close"
    189             onClick={() => setShowAbout(false)}
    190             aria-label="Close"
    191           >
    192             ×
    193           </button>
    194         </div>
    195       )}
    196     </div>
    197   );
    198 }