Merge remote-tracking branch 'origin/main-demo' into main-dev

This commit is contained in:
2025-09-05 16:00:54 +05:30
10 changed files with 373 additions and 319 deletions

View File

@@ -23,18 +23,6 @@ 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, versionStore } = useSceneContext();
@@ -188,7 +176,9 @@ 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>
))}

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