Browse Source

타입스크립트 마이그레이션

home 6 months ago
parent
commit
405ec47bd8

+ 1 - 1
index.html

@@ -9,6 +9,6 @@
   </head>
   <body>
     <div id="root"></div>
-    <script type="module" src="/src/main.jsx"></script>
+    <script type="module" src="/src/index.tsx"></script>
   </body>
 </html>

+ 4 - 1
package.json

@@ -21,13 +21,16 @@
   },
   "devDependencies": {
     "@eslint/js": "^9.25.0",
-    "@types/react": "^19.1.2",
+    "@types/navermaps": "^3.9.1",
+    "@types/node": "^24.0.0",
+    "@types/react": "^19.1.7",
     "@types/react-dom": "^19.1.2",
     "@vitejs/plugin-react": "^4.4.1",
     "eslint": "^9.25.0",
     "eslint-plugin-react-hooks": "^5.2.0",
     "eslint-plugin-react-refresh": "^0.4.19",
     "globals": "^16.0.0",
+    "typescript": "^5.8.3",
     "vite": "^6.3.5"
   }
 }

+ 0 - 27
src/App.jsx

@@ -1,27 +0,0 @@
-import {Routes, Route} from "react-router-dom";
-import Main from "@/pages/main/index.jsx";
-import Landmarks from "@/pages/business/Landmarks.jsx";
-import Header from "@/components/Header.jsx";
-import Layout from "@/components/Layout.jsx";
-import Sections from "@/pages/business/Sections.jsx";
-import Location from "@/pages/about/Location.jsx";
-
-function App() {
-    return (
-        <>
-            <Header/>
-            <Routes>
-                <Route path="/" element={<Main/>}/>
-                <Route path="/business" element={<Layout/>}>
-                    <Route path="landmarks" element={<Landmarks/>}/>
-                    <Route path="sections" element={<Sections/>}/>
-                </Route>
-                <Route path="/about" element={<Layout/>}>
-                    <Route path="location" element={<Location/>}/>
-                </Route>
-            </Routes>
-        </>
-    );
-}
-
-export default App;

+ 67 - 0
src/App.tsx

@@ -0,0 +1,67 @@
+/// <reference types="vite/client" />
+
+import React, { Suspense, lazy } from 'react';
+import { Routes, Route } from 'react-router-dom';
+import Header from '@/components/Header';
+import Layout from '@/components/Layout';
+import Main from '@/pages/main';
+import NotFound from '@/pages/NotFound';
+import menuItems from '@/data/menuItems';
+
+// 모든 페이지를 glob으로 가져오기 (파일 경로 기준)
+const pages = import.meta.glob('@/pages/**/*.tsx');
+
+// 페이지 경로로부터 Lazy 컴포넌트 생성
+const lazyImport = (path: string) => {
+  const importer = pages[`/src/pages/${path}.tsx`];
+  return importer
+    ? lazy(importer as () => Promise<{ default: React.ComponentType }>)
+    : lazy(() => Promise.resolve({ default: NotFound }));
+};
+
+// Suspense 래핑 함수
+const withSuspense = (Component: React.LazyExoticComponent<React.ComponentType>) => (
+  <Suspense fallback={<div>로딩 중...</div>}>
+    <Component />
+  </Suspense>
+);
+
+// 메뉴 기반으로 라우트 생성
+const renderRoutesFromMenu = () => {
+  return menuItems.map((parent) => {
+    const parentPath = parent.href.replace(/^\//, '');
+
+    return (
+      <Route key={parent.href} path={parentPath} element={<Layout />}>
+        {parent.children.map((child) => {
+          const childPath = child.href.replace(/^\//, '');
+          const fullPath = `${parentPath}/${childPath}`;
+          const Component = lazyImport(fullPath);
+
+          return (
+            <Route
+              key={child.href}
+              path={childPath}
+              element={withSuspense(Component)}
+            />
+          );
+        })}
+      </Route>
+    );
+  });
+};
+
+const App = () => {
+  return (
+    <>
+      <Header />
+      <Routes>
+        <Route path="/" element={<Main />} />
+        {renderRoutesFromMenu()}
+        <Route path="*" element={<NotFound />} />
+      </Routes>
+    </>
+  );
+};
+
+export default App;

+ 13 - 15
src/components/CommonModal.jsx → src/components/CommonModal.tsx

@@ -1,6 +1,16 @@
 import React from 'react';
+import {MajorProject} from "@/data/project";
 
-export const CommonModal = ({isOpen, onClose, item}) => {
+interface CommonModalProps {
+    isOpen: boolean;
+    onClose: () => void;
+    item: MajorProject;
+}
+
+/**
+ * 프로젝트 상세 정보를 보여주는 모달 컴포넌트
+ */
+export const CommonModal: React.FC<CommonModalProps> = ({ isOpen, onClose, item }) => {
     if (!isOpen) return null;
 
     return (
@@ -37,20 +47,8 @@ export const CommonModal = ({isOpen, onClose, item}) => {
                     {/*    alt={images[currentIndex]?.alt || `image-${currentIndex}`}*/}
                     {/*    className="rounded max-h-full"*/}
                     {/*/>*/}
-                    <button
-                        onClick={() => {
-                        }}
-                        className="absolute left-4 top-1/2 -translate-y-1/2 bg-gray-300/75 w-14 h-14 text-2xl"
-                    >
-                        {'<'}
-                    </button>
-                    <button
-                        onClick={() => {
-                        }}
-                        className="absolute right-4 top-1/2 -translate-y-1/2 bg-gray-300/75 w-14 h-14 text-2xl"
-                    >
-                        {'>'}
-                    </button>
+                    <button onClick={() => {}} className="absolute left-4 top-1/2 -translate-y-1/2 bg-gray-300/75 w-14 h-14 text-2xl">{'<'}</button>
+                    <button onClick={() => {}} className="absolute right-4 top-1/2 -translate-y-1/2 bg-gray-300/75 w-14 h-14 text-2xl">{'>'}</button>
                 </div>
 
                 {/* 페이지 인디케이터 */}

+ 0 - 0
src/components/DarkOverlay.jsx → src/components/DarkOverlay.tsx


+ 0 - 72
src/components/Header.jsx

@@ -1,72 +0,0 @@
-import React, {useState} from 'react';
-import menuItems from '@/data/menuItems.json';
-import {Link} from "react-router-dom";
-
-const Header = () => {
-    const [activeIndex, setActiveIndex] = useState(null);
-
-    return (
-        <header className="fixed top-0 left-0 right-0 flex items-center h-20 w-full bg-white z-30">
-            <article
-                id="sidebar-dimmer"
-                className={`fixed top-20 left-0 w-full h-full transition-opacity duration-500 ease-in-out ${activeIndex !== null ? 'bg-black/50 opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}`}
-            />
-
-            {/* 로고 영역 */}
-            <Link
-                to="/"
-                onClick={() => setActiveIndex(null)}
-                className="absolute left-[40px] top-[26px] flex items-center gap-1 w-32"
-            >
-                <img className="w-10 h-7" src="/logo.png" alt="logo"/>
-                <div className="text-gray-600 text-xl font-bold font-['Inter']">반도산전</div>
-            </Link>
-
-            {/* 메뉴 영역 */}
-            <nav className="flex justify-center items-center w-full h-full">
-                {menuItems.map((item, index) => (
-                    <div
-                        key={index}
-                        className="relative flex flex-col items-center px-[3vw] cursor-pointer"
-                        onMouseEnter={() => setActiveIndex(index)}
-                    >
-                        <div
-                            className={`relative group font-bold font-['Inter'] cursor-pointer ${activeIndex === index ? 'text-blue-600' : 'text-gray-600'}`}>
-                            {item.label}
-                            <span
-                                className={`absolute left-0 bottom-[-2px] h-[2px] bg-blue-600 transition-all duration-300 ${activeIndex === index ? 'w-full' : 'w-0'}`}></span>
-                        </div>
-
-                        {activeIndex === index && item.children.length > 0 && (
-                            <div
-                                id="overlab-menu"
-                                className="absolute flex flex-col space-y-6 mt-13 pt-4 z-10 bg-blue-900 text-white cursor-pointer"
-                            >
-                                {item.children.map((child, cIdx) => (
-                                    <Link
-                                        to={child.href}
-                                        key={cIdx}
-                                        className="relative inline-block text-md text-center font-medium font-['Inter'] text-white whitespace-normal max-w-[100px] break-words hover:text-white/80 transition-colors duration-200 group"
-                                        onClick={() => setActiveIndex(null)}
-                                    >
-                                        <span className="relative inline-block">
-                                            {child.label}
-                                            <span className="absolute left-0 bottom-[-2px] h-[2px] w-0 bg-white transition-all duration-300 group-hover:w-full"></span>
-                                        </span>
-                                    </Link>
-                                ))}
-                            </div>
-                        )}
-                    </div>
-                ))}
-            </nav>
-
-            {activeIndex !== null && (
-                <div id="menuBox" className="absolute w-full h-[50vh] top-20 border bg-blue-900"
-                     onMouseLeave={() => setActiveIndex(null)}></div>
-            )}
-        </header>
-    );
-};
-
-export default Header;

+ 73 - 0
src/components/Header.tsx

@@ -0,0 +1,73 @@
+import React, {useState} from 'react';
+import menuItems from '@/data/menuItems';
+import {Link} from "react-router-dom";
+
+const Header = () => {
+  const [activeIndex, setActiveIndex] = useState<number | null>(null);
+
+  return (
+    <header className="fixed top-0 left-0 right-0 flex items-center h-20 w-full bg-white z-30">
+      <article
+        id="sidebar-dimmer"
+        className={`fixed top-20 left-0 w-full h-full transition-opacity duration-500 ease-in-out ${activeIndex !== null ? 'bg-black/50 opacity-100 pointer-events-auto' : 'opacity-0 pointer-events-none'}`}
+      />
+
+      {/* 로고 영역 */}
+      <Link
+        to="/"
+        onClick={() => setActiveIndex(null)}
+        className="absolute left-[40px] top-[26px] flex items-center gap-1 w-32"
+      >
+        <img className="w-10 h-7" src="/logo.png" alt="logo"/>
+        <div className="text-gray-600 text-xl font-bold font-['Inter']">반도산전</div>
+      </Link>
+
+      {/* 메뉴 영역 */}
+      <nav className="flex justify-center items-center w-full h-full">
+        {menuItems.map((item, index) => (
+          <div
+            key={index}
+            className="relative flex flex-col items-center px-[3vw] cursor-pointer"
+            onMouseEnter={() => setActiveIndex(index)}
+          >
+            <div
+              className={`relative group font-bold font-['Inter'] cursor-pointer ${activeIndex === index ? 'text-blue-600' : 'text-gray-600'}`}>
+              {item.label}
+              <span
+                className={`absolute left-0 bottom-[-2px] h-[2px] bg-blue-600 transition-all duration-300 ${activeIndex === index ? 'w-full' : 'w-0'}`}></span>
+            </div>
+
+            {activeIndex === index && item.children.length > 0 && (
+              <div
+                id="overlab-menu"
+                className="absolute flex flex-col space-y-6 mt-13 pt-4 z-10 bg-blue-900 text-white cursor-pointer"
+              >
+                {item.children.map((child, cIdx) => (
+                  <Link
+                    to={`${item.href}/${child.href}`}
+                    key={cIdx}
+                    className="relative inline-block text-md text-center font-medium font-['Inter'] text-white whitespace-normal max-w-[100px] break-words hover:text-white/80 transition-colors duration-200 group"
+                    onClick={() => setActiveIndex(null)}
+                  >
+                    <span className="relative inline-block">
+                      {child.label}
+                      <span
+                        className="absolute left-0 bottom-[-2px] h-[2px] w-0 bg-white transition-all duration-300 group-hover:w-full"></span>
+                    </span>
+                  </Link>
+                ))}
+              </div>
+            )}
+          </div>
+        ))}
+      </nav>
+
+      {activeIndex !== null && (
+        <div id="menuBox" className="absolute w-full h-[50vh] top-20 border bg-blue-900"
+             onMouseLeave={() => setActiveIndex(null)}></div>
+      )}
+    </header>
+  );
+};
+
+export default Header;

+ 13 - 8
src/components/Layout.jsx → src/components/Layout.tsx

@@ -1,19 +1,24 @@
 import {Outlet, useLocation} from "react-router-dom";
-import menuItems from "@/data/menuItems.json";
+import menuItems from "@/data/menuItems";
 import PageHeader from "@/components/PageHeader.jsx";
 import React from "react";
 
 const Layout = () => {
     const { pathname } = useLocation();
 
-    const matched = menuItems.flatMap(parent => {
-        return parent.children.map(child => ({
-            parent,
-            child
-        }));
-    }).find(({ child }) => child.href === pathname);
+  const matched = menuItems
+    .flatMap(parent =>
+      (parent.children ?? []).map(child => ({
+        parent,
+        child,
+        fullHref: parent.href.endsWith("/") || child.href.startsWith("/")
+          ? `${parent.href}${child.href}`
+          : `${parent.href}/${child.href}`
+      }))
+    )
+    .find(({ fullHref }) => fullHref === pathname);
 
-    const title = matched?.parent.label || "";
+  const title = matched?.parent.label || "";
     const description = matched?.parent.description || "";
     const layoutImg = matched?.parent.layoutImg || "/hero.jpg";
     const subTitle = matched?.child.label || "";

+ 0 - 16
src/components/PageHeader.jsx

@@ -1,16 +0,0 @@
-const PageHeader = ({ title, subTitle }) => {
-    return (
-        <div className="w-full border-b-3 border-gray-300 py-5">
-            <div className="max-w-7xl mx-auto flex flex-col gap-2">
-                <h2 className="text-3xl text-blue-900 font-bold font-['Inter']">
-                    {subTitle}
-                </h2>
-                <div className="text-sm text-gray-400 font-['Inter'] self-end">
-                    {`HOME > ${title} > ${subTitle}`}
-                </div>
-            </div>
-        </div>
-    );
-};
-
-export default PageHeader;

+ 29 - 0
src/components/PageHeader.tsx

@@ -0,0 +1,29 @@
+import React from 'react';
+
+interface PageHeaderProps {
+  /** 현재 페이지의 상위 타이틀 (예: 사업소개) */
+  title: string;
+
+  /** 현재 페이지의 하위 타이틀 (예: 정보통신공사) */
+  subTitle: string;
+}
+
+/**
+ * 페이지 상단에 표시되는 헤더 컴포넌트
+ */
+const PageHeader: React.FC<PageHeaderProps> = ({ title, subTitle }) => {
+  return (
+    <div className="w-full border-b-3 border-gray-300 py-5">
+      <div className="max-w-7xl mx-auto flex flex-col gap-2">
+        <h2 className="text-3xl text-blue-900 font-bold font-['Inter']">
+          {subTitle}
+        </h2>
+        <div className="text-sm text-gray-400 font-['Inter'] self-end">
+          {`HOME > ${title} > ${subTitle}`}
+        </div>
+      </div>
+    </div>
+  );
+};
+
+export default PageHeader;

+ 17 - 8
src/components/Pagination.jsx → src/components/Pagination.tsx

@@ -1,17 +1,26 @@
 import React from 'react';
 
 /**
- * 공통 Pagination 컴포넌트
- *
- * @param {number} currentPage - 현재 페이지 번호 (1부터 시작)
- * @param {number} totalItems - 전체 항목 개수
- * @param {number} itemsPerPage - 페이지당 항목 개수
- * @param {function} onPageChange - 페이지 변경 시 호출되는 콜백 함수
+ * Pagination 컴포넌트의 props 타입 정의
  */
-const Pagination = ({ currentPage, totalItems, itemsPerPage, onPageChange }) => {
+interface PaginationProps {
+  /** 현재 페이지 번호 (1부터 시작) */
+  currentPage: number;
+
+  /** 전체 항목 개수 */
+  totalItems: number;
+
+  /** 페이지당 항목 개수 */
+  itemsPerPage: number;
+
+  /** 페이지 변경 시 호출되는 콜백 함수 */
+  onPageChange: (page: number) => void;
+}
+
+const Pagination: React.FC<PaginationProps> = ({currentPage, totalItems, itemsPerPage, onPageChange,}) => {
     const totalPages = Math.ceil(totalItems / itemsPerPage);
 
-    const handleClick = (page) => {
+    const handleClick = (page: number) => {
         if (page >= 1 && page <= totalPages) {
             onPageChange(page);
         }

+ 66 - 0
src/data/business.ts

@@ -0,0 +1,66 @@
+export interface BusinessItem {
+  label: string;
+  description?: string;
+  description2?: string;
+  image: string;
+  url: string;
+}
+
+const businessSections: BusinessItem[] = [
+  {
+    label: "정보통신공사",
+    description:
+      "지금의 반도산전이 있기까지\n 정보통신공사로 시작한 반도산전 30년의 탄탄한 시공능력과 도전정신이 있습니다.",
+    description2:
+      "정보통신공사란?\n정보통신 시스템 및 관련 설비를 설치·이전·변경·철거하거나,\n유지·보수하는 공사를 말합니다. 이는 전기공사와는 다르며,\n정보통신기술(IT) 기반의 인프라 구축에 초점을 둔 공사입니다.",
+    image: "/business/정보통신공사.jpg",
+    url: "/business/communication",
+  },
+  {
+    label: "전기설비공사",
+    image: "/business/전기설비공사.jpg",
+    url: "/business/electric",
+  },
+  {
+    label: "소방설비공사",
+    image: "/business/소방설비공사.jpg",
+    url: "/business/ict",
+  },
+  {
+    label: "OSP 기간통신사업",
+    image: "/business/OSP기간통신사업.jpg",
+    url: "/business/security",
+  },
+  {
+    label: "친환경공사",
+    image: "/business/친환경공사.jpg",
+    url: "/business/fire",
+  },
+  {
+    label: "ICT",
+    image: "/business/ICT.jpg",
+    url: "/business/osp",
+  },
+  {
+    label: "정보통신설비\n유지보수",
+    image: "/business/정보통신설비유지보수.jpg",
+    url: "/business/maintenance",
+  },
+  {
+    label: "통합보안시스템",
+    image: "/business/통합보안시스템.jpg",
+    url: "/business/eco",
+  },
+  {
+    label: "제품생산\nR&D",
+    image: "/business/제품생산RND.jpg",
+    url: "/business/production",
+  },
+  {
+    label: "설계(CAD)\n견적(실행 제안)",
+    image: "/business/설계견적.jpg",
+    url: "/business/cad",
+  },
+];
+
+export default businessSections;

+ 0 - 54
src/data/businessItems.json

@@ -1,54 +0,0 @@
-[
-  {
-    "label": "정보통신공사",
-    "description": "지금의 반도산전이 있기까지\n 정보통신공사로 시작한 반도산전 30년의 탄탄한 시공능력과 도전정신이 있습니다.",
-    "description2": "정보통신공사란?\n정보통신 시스템 및 관련 설비를 설치·이전·변경·철거하거나,\n유지·보수하는 공사를 말합니다. 이는 전기공사와는 다르며,\n정보통신기술(IT) 기반의 인프라 구축에 초점을 둔 공사입니다.",
-    "image": "/business/정보통신공사.jpg",
-    "url": "/business/communication"
-  },
-  {
-    "label": "전기설비공사",
-    "image": "/business/전기설비공사.jpg",
-    "url": "/business/electric"
-  },
-  {
-    "label": "소방설비공사",
-    "image": "/business/소방설비공사.jpg",
-    "url": "/business/ict"
-  },
-  {
-    "label": "OSP 기간통신사업",
-    "image": "/business/OSP기간통신사업.jpg",
-    "url": "/business/security"
-  },
-  {
-    "label": "친환경공사",
-    "image": "/business/친환경공사.jpg",
-    "url": "/business/fire"
-  },
-  {
-    "label": "ICT",
-    "image": "/business/ICT.jpg",
-    "url": "/business/osp"
-  },
-  {
-    "label": "정보통신설비\n유지보수",
-    "image": "/business/정보통신설비유지보수.jpg",
-    "url": "/business/maintenance"
-  },
-  {
-    "label": "통합보안시스템",
-    "image": "/business/통합보안시스템.jpg",
-    "url": "/business/eco"
-  },
-  {
-    "label": "제품생산\nR&D",
-    "image": "/business/제품생산RND.jpg",
-    "url": "/business/production"
-  },
-  {
-    "label": "설계(CAD)\n견적(실행 제안)",
-    "image": "/business/설계견적.jpg",
-    "url": "/business/cad"
-  }
-]

+ 0 - 42
src/data/menuItems.json

@@ -1,42 +0,0 @@
-[
-    {
-        "label": "사업소개",
-        "href": "/business",
-        "layoutImg": "/layout/사업소개.jpg",
-        "description": "반도산전의 사업을 소개합니다.",
-        "children": [
-            { "label": "주요 사업", "href": "/business/sections" },
-            { "label": "주요 랜드마크 시공현장", "href": "/business/landmarks" }
-        ]
-    },
-    {
-        "label": "기업소개",
-        "href": "/about",
-        "layoutImg": "/layout/기업소개.jpg",
-        "description": "반도산전을 소개합니다.",
-        "children": [
-            { "label": "CEO 인사말", "href": "/ceo" },
-            { "label": "협력사", "href": "/office" },
-            { "label": "연혁", "href": "/history" },
-            { "label": "찾아오시는 길", "href": "/about/location" },
-            { "label": "조직도", "href": "/organization" },
-            { "label": "갤러리", "href": "/partners" }
-        ]
-    },
-    {
-        "label": "지속가능경영",
-        "href": "/performance",
-        "children": [
-            { "label": "준법경영", "href": "/ceo" },
-            { "label": "ESG 보고서", "href": "/history" }
-        ]
-    },
-    {
-        "label": "주요실적",
-        "href": "/support",
-        "children": [
-            { "label": "주요실적", "href": "/notice" },
-            { "label": "인증서 및 면허", "href": "/notice" }
-        ]
-    }
-]

+ 58 - 0
src/data/menuItems.ts

@@ -0,0 +1,58 @@
+interface ChildItem {
+    label: string;
+    href: string;
+}
+
+export interface MenuItem {
+    label: string;
+    href: string;
+    layoutImg?: string;
+    description?: string;
+    children: ChildItem[];
+}
+
+
+const menuItems: MenuItem[] = [
+    {
+        label: "사업소개1",
+        href: "/business",
+        layoutImg: "/layout/사업소개.jpg",
+        description: "반도산전의 사업을 소개합니다.",
+        children: [
+            { label: "주요 사업", href: "sections" },
+            { label: "주요 랜드마크 시공현장", href: "landmarks" }
+        ]
+    },
+    {
+        label: "기업소개",
+        href: "/about",
+        layoutImg: "/layout/기업소개.jpg",
+        description: "반도산전을 소개합니다.",
+        children: [
+            { label: "CEO 인사말", href: "ceo" },
+            { label: "협력사", href: "office" },
+            { label: "연혁", href: "history" },
+            { label: "찾아오시는 길", href: "location" },
+            { label: "조직도", href: "organization" },
+            { label: "갤러리", href: "partners" }
+        ]
+    },
+    {
+        label: "지속가능경영",
+        href: "/performance",
+        children: [
+            { label: "준법경영", href: "ceo" },
+            { label: "ESG 보고서", href: "history" }
+        ]
+    },
+    {
+        label: "주요실적",
+        href: "/support",
+        children: [
+            { label: "주요실적", href: "notice" },
+            { label: "인증서 및 면허", href: "license" }
+        ]
+    }
+];
+
+export default menuItems;

+ 111 - 0
src/data/partners.ts

@@ -0,0 +1,111 @@
+export interface PartnerProject {
+  title: string;
+  description: string;
+  imageUrl: string;
+}
+
+const partnerProjects: PartnerProject[] = [
+  {
+    title: "BNK 경남은행 전산 & 데이터센터",
+    description: "",
+    imageUrl: "/partners/포스코.png",
+  },
+  {
+    title: "롯데캐슬 드메르",
+    description:
+      "발주처 : 롯데건설\n부산 북항 초고층 복합개발사업 중 통신공사 (롯데 드메르)\n계약금액 : 1,351,200,000원",
+    imageUrl: "/partners/DL.png",
+  },
+  {
+    title: "협성마리나 G7",
+    description: "",
+    imageUrl: "/partners/DL.png",
+  },
+  {
+    title: "국방광대역 통합망",
+    description: "",
+    imageUrl: "/partners/DL.png",
+  },
+  {
+    title: "수원 덕산병원",
+    description: "",
+    imageUrl: "/partners/GS건설.png",
+  },
+  {
+    title: "강서구 BGF 리테일 CU 물류창고",
+    description: "",
+    imageUrl: "/partners/sk에코플랜트.png",
+  },
+  {
+    title: "기장 오시리아 메디타운",
+    description: "",
+    imageUrl: "/partners/KT.png",
+  },
+  {
+    title: "부산대학교병원 아트리움",
+    description: "",
+    imageUrl: "/partners/HDC현대산업개발.png",
+  },
+  {
+    title: "김해물류센터",
+    description: "",
+    imageUrl: "/partners/kt넷코어.png",
+  },
+  {
+    title: "롯데호텔앤리조트 김해",
+    description: "",
+    imageUrl: "/partners/DLENC.png",
+  },
+  {
+    title: "롯데리조트제주 아트발라스",
+    description: "",
+    imageUrl: "/partners/현대건설.png",
+  },
+  {
+    title: "부암 서희 스타힐스",
+    description: "",
+    imageUrl: "/partners/한화.png",
+  },
+  {
+    title: "BNK 경남은행 전산 & 데이터센터",
+    description: "",
+    imageUrl: "/partners/KT2.png",
+  },
+  {
+    title: "BNK 경남은행 전산 & 데이터센터",
+    description: "",
+    imageUrl: "/partners/현대엔지니어링.png",
+  },
+  {
+    title: "BNK 경남은행 전산 & 데이터센터",
+    description: "",
+    imageUrl: "/partners/호반.png",
+  },
+  {
+    title: "BNK 경남은행 전산 & 데이터센터",
+    description: "",
+    imageUrl: "/partners/삼성물산.png",
+  },
+  {
+    title: "BNK 경남은행 전산 & 데이터센터",
+    description: "",
+    imageUrl: "/partners/몰라.png",
+  },
+  {
+    title: "BNK 경남은행 전산 & 데이터센터",
+    description: "",
+    imageUrl: "/partners/포스코.png",
+  },
+  {
+    title: "BNK 경남은행 전산 & 데이터센터",
+    description: "",
+    imageUrl: "/partners/sk에코플랜트.png",
+  },
+  {
+    title: "BNK 경남은행 전산 & 데이터센터",
+    description: "",
+    imageUrl: "/partners/현대건설.png",
+  },
+];
+
+export default partnerProjects;

+ 0 - 102
src/data/partnersList.json

@@ -1,102 +0,0 @@
-[
-  {
-    "title": "BNK 경남은행 전산 & 데이터센터",
-    "description": "",
-    "imageUrl": "/partners/포스코.png"
-  },
-  {
-    "title": "롯데캐슬 드메르",
-    "description": "발주처 : 롯데건설\n부산 북항 초고층 복합개발사업 중 통신공사 (롯데 드메르)\n계약금액 : 1,351,200,000원",
-    "imageUrl": "/partners/DL.png"
-  },
-  {
-    "title": "협성마리나 G7",
-    "description": "",
-    "imageUrl": "/partners/DL.png"
-  },
-  {
-    "title": "국방광대역 통합망",
-    "description": "",
-    "imageUrl": "/partners/DL.png"
-  },
-  {
-    "title": "수원 덕산병원",
-    "description": "",
-    "imageUrl": "/partners/GS건설.png"
-  },
-  {
-    "title": "강서구 BGF 리테일 CU 물류창고",
-    "description": "",
-    "imageUrl": "/partners/sk에코플랜트.png"
-  },
-  {
-    "title": "기장 오시리아 메디타운",
-    "description": "",
-    "imageUrl": "/partners/KT.png"
-  },
-  {
-    "title": "부산대학교병원 아트리움",
-    "description": "",
-    "imageUrl": "/partners/HDC현대산업개발.png"
-  },
-  {
-    "title": "김해물류센터",
-    "description": "",
-    "imageUrl": "/partners/kt넷코어.png"
-  },
-  {
-    "title": "롯데호텔앤리조트 김해",
-    "description": "",
-    "imageUrl": "/partners/DLENC.png"
-  },
-  {
-    "title": "롯데리조트제주 아트발라스",
-    "description": "",
-    "imageUrl": "/partners/현대건설.png"
-  },
-  {
-    "title": "부암 서희 스타힐스",
-    "description": "",
-    "imageUrl": "/partners/한화.png"
-  },
-  {
-    "title": "BNK 경남은행 전산 & 데이터센터",
-    "description": "",
-    "imageUrl": "/partners/KT2.png"
-  },
-  {
-    "title": "BNK 경남은행 전산 & 데이터센터",
-    "description": "",
-    "imageUrl": "/partners/현대엔지니어링.png"
-  },
-  {
-    "title": "BNK 경남은행 전산 & 데이터센터",
-    "description": "",
-    "imageUrl": "/partners/호반.png"
-  },
-  {
-    "title": "BNK 경남은행 전산 & 데이터센터",
-    "description": "",
-    "imageUrl": "/partners/삼성물산.png"
-  },
-  {
-    "title": "BNK 경남은행 전산 & 데이터센터",
-    "description": "",
-    "imageUrl": "/partners/몰라.png"
-  },
-  {
-    "title": "BNK 경남은행 전산 & 데이터센터",
-    "description": "",
-    "imageUrl": "/partners/포스코.png"
-  },
-  {
-    "title": "BNK 경남은행 전산 & 데이터센터",
-    "description": "",
-    "imageUrl": "/partners/sk에코플랜트.png"
-  },
-  {
-    "title": "BNK 경남은행 전산 & 데이터센터",
-    "description": "",
-    "imageUrl": "/partners/현대건설.png"
-  }
-]

+ 96 - 0
src/data/project.ts

@@ -0,0 +1,96 @@
+export interface MajorProject {
+  title: string;
+  description: string;
+  imageUrl: string;
+  client?: string;
+  contractAmount?: string;
+  period?: string;
+}
+
+const projectList: MajorProject[] = [
+  {
+    title: "BNK 경남은행 전산 & 데이터센터",
+    description: "",
+    imageUrl: "/project/데이터센터.jpg",
+  },
+  {
+    title: "롯데캐슬 드메르",
+    client: "롯데건설",
+    contractAmount: "1,531,200,000원",
+    period: "2023.12.19 - 2024.12.31",
+    description: "부산 북항 초고층 복합개발사업 중 통신공사 (롯데 드메르)",
+    imageUrl: "/project/드메르.jpg",
+  },
+  {
+    title: "협성마리나 G7",
+    description: "",
+    imageUrl: "/project/G7.png",
+  },
+  {
+    title: "국방광대역 통합망",
+    description: "",
+    imageUrl: "/project/통합망.jpg",
+  },
+  {
+    title: "수원 덕산병원",
+    description: "",
+    imageUrl: "/project/덕산병원.jpg",
+  },
+  {
+    title: "강서구 BGF 리테일 CU 물류창고",
+    description: "",
+    imageUrl: "/project/CU물류창고.jpg",
+  },
+  {
+    title: "기장 오시리아 메디타운",
+    description: "",
+    imageUrl: "/project/메디타운.jpg",
+  },
+  {
+    title: "부산대학교병원 아트리움",
+    description: "",
+    imageUrl: "/project/아트리움.jpg",
+  },
+  {
+    title: "김해물류센터",
+    description: "",
+    imageUrl: "/project/김해물류센터.png",
+  },
+  {
+    title: "롯데호텔앤리조트 김해",
+    description: "",
+    imageUrl: "/project/롯데호텔.jpg",
+  },
+  {
+    title: "롯데리조트제주 아트발라스",
+    description: "",
+    imageUrl: "/project/롯데리조트제주.jpg",
+  },
+  {
+    title: "부암 서희 스타힐스",
+    description: "",
+    imageUrl: "/project/서희스타힐스.jpg",
+  },
+  {
+    title: "부암 서희 스타힐스",
+    description: "",
+    imageUrl: "/project/서희스타힐스.jpg",
+  },
+  {
+    title: "부암 서희 스타힐스",
+    description: "",
+    imageUrl: "/project/서희스타힐스.jpg",
+  },
+  {
+    title: "부암 서희 스타힐스",
+    description: "",
+    imageUrl: "/project/서희스타힐스.jpg",
+  },
+  {
+    title: "부암 서희 스타힐스",
+    description: "",
+    imageUrl: "/project/서희스타힐스.jpg",
+  },
+];
+
+export default projectList;

+ 0 - 85
src/data/projectCardList.json

@@ -1,85 +0,0 @@
-[
-  {
-    "title": "BNK 경남은행 전산 & 데이터센터",
-    "description": "",
-    "imageUrl": "/project/데이터센터.jpg"
-  },
-  {
-    "title": "롯데캐슬 드메르",
-    "client": "롯데건설",
-    "contractAmount": "1,531,200,000원",
-    "period": "2023.12.19 - 2024.12.31",
-    "description": "부산 북항 초고층 복합개발사업 중 통신공사 (롯데 드메르)",
-    "imageUrl": "/project/드메르.jpg"
-  },
-  {
-    "title": "협성마리나 G7",
-    "description": "",
-    "imageUrl": "/project/G7.png"
-  },
-  {
-    "title": "국방광대역 통합망",
-    "description": "",
-    "imageUrl": "/project/통합망.jpg"
-  },
-  {
-    "title": "수원 덕산병원",
-    "description": "",
-    "imageUrl": "/project/덕산병원.jpg"
-  },
-  {
-    "title": "강서구 BGF 리테일 CU 물류창고",
-    "description": "",
-    "imageUrl": "/project/CU물류창고.jpg"
-  },
-  {
-    "title": "기장 오시리아 메디타운",
-    "description": "",
-    "imageUrl": "/project/메디타운.jpg"
-  },
-  {
-    "title": "부산대학교병원 아트리움",
-    "description": "",
-    "imageUrl": "/project/아트리움.jpg"
-  },
-  {
-    "title": "김해물류센터",
-    "description": "",
-    "imageUrl": "/project/김해물류센터.png"
-  },
-  {
-    "title": "롯데호텔앤리조트 김해",
-    "description": "",
-    "imageUrl": "/project/롯데호텔.jpg"
-  },
-  {
-    "title": "롯데리조트제주 아트발라스",
-    "description": "",
-    "imageUrl": "/project/롯데리조트제주.jpg"
-  },
-  {
-    "title": "부암 서희 스타힐스",
-    "description": "",
-    "imageUrl": "/project/서희스타힐스.jpg"
-  },
-  {
-    "title": "부암 서희 스타힐스",
-    "description": "",
-    "imageUrl": "/project/서희스타힐스.jpg"
-  },
-  {
-    "title": "부암 서희 스타힐스",
-    "description": "",
-    "imageUrl": "/project/서희스타힐스.jpg"
-  },
-  {
-    "title": "부암 서희 스타힐스",
-    "description": "",
-    "imageUrl": "/project/서희스타힐스.jpg"
-  },
-  {
-    "title": "부암 서희 스타힐스",
-    "description": "",
-    "imageUrl": "/project/서희스타힐스.jpg"
-  }
-]

+ 4 - 4
src/main.jsx → src/index.tsx

@@ -3,8 +3,8 @@ import './index.css'
 import App from './App.jsx'
 import {BrowserRouter} from "react-router-dom";
 
-createRoot(document.getElementById('root')).render(
-    <BrowserRouter>
-        <App />
-    </BrowserRouter>
+createRoot(document.getElementById('root')!).render(
+  <BrowserRouter>
+    <App />
+  </BrowserRouter>
 );

+ 3 - 0
src/pages/NotFound.tsx

@@ -0,0 +1,3 @@
+export default function NotFound() {
+  return <div>페이지를 찾을 수 없습니다</div>;
+}

+ 0 - 0
src/pages/about/Location.jsx → src/pages/about/Location.tsx


+ 0 - 78
src/pages/business/Landmarks.jsx

@@ -1,78 +0,0 @@
-import React, { useState } from 'react';
-import Pagination from "@/components/Pagination.jsx";
-import projectList from "@/data/projectCardList.json";
-import {getPaginatedList} from "@/utils/pagination.js";
-import {CommonModal} from "@/components/commonModal.jsx";
-
-const ITEMS_PER_PAGE = 12;
-
-const Landmarks = () => {
-    const [isOpen, setIsOpen] = useState(false);
-    const [currentPage, setCurrentPage] = useState(1);
-    const [selectedItem, setSelectedItem] = useState(null); // 클릭된 카드
-    
-    const paginatedList = getPaginatedList(projectList, currentPage, ITEMS_PER_PAGE);
-
-    return (
-        <div>
-            <div className="relative max-w-7xl mx-auto grid grid-cols-3 gap-x-8 gap-y-10 mt-10">
-                {paginatedList.map((project, index) => (
-                    <div  
-                        key={index} 
-                        className="relative shadow-lg group cursor-pointer"
-                        onClick={() => {
-                            setSelectedItem(project);
-                            setIsOpen(true);
-                        }}
-                    >
-                        <div className="absolute inset-0 bg-gradient-to-b from-transparent to-black/60" />
-
-                        <img
-                            src={project.imageUrl}
-                            alt={project.title}
-                            className="w-full aspect-[4/3] object-cover"
-                        />
-
-                        <div className="">
-                            <h3 className='absolute z-10 left-6 top-[77%] group-hover:top-[30%] transition-all duration-500 ease-in-out text-lg text-white font-semibold'>
-                                {project.title}
-                            </h3>
-                            <div className="absolute z-10 left-6 top-[85%] text-xs text-white leading-7">MORE +</div>
-                        </div>
-
-                        <div className="absolute inset-0 bg-gradient-to-t from-black/80 opacity-0 group-hover:opacity-100 transition-opacity duration-300 ease-in-out flex flex-col justify-center">
-                            {project.description && (
-                                <div className="absolute left-6 right-6 top-[60%] group-hover:top-[40%] transition-all duration-500 ease-in-out">
-                                    <p className="text-sm text-white leading-8 whitespace-pre-line">
-                                        발주처 : {project.client}
-                                    </p>
-                                    <p className="text-sm text-white leading-8 whitespace-pre-line">
-                                        {project.description}
-                                    </p>
-                                    <p className="text-sm text-white leading-8 whitespace-pre-line">
-                                        계약금액 : {project.contractAmount}
-                                    </p>
-                                </div>
-                            )}
-                        </div>
-                    </div>
-                ))}
-            </div>
-
-            <Pagination
-                currentPage={currentPage}
-                totalItems={projectList.length}
-                itemsPerPage={ITEMS_PER_PAGE}
-                onPageChange={setCurrentPage}
-            />
-
-            <CommonModal
-                isOpen={isOpen}
-                onClose={() => setIsOpen(false)}
-                item={selectedItem}
-            />
-        </div>
-    );
-};
-
-export default Landmarks;

+ 82 - 0
src/pages/business/Landmarks.tsx

@@ -0,0 +1,82 @@
+import React, {useState} from 'react';
+import Pagination from "@/components/Pagination";
+import {getPaginatedList} from "@/utils/pagination";
+import {CommonModal} from "@/components/CommonModal";
+import projectList from "@/data/project";
+import type {MajorProject} from "@/data/project";
+
+const ITEMS_PER_PAGE = 12;
+
+const Landmarks = () => {
+  const [isOpen, setIsOpen] = useState(false);
+  const [currentPage, setCurrentPage] = useState<number>(1);
+  const [selectedItem, setSelectedItem] = useState<MajorProject | null>(null); // 클릭된 카드
+
+  const paginatedList = getPaginatedList<MajorProject>(projectList, currentPage, ITEMS_PER_PAGE);
+
+  return (
+    <div>
+      <div className="relative max-w-7xl mx-auto grid grid-cols-3 gap-x-8 gap-y-10 mt-10">
+        {paginatedList.map((project, index) => (
+          <div
+            key={index}
+            className="relative shadow-lg group cursor-pointer"
+            onClick={() => {
+              setSelectedItem(project);
+              setIsOpen(true);
+            }}
+          >
+            <div className="absolute inset-0 bg-gradient-to-b from-transparent to-black/60"/>
+
+            <img
+              src={project.imageUrl}
+              alt={project.title}
+              className="w-full aspect-[4/3] object-cover"
+            />
+
+            <div className="">
+              <h3
+                className='absolute z-10 left-6 top-[77%] group-hover:top-[30%] transition-all duration-500 ease-in-out text-lg text-white font-semibold'>
+                {project.title}
+              </h3>
+              <div className="absolute z-10 left-6 top-[85%] text-xs text-white leading-7">MORE +</div>
+            </div>
+
+            <div
+              className="absolute inset-0 bg-gradient-to-t from-black/80 opacity-0 group-hover:opacity-100 transition-opacity duration-300 ease-in-out flex flex-col justify-center">
+              {project.description && (
+                <div
+                  className="absolute left-6 right-6 top-[60%] group-hover:top-[40%] transition-all duration-500 ease-in-out">
+                  <p className="text-sm text-white leading-8 whitespace-pre-line">
+                    발주처 : {project.client}
+                  </p>
+                  <p className="text-sm text-white leading-8 whitespace-pre-line">
+                    {project.description}
+                  </p>
+                  <p className="text-sm text-white leading-8 whitespace-pre-line">
+                    계약금액 : {project.contractAmount}
+                  </p>
+                </div>
+              )}
+            </div>
+          </div>
+        ))}
+      </div>
+
+      <Pagination
+        currentPage={currentPage}
+        totalItems={projectList.length}
+        itemsPerPage={ITEMS_PER_PAGE}
+        onPageChange={setCurrentPage}
+      />
+
+      <CommonModal
+        isOpen={isOpen}
+        onClose={() => setIsOpen(false)}
+        item={selectedItem}
+      />
+    </div>
+  );
+};
+
+export default Landmarks;

+ 0 - 99
src/pages/business/Sections.jsx

@@ -1,99 +0,0 @@
-import React, {useState} from 'react';
-import businessItems from "@/data/businessItems.json";
-import DarkOverlay from "@/components/DarkOverlay.jsx";
-
-const Landmarks = () => {
-    const [selectedItem, setSelectedItem] = useState(null);
-    
-    return (
-        <div className="relative h-[1453px] px-6 py-10 flex-col">
-            <div className="relative grid grid-cols-5 gap-2 w-7xl mx-auto h-44 bg-white">
-                {businessItems.map((item, index) => (
-                    <div
-                        key={index}
-                        className="relative bg-gray-100 bg-cover bg-center cursor-pointer"
-                        style={{ backgroundImage: `url(${item.image})` }}
-                        onClick={() => setSelectedItem(item)}
-                    >
-                        <DarkOverlay />
-                        <div className="relative p-2">
-                            <div className="text-white text-lg font-semibold font-['Inter']">{item.label}</div>
-                        </div>
-                    </div>
-                ))}
-            </div>
-
-
-            {selectedItem && (
-                <>
-                    {/* 타이틀 */}
-                    <div className="h-60 flex flex-col my-10 justify-center items-center gap-9">
-                        <div className="text-center justify-center text-neutral-800 text-5xl font-extrabold font-['Inter'] leading-10">{selectedItem?.label}</div>
-                        <div className="text-center justify-center text-neutral-800 text-2xl font-medium font-['Inter'] leading-10 whitespace-pre-line">{selectedItem?.description}</div>
-                    </div>
-
-                    {/* 그림 및 설명 */}
-                    <div className="max-w-7xl mx-auto h-96 bg-zinc-300/75 flex">
-                        <div className="w-1/2 h-full">
-                            <img
-                                className="w-full h-full object-cover"
-                                src={selectedItem.image}
-                                alt="image"
-                            />
-                        </div>
-                        <div className="w-1/2 flex items-center justify-center px-4">
-                            <div className="text-black text-xl font-light font-['Noto_Sans_KR'] leading-9 whitespace-pre-line">
-                                {selectedItem?.description2}
-                            </div>
-                        </div>
-                    </div>
-
-                    {/* 중간 타이틀 */}
-                    <div className="h-28 flex flex-col justify-center my-10 items-center gap-9">
-                        <div className="text-center justify-center text-neutral-800 text-2xl font-medium font-['Inter'] leading-10">대표공사실적</div>
-                    </div>
-
-                    {/* 그외 밑에 */}
-                    <div className="relative max-w-7xl mx-auto h-128 justify-start items-start">
-                        <div className="flex items-end w-full h-full px-4 py-3 bg-gradient-to-b from-white/0 to-black/80 gap-0.5">
-                            <div className="text-white text-2xl font-bold font-['Inter']">
-                                BNK 경남은행 전산 & 데이터센터
-                            </div>
-                        </div>
-
-                        {/* 왼쪽 화살표 */}
-                        <div className="absolute left-10 top-1/2 -translate-y-1/2 w-24 h-24 flex items-center justify-center">
-                            <div className="w-14 h-14 bg-zinc-300/75 flex items-center justify-center">
-                                <span className="text-black text-lg font-thin font-['Inter']">{'<'}</span>
-                            </div>
-                        </div>
-
-                        {/* 오른쪽 화살표 */}
-                        <div className="absolute right-10 top-1/2 -translate-y-1/2 w-24 h-24 flex items-center justify-center">
-                            <div className="w-14 h-14 bg-zinc-300/75 flex items-center justify-center">
-                                <span className="text-black text-lg font-thin font-['Inter']">{'>'}</span>
-                            </div>
-                        </div>
-
-                        {/* 인디케이터 */}
-                        <div className="py-10 flex justify-center items-center gap-2">
-                            <div className="w-2 h-2 relative bg-blue-900 rounded" />
-                            <div className="w-2 h-2 relative bg-blue-900/20 rounded" />
-                            <div className="w-2 h-2 relative bg-blue-900/20 rounded" />
-                            <div className="w-2 h-2 relative bg-blue-900/20 rounded" />
-                            <div className="w-2 h-2 relative bg-blue-900/20 rounded" />
-                            <div className="w-2 h-2 relative bg-blue-900/20 rounded" />
-                            <div className="w-2 h-2 relative bg-blue-900/20 rounded" />
-                            <div className="w-2 h-2 relative bg-blue-900/20 rounded" />
-                            <div className="w-2 h-2 relative bg-blue-900/20 rounded" />
-                            <div className="w-2 h-2 relative bg-blue-900/20 rounded" />
-                            <div className="w-2 h-2 relative bg-blue-900/20 rounded" />
-                        </div>
-                    </div>
-                </>
-            )}
-        </div>
-    );
-};
-
-export default Landmarks;

+ 105 - 0
src/pages/business/sections.tsx

@@ -0,0 +1,105 @@
+import React, { useState } from 'react';
+import businessItems from "@/data/business";
+import type { BusinessItem } from "@/data/business";
+import DarkOverlay from "@/components/DarkOverlay";
+
+
+const Landmarks = () => {
+  const [selectedItem, setSelectedItem] = useState<BusinessItem | null>(null);
+
+  return (
+    <div className="relative h-[1453px] px-6 py-10 flex-col">
+      <div className="relative grid grid-cols-5 gap-2 w-7xl mx-auto h-44 bg-white">
+        {businessItems.map((item, index) => (
+          <div
+            key={index}
+            className="relative bg-gray-100 bg-cover bg-center cursor-pointer"
+            style={{backgroundImage: `url(${item.image})`}}
+            onClick={() => setSelectedItem(item)}
+          >
+            <DarkOverlay/>
+            <div className="relative p-2">
+              <div className="text-white text-lg font-semibold font-['Inter']">{item.label}</div>
+            </div>
+          </div>
+        ))}
+      </div>
+
+
+      {selectedItem && (
+        <>
+          {/* 타이틀 */}
+          <div className="h-60 flex flex-col my-10 justify-center items-center gap-9">
+            <div
+              className="text-center justify-center text-neutral-800 text-5xl font-extrabold font-['Inter'] leading-10">{selectedItem?.label}</div>
+            <div
+              className="text-center justify-center text-neutral-800 text-2xl font-medium font-['Inter'] leading-10 whitespace-pre-line">{selectedItem?.description}</div>
+          </div>
+
+          {/* 그림 및 설명 */}
+          <div className="max-w-7xl mx-auto h-96 bg-zinc-300/75 flex">
+            <div className="w-1/2 h-full">
+              <img
+                className="w-full h-full object-cover"
+                src={selectedItem.image}
+                alt="image"
+              />
+            </div>
+            <div className="w-1/2 flex items-center justify-center px-4">
+              <div className="text-black text-xl font-light font-['Noto_Sans_KR'] leading-9 whitespace-pre-line">
+                {selectedItem?.description2}
+              </div>
+            </div>
+          </div>
+
+          {/* 중간 타이틀 */}
+          <div className="h-28 flex flex-col justify-center my-10 items-center gap-9">
+            <div
+              className="text-center justify-center text-neutral-800 text-2xl font-medium font-['Inter'] leading-10">대표공사실적
+            </div>
+          </div>
+
+          {/* 그외 밑에 */}
+          <div className="relative max-w-7xl mx-auto h-128 justify-start items-start">
+            <div className="flex items-end w-full h-full px-4 py-3 bg-gradient-to-b from-white/0 to-black/80 gap-0.5">
+              <div className="text-white text-2xl font-bold font-['Inter']">
+                BNK 경남은행 전산 & 데이터센터
+              </div>
+            </div>
+
+            {/* 왼쪽 화살표 */}
+            <div className="absolute left-10 top-1/2 -translate-y-1/2 w-24 h-24 flex items-center justify-center">
+              <div className="w-14 h-14 bg-zinc-300/75 flex items-center justify-center">
+                <span className="text-black text-lg font-thin font-['Inter']">{'<'}</span>
+              </div>
+            </div>
+
+            {/* 오른쪽 화살표 */}
+            <div className="absolute right-10 top-1/2 -translate-y-1/2 w-24 h-24 flex items-center justify-center">
+              <div className="w-14 h-14 bg-zinc-300/75 flex items-center justify-center">
+                <span className="text-black text-lg font-thin font-['Inter']">{'>'}</span>
+              </div>
+            </div>
+
+            {/* 인디케이터 */}
+            <div className="py-10 flex justify-center items-center gap-2">
+              <div className="w-2 h-2 relative bg-blue-900 rounded"/>
+              <div className="w-2 h-2 relative bg-blue-900/20 rounded"/>
+              <div className="w-2 h-2 relative bg-blue-900/20 rounded"/>
+              <div className="w-2 h-2 relative bg-blue-900/20 rounded"/>
+              <div className="w-2 h-2 relative bg-blue-900/20 rounded"/>
+              <div className="w-2 h-2 relative bg-blue-900/20 rounded"/>
+              <div className="w-2 h-2 relative bg-blue-900/20 rounded"/>
+              <div className="w-2 h-2 relative bg-blue-900/20 rounded"/>
+              <div className="w-2 h-2 relative bg-blue-900/20 rounded"/>
+              <div className="w-2 h-2 relative bg-blue-900/20 rounded"/>
+              <div className="w-2 h-2 relative bg-blue-900/20 rounded"/>
+            </div>
+          </div>
+        </>
+      )}
+    </div>
+  );
+};
+
+export default Landmarks;

+ 2 - 2
src/pages/main/Business.jsx → src/pages/main/Business.tsx

@@ -1,8 +1,8 @@
 import React from 'react';
 import DarkOverlay from "@/components/DarkOverlay.jsx";
-import businessItems from '@/data/businessItems.json';
+import businessItems from '@/data/business';
 
-const RankBox = (title, rank, topPercent) => (
+const RankBox = (title: string, rank: string, topPercent: string) => (
     <div className="w-80 inline-flex flex-col">
         <div className="h-7 mb-10 relative">
             <div className="w-36 left-[84px] top-0 absolute text-center justify-start text-black/40 text-2xl font-medium font-['Inter']">

+ 10 - 5
src/pages/main/Directions.jsx → src/pages/main/Directions.tsx

@@ -1,12 +1,17 @@
 import React, { useState, useEffect, useRef } from 'react';
 
+interface DirectionsProps {
+    disableTitle?: boolean;
+}
+
 const tabs = [
     { id: 'busan', label: '부산 본사' },
     { id: 'gimhae', label: '김해 공장' },
     { id: 'seoul', label: '서울 경기지사' },
 ];
 
-const tabContent = {
+
+const tabContent: Record<string, { lat: number; lng: number; address: string; phone: string }> = {
     busan: {
         lat: 35.170833,
         lng: 129.130833,
@@ -27,11 +32,11 @@ const tabContent = {
     },
 };
 
-const Directions = ({disableTitle}) => {
+const Directions: React.FC<DirectionsProps> = ({ disableTitle }) => {
     const [activeTab, setActiveTab] = useState('busan');
-    const mapRef = useRef(null);
-    const mapInstanceRef = useRef(null);
-    const markerRef = useRef(null); // ✅ 마커 참조 추가
+    const mapRef = useRef<HTMLDivElement>(null);
+    const mapInstanceRef = useRef<naver.maps.Map>(null);
+    const markerRef = useRef<naver.maps.Marker>(null);
 
     useEffect(() => {
         const { naver } = window;

+ 0 - 0
src/pages/main/Footer.jsx → src/pages/main/Footer.tsx


+ 2 - 2
src/pages/main/Hero.jsx → src/pages/main/Hero.tsx

@@ -20,7 +20,7 @@ const Hero = () => {
         return () => clearInterval(timer);
     }, [currentImageIndex]);
 
-    const handleDotClick = (index) => {
+    const handleDotClick = (index: number) => {
         setCurrentImageIndex(index);
     };
 
@@ -52,7 +52,7 @@ const Hero = () => {
 
             {/* 🔹 인디케이터 */}
             <div className="absolute bottom-[12vh] inline-flex gap-2">
-                {images.map((_, index) => (
+                {images.map((_, index: number) => (
                     <div
                         key={index}
                         onClick={() => handleDotClick(index)}

+ 1 - 1
src/pages/main/Partners.jsx → src/pages/main/Partners.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import DarkOverlay from "@/components/DarkOverlay.jsx";
-import partnerList from '@/data/partnersList.json';
+import partnerList from '@/data/partners';
 
 const Partners = () => {
     return (

+ 1 - 1
src/pages/main/Project.jsx → src/pages/main/Project.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import projectList from '@/data/projectCardList.json';
+import projectList from '@/data/project';
 
 const Project = () => {
     return (

+ 0 - 0
src/pages/main/index.jsx → src/pages/main/index.tsx


+ 0 - 13
src/utils/pagination.js

@@ -1,13 +0,0 @@
-/**
- * 페이지네이션된 리스트 반환 유틸
- *
- * @param {Array} list - 전체 리스트
- * @param {number} currentPage - 현재 페이지 번호 (1부터 시작)
- * @param {number} itemsPerPage - 페이지당 항목 수
- * @returns {Array} 페이징 처리된 리스트
- */
-export function getPaginatedList(list, currentPage, itemsPerPage) {
-    const startIndex = (currentPage - 1) * itemsPerPage;
-    const endIndex = currentPage * itemsPerPage;
-    return list.slice(startIndex, endIndex);
-}

+ 12 - 0
src/utils/pagination.ts

@@ -0,0 +1,12 @@
+/**
+ * 페이지네이션된 리스트 반환 유틸
+ * @param list 전체 리스트
+ * @param currentPage 현재 페이지 번호 (1부터 시작)
+ * @param itemsPerPage 페이지당 항목 수
+ * @return 페이징 처리된 리스트
+ */
+export function getPaginatedList<T>(list: T[], currentPage: number, itemsPerPage: number): T[] {
+    const startIndex = (currentPage - 1) * itemsPerPage;
+    const endIndex = currentPage * itemsPerPage;
+    return list.slice(startIndex, endIndex);
+}

+ 123 - 0
tsconfig.json

@@ -0,0 +1,123 @@
+{
+  "compilerOptions": {
+    /* Visit https://aka.ms/tsconfig to read more about this file */
+
+    /* Projects */
+    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
+    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
+    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
+    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
+    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
+    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
+
+    /* Language and Environment */
+    "target": "esnext",
+    /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
+    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
+    "jsx": "react-jsx",                                /* Specify what JSX code is generated. */
+    // "libReplacement": true,                           /* Enable lib replacement. */
+    // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
+    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
+    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
+    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
+    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
+    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
+    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
+    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
+    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
+
+    /* Modules */
+    "module": "esnext", // commonjs
+    /* Specify what module code is generated. */
+    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
+    "moduleResolution": "bundler",                     /* Specify how TypeScript looks up a file from a given module specifier. */
+    "baseUrl": ".",
+    /* Specify the base directory to resolve non-relative module names. */
+    "paths": {
+      "@/*": ["src/*"]
+    },
+    /* Specify a set of entries that re-map imports to additional lookup locations. */
+    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
+    "typeRoots": ["./node_modules/@types", "./src/types"],                                  /* Specify multiple folders that act like './node_modules/@types'. */
+    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
+    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
+    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
+    // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
+    // "rewriteRelativeImportExtensions": true,          /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
+    // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
+    // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
+    // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
+    // "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
+    // "resolveJsonModule": true,                        /* Enable importing .json files. */
+    // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
+    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
+
+    /* JavaScript Support */
+    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
+    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
+    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
+
+    /* Emit */
+    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
+    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
+    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
+    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
+    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
+    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
+    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
+    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
+    // "removeComments": true,                           /* Disable emitting comments. */
+    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
+    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
+    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
+    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
+    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
+    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
+    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
+    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
+    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
+    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
+    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
+    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
+
+    /* Interop Constraints */
+    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
+    // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
+    // "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
+    // "erasableSyntaxOnly": true,                       /* Do not allow runtime constructs that are not part of ECMAScript. */
+    "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
+    "esModuleInterop": true,
+    /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
+    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
+    "forceConsistentCasingInFileNames": true,
+    /* Ensure that casing is correct in imports. */
+
+    /* Type Checking */
+    "strict": true,
+    /* Enable all strict type-checking options. */
+    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
+    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
+    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
+    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
+    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
+    // "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
+    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
+    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
+    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
+    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
+    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
+    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
+    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
+    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
+    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
+    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
+    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
+    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
+    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
+
+    /* Completeness */
+    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
+    "skipLibCheck": true
+    /* Skip type checking all .d.ts files. */
+  }
+}