diff --git a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx index ac33667..e7b24ce 100644 --- a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx +++ b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx @@ -23,18 +23,6 @@ interface Event { modelId: string; } -interface ListProps { - val: Event; -} - -const List: React.FC = ({ val }) => { - return ( -
-
{val.modelName}
-
- ); -}; - const Simulations: React.FC = () => { const productsContainerRef = useRef(null); const { eventStore, productStore, versionStore } = useSceneContext(); @@ -188,7 +176,9 @@ const Simulations: React.FC = () => { processes?.map((process, index) => (
{process.map((event, index) => ( - +
+
{event.modelName}
+
))}
))} diff --git a/app/src/components/ui/list/DropDownList.tsx b/app/src/components/ui/list/DropDownList.tsx index bbf386d..ba5acdd 100644 --- a/app/src/components/ui/list/DropDownList.tsx +++ b/app/src/components/ui/list/DropDownList.tsx @@ -1,7 +1,7 @@ import React from "react"; -import List from "./List"; import { AddIcon, ArrowIcon, FocusIcon } from "../../icons/ExportCommonIcons"; import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect"; +import List from "./OutlineList/ListNew"; interface DropDownListProps { value?: string; diff --git a/app/src/components/ui/list/List.tsx b/app/src/components/ui/list/List.tsx deleted file mode 100644 index 45f4471..0000000 --- a/app/src/components/ui/list/List.tsx +++ /dev/null @@ -1,304 +0,0 @@ -import React, { useEffect, useState } from "react"; -import RenameInput from "../inputs/RenameInput"; - -import { useSelectedZoneStore } from "../../../store/visualization/useZoneStore"; -import { getZoneData } from "../../../services/visulization/zone/getZones"; -import useModuleStore, { useSubModuleStore } from "../../../store/ui/useModuleStore"; -import { ArrowIcon, EyeIcon, LockIcon, RemoveIcon } from "../../icons/ExportCommonIcons"; -import { useZoneAssetId } from "../../../store/builder/store"; -import { zoneCameraUpdate } from "../../../services/visulization/zone/zoneCameraUpdation"; -import { setAssetsApi } from "../../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; -import { useParams } from "react-router-dom"; -import { getUserData } from "../../../functions/getUserData"; -import { useSceneContext } from "../../../modules/scene/sceneContext"; - -interface Asset { - id: string; - name: string; - position?: [number, number, number]; // Proper 3D vector - rotation?: { x: number; y: number; z: number }; // Proper rotation format -} - -interface ZoneItem { - id: string; - name: string; - assets?: Asset[]; - active?: boolean; -} - -interface ListProps { - items?: ZoneItem[]; - remove?: boolean; -} - -const List: React.FC = ({ items = [], remove }) => { - const { activeModule } = useModuleStore(); - const { selectedZone, setSelectedZone } = useSelectedZoneStore(); - const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); - - const { setSubModule } = useSubModuleStore(); - const [expandedZones, setExpandedZones] = useState>({}); - const { projectId } = useParams(); - const { assetStore, versionStore, zoneStore } = useSceneContext(); - const { setName } = assetStore(); - const { selectedVersion } = versionStore(); - const { zones, setZoneName } = zoneStore(); - const { organization } = getUserData(); - - useEffect(() => { - useSelectedZoneStore.getState().setSelectedZone({ - zoneName: "", - activeSides: [], - panelOrder: [], - lockedPanels: [], - zoneUuid: "", - zoneViewPortTarget: [], - zoneViewPortPosition: [], - widgets: [], - }); - }, [activeModule]); - - useEffect(() => { - const expanded: Record = { "unassigned-zone": true }; - if (zones.length > 0) { - zones.forEach((zone: any) => { - expanded[zone.zoneUuid] = true; - }); - } - setExpandedZones(expanded); - // eslint-disable-next-line - }, [zones.length]); - - const toggleZoneExpansion = (zoneUuid: string) => { - setExpandedZones((prev) => ({ - ...prev, - [zoneUuid]: !prev[zoneUuid], - })); - }; - - async function handleSelectZone(id: string) { - try { - if (selectedZone?.zoneUuid === id || id === "unassigned-zone") { - return; - } - - setSubModule("zoneProperties"); - - let response = await getZoneData(id, organization, projectId, selectedVersion?.versionId || ""); - - if (!response) return; - - setSelectedZone({ - zoneName: response?.zoneName, - activeSides: response?.activeSides ?? [], - panelOrder: response?.panelOrder ?? [], - lockedPanels: response?.lockedPanels ?? [], - widgets: response?.widgets ?? [], - zoneUuid: response?.zoneUuid, - zoneViewPortTarget: response?.viewPortTarget ?? [], - zoneViewPortPosition: response?.viewPortPosition ?? [], - }); - } catch (error) { - echo.error("Failed to select zone"); - } - } - - function handleAssetClick(asset: Asset) { - setZoneAssetId(asset); - } - - async function handleZoneNameChange(newName: string) { - const isDuplicate = zones.some((zone: any) => zone.zoneName?.trim().toLowerCase() === newName?.trim().toLowerCase() && zone.zoneUuid !== selectedZone.zoneUuid); - - if (isDuplicate) { - alert("Zone name already exists. Please choose a different name."); - return; // DO NOT update state - } - const zonesdata = { - zoneUuid: selectedZone.zoneUuid, - zoneName: newName, - }; - const response = await zoneCameraUpdate(zonesdata, organization, projectId, selectedVersion?.versionId || ""); - if (response.message === "zone updated") { - setSelectedZone((prev) => ({ ...prev, zoneName: newName })); - setZoneName(selectedZone.zoneUuid, newName); - } - } - - async function handleZoneAssetName(newName: string) { - if (zoneAssetId?.id) { - let response = await setAssetsApi({ - modelUuid: zoneAssetId.id, - modelName: newName, - projectId, - versionId: selectedVersion?.versionId || "", - }); - - setName(zoneAssetId.id, response.modelName); - } - } - const checkZoneNameDuplicate = (name: string) => { - return zones.some((zone: any) => zone.zoneName?.trim().toLowerCase() === name?.trim().toLowerCase() && zone.zoneUuid !== selectedZone.zoneUuid); - }; - - useEffect(() => { - let drag = false; - let isLeftMouseDown = false; - - const contextClassNames = ["list-wrapper", "zone-properties-container", "list-container"]; - - const isOutsideClick = (target: EventTarget | null) => { - if (!(target instanceof HTMLElement)) return true; - return !contextClassNames.some((className) => target.closest(`.${className}`)); - }; - - const onMouseDown = (evt: MouseEvent) => { - if (evt.button === 0) { - isLeftMouseDown = true; - drag = false; - } - }; - - const onMouseMove = () => { - if (isLeftMouseDown) { - drag = true; - } - }; - - const onMouseUp = (evt: MouseEvent) => { - if (evt.button === 0) { - isLeftMouseDown = false; - if (drag) return; - - if (isOutsideClick(evt.target)) { - // Clear selected zone - setSelectedZone({ - zoneUuid: "", - zoneName: "", - activeSides: [], - panelOrder: [], - lockedPanels: [], - widgets: [], - zoneViewPortTarget: [], - zoneViewPortPosition: [], - }); - setZoneAssetId({ - id: "", - name: "", - }); - setSubModule("properties"); - } - } - }; - - if (selectedZone.zoneName! === "" && activeModule === "Builder") { - document.addEventListener("mousedown", onMouseDown); - document.addEventListener("mousemove", onMouseMove); - document.addEventListener("mouseup", onMouseUp); - } - - return () => { - document.removeEventListener("mousedown", onMouseDown); - document.removeEventListener("mousemove", onMouseMove); - document.removeEventListener("mouseup", onMouseUp); - }; - // eslint-disable-next-line - }, [selectedZone, activeModule]); - - return ( - <> - {items?.length > 0 ? ( -
    - {items?.map((item) => ( - -
  • { - handleSelectZone(item.id); - toggleZoneExpansion(item.id); - }} - > -
    -
    - -
    -
    -
    - -
    -
    - -
    - {remove && ( -
    - -
    - )} - {item.assets && item.assets.length > 0 && ( - - )} -
    -
    -
  • - {/* Nested assets list - only shown when expanded */} - {item.assets && item.assets.length > 0 && expandedZones[item.id] && ( -
      - {item.assets.map((asset) => ( -
    • handleAssetClick(asset)}> -
      - -
      - - - {remove && ( - - )} -
      -
      -
    • - ))} -
    - )} -
    - ))} -
- ) : ( -
-
No items to display
-
- )} - - ); -}; - -export default List; diff --git a/app/src/components/ui/list/OutlineList/AssetItem.tsx b/app/src/components/ui/list/OutlineList/AssetItem.tsx new file mode 100644 index 0000000..e99080c --- /dev/null +++ b/app/src/components/ui/list/OutlineList/AssetItem.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import ListItemBase from "./ListItemBase"; + +const AssetItem: React.FC = ({ asset, isActive, remove, onClick, onRename }) => ( + onClick(asset)} + onRename={onRename} + /> +); + +export default AssetItem; diff --git a/app/src/components/ui/list/OutlineList/ListItemBase.tsx b/app/src/components/ui/list/OutlineList/ListItemBase.tsx new file mode 100644 index 0000000..0b2741f --- /dev/null +++ b/app/src/components/ui/list/OutlineList/ListItemBase.tsx @@ -0,0 +1,83 @@ +import React from "react"; +import RenameInput from "../../inputs/RenameInput"; +import { ArrowIcon, EyeIcon, LockIcon, RemoveIcon } from "../../../icons/ExportCommonIcons"; + +interface ListItemBaseProps { + id: string; + name: string; + isActive?: boolean; + canEdit?: boolean; + remove?: boolean; + expandable?: boolean; + expanded?: boolean; + onClick?: () => void; + onRename: (newName: string) => void; + onToggleExpand?: () => void; + children?: React.ReactNode; // For nested assets +} + +const ListItemBase: React.FC = ({ + id, + name, + isActive, + canEdit = true, + remove, + expandable, + expanded, + onClick, + onRename, + onToggleExpand, + children, +}) => ( + <> +
  • +
    + + +
    + + + {remove && ( + + )} + {expandable && ( + + )} +
    +
    +
  • + + {expandable && expanded &&
      {children}
    } + +); + +export default ListItemBase; diff --git a/app/src/components/ui/list/OutlineList/ListNew.tsx b/app/src/components/ui/list/OutlineList/ListNew.tsx new file mode 100644 index 0000000..674ea37 --- /dev/null +++ b/app/src/components/ui/list/OutlineList/ListNew.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import ZoneItemComponent from "./ZoneItem"; +import { useSelectedZoneStore } from "../../../../store/visualization/useZoneStore"; +import { useZoneAssetId } from "../../../../store/builder/store"; +import { useZoneAssetHandlers } from "../../../../functions/outlineHelpers/useZoneAssetHandlers"; +import { useZonesExpansion } from "../../../../functions/outlineHelpers/useZonesExpansion"; + +const List: React.FC = ({ items = [], remove }) => { + const { selectedZone } = useSelectedZoneStore(); + const { zoneAssetId } = useZoneAssetId(); + const { expandedZones, toggleZoneExpansion } = useZonesExpansion(items); + + const { + handleSelectZone, + handleZoneNameChange, + handleAssetClick, + handleZoneAssetName, + } = useZoneAssetHandlers(); + + if (items.length === 0) { + return ( +
    +
    No items to display
    +
    + ); + } + + return ( +
      + {items.map((item) => ( + + ))} +
    + ); +}; + +export default List; diff --git a/app/src/components/ui/list/OutlineList/ZoneItem.tsx b/app/src/components/ui/list/OutlineList/ZoneItem.tsx new file mode 100644 index 0000000..c515e68 --- /dev/null +++ b/app/src/components/ui/list/OutlineList/ZoneItem.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import ListItemBase from "./ListItemBase"; +import AssetItem from "./AssetItem"; + +const ZoneItemComponent: React.FC = ({ + item, + isActive, + expanded, + remove, + activeAssetId, + onSelect, + onRename, + onToggleExpand, + onAssetClick, + onAssetRename, +}) => ( + { + onSelect(item.id); + onToggleExpand(item.id); + }} + onRename={onRename} + onToggleExpand={() => onToggleExpand(item.id)} + > + {item.assets?.map((asset) => ( + + ))} + +); + +export default ZoneItemComponent; diff --git a/app/src/functions/outlineHelpers/useZoneAssetHandlers.ts b/app/src/functions/outlineHelpers/useZoneAssetHandlers.ts new file mode 100644 index 0000000..4df2b08 --- /dev/null +++ b/app/src/functions/outlineHelpers/useZoneAssetHandlers.ts @@ -0,0 +1,111 @@ +import { useCallback } from "react"; +import { useParams } from "react-router-dom"; +import { getUserData } from "../getUserData"; +import { useVersionContext } from "../../modules/builder/version/versionContext"; +import { getZoneData } from "../../services/visulization/zone/getZones"; +import { zoneCameraUpdate } from "../../services/visulization/zone/zoneCameraUpdation"; +import { setAssetsApi } from "../../services/factoryBuilder/asset/floorAsset/setAssetsApi"; +import { useSelectedZoneStore } from "../../store/visualization/useZoneStore"; +import { useSubModuleStore } from "../../store/useModuleStore"; +import { useSceneContext } from "../../modules/scene/sceneContext"; +import { useZoneAssetId } from "../../store/builder/store"; + +export const useZoneAssetHandlers = () => { + const { projectId } = useParams(); + const { organization } = getUserData(); + const { selectedVersionStore } = useVersionContext(); + const { selectedVersion } = selectedVersionStore(); + + const { selectedZone, setSelectedZone } = useSelectedZoneStore(); + const { setSubModule } = useSubModuleStore(); + const { zoneAssetId, setZoneAssetId } = useZoneAssetId(); + const { assetStore, zoneStore } = useSceneContext(); + const { setName } = assetStore(); + const { zones, setZoneName } = zoneStore(); + + // 🔹 Zone selection + const handleSelectZone = useCallback( + async (id: string) => { + if (selectedZone?.zoneUuid === id || id === "unassigned-zone") return; + setSubModule("zoneProperties"); + + const response = await getZoneData( + id, + organization, + projectId, + selectedVersion?.versionId || "" + ); + if (!response) return; + + setSelectedZone({ + zoneName: response.zoneName, + activeSides: response.activeSides ?? [], + panelOrder: response.panelOrder ?? [], + lockedPanels: response.lockedPanels ?? [], + widgets: response.widgets ?? [], + zoneUuid: response.zoneUuid, + zoneViewPortTarget: response.viewPortTarget ?? [], + zoneViewPortPosition: response.viewPortPosition ?? [], + }); + }, + [organization, projectId, selectedVersion, selectedZone?.zoneUuid, setSelectedZone, setSubModule] + ); + + // 🔹 Zone rename + const handleZoneNameChange = useCallback( + async (newName: string) => { + const isDuplicate = zones.some( + (zone: any) => + zone.zoneName?.trim().toLowerCase() === newName.trim().toLowerCase() && + zone.zoneUuid !== selectedZone.zoneUuid + ); + if (isDuplicate) { + alert("Zone name already exists. Please choose a different name."); + return; + } + + const response = await zoneCameraUpdate( + { zoneUuid: selectedZone.zoneUuid, zoneName: newName }, + organization, + projectId, + selectedVersion?.versionId || "" + ); + + if (response.message === "zone updated") { + setSelectedZone((prev) => ({ ...prev, zoneName: newName })); + setZoneName(selectedZone.zoneUuid, newName); + } + }, + [organization, projectId, selectedVersion, selectedZone, setSelectedZone, setZoneName, zones] + ); + + // 🔹 Asset selection + const handleAssetClick = useCallback( + (asset: ListAsset) => { + setZoneAssetId(asset); + }, + [setZoneAssetId] + ); + + // 🔹 Asset rename + const handleZoneAssetName = useCallback( + async (newName: string) => { + if (!zoneAssetId?.id) return; + const response = await setAssetsApi({ + modelUuid: zoneAssetId.id, + modelName: newName, + projectId, + versionId: selectedVersion?.versionId || "", + }); + setName(zoneAssetId.id, response.modelName); + }, + [projectId, selectedVersion, setName, zoneAssetId] + ); + + return { + handleSelectZone, + handleZoneNameChange, + handleAssetClick, + handleZoneAssetName, + }; +}; diff --git a/app/src/functions/outlineHelpers/useZonesExpansion.ts b/app/src/functions/outlineHelpers/useZonesExpansion.ts new file mode 100644 index 0000000..83db4d1 --- /dev/null +++ b/app/src/functions/outlineHelpers/useZonesExpansion.ts @@ -0,0 +1,22 @@ +import { useEffect, useState, useCallback } from "react"; + +export const useZonesExpansion = (zones: any[]) => { + const [expandedZones, setExpandedZones] = useState>({}); + + useEffect(() => { + const expanded: Record = { "unassigned-zone": true }; + zones.forEach((zone) => { + expanded[zone.zoneUuid] = true; + }); + setExpandedZones(expanded); + }, [zones, zones.length]); + + const toggleZoneExpansion = useCallback((zoneUuid: string) => { + setExpandedZones((prev) => ({ + ...prev, + [zoneUuid]: !prev[zoneUuid], + })); + }, []); + + return { expandedZones, toggleZoneExpansion }; +}; diff --git a/app/src/types/uiTypes.d.ts b/app/src/types/uiTypes.d.ts index 16cb72c..d6b1c79 100644 --- a/app/src/types/uiTypes.d.ts +++ b/app/src/types/uiTypes.d.ts @@ -26,4 +26,47 @@ interface DecalProp { interface CategoryListProp { categoryImage: string; category: string; -} \ No newline at end of file +} + +interface ListAsset { + id: string; + name: string; +} + +interface AssetItemProps { + asset: ListAsset; + isActive: boolean; + remove?: boolean; + onClick: (asset: ListAsset) => void; + onRename: (newName: string) => void; +} + +interface ZoneItem { + id: string; + name: string; + assets?: ListAsset[]; +} + +interface ZoneItemProps { + item: ZoneItem; + isActive: boolean; + expanded: boolean; + remove?: boolean; + activeAssetId?: string; + onSelect: (id: string) => void; + onRename: (newName: string) => void; + onToggleExpand: (id: string) => void; + onAssetClick: (asset: ListAsset) => void; + onAssetRename: (newName: string) => void; +} + +interface ZoneItem { + id: string; + name: string; + assets?: ListAsset[]; +} + +interface ListProps { + items?: ZoneItem[]; + remove?: boolean; +}