feat: add WallProperties component for managing wall dimensions and materials

This commit is contained in:
Nalvazhuthi 2025-06-04 15:36:55 +05:30
parent 13ec906fac
commit 5787213882
10 changed files with 378 additions and 96 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -0,0 +1,213 @@
import { useEffect, useState } from "react";
import InputWithDropDown from "../../../../ui/inputs/InputWithDropDown";
import { AddIcon, RemoveIcon } from "../../../../icons/ExportCommonIcons";
// Texture Imports
import wallTexture1 from "../../../../../assets/image/wallTextures/wallTexture.png";
import defaultTexture from "../../../../../assets/image/wallTextures/defaultTexture.jpg";
// Define Material type
type Material = {
texture: string;
textureName: string;
};
// Initial and default material
const initialMaterial: Material = {
texture: wallTexture1,
textureName: "Grunge Concrete Wall",
};
const defaultMaterial: Material = {
texture: defaultTexture,
textureName: "Default Material",
};
const WallProperties = () => {
const [wallProperties, setWallProperties] = useState({
height: "10",
thickness: "10",
length: "10",
});
const [activeSide, setActiveSide] = useState<"side1" | "side2">("side1");
const [materials, setMaterials] = useState<Material[]>([initialMaterial]);
const [selectedMaterials, setSelectedMaterials] = useState<{
side1: Material | null;
side2: Material | null;
}>({
side1: null,
side2: null,
});
// Select initial material for both sides on mount
useEffect(() => {
setSelectedMaterials({
side1: initialMaterial,
side2: initialMaterial,
});
}, []);
const handleInputChange = (
key: keyof typeof wallProperties,
newValue: string
) => {
setWallProperties((prev) => ({
...prev,
[key]: newValue,
}));
};
const handleAddMaterial = () => {
const newMaterial: Material = {
texture: defaultMaterial.texture,
textureName: `Material ${materials.length + 1}`,
};
setMaterials([...materials, newMaterial]);
};
const handleSelectMaterial = (material: Material) => {
setSelectedMaterials((prev) => ({
...prev,
[activeSide]: material,
}));
};
const handleRemoveMaterial = (index: number) => {
const updatedMaterials = materials.filter((_, i) => i !== index);
// Ensure there's always at least one material
const newMaterials =
updatedMaterials.length === 0 ? [defaultMaterial] : updatedMaterials;
setMaterials(newMaterials);
// Deselect the material if it's the one removed
setSelectedMaterials((prev) => {
const updated = { ...prev };
["side1", "side2"].forEach((side) => {
if (
updated[side as "side1" | "side2"]?.texture ===
materials[index].texture
) {
updated[side as "side1" | "side2"] = defaultMaterial;
}
});
return updated;
});
};
return (
<div className="wall-properties-container">
<div className="header">Wall</div>
<div className="wall-properties">
<InputWithDropDown
label="Height"
value={wallProperties.height}
onChange={(val) => handleInputChange("height", val)}
/>
<InputWithDropDown
label="Thickness"
value={wallProperties.thickness}
onChange={(val) => handleInputChange("thickness", val)}
/>
<InputWithDropDown
label="Length"
value={wallProperties.length}
onChange={(val) => handleInputChange("length", val)}
/>
</div>
<section>
<div className="header-wrapper">
<div className="header">Materials</div>
<button className="addMaterial" onClick={handleAddMaterial}>
<AddIcon />
</button>
</div>
<div className="material-preview">
<div className="sides-wrapper">
<div
className={`side-wrapper ${
activeSide === "side1" ? "active" : ""
}`}
onClick={() => setActiveSide("side1")}
>
<div className="label">Side 1</div>
<div className="texture-image">
{selectedMaterials.side1 && (
<img
src={selectedMaterials.side1.texture}
alt={selectedMaterials.side1.textureName}
/>
)}
</div>
</div>
<div
className={`side-wrapper ${
activeSide === "side2" ? "active" : ""
}`}
onClick={() => setActiveSide("side2")}
>
<div className="label">Side 2</div>
<div className="texture-image">
{selectedMaterials.side2 && (
<img
src={selectedMaterials.side2.texture}
alt={selectedMaterials.side2.textureName}
/>
)}
</div>
</div>
</div>
<div className="preview">
{selectedMaterials[activeSide] && (
<img
src={selectedMaterials[activeSide]!.texture}
alt={selectedMaterials[activeSide]!.textureName}
/>
)}
</div>
</div>
<div className="materials">
{materials.length === 0 ? (
<div className="no-materials">No materials added yet.</div>
) : (
<div className="material-container">
{materials.map((material, index) => (
<div
className="material-wrapper"
key={`${material.textureName}_${index}`}
onClick={() => handleSelectMaterial(material)}
>
<div className="material-property">
<div className="material-image">
<img src={material.texture} alt={material.textureName} />
</div>
<div className="material-name">{material.textureName}</div>
</div>
<div
className="delete-material"
onClick={(e) => {
e.stopPropagation();
handleRemoveMaterial(index);
}}
>
<RemoveIcon />
</div>
</div>
))}
</div>
)}
</div>
</section>
</div>
);
};
export default WallProperties;

View File

@ -12,6 +12,7 @@ import {
useRemovedLayer,
useZones,
useZonePoints,
useViewSceneStore,
} from "../../../store/builder/store";
import { getZonesApi } from "../../../services/factoryBuilder/zones/getZonesApi";
@ -525,7 +526,7 @@ const ZoneGroup: React.FC = () => {
setEndPoint(point);
}
});
const { viewSceneLabels } = useViewSceneStore();
return (
<group ref={groupsRef} name="zoneGroup">
<group name="zones" visible={!toggleView}>
@ -577,6 +578,7 @@ const ZoneGroup: React.FC = () => {
);
})}
{!toggleView &&
viewSceneLabels &&
(() => {
const points3D = zone.points || [];
const coords2D = points3D.map((p: any) => [p[0], p[2]]);

View File

@ -2,19 +2,21 @@ import React from "react";
import MachineInstance from "./machineInstance/machineInstance";
import { useMachineStore } from "../../../../store/simulation/useMachineStore";
import MachineContentUi from "../../ui3d/MachineContentUi";
import { useViewSceneStore } from "../../../../store/builder/store";
function MachineInstances() {
const { machines } = useMachineStore();
return (
<>
{machines.map((machine: MachineStatus) => (
<React.Fragment key={machine.modelUuid}>
<MachineInstance machineDetail={machine} />
<MachineContentUi machine={machine} />
</React.Fragment>
))}
</>
);
const { machines } = useMachineStore();
const { viewSceneLabels } = useViewSceneStore();
return (
<>
{machines.map((machine: MachineStatus) => (
<React.Fragment key={machine.modelUuid}>
<MachineInstance machineDetail={machine} />
{viewSceneLabels && <MachineContentUi machine={machine} />}
</React.Fragment>
))}
</>
);
}
export default MachineInstances;

View File

@ -1,21 +1,23 @@
import React from 'react'
import StorageUnitInstance from './storageUnitInstance/storageUnitInstance'
import { useStorageUnitStore } from '../../../../store/simulation/useStorageUnitStore'
import StorageContentUi from '../../ui3d/StorageContentUi';
import React from "react";
import StorageUnitInstance from "./storageUnitInstance/storageUnitInstance";
import { useStorageUnitStore } from "../../../../store/simulation/useStorageUnitStore";
import StorageContentUi from "../../ui3d/StorageContentUi";
import { useViewSceneStore } from "../../../../store/builder/store";
function StorageUnitInstances() {
const { storageUnits } = useStorageUnitStore();
const { storageUnits } = useStorageUnitStore();
const { viewSceneLabels } = useViewSceneStore();
return (
<>
{storageUnits.map((storageUnit: StorageUnitStatus) => (
<React.Fragment key={storageUnit.modelUuid}>
<StorageUnitInstance storageUnit={storageUnit} />
<StorageContentUi storageUnit={storageUnit}/>
</React.Fragment>
))}
</>
)
return (
<>
{storageUnits.map((storageUnit: StorageUnitStatus) => (
<React.Fragment key={storageUnit.modelUuid}>
<StorageUnitInstance storageUnit={storageUnit} />
{viewSceneLabels && <StorageContentUi storageUnit={storageUnit} />}
</React.Fragment>
))}
</>
);
}
export default StorageUnitInstances
export default StorageUnitInstances;

View File

@ -2,20 +2,22 @@ import React from "react";
import VehicleInstance from "./instance/vehicleInstance";
import { useVehicleStore } from "../../../../store/simulation/useVehicleStore";
import VehicleContentUi from "../../ui3d/VehicleContentUi";
import { useViewSceneStore } from "../../../../store/builder/store";
function VehicleInstances() {
const { vehicles } = useVehicleStore();
const { vehicles } = useVehicleStore();
const { viewSceneLabels } = useViewSceneStore();
return (
<>
{vehicles.map((vehicle: VehicleStatus) => (
<React.Fragment key={vehicle.modelUuid}>
<VehicleInstance agvDetail={vehicle} />
<VehicleContentUi vehicle={vehicle} />
</React.Fragment>
))}
</>
);
return (
<>
{vehicles.map((vehicle: VehicleStatus) => (
<React.Fragment key={vehicle.modelUuid}>
<VehicleInstance agvDetail={vehicle} />
{viewSceneLabels && <VehicleContentUi vehicle={vehicle} />}
</React.Fragment>
))}
</>
);
}
export default VehicleInstances;

View File

@ -14,6 +14,7 @@ import {
useLoadingProgress,
useWidgetSubOption,
useSaveVersion,
useViewSceneStore,
} from "../store/builder/store";
import { useNavigate } from "react-router-dom";
import { usePlayButtonStore } from "../store/usePlayButtonStore";
@ -38,6 +39,7 @@ import RegularDropDown from "../components/ui/inputs/RegularDropDown";
import VersionSaved from "../components/layout/sidebarRight/versionHisory/VersionSaved";
import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
import { useAssetsStore } from "../store/builder/useAssetStore";
import InputToggle from "../components/ui/inputs/InputToggle";
const Project: React.FC = () => {
let navigate = useNavigate();
@ -110,6 +112,9 @@ const Project: React.FC = () => {
setSelectedLayout(option); // Set selected layout
console.log("Selected layout:", option);
};
const { viewSceneLabels, setViewSceneLabels } = useViewSceneStore();
return (
<div className="project-main">
{!selectedUser && (
@ -128,6 +133,16 @@ const Project: React.FC = () => {
{activeModule !== "market" && !isPlaying && !isVersionSaved && (
<Tools />
)}
{isPlaying && activeModule === "simulation" && (
<div className="label-toogler">
<InputToggle
value={viewSceneLabels}
inputKey="1"
label="Enable View Labels"
onClick={() => setViewSceneLabels(!viewSceneLabels)}
/>
</div>
)}
{isPlaying && activeModule === "simulation" && <SimulationPlayer />}
{isPlaying && activeModule !== "simulation" && <ControlsPlayer />}

View File

@ -450,88 +450,85 @@ interface ShortcutStore {
}
export const useShortcutStore = create<ShortcutStore>((set) => ({
showShortcuts: false,
setShowShortcuts: (value) => set({ showShortcuts: value }),
toggleShortcuts: () =>
set((state) => ({ showShortcuts: !state.showShortcuts })),
showShortcuts: false,
setShowShortcuts: (value) => set({ showShortcuts: value }),
toggleShortcuts: () =>
set((state) => ({ showShortcuts: !state.showShortcuts })),
}));
export const useMachineCount = create<any>((set: any) => ({
machineCount: 0,
setMachineCount: (x: any) => set({ machineCount: x }),
machineCount: 0,
setMachineCount: (x: any) => set({ machineCount: x }),
}));
export const useMachineUptime = create<any>((set: any) => ({
machineActiveTime: 0,
setMachineActiveTime: (x: any) => set({ machineActiveTime: x }),
machineActiveTime: 0,
setMachineActiveTime: (x: any) => set({ machineActiveTime: x }),
}));
export const useMaterialCycle = create<any>((set: any) => ({
materialCycleTime: 0,
setMaterialCycleTime: (x: any) => set({ materialCycleTime: x }),
materialCycleTime: 0,
setMaterialCycleTime: (x: any) => set({ materialCycleTime: x }),
}));
export const useThroughPutData = create<any>((set: any) => ({
throughputData: 0,
setThroughputData: (x: any) => set({ throughputData: x }),
throughputData: 0,
setThroughputData: (x: any) => set({ throughputData: x }),
}));
export const useProductionCapacityData = create<any>((set: any) => ({
productionCapacityData: 0,
setProductionCapacityData: (x: any) => set({ productionCapacityData: x }),
productionCapacityData: 0,
setProductionCapacityData: (x: any) => set({ productionCapacityData: x }),
}));
export const useProcessBar = create<any>((set: any) => ({
processBar: [],
setProcessBar: (x: any) => set({ processBar: x }),
processBar: [],
setProcessBar: (x: any) => set({ processBar: x }),
}));
type InputValuesStore = {
inputValues: Record<string, string>;
setInputValues: (values: Record<string, string>) => void;
updateInputValue: (label: string, value: string) => void; // <- New
inputValues: Record<string, string>;
setInputValues: (values: Record<string, string>) => void;
updateInputValue: (label: string, value: string) => void; // <- New
};
export const useInputValues = create<InputValuesStore>((set) => ({
inputValues: {},
setInputValues: (values) => set({ inputValues: values }),
updateInputValue: (label, value) =>
set((state) => ({
inputValues: {
...state.inputValues,
[label]: value,
},
})),
inputValues: {},
setInputValues: (values) => set({ inputValues: values }),
updateInputValue: (label, value) =>
set((state) => ({
inputValues: {
...state.inputValues,
[label]: value,
},
})),
}));
export interface ROISummaryData {
productName: string;
roiPercentage: number;
paybackPeriod: number;
totalCost: number;
revenueGenerated: number;
netProfit: number;
netLoss: number;
productName: string;
roiPercentage: number;
paybackPeriod: number;
totalCost: number;
revenueGenerated: number;
netProfit: number;
netLoss: number;
}
interface ROISummaryStore {
roiSummary: ROISummaryData;
setRoiSummaryData: (values: ROISummaryData) => void;
roiSummary: ROISummaryData;
setRoiSummaryData: (values: ROISummaryData) => void;
}
export const useROISummaryData = create<ROISummaryStore>((set) => ({
roiSummary: {
productName: "",
roiPercentage: 0,
paybackPeriod: 0,
totalCost: 0,
revenueGenerated: 0,
netProfit: 0,
netLoss: 0,
},
setRoiSummaryData: (values) => set({ roiSummary: values }),
roiSummary: {
productName: "",
roiPercentage: 0,
paybackPeriod: 0,
totalCost: 0,
revenueGenerated: 0,
netProfit: 0,
netLoss: 0,
},
setRoiSummaryData: (values) => set({ roiSummary: values }),
}));
interface CompareStore {
comparePopUp: boolean;
setComparePopUp: (value: boolean) => void;
@ -593,3 +590,13 @@ export const useVersionStore = create<VersionListStore>((set) => ({
),
})),
}));
interface ViewSceneState {
viewSceneLabels: boolean;
setViewSceneLabels: (value: boolean) => void;
}
export const useViewSceneStore = create<ViewSceneState>((set) => ({
viewSceneLabels: false,
setViewSceneLabels: (value) => set({ viewSceneLabels: value }),
}));

View File

@ -4,11 +4,21 @@
.distance-text-wrapper,
.zone-name-wrapper,
.pointer-none {
pointer-events: none !important;
pointer-events: auto !important;
background-color: gray;
}
.zone-name-wrapper {
background: var(--background-color-accent);
color: var(--text-button-color);
outline: 1px solid var(--border-color);
border-radius: #{$border-radius-medium};
backdrop-filter: blur(12px);
}
.distance-text {
pointer-events: none !important;
div {
position: absolute;
transform: translate(-50%, -50%) scale(0.8);
@ -22,18 +32,16 @@
border-radius: #{$border-radius-medium};
box-shadow: var(--box-shadow-light);
}
.area {
background: #008cff;
}
}
.zone-name{
background: var(--background-color);
padding: 2px 8px;
.zone-name {
padding: 2px 10px;
text-wrap: nowrap;
backdrop-filter: blur(12px);
border-radius: #{$border-radius-medium};
outline: 1px solid var(--border-color);
color: var(--text-button-color);
}
//
@ -55,18 +63,49 @@
outline: 1px solid var(--border-color);
transform: translate(-50%, 12px);
z-index: 100;
.presets-container {
@include flex-center;
gap: 4px;
.preset {
background: var(--background-color);
padding: 2px 8px;
border-radius: #{$border-radius-large};
outline: 1px solid var(--border-color);
}
.active {
background: var(--background-color-accent);
color: var(--text-button-color);
}
}
}
.label-toogler {
position: fixed;
bottom: 5%;
right: 2%;
z-index: 10;
background: var(--background-color);
backdrop-filter: blur(10px);
outline: 1px solid var(--border-color);
border-radius: 8px;
.input-toggle-container {
display: flex;
flex-direction: column;
gap: 12px;
.check-box {
.check-box-style {
// background: var(--text-button-color) !important;
}
}
}
}