From 454882392b52ff6271b64dd6fca42bd79eeaa4c7 Mon Sep 17 00:00:00 2001 From: inmake Date: Wed, 21 Jan 2026 19:11:32 +0500 Subject: [PATCH] Refactor FloorsPage component to streamline floor selection and rendering; replace individual floor components with a centralized FloorPlanViewer for improved maintainability. Introduce new floor data structure in Project type for better data management. --- .../floor-plans/AmenitiesFloorView.tsx | 142 ++++++++ .../floor-plans/FloorPlanViewer.tsx | 46 +++ .../floor-plans/ResidentialFloorView.tsx | 283 +++++++++++++++ .../floor-plans/ViewToggleButtons.tsx | 47 +++ src/data/floors/dubai-marina.ts | 156 ++++++++ src/data/floors/marasi-drive.ts | 207 +++++++++++ src/pages/FloorsPage.tsx | 336 ++---------------- src/types/Floor.ts | 36 ++ src/types/Project.ts | 2 + 9 files changed, 950 insertions(+), 305 deletions(-) create mode 100644 src/components/floor-plans/AmenitiesFloorView.tsx create mode 100644 src/components/floor-plans/FloorPlanViewer.tsx create mode 100644 src/components/floor-plans/ResidentialFloorView.tsx create mode 100644 src/components/floor-plans/ViewToggleButtons.tsx create mode 100644 src/data/floors/dubai-marina.ts create mode 100644 src/data/floors/marasi-drive.ts create mode 100644 src/types/Floor.ts diff --git a/src/components/floor-plans/AmenitiesFloorView.tsx b/src/components/floor-plans/AmenitiesFloorView.tsx new file mode 100644 index 0000000..93ab6b9 --- /dev/null +++ b/src/components/floor-plans/AmenitiesFloorView.tsx @@ -0,0 +1,142 @@ +import { useState } from "react"; +import { AmenitiesFloorData } from "../../types/Floor"; +import Badge from "../ui/Badge"; +import Button from "../ui/Button"; +import PlayIcon from "../icons/PlayIcon"; +import useModalStore from "../../stores/useModalStore"; +import VideoModal from "../VideoModal"; +import ViewToggleButtons from "./ViewToggleButtons"; +import AmentitiesBadge from "../AmentitiesCard"; +import AmenitiesBadge from "../icons/AmenitiesBadge"; +import AmentitiesContentSlider from "../AmentitiesContentSlider"; + +interface AmenitiesFloorViewProps { + floor: AmenitiesFloorData; +} + +function AmenitiesFloorView({ floor }: AmenitiesFloorViewProps) { + const { setModal } = useModalStore(); + const [currentView, setCurrentView] = useState<"exterior" | "interior">( + "exterior" + ); + + const hasInteriorView = !!floor.images.interior; + const currentImage = + currentView === "interior" && floor.images.interior + ? floor.images.interior + : floor.images.main; + + // Split amenities into indoor and outdoor if counts are provided + const hasIndoorOutdoorSplit = + floor.amenitiesCount.indoor !== undefined && + floor.amenitiesCount.outdoor !== undefined; + + const indoorAmenities = hasIndoorOutdoorSplit + ? floor.amenitiesList.slice(0, floor.amenitiesCount.indoor) + : []; + + const outdoorAmenities = hasIndoorOutdoorSplit + ? floor.amenitiesList.slice(floor.amenitiesCount.indoor) + : floor.amenitiesList; + + return ( +
+
+
+

{floor.displayName}

+ +
+ + {hasIndoorOutdoorSplit && ( +
+ + +
+ )} + + + +
+ + {floor.name} + {floor.video && ( + + )} +
+
+ + {hasIndoorOutdoorSplit && indoorAmenities.length > 0 && ( +
+

Indoor Amenities

+
+ {indoorAmenities.map((amenity, index) => ( + + ))} +
+
+ )} + + {hasIndoorOutdoorSplit && indoorAmenities.length > 0 && ( +
+ )} + +
+

+ {hasIndoorOutdoorSplit ? "Outdoor Amenities" : "Amenities"} +

+
+ {outdoorAmenities.map((amenity, index) => ( + + ))} +
+
+ + {floor.images.content.length > 1 ? ( + + ) : ( + floor.images.content.length === 1 && ( + {floor.name} + ) + )} +
+ ); +} + +export default AmenitiesFloorView; diff --git a/src/components/floor-plans/FloorPlanViewer.tsx b/src/components/floor-plans/FloorPlanViewer.tsx new file mode 100644 index 0000000..e596b86 --- /dev/null +++ b/src/components/floor-plans/FloorPlanViewer.tsx @@ -0,0 +1,46 @@ +import { FloorData } from "../../types/Floor"; +import { Unit } from "../../types/IUnit"; +import { ComplexName } from "../../types/ComplexName"; +import { FloorsData } from "../FloorSelect"; +import ResidentialFloorView from "./ResidentialFloorView"; +import AmenitiesFloorView from "./AmenitiesFloorView"; + +interface FloorPlanViewerProps { + floor: FloorData; + complexName: ComplexName; + unitsOnFloor?: Unit[]; + floorsData?: FloorsData[]; + selectedFloor: string; + onFloorSelect: (floor: string) => void; +} + +function FloorPlanViewer({ + floor, + complexName, + unitsOnFloor, + floorsData, + selectedFloor, + onFloorSelect, +}: FloorPlanViewerProps) { + if (floor.type === "residential") { + return ( + + ); + } + + if (floor.type === "amenities") { + return ; + } + + // This should never happen with proper TypeScript typing + return null; +} + +export default FloorPlanViewer; diff --git a/src/components/floor-plans/ResidentialFloorView.tsx b/src/components/floor-plans/ResidentialFloorView.tsx new file mode 100644 index 0000000..00cd0e2 --- /dev/null +++ b/src/components/floor-plans/ResidentialFloorView.tsx @@ -0,0 +1,283 @@ +import { useState } from "react"; +import { ResidentialFloorData } from "../../types/Floor"; +import { Unit } from "../../types/IUnit"; +import { ComplexName } from "../../types/ComplexName"; +import { FloorsData } from "../FloorSelect"; +import Badge from "../ui/Badge"; +import Select from "../ui/Select"; +import UnitTypeBadge from "../UnitTypeBadge"; +import Button from "../ui/Button"; +import { usePopupStore } from "../../stores/usePopupStore"; +import { isMobile } from "react-device-detect"; + +// Import floor plan components +import FloorPlanMarasiDriveEast from "../FloorPlanMarasiDriveEast"; +import FloorPlanMarasiDriveWestLower from "../FloorPlanMarasiDriveWestLower"; +import FloorPlanMarasiDriveWestUpper from "../FloorPlanMarasiDriveWestUpper"; +import FloorPlanDubaiMarina7_38 from "../FloorPlanDubaiMarina7_38"; +import FloorPlanDubaiMarina7_38Comb from "../FloorPlanDubaiMarina7_38Comb"; +import FloorPlanDubaiMarina39_40 from "../FloorPlanDubaiMarina39_40"; +import FloorPlanDubaiMarina41_42 from "../FloorPlanDubaiMarina41_42"; + +interface ResidentialFloorViewProps { + floor: ResidentialFloorData; + complexName: ComplexName; + unitsOnFloor?: Unit[]; + floorsData?: FloorsData[]; + selectedFloor: string; + onFloorSelect: (floor: string) => void; +} + +function ResidentialFloorView({ + floor, + complexName, + unitsOnFloor, + floorsData, + selectedFloor, + onFloorSelect, +}: ResidentialFloorViewProps) { + const { setPopup, setPosition } = usePopupStore(); + const [isCombinable, setIsCombinable] = useState(false); + + // Marasi Drive specific logic + if (complexName === "marasi-drive") { + const floorNumber = floor.floorNumber; + const wing = floor.wing || selectedFloor.split(" ")[0]; + const currentFloorData = floorsData?.find( + (item) => item.floor === floorNumber + ); + + const totalUnits = + (currentFloorData?.East?.totalUnits || 0) + + (currentFloorData?.West?.totalUnits || 0); + + const wingData = + currentFloorData?.[selectedFloor.split(" ")[0] as "West" | "East"]; + + return ( +
setPopup(null)}> +
+

{floorNumber} floor

+
+ +
+
+
+
+ { + if (item.floor === 39) { + return "39-40"; + } + if (item.floor === 41) { + return "41-42"; + } + return item.floor.toString(); + }) || [] + } + defaultOption={selectedFloor?.toString() || ""} + onSelect={onFloorSelect} + className="2xl:w-[8.333vw] md:max-xl:w-[120px] w-full" + maxOptionsCount={7} + /> +
+
+ + item.floor === + parseInt(selectedFloor!.split(" ").at(-1)!) + )?.others.types["Studio2"] || 0 + } + /> + + item.floor === + parseInt(selectedFloor!.split(" ").at(-1)!) + )?.others.types["One Bedroom2"] || 0 + } + /> + + item.floor === + parseInt(selectedFloor!.split(" ").at(-1)!) + )?.others.types["One Bedroom Loft"] || 0 + } + /> + + item.floor === + parseInt(selectedFloor!.split(" ").at(-1)!) + )?.others.types["Two Bedroom Loft"] || 0 + } + /> +
+
+ {!isSpecialFloor && ( +
+ + +
+ )} +
+ !isMobile && setPosition({ x: e.clientX, y: e.clientY }) + } + > + {selectedFloor && unitsOnFloor && ( + <> + {+selectedFloor >= 7 && +selectedFloor < 39 && ( + <> + {!isCombinable ? ( + + ) : ( + + )} + + )} + {selectedFloor === "39-40" && ( + + )} + {selectedFloor === "41-42" && ( + + )} + + )} +
+
+
+ ); + } + + // Default fallback + return
Unsupported complex: {complexName}
; +} + +export default ResidentialFloorView; diff --git a/src/components/floor-plans/ViewToggleButtons.tsx b/src/components/floor-plans/ViewToggleButtons.tsx new file mode 100644 index 0000000..ea94d95 --- /dev/null +++ b/src/components/floor-plans/ViewToggleButtons.tsx @@ -0,0 +1,47 @@ +import clsx from "clsx"; +import Button from "../ui/Button"; + +interface ViewToggleButtonsProps { + currentView: "exterior" | "interior"; + onViewChange: (view: "exterior" | "interior") => void; + hasInteriorView: boolean; +} + +function ViewToggleButtons({ + currentView, + onViewChange, + hasInteriorView, +}: ViewToggleButtonsProps) { + if (!hasInteriorView) { + return null; + } + + return ( +
+ + +
+ ); +} + +export default ViewToggleButtons; diff --git a/src/data/floors/dubai-marina.ts b/src/data/floors/dubai-marina.ts new file mode 100644 index 0000000..98d7b09 --- /dev/null +++ b/src/data/floors/dubai-marina.ts @@ -0,0 +1,156 @@ +import { FloorData } from "../../types/Floor"; + +export const dubaiMarinaFloors: FloorData[] = [ + // Ground Level + { + id: "ground-level", + name: "Ground Level", + displayName: "Ground Level", + type: "amenities", + amenitiesCount: { + total: 14, + }, + amenitiesList: [ + { icon: "text", title: "Residential Entrance" }, + { icon: "text", title: "Multifunctional Feature Staircase" }, + { icon: "text", title: "Lobby Lounge & Concierge" }, + { icon: "text", title: "Outdoor Landscape Seating Area" }, + { icon: "text", title: "Lift Lobby" }, + { icon: "text", title: "Rove Cafe & Energize Bar" }, + { icon: "text", title: "Organic Smart Gardens & Seating" }, + { icon: "text", title: "Co-working Area " }, + { icon: "text", title: "24x7 Convenience Store" }, + { icon: "text", title: "WCs" }, + { icon: "text", title: "Visitor Parking" }, + { icon: "text", title: "EV Charging Stations" }, + { icon: "text", title: "Bicycle/Scooter Rental & Storage" }, + { icon: "text", title: "Drop-off Area" }, + ], + images: { + main: "/images/floor-plans/dubai-marina/ground.png", + content: ["/images/floor-plans/dubai-marina/ground/content.jpg"], + }, + video: "/videos/dubai-marina/GroundDubaiMarina.mp4", + }, + + // Podium Level + { + id: "podium-level", + name: "Podium Level", + displayName: "Podium Level", + type: "amenities", + amenitiesCount: { + total: 14, + indoor: 3, + outdoor: 12, + }, + amenitiesList: [ + // Indoor + { icon: "text", title: "Multipurpose Hall" }, + { icon: "text", title: "Gaming Lounge" }, + { icon: "text", title: "State-of-the-art Gym" }, + { icon: "text", title: "7m Climbing Wall" }, + { icon: "text", title: "Changing Rooms & Lockers" }, + { icon: "text", title: "Hydration Station" }, + { icon: "text", title: "Boutique Fitness Studio - Crank" }, + { icon: "text", title: "Rentable Guest Rooms" }, + // Outdoor + { icon: "text", title: "Semi-Olympic Leisure Pool" }, + { icon: "text", title: "Outdoor Cinema & Amphitheatre" }, + { icon: "text", title: "Water Feature Wall" }, + { icon: "text", title: "Multipurpose Fitness Pool" }, + { icon: "text", title: "Communal Gardens" }, + { icon: "text", title: "BBQ & Social Zone" }, + { icon: "text", title: "Popsicle Cart" }, + { icon: "text", title: "Gaming Lounge - Terrace" }, + { icon: "text", title: "Zen Library" }, + { icon: "text", title: "Co-working Area" }, + { icon: "text", title: "Multipurpose Hall with Terrace" }, + { icon: "text", title: "Marina View Chill Zone" }, + { icon: "text", title: "Outdoor Gym" }, + ], + images: { + main: "/images/floor-plans/dubai-marina/podium.png", + content: [ + "/images/floor-plans/dubai-marina/podium/content1.jpg", + "/images/floor-plans/dubai-marina/podium/content2.jpg", + "/images/floor-plans/dubai-marina/podium/content3.jpg", + ], + }, + video: "/videos/dubai-marina/PodiumDubaiMarina.mp4", + }, + + // Residential floors 7-20 + ...Array.from({ length: 14 }, (_, i) => { + const floor = i + 7; + return { + id: `floor-${floor}`, + name: `${floor}`, + displayName: `${floor}`, + type: "residential" as const, + floorNumber: floor, + }; + }), + + // Residential floors 22-38 + ...Array.from({ length: 17 }, (_, i) => { + const floor = i + 22; + return { + id: `floor-${floor}`, + name: `${floor}`, + displayName: `${floor}`, + type: "residential" as const, + floorNumber: floor, + }; + }), + + // Residential floors 39-40 (special layout) + { + id: "floor-39-40", + name: "39-40", + displayName: "39-40", + type: "residential", + floorNumber: 39, + }, + + // Residential floors 41-42 (special layout) + { + id: "floor-41-42", + name: "41-42", + displayName: "41-42", + type: "residential", + floorNumber: 41, + }, + + // Rooftop (Sky 44) + { + id: "rooftop", + name: "Rooftop", + displayName: "Sky 44 - Rooftop", + type: "amenities", + amenitiesCount: { + total: 14, + }, + amenitiesList: [ + { icon: "text", title: "Sky Viewing Lounges" }, + { icon: "text", title: "Convertible Indoor Infinity Pool" }, + { icon: "text", title: "Marina View Amphitheatre" }, + { icon: "text", title: "Ultra Shield Oxygen Pod" }, + { icon: "text", title: "Aroma Steam Pod" }, + { icon: "text", title: "Reflexology Pool" }, + { icon: "text", title: "Cold Bucket Experience Shower Pod" }, + { icon: "text", title: "Experience Shower Pod" }, + { icon: "text", title: "Cold Plunge Pool" }, + { icon: "text", title: "Salt Steam Pod" }, + { icon: "text", title: "Finnish Sauna Pod" }, + { icon: "text", title: "Water Feature Wall" }, + { icon: "text", title: "Vitality Pool" }, + { icon: "text", title: "Changing Rooms and Lockers" }, + ], + images: { + main: "/images/floor-plans/dubai-marina/rooftop.png", + content: ["/images/floor-plans/dubai-marina/rooftop/content.jpg"], + }, + video: "/videos/dubai-marina/SkyDubaiMarina.mp4", + }, +]; diff --git a/src/data/floors/marasi-drive.ts b/src/data/floors/marasi-drive.ts new file mode 100644 index 0000000..2ecb928 --- /dev/null +++ b/src/data/floors/marasi-drive.ts @@ -0,0 +1,207 @@ +import { FloorData } from "../../types/Floor"; + +export const marasiDriveFloors: FloorData[] = [ + // Ground Level + { + id: "ground-level", + name: "Ground Level", + displayName: "Ground Level", + type: "amenities", + amenitiesCount: { + total: 7, + }, + amenitiesList: [ + { icon: "RoveCafe", title: "Rove Café" }, + { icon: "LoungingSpaceIcon", title: "Lobby Lounge" }, + { icon: "CoworkingIcon", title: "Coworking Space" }, + { icon: "LushLandscapeIcon", title: "Outdoor Terrace" }, + { icon: "PrivateMeetingRoomsIcon", title: "Private Meeting Rooms" }, + { icon: "ConvenienceIcon", title: "Convenience Store" }, + { icon: "SoundproofMeetingPodsIcon", title: "Soundproof Meeting Pods" }, + ], + images: { + main: "/images/floor-plans/marasi-drive/ground.png", + content: [ + "/images/floor-plans/marasi-drive/ground/content1.jpg", + "/images/floor-plans/marasi-drive/ground/content2.jpg", + "/images/floor-plans/marasi-drive/ground/content3.jpg", + "/images/floor-plans/marasi-drive/ground/content4.jpg", + "/images/floor-plans/marasi-drive/ground/content5.jpg", + "/images/floor-plans/marasi-drive/ground/content6.jpg", + ], + }, + video: "/videos/marasi-drive/GroundMarasiDrive.mp4", + }, + + // Podium Level + { + id: "podium-level", + name: "Podium Level", + displayName: "Podium Level", + type: "amenities", + amenitiesCount: { + total: 27, + indoor: 13, + outdoor: 14, + }, + amenitiesList: [ + // Indoor + { icon: "LoungeIcon", title: "Indoor Lounge" }, + { icon: "MonkeyBarsIcon", title: "Monkey Bars" }, + { icon: "KaraokeIcon", title: "Karaoke Room" }, + { icon: "ArcadeGameIcon", title: "Arcade Games" }, + { icon: "ClimbingWallIcon", title: "Climbing Wall" }, + { icon: "PlaystationIcon", title: "Playstation Deck" }, + { icon: "FullyEquippedGymIcon", title: "Fully Equipped Gym" }, + { icon: "ChangingRoomIcon", title: "Changing Rooms" }, + { icon: "HammockMovieLoungeIcon", title: "Hammock Movie Lounge" }, + { icon: "GuestRooms", title: "Guest Rooms" }, + { icon: "MultiballInteractiveGamingIcon", title: "Multi Ball Interactive Gaming" }, + { icon: "MultiPurposeRoomWithKitchenIcon", title: "Multi-purpose Room for Kitchen" }, + { icon: "GamingLoungeIcon", title: "Gaming Lounge" }, + // Outdoor + { icon: "UrbanBeachPoolIcon", title: "Urban Beach Pool" }, + { icon: "JacuzziIcon", title: "Jacuzzi" }, + { icon: "YogaLoungeIcon", title: "Yoga Lounge" }, + { icon: "SunLoungeIcon", title: "Sun Lounging Pool" }, + { icon: "CascadingLeisurePoolIcon", title: "Cascading Leisure Pool" }, + { icon: "AquaCyclingIcon", title: "AquaCycling" }, + { icon: "OpenAirGymIcon", title: "Open-Air Gym" }, + { icon: "RoveBeverageTruckIcon", title: "Rove Beverage Truck" }, + { icon: "CabanasWithDaybeds", title: "Cabanas with Daybeds" }, + { icon: "IntegratedLapPoolIcon", title: "Integrated Lap Pool" }, + { icon: "SunkenGardensIcon", title: "Sunken Gardens" }, + { icon: "MultiPurposeRoomWithKitchenIcon", title: "Outdoor Multi-Purpose Terrace" }, + { icon: "GamingTerraceIcon", title: "Outdoor Gaming Terrace" }, + { icon: "CoworkingIcon", title: "Outdoor Coworking Space" }, + ], + images: { + main: "/images/floor-plans/marasi-drive/podium.png", + content: [ + "/images/floor-plans/marasi-drive/podium/content1.jpg", + "/images/floor-plans/marasi-drive/podium/content2.jpg", + "/images/floor-plans/marasi-drive/podium/content3.jpg", + "/images/floor-plans/marasi-drive/podium/content4.jpg", + "/images/floor-plans/marasi-drive/podium/content5.jpg", + "/images/floor-plans/marasi-drive/podium/content6.jpg", + "/images/floor-plans/marasi-drive/podium/content7.jpg", + "/images/floor-plans/marasi-drive/podium/content8.jpg", + "/images/floor-plans/marasi-drive/podium/content9.jpg", + "/images/floor-plans/marasi-drive/podium/content10.jpg", + "/images/floor-plans/marasi-drive/podium/content11.jpg", + ], + }, + video: "/videos/marasi-drive/PodiumMarasiDrive.mp4", + }, + + // Residential floors 5-21 East Wing + ...Array.from({ length: 17 }, (_, i) => { + const floor = i + 5; + return { + id: `east-${floor}`, + name: `East ${floor}`, + displayName: `East Wing ${floor}`, + type: "residential" as const, + floorNumber: floor, + wing: "East" as const, + }; + }), + + // Residential floors 5-21 West Wing + ...Array.from({ length: 17 }, (_, i) => { + const floor = i + 5; + return { + id: `west-${floor}`, + name: `West ${floor}`, + displayName: `West Wing ${floor}`, + type: "residential" as const, + floorNumber: floor, + wing: "West" as const, + }; + }), + + // Residential floors 24-31 West Wing (upper) + ...Array.from({ length: 8 }, (_, i) => { + const floor = i + 24; + return { + id: `west-${floor}`, + name: `West ${floor}`, + displayName: `West Wing ${floor}`, + type: "residential" as const, + floorNumber: floor, + wing: "West" as const, + }; + }), + + // Sky Garden + { + id: "sky-garden", + name: "Sky Garden", + displayName: "Sky Garden", + type: "amenities", + amenitiesCount: { + total: 15, + indoor: 3, + outdoor: 12, + }, + amenitiesList: [ + // Indoor + { icon: "PoolIcon", title: "Indoor Lap Pool" }, + { icon: "WellnessIcon", title: "Wellness Features" }, + { icon: "ChangingRoomIcon", title: "Changing Rooms" }, + // Outdoor + { icon: "PingPongIcon", title: "Padel Pong" }, + { icon: "SunLoungeIcon", title: "Sun Lounging Deck" }, + { icon: "CinemaIcon", title: "Outdoor Cinema" }, + { icon: "BoulderingWallIcon", title: "Bouldering Wall" }, + { icon: "PingPongInTubeIcon", title: "Ping Pong in a Tube" }, + { icon: "AmphitheatreIcon", title: "Amphitheatre" }, + { icon: "CommunalDiningTablesIcon", title: "Communal Dining Tables" }, + { icon: "SuspendedLoungingNetsIcon", title: "Suspended Lounging Nets " }, + { icon: "LushLandscapeIcon", title: "Lush Landscape" }, + { icon: "RunningWheelIcon", title: "Running Wheel" }, + { icon: "ChessIcon", title: "Chess Tables" }, + { icon: "ClimbingWallIcon", title: "Climbing Wall" }, + { icon: "CoworkingIcon", title: "Outdoor Coworking Space" }, + { icon: "MultiPurposeIcon", title: "Multi-purpose Court" }, + ], + images: { + main: "/images/floor-plans/marasi-drive/sky-garden.png", + content: [ + "/images/floor-plans/marasi-drive/skygarden/content1.jpg", + "/images/floor-plans/marasi-drive/skygarden/content2.jpg", + "/images/floor-plans/marasi-drive/skygarden/content3.jpg", + "/images/floor-plans/marasi-drive/skygarden/content4.jpg", + ], + }, + video: "/videos/marasi-drive/SkyGardenMarasiDrive.mp4", + }, + + // Rooftop + { + id: "rooftop", + name: "Rooftop", + displayName: "Rooftop", + type: "amenities", + amenitiesCount: { + total: 10, + }, + amenitiesList: [ + { icon: "StargazingIcon", title: "Stargazing Point" }, + { icon: "BBQTerraceIcon", title: "BBQ Terrace" }, + { icon: "OutdoorKitchenIcon", title: "Outdoor Kitchen" }, + { icon: "CabanasWithDaybeds", title: "Cabanas with Daybeds" }, + { icon: "ViewingDeckWithWingsIcon", title: "Viewing Deck with Wings" }, + { icon: "LoungingSpaceIcon", title: "Lounging Space" }, + { icon: "SunkenSeatingIcon", title: "Sunken Seating" }, + { icon: "FirePitIcon", title: "Firepit" }, + { icon: "RooftopGardenIcon", title: "Rooftop Garden" }, + { icon: "CommunalDiningTablesRoundedIcon", title: "Communal Dining Tables" }, + ], + images: { + main: "/images/floor-plans/marasi-drive/rooftop.png", + content: ["/images/floor-plans/marasi-drive/rooftop/content.jpg"], + }, + video: "/videos/marasi-drive/RooftopMarasiDrive.mp4", + }, +]; diff --git a/src/pages/FloorsPage.tsx b/src/pages/FloorsPage.tsx index 1a81f16..5684aee 100644 --- a/src/pages/FloorsPage.tsx +++ b/src/pages/FloorsPage.tsx @@ -2,33 +2,16 @@ import FloorSelect, { FloorsData } from "../components/FloorSelect"; import { useParams } from "react-router"; import FloorSidebar from "../components/FloorSidebar"; -import { useEffect, useState } from "react"; -import Select from "../components/ui/Select"; +import { useState, useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { api } from "../api/ky"; -import UnitTypeBadge from "../components/UnitTypeBadge"; -import FloorPlanMarasiDriveEast from "../components/FloorPlanMarasiDriveEast"; -import RooftopMarasiDrive from "../components/floor-plans/marasi-drive/RooftopMarasiDrive"; -import GroundMarasiDrive from "../components/floor-plans/marasi-drive/GroundMarasiDrive"; -import PodiumMarasiDrive from "../components/floor-plans/marasi-drive/PodiumMarasiDrive"; -import SkyGardenMarasiDrive from "../components/floor-plans/marasi-drive/SkyGardenMarasiDrive"; -import Badge from "../components/ui/Badge"; -import RooftopDubaiMarina from "../components/floor-plans/dubai-marina/RooftopDubaiMarina"; -import GroundDubaiMarina from "../components/floor-plans/dubai-marina/GroundDubaiMarina"; -import PodiumDubaiMarina from "../components/floor-plans/dubai-marina/PodiumDubaiMarina"; -import FloorPlanMarasiDriveWestLower from "../components/FloorPlanMarasiDriveWestLower"; -import FloorPlanMarasiDriveWestUpper from "../components/FloorPlanMarasiDriveWestUpper"; import { SPECIAL_FLOORS } from "../constants/floors"; import { Unit } from "../types/IUnit"; import slugToComplexName from "../utils/slugToComplexName"; -import { usePopupStore } from "../stores/usePopupStore"; -import { isMobile } from "react-device-detect"; -import FloorPlanDubaiMarina41_42 from "../components/FloorPlanDubaiMarina41_42"; -import FloorPlanDubaiMarina39_40 from "../components/FloorPlanDubaiMarina39_40"; -import FloorPlanDubaiMarina7_38Comb from "../components/FloorPlanDubaiMarina7_38Comb"; -import FloorPlanDubaiMarina7_38 from "../components/FloorPlanDubaiMarina7_38"; -import Button from "../components/ui/Button"; import { ComplexName } from "../types/ComplexName"; +import FloorPlanViewer from "../components/floor-plans/FloorPlanViewer"; +import { marasiDriveFloors } from "../data/floors/marasi-drive"; +import { dubaiMarinaFloors } from "../data/floors/dubai-marina"; function FloorsPage() { const [selectedFloor, setSelectedFloor] = useState(null); @@ -66,12 +49,22 @@ function FloorsPage() { .json(), }); - const { setPosition, setPopup } = usePopupStore(); - const [isCombinable, setIsCombinable] = useState(false); + // Get floor data based on complex + const allFloors = useMemo(() => { + if (complexName === "marasi-drive") { + return marasiDriveFloors; + } + if (complexName === "dubai-marina") { + return dubaiMarinaFloors; + } + return []; + }, [complexName]); - useEffect(() => { - setIsCombinable(false); - }, [selectedFloor]); + // Find current floor + const currentFloor = useMemo(() => { + if (!selectedFloor) return null; + return allFloors.find((floor) => floor.name === selectedFloor); + }, [selectedFloor, allFloors]); return (
@@ -84,287 +77,20 @@ function FloorsPage() { isOpen={!!selectedFloor} onClose={() => setSelectedFloor(null)} > - {complexName === "dubai-marina" && ( - <> - {selectedFloor === "Rooftop" && } - {selectedFloor === "Ground Level" && } - {selectedFloor === "Podium Level" && } - {!!parseInt(selectedFloor!) && ( -
setPopup(null)} - > -
-

{selectedFloor} floor

-
- item.floor === parseInt(selectedFloor!), - )?.others.totalUnits || 0 - } Apartments`} - /> - -
-
- -
-
- [ - `East ${item.floor}`, - `West ${item.floor}`, - ]) || [] - } - defaultOption={selectedFloor?.toString() || ""} - onSelect={setSelectedFloor} - className="2xl:w-[8.333vw] md:max-2xl:w-[120px] w-full" - maxOptionsCount={7} - /> -
-
- - item.floor === - parseInt(selectedFloor.split(" ").at(-1)!), - )?.[selectedFloor.split(" ")[0] as "West" | "East"] - .types["Studio Flex"] || 0 - } - /> - - item.floor === - parseInt(selectedFloor.split(" ").at(-1)!), - )?.[selectedFloor.split(" ")[0] as "West" | "East"] - .types["Studio Squared"] || 0 - } - /> - - item.floor === - parseInt(selectedFloor.split(" ").at(-1)!), - )?.[selectedFloor.split(" ")[0] as "West" | "East"] - .types["1 BR Squared"] || 0 - } - /> - - item.floor === - parseInt(selectedFloor.split(" ").at(-1)!), - )?.[selectedFloor.split(" ")[0] as "West" | "East"] - .types["2 BR Squared"] || 0 - } - /> -
-
-
-
- !isMobile && setPosition({ x: e.clientX, y: e.clientY }) - } - > - {unitsOnFloor && selectedFloor.split(" ")[0] === "East" && ( - - )} - {selectedFloor.split(" ")[0] === "West" && unitsOnFloor && ( - <> - {+selectedFloor.split(" ")[1] < 24 ? ( - - ) : ( - - )} - - )} -
-
- )} - + {!currentFloor && complexName === "hq" && <>HQ} + {!currentFloor && selectedFloor && ( +
Floor not found: {selectedFloor}
)} - {complexName === "hq" && <>HQ}
); diff --git a/src/types/Floor.ts b/src/types/Floor.ts new file mode 100644 index 0000000..0a1b029 --- /dev/null +++ b/src/types/Floor.ts @@ -0,0 +1,36 @@ +// Базовый интерфейс для всех этажей +export interface BaseFloorData { + id: string; + name: string; // "1", "Rooftop", "Ground Level" и т.д. + displayName: string; // "1st Floor", "Rooftop" +} + +// Жилой этаж с квартирами и SVG масками +export interface ResidentialFloorData extends BaseFloorData { + type: 'residential'; + floorNumber: number; + wing?: 'East' | 'West'; // для Marasi Drive +} + +// Этаж с удобствами +export interface AmenitiesFloorData extends BaseFloorData { + type: 'amenities'; + amenitiesCount: { + total: number; + indoor?: number; + outdoor?: number; + }; + amenitiesList: { + icon: string; // название компонента иконки + title: string; + }[]; + images: { + main: string; // основное изображение (exterior) + interior?: string; // для переключения вида + content: string[]; // изображения для слайдера/галереи + }; + video?: string; // путь к видео +} + +// Discriminated union +export type FloorData = ResidentialFloorData | AmenitiesFloorData; diff --git a/src/types/Project.ts b/src/types/Project.ts index dd66aeb..bc589ca 100644 --- a/src/types/Project.ts +++ b/src/types/Project.ts @@ -1,4 +1,5 @@ import UnitType from "./UnitType"; +import { FloorData } from "./Floor"; export default interface Project { title: string; @@ -7,6 +8,7 @@ export default interface Project { buildingType: "residential" | "commercial"; types: UnitType[]; amenitiesFloors: AmenitiesFloor[]; + floors?: FloorData[]; // New centralized floor data structure } export interface AmenitiesFloor {