refactor: update component structure and enhance list functionality with new asset and zone handling

This commit is contained in:
2025-09-05 15:50:07 +05:30
parent 18562307a2
commit f27af9a58d
10 changed files with 408 additions and 467 deletions

View File

@@ -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<ListProps> = ({ val }) => {
return (
<div className="process-container">
<div className="value">{val.modelName}</div>
</div>
);
};
const Simulations: React.FC = () => {
const productsContainerRef = useRef<HTMLDivElement>(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,8 +116,7 @@ const Simulations: React.FC = () => {
const selectedProductData = getProductById(selectedProduct.productUuid);
if (selectedProductData) {
determineExecutionMachineSequences([selectedProductData]).then(
(sequences) => {
determineExecutionMachineSequences([selectedProductData]).then((sequences) => {
sequences.forEach((sequence) => {
const events: Event[] =
sequence.map((event) => ({
@@ -146,10 +126,8 @@ const Simulations: React.FC = () => {
processes.push(events);
});
setProcesses(processes);
});
}
);
}
}, [selectedProduct.productUuid, products]);
return (
@@ -159,77 +137,41 @@ const Simulations: React.FC = () => {
<div className="actions section">
<div className="header">
<div className="header-value">Products</div>
<button
id="add-simulation"
className="add-button"
onClick={handleAddProduct}
>
<button id="add-simulation" className="add-button" onClick={handleAddProduct}>
<AddIcon /> Add
</button>
</div>
<div
className="lists-main-container"
ref={productsContainerRef}
style={{ height: "120px" }}
>
<div className="lists-main-container" ref={productsContainerRef} style={{ height: "120px" }}>
<div className="list-container">
{products.map((product, index) => (
<div
key={product.productUuid}
className={`list-item ${selectedProduct.productUuid === product.productUuid
? "active"
: ""
}`}
>
<div key={product.productUuid} className={`list-item ${selectedProduct.productUuid === product.productUuid ? "active" : ""}`}>
{/* eslint-disable-next-line */}
<div
className="value"
onClick={() => {
setSelectedProduct(product.productUuid, product.productName)
setMainProduct(product.productUuid, product.productName)
setSelectedProduct(product.productUuid, product.productName);
setMainProduct(product.productUuid, product.productName);
}}
>
<input
type="radio"
name="products"
checked={selectedProduct.productUuid === product.productUuid}
readOnly
/>
<RenameInput
value={product.productName}
onRename={(newName) =>
handleRenameProduct(product.productUuid, newName)
}
/>
<input type="radio" name="products" checked={selectedProduct.productUuid === product.productUuid} readOnly />
<RenameInput value={product.productName} onRename={(newName) => handleRenameProduct(product.productUuid, newName)} />
</div>
{products.length > 1 && (
<button
id="remove-product-button"
className="remove-button"
onClick={() => handleRemoveProduct(product.productUuid)}
>
<button id="remove-product-button" className="remove-button" onClick={() => handleRemoveProduct(product.productUuid)}>
<RemoveIcon />
</button>
)}
</div>
))}
</div>
<button
className="resize-icon"
id="action-resize"
onMouseDown={(e: any) => handleResize(e, productsContainerRef)}
>
<button className="resize-icon" id="action-resize" onMouseDown={(e: any) => handleResize(e, productsContainerRef)}>
<ResizeHeightIcon />
</button>
</div>
</div>
<div className="simulation-process section">
<button
id="collapse-header"
className="collapse-header-container"
onClick={() => setOpenObjects(!openObjects)}
>
<button id="collapse-header" className="collapse-header-container" onClick={() => setOpenObjects(!openObjects)}>
<div className="header">Process Flow</div>
<div className="arrow-container">
<ArrowIcon />
@@ -239,19 +181,18 @@ const Simulations: React.FC = () => {
processes?.map((process, index) => (
<section key={index}>
{process.map((event, index) => (
<List key={`${index}-${event.modelName}`} val={event} />
<div className="process-container" key={index}>
<div className="value">{event.modelName}</div>
</div>
))}
</section>
))}
</div>
<div className="compare-simulations-container">
<div className="compare-simulations-header">
Need to Compare Layout?
</div>
<div className="compare-simulations-header">Need to Compare Layout?</div>
<div className="content">
Click '<span>Compare</span>' to review and analyze the layout
differences between them.
Click '<span>Compare</span>' to review and analyze the layout differences between them.
</div>
<button className="input" onClick={() => setComparePopUp(true)}>
<input type="button" value={"Compare"} className="submit" />
@@ -270,8 +211,8 @@ const Simulations: React.FC = () => {
addEvent,
selectedProduct,
clearSelectedAsset,
projectId: projectId || '',
versionId: selectedVersion?.versionId || '',
projectId: projectId || "",
versionId: selectedVersion?.versionId || "",
});
} else {
handleRemoveEventFromProduct();

View File

@@ -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;

View File

@@ -1,368 +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/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";
import { useVersionContext } from "../../../modules/builder/version/versionContext";
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<ListProps> = ({ items = [], remove }) => {
const { activeModule } = useModuleStore();
const { selectedZone, setSelectedZone } = useSelectedZoneStore();
const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
const { setSubModule } = useSubModuleStore();
const [expandedZones, setExpandedZones] = useState<Record<string, boolean>>(
{}
);
const { projectId } = useParams();
const { assetStore } = useSceneContext();
const { setName } = assetStore();
const { organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { zoneStore } = useSceneContext();
const { zones, setZoneName } = zoneStore();
useEffect(() => {
useSelectedZoneStore.getState().setSelectedZone({
zoneName: "",
activeSides: [],
panelOrder: [],
lockedPanels: [],
zoneUuid: "",
zoneViewPortTarget: [],
zoneViewPortPosition: [],
widgets: [],
});
}, [activeModule]);
useEffect(() => {
const expanded: Record<string, boolean> = { "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 ? (
<ul className="list-wrapper">
{items?.map((item) => (
<React.Fragment key={`zone-${item.id}`}>
<li
className="list-container"
onClick={() => {
handleSelectZone(item.id);
toggleZoneExpansion(item.id);
}}
>
<div
className={`list-item ${selectedZone.zoneUuid === item.id ? "active" : ""
}`}
>
<div className="zone-header">
<button className="value" id="zone-name">
<RenameInput
value={item.name}
onRename={handleZoneNameChange}
checkDuplicate={checkZoneNameDuplicate}
canEdit={item.id !== "unassigned-zone"}
/>
</button>
</div>
<div className="options-container">
<div className="lock option">
<LockIcon isLocked />
</div>
<div className="visibe option">
<EyeIcon isClosed />
</div>
{remove && (
<div className="remove option">
<RemoveIcon />
</div>
)}
{item.assets && item.assets.length > 0 && (
<button
id="expand-btn"
className="expand-icon option"
onClick={() => toggleZoneExpansion(item.id)}
>
<ArrowIcon />
</button>
)}
</div>
</div>
</li>
{/* Nested assets list - only shown when expanded */}
{item.assets &&
item.assets.length > 0 &&
expandedZones[item.id] && (
<ul className="asset-list">
{item.assets.map((asset) => (
<li
key={`asset-${asset.id}`}
className={`list-container asset-item ${zoneAssetId?.id === asset.id ? "active" : ""
}`}
onClick={() => handleAssetClick(asset)}
>
<div className="list-item">
<button
id={`${asset.name}-${asset.id}`}
className="value"
>
<RenameInput
value={asset.name}
onRename={handleZoneAssetName}
/>
</button>
<div className="options-container">
<button
className="lock option"
id="lock-btn"
onClick={(e) => {
e.stopPropagation();
}}
>
<LockIcon isLocked />
</button>
<button
className="visibe option"
id="visible-btn"
onClick={(e) => {
e.stopPropagation();
}}
>
<EyeIcon isClosed />
</button>
{remove && (
<button
className="remove option"
id="remove-btn"
onClick={(e) => {
e.stopPropagation();
}}
>
<RemoveIcon />
</button>
)}
</div>
</div>
</li>
))}
</ul>
)}
</React.Fragment>
))}
</ul>
) : (
<div className="list-wrapper">
<div className="no-item">No items to display</div>
</div>
)}
</>
);
};
export default List;

View File

@@ -0,0 +1,15 @@
import React from "react";
import ListItemBase from "./ListItemBase";
const AssetItem: React.FC<AssetItemProps> = ({ asset, isActive, remove, onClick, onRename }) => (
<ListItemBase
id={asset.id}
name={asset.name}
isActive={isActive}
remove={remove}
onClick={() => onClick(asset)}
onRename={onRename}
/>
);
export default AssetItem;

View File

@@ -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<ListItemBaseProps> = ({
id,
name,
isActive,
canEdit = true,
remove,
expandable,
expanded,
onClick,
onRename,
onToggleExpand,
children,
}) => (
<>
<li className={`list-container ${isActive ? "active" : ""}`} onClick={onClick}>
<div className="list-item">
<button className="value" id={`${name}-${id}`}>
<RenameInput value={name} onRename={onRename} canEdit={canEdit} />
</button>
<div className="options-container">
<button
className="lock option"
id="lock-btn"
onClick={(e) => e.stopPropagation()}
>
<LockIcon isLocked />
</button>
<button
className="visibe option"
id="visible-btn"
onClick={(e) => e.stopPropagation()}
>
<EyeIcon isClosed />
</button>
{remove && (
<button
className="remove option"
id="remove-btn"
onClick={(e) => e.stopPropagation()}
>
<RemoveIcon />
</button>
)}
{expandable && (
<button
id="expand-btn"
className="expand-icon option"
onClick={(e) => {
e.stopPropagation();
onToggleExpand?.();
}}
>
<ArrowIcon />
</button>
)}
</div>
</div>
</li>
{expandable && expanded && <ul className="asset-list">{children}</ul>}
</>
);
export default ListItemBase;

View File

@@ -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<ListProps> = ({ items = [], remove }) => {
const { selectedZone } = useSelectedZoneStore();
const { zoneAssetId } = useZoneAssetId();
const { expandedZones, toggleZoneExpansion } = useZonesExpansion(items);
const {
handleSelectZone,
handleZoneNameChange,
handleAssetClick,
handleZoneAssetName,
} = useZoneAssetHandlers();
if (items.length === 0) {
return (
<div className="list-wrapper">
<div className="no-item">No items to display</div>
</div>
);
}
return (
<ul className="list-wrapper">
{items.map((item) => (
<ZoneItemComponent
key={item.id}
item={item}
remove={remove}
expanded={expandedZones[item.id]}
isActive={selectedZone.zoneUuid === item.id}
activeAssetId={zoneAssetId?.id}
onSelect={handleSelectZone}
onRename={handleZoneNameChange}
onToggleExpand={toggleZoneExpansion}
onAssetClick={handleAssetClick}
onAssetRename={handleZoneAssetName}
/>
))}
</ul>
);
};
export default List;

View File

@@ -0,0 +1,45 @@
import React from "react";
import ListItemBase from "./ListItemBase";
import AssetItem from "./AssetItem";
const ZoneItemComponent: React.FC<ZoneItemProps> = ({
item,
isActive,
expanded,
remove,
activeAssetId,
onSelect,
onRename,
onToggleExpand,
onAssetClick,
onAssetRename,
}) => (
<ListItemBase
id={item.id}
name={item.name}
isActive={isActive}
canEdit={item.id !== "unassigned-zone"}
remove={remove}
expandable={Boolean(item.assets?.length)}
expanded={expanded}
onClick={() => {
onSelect(item.id);
onToggleExpand(item.id);
}}
onRename={onRename}
onToggleExpand={() => onToggleExpand(item.id)}
>
{item.assets?.map((asset) => (
<AssetItem
key={asset.id}
asset={asset}
isActive={activeAssetId === asset.id}
remove={remove}
onClick={onAssetClick}
onRename={onAssetRename}
/>
))}
</ListItemBase>
);
export default ZoneItemComponent;

View File

@@ -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,
};
};

View File

@@ -0,0 +1,22 @@
import { useEffect, useState, useCallback } from "react";
export const useZonesExpansion = (zones: any[]) => {
const [expandedZones, setExpandedZones] = useState<Record<string, boolean>>({});
useEffect(() => {
const expanded: Record<string, boolean> = { "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 };
};

View File

@@ -27,3 +27,46 @@ interface CategoryListProp {
categoryImage: string;
category: string;
}
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;
}