diff --git a/eslint.config.js b/eslint.config.js
index 0387e22..27c452e 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -20,6 +20,7 @@ export default [
"react/react-in-jsx-scope": "off", // React 17 이상에서는 불필요
"react/jsx-no-target-blank": ["error", { allowReferrer: true }], // 보안 문제 해결
"@typescript-eslint/no-explicit-any": "off",
+ "react/prop-types": "off",
},
},
];
diff --git a/src/App.tsx b/src/App.tsx
index c2276ee..7e1d97e 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -11,6 +11,7 @@ function App() {
} />
+
diff --git a/src/components/Building.tsx b/src/components/Building.tsx
index d2f788b..bd1f1ee 100644
--- a/src/components/Building.tsx
+++ b/src/components/Building.tsx
@@ -1,22 +1,76 @@
import styled from "@emotion/styled";
+import Divider from "./Divider.tsx";
+import { buildingData, BuildingInfo } from "./data/buildingData.ts";
+import Overflow from "./Overflow.tsx";
+
+interface BuildingProps {
+ onBuildingClick: (building: BuildingInfo) => void;
+}
+const Building: React.FC = ({ onBuildingClick }) => {
+ return (
+ <>
+
+
+ 홍익대학교
+
+
+
+ 내부건물
+ {buildingData.map((building) => (
+ <>
+ onBuildingClick(building)}
+ >
+
+
+ {building.name}
+ 운영 시간: {building.time}
+
+
+
+ >
+ ))}
+
+
+ >
+ );
+};
+export default Building;
const Container = styled.div`
+ width: 400px;
position: relative;
- padding: 45px;
- overflow: hidden;
+ padding: 20px 30px;
`;
const Title = styled.div`
+ margin: 10px 0px;
width: 300px;
- font-size: 30px;
+ font-size: 35px;
font-weight: 600;
`;
-const Building = () => {
- return (
- <>
-
- 홍익대학교
-
- >
- );
-};
-export default Building;
+const SubTitle = styled.div`
+ font-size: 25px;
+ font-weight: 500;
+ margin-bottom: 10px;
+`;
+const BuildingItem = styled.a`
+ display: flex;
+ margin: 20px 0px;
+ color: black;
+ cursor: pointer;
+`;
+const Image = styled.img`
+ width: 100px;
+ height: 100px;
+ margin-right: 20px;
+`;
+const Detail = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+`;
+const Name = styled.div`
+ font-size: 20px;
+ font-weight: 500;
+`;
diff --git a/src/components/BuildingDetail.tsx b/src/components/BuildingDetail.tsx
new file mode 100644
index 0000000..6242d2f
--- /dev/null
+++ b/src/components/BuildingDetail.tsx
@@ -0,0 +1,187 @@
+import styled from "@emotion/styled";
+import { BuildingInfo, FacilityInfo } from "./data/buildingData";
+import Divider from "./Divider";
+import { useState } from "react";
+import FacilityItem from "./FacilityItem.tsx";
+import Overflow from "./Overflow.tsx";
+interface BuildingDetailProps {
+ building: BuildingInfo;
+ onFacilityClick?: (facility: FacilityInfo) => void;
+}
+const BuildingDetail: React.FC = ({
+ building,
+ onFacilityClick,
+}) => {
+ const [selectedFloor, setSelectedFloor] = useState(null);
+ const [selectedType, setSelectedType] = useState(null);
+ const handleTypeChange = (type: number) => {
+ setSelectedType(Number(type));
+ if (selectedType == type) {
+ setSelectedType(null);
+ }
+ };
+ const handleFloorChange = (event: React.ChangeEvent) => {
+ setSelectedFloor(event.target.value);
+ };
+ return (
+ <>
+
+
+
+ {building.name}
+ 운영 시간: {building.time}
+
+
+ {building.floors.map((floor) => (
+
+ ))}
+
+
+
+
+
+ {selectedFloor ? `${selectedFloor}층 내부 시설` : "내부 시설"}
+
+
+
+
+
+
+
+ {building.facilities?.map((facility) => (
+ <>
+ onFacilityClick?.(facility)}
+ >
+ {selectedFloor ? (
+ <>
+ {selectedFloor === facility.floor && (
+ <>
+ {selectedType ? (
+ facility.type === selectedType && (
+ <>
+
+
+ >
+ )
+ ) : (
+ <>
+
+
+ >
+ )}
+ >
+ )}
+ >
+ ) : (
+ <>
+ {" "}
+ {selectedType ? (
+ facility.type === selectedType && (
+ <>
+
+
+ >
+ )
+ ) : (
+ <>
+
+
+ >
+ )}
+ >
+ )}
+
+ >
+ ))}
+
+
+ >
+ );
+};
+
+export default BuildingDetail;
+
+const FacilityItems = styled.a`
+ display: flex;
+ flex-direction: column;
+ cursor: pointer;
+`;
+
+const Facilities = styled.div`
+ display: flex;
+ gap: 10px;
+ margin-bottom: 20px;
+`;
+const DetailTitle = styled.p`
+ font-size: 20px;
+ font-weight: 500;
+ margin-bottom: 10px;
+`;
+interface ButtonProps {
+ selected: boolean;
+}
+const Button = styled.button`
+ border: none;
+ background: #a7a7a7;
+ color: white;
+ padding: 5px 10px;
+ border-radius: 10px;
+ font-size: 17px;
+ font-weight: 300;
+ width: 84px;
+ cursor: pointer;
+ flex-shrink: 0;
+ white-space: nowrap;
+ &:hover {
+ background: rgb(0, 51, 99, 0.5);
+ }
+ ${({ selected }) =>
+ selected &&
+ `
+ background: rgb(0, 51, 99, 0.5);
+ `}//
+`;
+
+const Image = styled.img`
+ width: 100%;
+ height: 200px;
+ object-fit: cover;
+`;
+const Container = styled.div`
+ width: 400px;
+ padding: 20px 30px;
+`;
+const DropDown = styled.select`
+ border: none;
+ margin: 10px 0px;
+ width: 100px;
+ padding: 4px 15px;
+ text-align: left;
+ border-radius: 10px;
+ font-size: 13.5px;
+ font-weight: 300;
+ background: #a7a7a7;
+ color: white;
+ appearance: none;
+`;
diff --git a/src/components/Divider.tsx b/src/components/Divider.tsx
new file mode 100644
index 0000000..5ee4db2
--- /dev/null
+++ b/src/components/Divider.tsx
@@ -0,0 +1,12 @@
+import styled from "@emotion/styled";
+interface DividerProps {
+ size: boolean;
+}
+const Divider = styled.div`
+ width: 100%;
+ height: ${({ size }) => (size ? "10px" : "1px")};
+ background: #e9e9e9;
+ margin-bottom: 10px;
+`;
+
+export default Divider;
diff --git a/src/components/FacilityDetail.tsx b/src/components/FacilityDetail.tsx
new file mode 100644
index 0000000..69b877f
--- /dev/null
+++ b/src/components/FacilityDetail.tsx
@@ -0,0 +1,128 @@
+import React from "react";
+import { FacilityInfo } from "./data/buildingData";
+import styled from "@emotion/styled";
+import LikeButton from "./LikeButton";
+import Divider from "./Divider";
+import { IoMdHeart } from "react-icons/io";
+
+interface FacilityDetailProps {
+ facility: FacilityInfo;
+}
+
+const FacilityDetail: React.FC = ({ facility }) => {
+ return (
+ <>
+
+
+
+ {facility.floor}층 {facility.name}
+
+ {facility.building}
+
+
+ 좋아요 {facility.like}개
+ 싫어요 {facility.dislike}개
+
+
+
+
+
+ 리뷰 {facility.reviewCount}개
+
+
+
+ 등록
+
+
+ {facility.review.map((review) => (
+ <>
+
+
+
+ {review.contents}
+
+
+ {review.like}
+
+
+
+
+ {review.user}
+
+
+ {review.date}
+
+
+
+ >
+ ))}
+
+ >
+ );
+};
+
+export default FacilityDetail;
+const ReviewContainer = styled.div`
+ padding: 0px 10px;
+ margin-bottom: 10px;
+`;
+const Container = styled.div`
+ width: 400px;
+ padding: 20px 30px;
+`;
+const Title = styled.div`
+ display: flex;
+ align-items: center;
+ margin-top: 10px;
+`;
+const Building = styled.p`
+ margin-left: 10px;
+ color: #a7a7a7;
+`;
+const Like = styled.div`
+ display: flex;
+ gap: 10px;
+ margin: 15px 0px 10px 0px;
+`;
+const Review = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+ margin-top: 10px;
+ margin-bottom: 5px;
+ font-weight: 300;
+`;
+const Input = styled.input`
+ width: 83%;
+ height: 35px;
+ border: none;
+ background: #f2f2f2;
+ border-radius: 5px;
+ padding: 0px 10px;
+`;
+const ReviewButton = styled.button`
+ border: none;
+ width: 15%;
+ border-radius: 5px;
+ background: #8099b1;
+ color: white;
+ &:hover {
+ background: #003363;
+ }
+`;
+const ReviewInput = styled.div`
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 25px;
+ margin-top: 20px;
+`;
diff --git a/src/components/FacilityItem.tsx b/src/components/FacilityItem.tsx
new file mode 100644
index 0000000..c5544ce
--- /dev/null
+++ b/src/components/FacilityItem.tsx
@@ -0,0 +1,71 @@
+import styled from "@emotion/styled";
+import LikeButton from "./LikeButton.tsx";
+import { FacilityInfo } from "./data/buildingData.ts";
+interface FacilityItemProps {
+ facility?: FacilityInfo | null;
+}
+const FacilityItem: React.FC = ({ facility }) => {
+ return (
+ <>
+ {facility ? (
+ <>
+
+
+ {facility.floor}층 {facility.name}
+
+ + 저장
+
+ 아직까지 작성된 리뷰가 없습니다!
+
+ 좋아요 {facility.like}개
+ 싫어요 {facility.dislike}개
+
+ >
+ ) : (
+ 시설 정보가 없습니다.
+ )}
+ >
+ );
+};
+
+export default FacilityItem;
+
+const TitleItems = styled.div`
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ width: 100%;
+`;
+const StoreButton = styled.button`
+ border: none;
+ background: #8099b1;
+ color: white;
+ padding: 2px;
+ width: 50px;
+ height: 27px;
+ border-radius: 10px;
+ font-size: 13px;
+ font-weight: 300;
+ cursor: pointer;
+ white-space: nowrap;
+ &:hover {
+ background: rgb(0, 51, 99, 0.8);
+ }
+`;
+
+const DetailTitle = styled.p`
+ font-size: 20px;
+ font-weight: 500;
+ margin: 10px 0px;
+`;
+
+const Review = styled.div`
+ font-size: 15px;
+ font-weight: 300;
+ color: #a7a7a7;
+`;
+const Like = styled.div`
+ display: flex;
+ gap: 10px;
+ margin: 15px 0px 10px 0px;
+`;
diff --git a/src/components/HomeBoard.tsx b/src/components/HomeBoard.tsx
index 7f11700..254149d 100644
--- a/src/components/HomeBoard.tsx
+++ b/src/components/HomeBoard.tsx
@@ -2,6 +2,73 @@ import styled from "@emotion/styled";
import { useState } from "react";
import { IoIosArrowBack, IoIosArrowForward } from "react-icons/io";
import Building from "./Building";
+import { BuildingInfo, FacilityInfo } from "./data/buildingData.ts";
+import BuildingDetail from "./BuildingDetail.tsx";
+import FacilityDetail from "./FacilityDetail.tsx";
+
+interface HomeBoardProps {
+ onBuildingClick: (building: BuildingInfo) => void;
+ selectedBuilding: BuildingInfo | null;
+ isPanelOpen: boolean;
+ setIsPanelOpen: (isPanelOpen: boolean) => void;
+}
+const HomeBoard: React.FC = ({
+ selectedBuilding,
+ onBuildingClick,
+ isPanelOpen,
+ setIsPanelOpen,
+}) => {
+ const [facility, setFacility] = useState(null);
+ const toggleMenu = () => {
+ setIsPanelOpen(!isPanelOpen);
+ };
+ const handleFacilityClick = (facility: FacilityInfo) => {
+ setFacility(facility);
+ };
+ return (
+ <>
+
+
+
+ 화장실
+
+
+ 정수기
+
+
+ 카페
+
+
+
+
+ {facility ? (
+
+ ) : selectedBuilding ? (
+
+ ) : (
+
+ )}
+
+
+ >
+ );
+};
+
+export default HomeBoard;
+
interface PanelProps {
isOpen: boolean;
}
@@ -52,43 +119,3 @@ const Button = styled.button`
cursor: pointer;
font-size: 17px;
`;
-
-const HomeBoard: React.FC = () => {
- const [isOpen, setIsOpen] = useState(false);
- const toggleMenu = () => {
- setIsOpen(!isOpen);
- };
- return (
- <>
-
-
-
- 화장실
-
-
- 정수기
-
-
- 카페
-
-
-
-
-
-
-
- >
- );
-};
-
-export default HomeBoard;
diff --git a/src/components/LikeButton.tsx b/src/components/LikeButton.tsx
new file mode 100644
index 0000000..8455a3d
--- /dev/null
+++ b/src/components/LikeButton.tsx
@@ -0,0 +1,20 @@
+import styled from "@emotion/styled";
+
+const LikeButton = styled.button`
+ border: none;
+ background: rgb(0, 51, 99, 0.2);
+ color: black;
+ padding: 5px 10px;
+ border-radius: 10px;
+ font-size: 13px;
+ font-weight: 300;
+ cursor: pointer;
+ white-space: nowrap;
+ display: flex;
+ align-items: center;
+ &:hover {
+ background: rgb(0, 51, 99, 0.5);
+ color: white;
+ }
+`;
+export default LikeButton;
diff --git a/src/components/Overflow.tsx b/src/components/Overflow.tsx
new file mode 100644
index 0000000..dc4b263
--- /dev/null
+++ b/src/components/Overflow.tsx
@@ -0,0 +1,9 @@
+import styled from "@emotion/styled";
+
+const Overflow = styled.div`
+ overflow-y: auto;
+ height: 100vh;
+ overflow-x: hidden;
+`;
+
+export default Overflow;
diff --git a/src/components/data/buildingData.ts b/src/components/data/buildingData.ts
new file mode 100644
index 0000000..6c687f2
--- /dev/null
+++ b/src/components/data/buildingData.ts
@@ -0,0 +1,132 @@
+export interface BuildingInfo {
+ image: string;
+ name: string;
+ time: string;
+ floors: number[];
+ facilities?: FacilityInfo[];
+ coordinates: { lat: number; lng: number };
+}
+export interface FacilityInfo {
+ building: string;
+ type: number;
+ floor: string;
+ name: string;
+ like: number;
+ dislike: number;
+ reviewCount: number;
+ review: ReviewInfo[];
+}
+interface ReviewInfo {
+ contents: string;
+ user: string;
+ date: string;
+ like: number;
+}
+export const buildingData: BuildingInfo[] = [
+ {
+ image:
+ "https://photo.hongik.ac.kr/app/board/attach/image/thumb_353_1697760691000.do",
+ name: "홍문관",
+ time: "10:00~18:00",
+ floors: [1, 2, 3, 4, 5, 6, 7, 8, 9],
+ coordinates: { lat: 37.55258227611087, lng: 126.92491801500331 },
+ facilities: [
+ {
+ building: "홍문관",
+ floor: "1",
+ type: 1,
+ name: "화장실",
+ like: 0,
+ dislike: 0,
+ reviewCount: 2,
+ review: [
+ {
+ contents: "안녕하세요",
+ user: "컴공생",
+ date: "2021.09.01",
+ like: 0,
+ },
+ {
+ contents: "깨끗해요!",
+ user: "자율전공샹",
+ date: "2025.10.01",
+ like: 3,
+ },
+ ],
+ },
+ {
+ building: "홍문관",
+ floor: "3",
+ type: 1,
+ name: "화장실",
+ like: 0,
+ dislike: 0,
+ reviewCount: 2,
+ review: [
+ {
+ contents: "안녕하세요",
+ user: "컴공생",
+ date: "2021.09.01",
+ like: 0,
+ },
+ {
+ contents: "깨끗해요!",
+ user: "자율전공샹",
+ date: "2025.10.01",
+ like: 3,
+ },
+ ],
+ },
+ {
+ building: "홍문관",
+ floor: "1",
+ type: 3,
+ name: "카페나무",
+ like: 4,
+ dislike: 2,
+ reviewCount: 1,
+ review: [
+ {
+ contents: "안녕하세요",
+ user: "컴공생",
+ date: "2021.09.01",
+ like: 0,
+ },
+ ],
+ },
+ ],
+ },
+ {
+ image:
+ "https://photo.hongik.ac.kr/app/board/attach/image/thumb_732_1700796628000.do",
+ name: "와우관",
+ time: "00:00~24:00",
+ coordinates: { lat: 37.55167104651813, lng: 126.926557030475 },
+ floors: [1, 2, 3, 4, 5, 6, 7, 8],
+ facilities: [
+ {
+ building: "와우관",
+ floor: "3",
+ type: 1,
+ name: "화장실",
+ like: 3,
+ dislike: 0,
+ reviewCount: 2,
+ review: [
+ {
+ contents: "안녕하세요",
+ user: "컴공생",
+ date: "2021.09.01",
+ like: 0,
+ },
+ {
+ contents: "깨끗해요!",
+ user: "자율전공샹",
+ date: "2025.10.01",
+ like: 3,
+ },
+ ],
+ },
+ ],
+ },
+];
diff --git a/src/components/map/Kakaomap.tsx b/src/components/map/Kakaomap.tsx
index 3bcf003..16d0633 100644
--- a/src/components/map/Kakaomap.tsx
+++ b/src/components/map/Kakaomap.tsx
@@ -1,19 +1,59 @@
import { useEffect } from "react";
+import { buildingData, BuildingInfo } from "../data/buildingData";
+
declare global {
interface Window {
kakao: any;
}
}
+
const { kakao } = window;
-const Kakaomap = () => {
+
+interface KakaomapProps {
+ selectedBuilding: BuildingInfo | null;
+ onBuildingClick: (building: BuildingInfo) => void;
+}
+const Kakaomap: React.FC = ({
+ selectedBuilding,
+ onBuildingClick,
+}) => {
+ const addMarkers = (map: any) => {
+ {
+ buildingData.forEach((building) => {
+ const markerPosition = new kakao.maps.LatLng(
+ building.coordinates.lat,
+ building.coordinates.lng
+ );
+ const marker = new kakao.maps.Marker({
+ position: markerPosition,
+ });
+ marker.setMap(map);
+ kakao.maps.event.addListener(marker, "click", () => {
+ onBuildingClick(building);
+ });
+ });
+ }
+ };
+
useEffect(() => {
const container = document.getElementById("map");
const options = {
center: new kakao.maps.LatLng(37.552635722509, 126.92436042413),
level: 1,
};
- new kakao.maps.Map(container, options);
- }, []);
+ const map = new kakao.maps.Map(container, options);
+ addMarkers(map);
+
+ // 선택된 건물이 변경될 때 지도를 해당 위치로 이동
+ if (selectedBuilding) {
+ const moveLatLon = new kakao.maps.LatLng(
+ selectedBuilding.coordinates.lat,
+ selectedBuilding.coordinates.lng
+ );
+ map.setCenter(moveLatLon);
+ }
+ }, [selectedBuilding]);
+
return (
{
>
);
};
+
export default Kakaomap;
diff --git a/src/components/pages/Homepage.tsx b/src/components/pages/Homepage.tsx
index 8fca37e..62cae8f 100644
--- a/src/components/pages/Homepage.tsx
+++ b/src/components/pages/Homepage.tsx
@@ -2,18 +2,36 @@ import styled from "@emotion/styled";
import HomeBoard from "../HomeBoard.tsx";
import Kakaomap from "../map/Kakaomap.tsx";
import NavBar from "../nav/NavBar.tsx";
+import { useState } from "react";
+import { BuildingInfo } from "../data/buildingData.ts";
const HomePageWrapper = styled.div`
position: relative;
width: calc(100vw - 70px);
height: 100vh;
`;
const HomePage = () => {
+ const [selectedBuilding, setSelectedBuilding] = useState(
+ null
+ );
+ const [isPanelOpen, setIsPanelOpen] = useState(false);
+ const handleBuildingClick = (building: BuildingInfo) => {
+ setSelectedBuilding(building);
+ setIsPanelOpen(true);
+ };
return (
<>
-
-
+
+
>
);
diff --git a/src/components/style/GlobalStyle.tsx b/src/components/style/GlobalStyle.tsx
index 4cf0edb..b305a7d 100644
--- a/src/components/style/GlobalStyle.tsx
+++ b/src/components/style/GlobalStyle.tsx
@@ -10,6 +10,7 @@ const GlobalStyle = () => (
box-sizing: border-box;
margin: 0px;
padding: 0px;
+ text-decoration: none;
}
body {
margin-left: 70px;