home 5 ヶ月 前
コミット
bff945ee27
1 ファイル変更175 行追加128 行削除
  1. 175 128
      src/components/main/Business.tsx

+ 175 - 128
src/components/main/Business.tsx

@@ -1,156 +1,203 @@
-import React, { useState, useEffect } from 'react';
+import React from 'react';
 import DarkOverlay from "@/components/common/DarkOverlay.jsx";
 import businessItems from '@/data/business';
 import {useScrollAnimation} from "@/hooks/useScrollAnimation";
-import { useCountUp } from "@/hooks/useCountUp";
-import { useNavigate } from 'react-router-dom';
+import {useCountUp} from "@/hooks/useCountUp";
+import {useNavigate} from 'react-router-dom';
 
-const RankBox = (title: string, rank: string, topPercent: string, isVisible: boolean) => {
-    const rankNumber = parseInt(rank.replace('위', ''));
-    const percentNumber = parseInt(topPercent.replace('%', ''));
+const RankSection = ({title, rank, topPercent, visible, colorPrimary, colorSecondary}: {
+  title: string;
+  rank: string;
+  topPercent: string;
+  visible: boolean;
+  colorPrimary: string;
+  colorSecondary?: string;
+}) => {
+  const rankNumber = parseInt(rank.replace("위", ""));
+  const percentNumber = parseInt(topPercent.replace("%", ""));
 
-    const animatedRank = useCountUp({
-        end: rankNumber,
-        start: rankNumber === 1 ? 100 : 50,
-        duration: 2000,
-        isVisible
-    });
+  const animatedRank = useCountUp({
+    end: rankNumber,
+    start: rankNumber === 1 ? 100 : 50,
+    duration: 2000,
+    isVisible: visible,
+  });
 
-    const animatedPercent = useCountUp({
-        end: percentNumber,
-        start: 20,
-        duration: 2000,
-        isVisible
-    });
+  const animatedPercent = useCountUp({
+    end: percentNumber,
+    start: 20,
+    duration: 2000,
+    isVisible: visible,
+  });
 
-    return (
+  const secondary = colorSecondary ?? colorPrimary;
+
+  return (
+    <>
+      <div className="flex justify-center items-center">
+        <svg
+          width={40}
+          height={41}
+          viewBox="0 0 98 99"
+          fill="none"
+          xmlns="http://www.w3.org/2000/svg"
+          className="w-8 h-8 sm:w-10 sm:h-10 lg:w-16 lg:h-16"
+          aria-hidden
+        >
+          <path d="M40.833 45.417H65.333L89.793 4.583H65.293L40.833 45.417Z" fill={colorPrimary}/>
+          <path d="M57.126 45.417H32.626L8.167 4.583h24.5L57.126 45.417Z" fill={secondary}/>
+          <path
+            d="M49 94.417c15.786 0 28.583-12.797 28.583-28.583 0-15.786-12.797-28.583-28.583-28.583S20.417 50.048 20.417 65.834 33.214 94.417 49 94.417Z"
+            fill="#E7ECEF"/>
+        </svg>
+      </div>
+
+      <div className="flex justify-center">
         <div className="flex flex-col items-center justify-center w-full max-w-[280px] mx-auto">
-            <div className="text-center text-black/40 text-xs sm:text-lg lg:text-2xl font-semibold font-['Noto_Sans_KR'] mb-3 sm:mb-6 lg:mb-10 antialiased">
-                {title}
+          <div className="text-center text-black/40 text-xs sm:text-lg lg:text-2xl font-semibold font-['Noto_Sans_KR'] mb-3 sm:mb-6 lg:mb-10 antialiased">
+            {title}
+          </div>
+          <div className="flex w-full justify-center items-center mb-2 sm:mb-4">
+            <div className="text-black/40 text-sm sm:text-lg lg:text-3xl font-semibold font-['Noto_Sans_KR'] mr-4 sm:mr-6 antialiased">
+              지역
             </div>
-            <div className="flex w-full justify-center items-center mb-2 sm:mb-4">
-                <div className="text-black/40 text-sm sm:text-lg lg:text-3xl font-semibold font-['Noto_Sans_KR'] mr-4 sm:mr-6 antialiased">
-                    지역
-                </div>
-                <div className="text-blue-900 text-xl sm:text-3xl lg:text-6xl font-bold font-['Noto_Sans_KR'] antialiased" style={{opacity: animatedRank.opacity}}>
-                    {animatedRank.count}<span className="text-lg sm:text-xl lg:text-3xl ml-1">위</span>
-                </div>
+            <div className="flex text-blue-900 text-xl sm:text-3xl lg:text-6xl font-bold font-['Noto_Sans_KR'] antialiased" style={{opacity: animatedRank.opacity}}>
+              {animatedRank.count}
+              <span className="text-lg sm:text-xl lg:text-3xl ml-1">위</span>
             </div>
-            <div className="flex w-full justify-center items-center">
-                <div className="text-black/40 text-sm sm:text-lg lg:text-3xl font-semibold font-['Noto_Sans_KR'] mr-4 sm:mr-6 antialiased">
-                    상위
-                </div>
-                <div className="text-blue-900 text-xl sm:text-3xl lg:text-6xl font-bold font-['Noto_Sans_KR'] antialiased" style={{opacity: animatedPercent.opacity}}>
-                    {animatedPercent.count}<span className="text-lg sm:text-xl lg:text-3xl ml-1">%</span>
-                </div>
+          </div>
+          <div className="flex w-full justify-center items-center">
+            <div className="text-black/40 text-sm sm:text-lg lg:text-3xl font-semibold font-['Noto_Sans_KR'] mr-4 sm:mr-6 antialiased">
+              상위
+            </div>
+            <div
+              className="text-blue-900 text-xl sm:text-3xl lg:text-6xl font-bold font-['Noto_Sans_KR'] antialiased"
+              style={{opacity: animatedPercent.opacity}}
+            >
+              {animatedPercent.count}
+              <span className="text-lg sm:text-xl lg:text-3xl ml-1">%</span>
             </div>
+          </div>
         </div>
-    );
+      </div>
+    </>
+  );
 };
 
+const rankItems = [
+  {
+    title: "소방공사",
+    rank: "15위",
+    topPercent: "5%",
+    colorPrimary: "#CC0000",
+  },
+  {
+    title: "통신공사",
+    rank: "1위",
+    topPercent: "1%",
+    colorPrimary: "#005CFF",
+    colorSecondary: "#1E5CFF",
+  },
+  {
+    title: "전기공사",
+    rank: "9위",
+    topPercent: "2%",
+    colorPrimary: "#FFCC00",
+  },
+];
 
 const Business = () => {
-    const { ref: businessRef, isVisible: businessVisible } = useScrollAnimation({ threshold: 0.1 });
-    const { ref: rankRef, isVisible: rankVisible } = useScrollAnimation({ threshold: 0.1 });
-    const navigate = useNavigate();
+  const {ref: businessRef, isVisible: businessVisible} = useScrollAnimation({threshold: 0.1});
+  const {ref: rankRef, isVisible: rankVisible} = useScrollAnimation({threshold: 0.1});
+  const navigate = useNavigate();
 
-    const years27 = useCountUp({ end: 27, start: 0, duration: 2000, isVisible: rankVisible });
-    const years947 = useCountUp({ end: 947, start: 0, duration: 2500, isVisible: rankVisible });
-    const rank1 = useCountUp({ end: 1, start: 30, duration: 2000, isVisible: rankVisible });
+  const years27 = useCountUp({end: 27, start: 0, duration: 2000, isVisible: rankVisible});
+  const years947 = useCountUp({end: 947, start: 0, duration: 2500, isVisible: rankVisible});
+  const rank1 = useCountUp({end: 1, start: 30, duration: 2000, isVisible: rankVisible});
 
-    const handleCardClick = (item: any) => {
-        navigate('/business/sections', { state: { selectedItem: item } });
-    };
+  const handleCardClick = (item: any) => {
+    navigate('/business/sections', {state: {selectedItem: item}});
+  };
 
-    return (
-        <div className="max-w-7xl mx-auto px-[2vw] py-[4vw] space-y-[5vw]">
-            <div ref={businessRef} className={`transition-all duration-1000 ${businessVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'}`}>
-                <div className="space-y-3 mb-4">
-                    <div className="text-blue-900 text-2xl sm:text-3xl lg:text-4xl font-bold font-['Noto_Sans_KR'] antialiased">반도산전 Business Solution</div>
-                    <div className="text-zinc-500 text-sm sm:text-md font-semibold font-['Noto_Sans_KR'] antialiased">반도산전이 선도하는 힘</div>
-                </div>
+  return (
+    <div className="max-w-7xl mx-auto px-[2vw] py-[4vw] space-y-[5vw]">
+      <div ref={businessRef}
+           className={`transition-all duration-1000 ${businessVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'}`}>
+        <div className="space-y-3 mb-4">
+          <div
+            className="text-blue-900 text-2xl sm:text-3xl lg:text-4xl font-bold font-['Noto_Sans_KR'] antialiased">반도산전
+            Business Solution
+          </div>
+          <div className="text-zinc-500 text-sm sm:text-md font-semibold font-['Noto_Sans_KR'] antialiased">반도산전이 선도하는
+            힘
+          </div>
+        </div>
 
-                <div className="bg-white flex flex-wrap gap-2 sm:gap-[0.3vw] w-full">
-                    {businessItems.map((item, index) => (
-                        <div
-                            key={index}
-                            onClick={() => handleCardClick(item)}
-                            className="relative flex-[0_0_calc(50%-4px)] sm:flex-[0_0_calc(33.33%-8px)] lg:flex-[0_0_calc((100%-24px)/5)] bg-cover bg-center h-36 sm:h-40 lg:h-44 cursor-pointer"
-                            style={{backgroundImage: `url(${item.image})`}}
-                        >
-                            <DarkOverlay />
-                            
-                            <div className="relative p-3 sm:p-4 h-full flex flex-col justify-end">
-                                <div className="text-white text-sm sm:text-base lg:text-lg font-bold font-['Noto_Sans_KR'] antialiased leading-tight mb-1">
-                                    {item.label.split('\n').map((line, idx) => (
-                                        <div key={idx}>{line}</div>
-                                    ))}
-                                </div>
-                                <div className="text-white text-xs sm:text-sm lg:text-base font-medium font-['Noto_Sans_KR'] antialiased">상세보기</div>
-                            </div>
-                        </div>
-                    ))}
-                </div>
-            </div>
+        <div className="bg-white flex flex-wrap gap-2 sm:gap-[0.3vw] w-full">
+          {businessItems.map((item, index) => (
+            <div
+              key={index}
+              onClick={() => handleCardClick(item)}
+              className="relative flex-[0_0_calc(50%-4px)] sm:flex-[0_0_calc(33.33%-8px)] lg:flex-[0_0_calc((100%-24px)/5)] bg-cover bg-center h-36 sm:h-40 lg:h-44 cursor-pointer"
+              style={{backgroundImage: `url(${item.image})`}}
+            >
+              <DarkOverlay/>
 
-            <div ref={rankRef} className={`transition-all duration-1000 ${rankVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'}`}>
-                <div className="space-y-2 mb-2.5 pb-6">
-                    <div className="text-2xl sm:text-3xl lg:text-4xl text-blue-900 font-bold font-['Noto_Sans_KR'] antialiased">반도산전은</div>
+              <div className="relative p-3 sm:p-4 h-full flex flex-col justify-end">
+                <div
+                  className="text-white text-sm sm:text-base lg:text-lg font-bold font-['Noto_Sans_KR'] antialiased leading-tight mb-1">
+                  {item.label.split('\n').map((line, idx) => (
+                    <div key={idx}>{line}</div>
+                  ))}
                 </div>
-                <div>
-                    <div className="text-center mb-10 sm:mb-15 px-4">
-                        <div className="text-zinc-600 font-semibold font-['Noto_Sans_KR'] leading-loose sm:leading-[60px] text-xs sm:text-sm lg:text-lg xl:text-xl antialiased">
-                            1997년부터 2025년까지 <span className="text-blue-900 text-lg sm:text-xl lg:text-2xl xl:text-3xl" style={{opacity: years27.opacity}}>{years27.count}</span>년간 축적된 기술력과 신뢰를 바탕으로,
-                        </div>
-                        <div className="text-zinc-600 font-semibold font-['Noto_Sans_KR'] leading-loose sm:leading-[60px] text-xs sm:text-sm lg:text-lg xl:text-xl antialiased">
-                            부산 · 울산 · 경남 시공능력평가 <span className="text-blue-900 text-lg sm:text-xl lg:text-2xl xl:text-3xl" style={{opacity: rank1.opacity}}>{rank1.count}</span>위를 달성하였으며,
-                        </div>
-                        <div className="text-zinc-600 font-semibold font-['Noto_Sans_KR'] leading-loose sm:leading-[60px] text-xs sm:text-sm lg:text-lg xl:text-xl antialiased">
-                            <span className="text-blue-900 text-lg sm:text-xl lg:text-2xl xl:text-3xl" style={{opacity: years947.opacity}}>{years947.count}</span>억 매출을 달성한 외감법인입니다.
-                        </div>
-                    </div>
-                    <div className="flex flex-wrap justify-center items-center gap-3 sm:gap-4 lg:gap-8 max-w-6xl mx-auto">
-                        <div className="flex justify-center items-center">
-                            <svg width="40" height="41" viewBox="0 0 98 99" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-8 h-8 sm:w-10 sm:h-10 lg:w-16 lg:h-16">
-                                <path d="M40.8333 45.4166H65.3333L89.7925 4.58325H65.2925L40.8333 45.4166Z" fill="#CC0000"/>
-                                <path d="M57.1258 45.4166H32.6258L8.16666 4.58325H32.6667L57.1258 45.4166Z" fill="#CC0000"/>
-                                <path d="M49 94.4167C64.7862 94.4167 77.5833 81.6195 77.5833 65.8333C77.5833 50.0472 64.7862 37.25 49 37.25C33.2139 37.25 20.4167 50.0472 20.4167 65.8333C20.4167 81.6195 33.2139 94.4167 49 94.4167Z" fill="#E7ECEF"/>
-                            </svg>
-                        </div>
-                        <div className="flex justify-center">
-                            {RankBox("소방공사", "15위", "5%", rankVisible)}
-                        </div>
-                        
-                        <div className="w-px h-16 lg:h-24 bg-gray-200"></div>
-
-                        <div className="flex justify-center items-center">
-                            <svg width="40" height="41" viewBox="0 0 98 99" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-8 h-8 sm:w-10 sm:h-10 lg:w-16 lg:h-16">
-                                <path d="M40.8333 45.4166H65.3333L89.7925 4.58325H65.2925L40.8333 45.4166Z" fill="#005CFF"/>
-                                <path d="M57.1258 45.4166H32.6258L8.16666 4.58325H32.6667L57.1258 45.4166Z" fill="#1E5CFF"/>
-                                <path d="M49 94.4167C64.7862 94.4167 77.5833 81.6195 77.5833 65.8333C77.5833 50.0472 64.7862 37.25 49 37.25C33.2139 37.25 20.4167 50.0472 20.4167 65.8333C20.4167 81.6195 33.2139 94.4167 49 94.4167Z" fill="#E7ECEF"/>
-                            </svg>
-                        </div>
-                        
-                        <div className="flex justify-center">
-                            {RankBox("통신공사", "1위", "1%", rankVisible)}
-                        </div>
-                        
-                        <div className="w-px h-16 lg:h-24 bg-gray-200"></div>
-                        <div className="flex justify-center items-center">
-                            <svg width="40" height="41" viewBox="0 0 98 99" fill="none" xmlns="http://www.w3.org/2000/svg" className="w-8 h-8 sm:w-10 sm:h-10 lg:w-16 lg:h-16">
-                                <path d="M40.8333 45.4166H65.3333L89.7925 4.58325H65.2925L40.8333 45.4166Z" fill="#FFCC00"/>
-                                <path d="M57.1258 45.4166H32.6258L8.16666 4.58325H32.6667L57.1258 45.4166Z" fill="#FFCC00"/>
-                                <path d="M49 94.4167C64.7862 94.4167 77.5833 81.6195 77.5833 65.8333C77.5833 50.0472 64.7862 37.25 49 37.25C33.2139 37.25 20.4167 50.0472 20.4167 65.8333C20.4167 81.6195 33.2139 94.4167 49 94.4167Z" fill="#E7ECEF"/>
-                            </svg>
-                        </div>
-                        <div className="flex justify-center">
-                            {RankBox("전기공사", "9위", "2%", rankVisible)}
-                        </div>
-                    </div>
+                <div
+                  className="text-white text-xs sm:text-sm lg:text-base font-medium font-['Noto_Sans_KR'] antialiased">상세보기
                 </div>
+              </div>
+            </div>
+          ))}
+        </div>
+      </div>
+
+      <div ref={rankRef}
+           className={`transition-all duration-1000 ${rankVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'}`}>
+        <div className="space-y-2 mb-2.5 pb-6">
+          <div
+            className="text-2xl sm:text-3xl lg:text-4xl text-blue-900 font-bold font-['Noto_Sans_KR'] antialiased">반도산전은
+          </div>
+        </div>
+        <div>
+          <div className="text-center mb-10 sm:mb-15 px-4">
+            <div
+              className="text-zinc-600 font-semibold font-['Noto_Sans_KR'] leading-loose sm:leading-[60px] text-xs sm:text-sm lg:text-lg xl:text-xl antialiased">
+              1997년부터 2025년까지 <span className="text-blue-900 text-lg sm:text-xl lg:text-2xl xl:text-3xl"
+                                    style={{opacity: years27.opacity}}>{years27.count}</span>년간 축적된 기술력과 신뢰를 바탕으로,
+            </div>
+            <div
+              className="text-zinc-600 font-semibold font-['Noto_Sans_KR'] leading-loose sm:leading-[60px] text-xs sm:text-sm lg:text-lg xl:text-xl antialiased">
+              부산 · 울산 · 경남 시공능력평가 <span className="text-blue-900 text-lg sm:text-xl lg:text-2xl xl:text-3xl"
+                                        style={{opacity: rank1.opacity}}>{rank1.count}</span>위를 달성하였으며,
+            </div>
+            <div
+              className="text-zinc-600 font-semibold font-['Noto_Sans_KR'] leading-loose sm:leading-[60px] text-xs sm:text-sm lg:text-lg xl:text-xl antialiased">
+              <span className="text-blue-900 text-lg sm:text-xl lg:text-2xl xl:text-3xl"
+                    style={{opacity: years947.opacity}}>{years947.count}</span>억 매출을 달성한 외감법인입니다.
             </div>
+          </div>
+          <div className="flex justify-center items-center gap-3 sm:gap-4 lg:gap-8 max-w-6xl mx-auto overflow-auto">
+            {rankItems.map((item, idx) => (
+              <React.Fragment key={item.title}>
+                <RankSection {...item} visible={rankVisible}/>
+                {idx < rankItems.length - 1 && (
+                  <div className="w-px h-16 lg:h-24 bg-gray-200"></div>
+                )}
+              </React.Fragment>
+            ))}
+          </div>
         </div>
-    )
+      </div>
+    </div>
+  )
 }
 
 export default Business;