diff --git a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx index 0858e5d..212a8b0 100644 --- a/app/src/components/layout/sidebarRight/simulation/Simulations.tsx +++ b/app/src/components/layout/sidebarRight/simulation/Simulations.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from "react"; -import { AddIcon, ArrowIcon, RemoveIcon, ResizeHeightIcon, } from "../../../icons/ExportCommonIcons"; +import { AddIcon, ArrowIcon, RemoveIcon, ResizeHeightIcon } from "../../../icons/ExportCommonIcons"; import RenameInput from "../../../ui/inputs/RenameInput"; import { handleResize } from "../../../../functions/handleResizePannel"; import { useMainProduct, useSelectedAsset } from "../../../../store/simulation/useSimulationStore"; @@ -13,7 +13,7 @@ import { deleteProductApi } from "../../../../services/simulation/products/delet import { renameProductApi } from "../../../../services/simulation/products/renameProductApi"; import { determineExecutionMachineSequences } from "../../../../modules/simulation/simulator/functions/determineExecutionMachineSequences"; import ComparePopUp from "../../../ui/compareVersion/Compare"; -import { useCompareStore, useSaveVersion, } from "../../../../store/builder/store"; +import { useCompareStore, useSaveVersion } from "../../../../store/builder/store"; import { useToggleStore } from "../../../../store/useUIToggleStore"; import { useProductContext } from "../../../../modules/simulation/products/productContext"; import { useParams } from "react-router-dom"; @@ -25,22 +25,10 @@ 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 } = useSceneContext(); - const { products, addProduct, removeProduct, renameProduct, addEvent, removeEvent, getProductById, } = productStore(); + const { products, addProduct, removeProduct, renameProduct, addEvent, removeEvent, getProductById } = productStore(); const { selectedProductStore } = useProductContext(); const { selectedProduct, setSelectedProduct } = selectedProductStore(); const { getEventByModelUuid } = eventStore(); @@ -69,7 +57,7 @@ const Simulations: React.FC = () => { productName: name, productUuid: id, projectId: projectId, - versionId: selectedVersion?.versionId || '', + versionId: selectedVersion?.versionId || "", }); }; @@ -85,14 +73,8 @@ const Simulations: React.FC = () => { if (currentIndex >= updatedProducts.length) { newSelectedIndex = updatedProducts.length - 1; } - setSelectedProduct( - updatedProducts[newSelectedIndex].productUuid, - updatedProducts[newSelectedIndex].productName - ); - setMainProduct( - updatedProducts[newSelectedIndex].productUuid, - updatedProducts[newSelectedIndex].productName - ); + setSelectedProduct(updatedProducts[newSelectedIndex].productUuid, updatedProducts[newSelectedIndex].productName); + setMainProduct(updatedProducts[newSelectedIndex].productUuid, updatedProducts[newSelectedIndex].productName); } else { setSelectedProduct("", ""); setMainProduct("", ""); @@ -102,14 +84,14 @@ const Simulations: React.FC = () => { removeProduct(productUuid); deleteProductApi({ productUuid, - versionId: selectedVersion?.versionId || '', - projectId + versionId: selectedVersion?.versionId || "", + projectId, }); }; const handleRenameProduct = (productUuid: string, newName: string) => { renameProduct(productUuid, newName); - renameProductApi({ productName: newName, productUuid, projectId: projectId || '', versionId: selectedVersion?.versionId || '' }); + renameProductApi({ productName: newName, productUuid, projectId: projectId || "", versionId: selectedVersion?.versionId || "" }); if (selectedProduct.productUuid === productUuid) { setSelectedProduct(productUuid, newName); setMainProduct(productUuid, newName); @@ -118,11 +100,10 @@ const Simulations: React.FC = () => { const handleRemoveEventFromProduct = () => { if (selectedAsset) { - deleteEventDataApi({ productUuid: selectedProduct.productUuid, modelUuid: selectedAsset.modelUuid, - versionId: selectedVersion?.versionId || '', + versionId: selectedVersion?.versionId || "", projectId: projectId, }); removeEvent(selectedProduct.productUuid, selectedAsset.modelUuid); @@ -135,21 +116,18 @@ const Simulations: React.FC = () => { const selectedProductData = getProductById(selectedProduct.productUuid); if (selectedProductData) { - determineExecutionMachineSequences([selectedProductData]).then( - (sequences) => { - sequences.forEach((sequence) => { - const events: Event[] = - sequence.map((event) => ({ - modelName: event.modelName, - modelId: event.modelUuid, - })) || []; - processes.push(events); - }); - setProcesses(processes); - } - ); + determineExecutionMachineSequences([selectedProductData]).then((sequences) => { + sequences.forEach((sequence) => { + const events: Event[] = + sequence.map((event) => ({ + modelName: event.modelName, + modelId: event.modelUuid, + })) || []; + processes.push(events); + }); + setProcesses(processes); + }); } - }, [selectedProduct.productUuid, products]); return ( @@ -159,77 +137,41 @@ const Simulations: React.FC = () => {
Products
-
-
+
{products.map((product, index) => ( -
+
{/* eslint-disable-next-line */}
{ - setSelectedProduct(product.productUuid, product.productName) - setMainProduct(product.productUuid, product.productName) + setSelectedProduct(product.productUuid, product.productName); + setMainProduct(product.productUuid, product.productName); }} > - - - handleRenameProduct(product.productUuid, newName) - } - /> + + handleRenameProduct(product.productUuid, newName)} />
{products.length > 1 && ( - )}
))}
-
- -
-
-
- -
-
- -
- {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; +}