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

This commit is contained in:
2025-08-29 16:58:27 +05:30
5 changed files with 314 additions and 255 deletions

View File

@@ -336,18 +336,35 @@ export const MachineIcon = () => {
); );
}; };
export const CraneIcon = () => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18.5 5.7502L12.875 1.3752C12.6875 1.1877 12.375 1.1877 12.125 1.3127L5.4375 5.6252H1.875C1.5 5.6252 1.25 5.8752 1.25 6.2502C1.25 6.6252 1.5 6.8752 1.875 6.8752H4.375V10.0002H1.875C1.5 10.0002 1.25 10.2502 1.25 10.6252V13.7502C1.25 14.1252 1.5 14.3752 1.875 14.3752H8.125C8.5 14.3752 8.75 14.1252 8.75 13.7502V10.6252C8.75 10.2502 8.5 10.0002 8.125 10.0002H5.625V6.8752H10.625V15.9377L8.375 17.6252C8.1875 17.8127 8.0625 18.0627 8.1875 18.3127C8.25 18.5627 8.5 18.7502 8.75 18.7502H16.25C16.5 18.7502 16.75 18.5627 16.8125 18.3127C16.875 18.0627 16.8125 17.7502 16.625 17.6252L14.375 15.9377V6.8752H18.125C18.375 6.8752 18.625 6.6877 18.6875 6.4377C18.8125 6.1877 18.75 5.9377 18.5 5.7502ZM3.6875 11.2502L2.5 12.7502V11.2502H3.6875ZM5 11.6252L6.1875 13.1252H3.8125L5 11.6252ZM7.5 12.7502L6.3125 11.2502H7.5V12.7502ZM11.875 3.0002V5.6252H11.25H7.75L11.875 3.0002ZM13.75 5.6252H13.125V3.1252L16.3125 5.6252H13.75Z"
fill="white"
/>
</svg>
);
};
type TypeBasedAssetIconsProps = { type TypeBasedAssetIconsProps = {
assetType: string; assetType: string;
}; };
export function TypeBasedAssetIcons({ assetType }: TypeBasedAssetIconsProps) { export function TypeBasedAssetIcons({ assetType }: TypeBasedAssetIconsProps) {
console.log("assetType: ", assetType);
return ( return (
<div> <div>
{assetType === "machine" && <MachineIcon />} {assetType === "StaticMachine" && <MachineIcon />}
{assetType === "vehicle" && <ForkLiftIcon />} {assetType === "Vehicle" && <ForkLiftIcon />}
{assetType === "transfer" && <ConveyorIcon />} {assetType === "Conveyor" && <ConveyorIcon />}
{assetType === "roboticArm" && <RoboticArmIcon />} {assetType === "Crane" && <CraneIcon />}
{assetType === "ArmBot" && <RoboticArmIcon />}
</div> </div>
); );
} }

View File

@@ -1,199 +1,200 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { ClockThreeIcon, LocationPinIcon, TargetIcon } from '../../../../icons/ExportCommonIcons' import { ClockThreeIcon, LocationPinIcon, TargetIcon } from '../../../../icons/ExportCommonIcons'
import { useSceneContext } from '../../../../../modules/scene/sceneContext'; import { useSceneContext } from '../../../../../modules/scene/sceneContext';
import { useProductContext } from '../../../../../modules/simulation/products/productContext';
import RenameInput from '../../../../ui/inputs/RenameInput'; import RenameInput from '../../../../ui/inputs/RenameInput';
import { useResourceManagementId } from '../../../../../store/builder/store'; import { useResourceManagementId } from '../../../../../store/builder/store';
import { set } from 'immer/dist/internal'; import { getAssetThumbnail } from '../../../../../services/factoryBuilder/asset/assets/getAssetThumbnail';
// import NavigateCatagory from '../NavigateCatagory' // import NavigateCatagory from '../NavigateCatagory'
const Hrm = () => { const Hrm = () => {
const [selectedCard, setSelectedCard] = useState(0); const [selectedCard, setSelectedCard] = useState(0);
const [workers, setWorkers] = useState<any[]>([]); const [workers, setWorkers] = useState<any[]>([]);
const { productStore } = useSceneContext();
const { products, getProductById } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { setResourceManagementId } = useResourceManagementId(); const { setResourceManagementId } = useResourceManagementId();
const { assetStore } = useSceneContext();
const { assets: allAssets } = assetStore();
useEffect(() => { async function getAsset(assetId: string) {
if (selectedProduct) { let thumbnail = await getAssetThumbnail(assetId)
const productDetails = getProductById(selectedProduct.productUuid); if (thumbnail.thumbnail) {
const workerDetails = productDetails?.eventDatas || []; let assetImage = thumbnail.thumbnail
return assetImage;
}
}
const formattedWorkers = workerDetails useEffect(() => {
.filter((worker: any) => worker.type === "human") if (allAssets.length > 0) {
.map((worker: any, index: number) => ({ const fetchWorkers = async () => {
employee: { const humans = allAssets.filter((worker: any) => worker.eventData.type === "Human");
image: "",
name: worker.modelName, const formattedWorkers = await Promise.all(
modelId: worker.modelUuid, humans.map(async (worker: any, index: number) => {
employee_id: `HR-${204 + index}`, const assetImage = await getAsset(worker.assetId);
status: "Active",
}, return {
task: { employee: {
status: "Ongoing", image: assetImage,
title: worker.taskTitle || "No Task Assigned", name: worker.modelName,
location: { modelId: worker.modelUuid,
floor: worker.floor || 0, employee_id: `HR-${204 + index}`,
zone: worker.zone || "N/A" status: "Active",
}, },
planned_time_hours: worker.plannedTime || 0, task: {
time_spent_hours: worker.timeSpent || 0, status: "Ongoing",
total_tasks: worker.totalTasks || 0, title: worker.taskTitle ?? "No Task Assigned",
completed_tasks: worker.completedTasks || 0 location: {
}, floor: worker.floor ?? 0,
actions: [ zone: worker.zone ?? "N/A",
"Assign Task", },
"Reassign Task", planned_time_hours: worker.plannedTime ?? 0,
"Pause", time_spent_hours: worker.timeSpent ?? 0,
"Emergency Stop" total_tasks: worker.totalTasks ?? 0,
], completed_tasks: worker.completedTasks ?? 0,
location: `Floor ${worker.floor || "-"} . Zone ${worker.zone || "-"}` },
})); actions: ["Assign Task", "Reassign Task", "Pause", "Emergency Stop"],
location: `Floor ${worker.floor || "-"} . Zone ${worker.zone || "-"}`,
};
})
);
setWorkers(formattedWorkers); setWorkers(formattedWorkers);
} };
}, [selectedProduct, getProductById]);
useEffect(() => {
//
}, [workers]);
// const employee_details = [
// {
// "employee": {
// image: "",
// "name": "John Doe",
// "employee_id": "HR-204",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Inspecting Machine X",
// "location": {
// "floor": 4,
// "zone": "B"
// },
// "planned_time_hours": 6,
// "time_spent_hours": 2,
// "total_tasks": 12,
// "completed_tasks": 3
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// {
// "employee": {
// image: "",
// "name": "Alice Smith",
// "employee_id": "HR-205",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Calibrating Sensor Y",
// "location": {
// "floor": 2,
// "zone": "A"
// },
// "planned_time_hours": 4,
// "time_spent_hours": 1.5,
// "total_tasks": 10,
// "completed_tasks": 2
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// {
// "employee": {
// image: "",
// "name": "Michael Lee",
// "employee_id": "HR-206",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Testing Conveyor Belt Z",
// "location": {
// "floor": 5,
// "zone": "C"
// },
// "planned_time_hours": 5,
// "time_spent_hours": 3,
// "total_tasks": 8,
// "completed_tasks": 5
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// ]
function handleRenameWorker(newName: string) {
//
fetchWorkers();
} }
function handleHumanClick(employee: any) { }, [allAssets]);
if (employee.modelId) {
setResourceManagementId(employee.modelId);
}
// const employee_details = [
// {
// "employee": {
// image: "",
// "name": "John Doe",
// "employee_id": "HR-204",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Inspecting Machine X",
// "location": {
// "floor": 4,
// "zone": "B"
// },
// "planned_time_hours": 6,
// "time_spent_hours": 2,
// "total_tasks": 12,
// "completed_tasks": 3
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// {
// "employee": {
// image: "",
// "name": "Alice Smith",
// "employee_id": "HR-205",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Calibrating Sensor Y",
// "location": {
// "floor": 2,
// "zone": "A"
// },
// "planned_time_hours": 4,
// "time_spent_hours": 1.5,
// "total_tasks": 10,
// "completed_tasks": 2
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// {
// "employee": {
// image: "",
// "name": "Michael Lee",
// "employee_id": "HR-206",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Testing Conveyor Belt Z",
// "location": {
// "floor": 5,
// "zone": "C"
// },
// "planned_time_hours": 5,
// "time_spent_hours": 3,
// "total_tasks": 8,
// "completed_tasks": 5
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// ]
function handleRenameWorker(newName: string) {
}
function handleHumanClick(employee: any) {
if (employee.modelId) {
setResourceManagementId(employee.modelId);
} }
}
return ( return (
<> <>
{/* <NavigateCatagory {/* <NavigateCatagory
category={["All People", "Technician", "Operator", "Supervisor", "Safety Officer"]} category={["All People", "Technician", "Operator", "Supervisor", "Safety Officer"]}
selectedCategory={selectedCategory} selectedCategory={selectedCategory}
setSelectedCategory={setSelectedCategory} setSelectedCategory={setSelectedCategory}
/> */} /> */}
<div className='hrm-container assetManagement-wrapper'> <div className='hrm-container assetManagement-wrapper'>
{workers.map((employee, index) => ( {workers.map((employee, index) => (
<div <div
className={`analysis-wrapper ${selectedCard === index ? "active" : ""}`} className={`analysis-wrapper ${selectedCard === index ? "active" : ""}`}
onClick={() => setSelectedCard(index)} onClick={() => setSelectedCard(index)}
key={index} key={index}
> >
<header> <header>
<div className="user-details"> <div className="user-details">
<div className="user-image-wrapper"> <div className="user-image-wrapper">
<img className='user-image' src={employee.employee.image} alt="" /> <img className='user-image' src={employee.employee.image} alt="" />
<div className={`status ${employee.employee.status}`}></div> <div className={`status ${employee.employee.status}`}></div>
</div>
<div className="details" >
{/* <div className="employee-name">{employee.employee.name}</div> */}
<RenameInput value={employee.employee.name} onRename={handleRenameWorker} />
<div className="employee-id">{employee.employee.employee_id}</div>
</div>
</div> </div>
<div className="details" >
{/* <div className="employee-name">{employee.employee.name}</div> */}
<RenameInput value={employee.employee.name} onRename={handleRenameWorker} />
<div className="employee-id">{employee.employee.employee_id}</div>
</div>
</div>
<div className="see-more" onClick={() => { handleHumanClick(employee.employee) }}>View in Scene</div> <div className="see-more" onClick={() => { handleHumanClick(employee.employee) }}>View in Scene</div>
</header> </header>
<div className="content"> <div className="content">
{/* <div className="task-info"> {/* <div className="task-info">
<div className="task-wrapper"> <div className="task-wrapper">
<div className="task-label"> <div className="task-label">
<span className='label-icon'><ListTaskIcon /></span> <span className='label-icon'><ListTaskIcon /></span>
@@ -214,17 +215,17 @@ const Hrm = () => {
</div> </div>
</div> */} </div> */}
<div className="task-stats"> <div className="task-stats">
<div className="stat-item"> <div className="stat-item">
<div className="stat-wrapper"> <div className="stat-wrapper">
<span className="stat-icon"><ClockThreeIcon /></span> <span className="stat-icon"><ClockThreeIcon /></span>
<span>Planned time:</span> <span>Planned time:</span>
</div>
<span className='stat-value'>{employee.task.planned_time_hours} hr</span>
</div> </div>
{/* <div className="stat-item">
<span className='stat-value'>{employee.task.planned_time_hours} hr</span>
</div>
{/* <div className="stat-item">
<div className="stat-wrapper"> <div className="stat-wrapper">
<span className="stat-icon"><SlectedTickIcon /></span> <span className="stat-icon"><SlectedTickIcon /></span>
@@ -242,39 +243,39 @@ const Hrm = () => {
<span className='stat-value'>{employee.task.time_spent_hours} hr</span> <span className='stat-value'>{employee.task.time_spent_hours} hr</span>
</div> */} </div> */}
<div className="stat-item"> <div className="stat-item">
<div className="stat-wrapper"> <div className="stat-wrapper">
<span className="stat-icon"><TargetIcon /></span> <span className="stat-icon"><TargetIcon /></span>
<span>Cost per hr:</span> <span>Cost per hr:</span>
</div>
<span className='stat-value'>{employee.task.completed_tasks}</span>
</div> </div>
</div>
<div className="location-wrapper"> <span className='stat-value'>{employee.task.completed_tasks}</span>
<div className="location-header">
<div className="icon">
<LocationPinIcon />
</div>
<div className="header">Location:</div>
</div>
<div className="location-value">{employee.location}</div>
</div>
<div className="task-actions">
{/* <button className="btn btn-default">Assign Task</button>
<button className="btn btn-default">Reassign Task</button> */}
<button className="btn btn-default">Pause</button>
<button className="btn btn-danger">Emergency Stop</button>
</div> </div>
</div> </div>
<div className="location-wrapper">
<div className="location-header">
<div className="icon">
<LocationPinIcon />
</div>
<div className="header">Location:</div>
</div>
<div className="location-value">{employee.location}</div>
</div>
<div className="task-actions">
{/* <button className="btn btn-default">Assign Task</button>
<button className="btn btn-default">Reassign Task</button> */}
<button className="btn btn-default">Pause</button>
<button className="btn btn-danger">Emergency Stop</button>
</div>
</div> </div>
))}
</div> </div>
</> ))}
) </div>
</>
)
} }
export default Hrm export default Hrm

View File

@@ -1,65 +1,70 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
// import NavigateCatagory from '../../NavigateCatagory' // import NavigateCatagory from '../../NavigateCatagory'
import { EyeIcon, KebabIcon, LocationPinIcon, RightHalfFillCircleIcon } from '../../../../../icons/ExportCommonIcons'; import { EyeIcon, KebabIcon, LocationPinIcon, RightHalfFillCircleIcon } from '../../../../../icons/ExportCommonIcons';
import assetImage from "../../../../../../assets/image/asset-image.png" import assetImageFallback from "../../../../../../assets/image/asset-image.png"
import { useSceneContext } from '../../../../../../modules/scene/sceneContext'; import { useSceneContext } from '../../../../../../modules/scene/sceneContext';
import { useProductContext } from '../../../../../../modules/simulation/products/productContext';
import RenameInput from '../../../../../ui/inputs/RenameInput'; import RenameInput from '../../../../../ui/inputs/RenameInput';
import { useResourceManagementId } from '../../../../../../store/builder/store'; import { useResourceManagementId } from '../../../../../../store/builder/store';
import { getAssetThumbnail } from '../../../../../../services/factoryBuilder/asset/assets/getAssetThumbnail';
import { TypeBasedAssetIcons } from '../../../../../icons/AssetTypeIcons'; import { TypeBasedAssetIcons } from '../../../../../icons/AssetTypeIcons';
const AssetManagement = () => { const AssetManagement = () => {
// const [selectedCategory, setSelectedCategory] = useState("All Assets"); // const [selectedCategory, setSelectedCategory] = useState("All Assets");
const [expandedAssetId, setExpandedAssetId] = useState<string | null>(null); const [expandedAssetId, setExpandedAssetId] = useState<string | null>(null);
const [assets, setAssets] = useState<any[]>([]); const [assets, setAssets] = useState<any[]>([]);
const { productStore } = useSceneContext();
const { getProductById } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { setResourceManagementId } = useResourceManagementId(); const { setResourceManagementId } = useResourceManagementId();
const { assetStore } = useSceneContext();
const { assets: allAssets } = assetStore();
async function getAsset(assetId: string) {
let thumbnail = await getAssetThumbnail(assetId)
if (thumbnail.thumbnail) {
let assetImage = thumbnail.thumbnail ?? assetImageFallback;
return assetImage;
}
}
useEffect(() => { useEffect(() => {
if (selectedProduct) { if (allAssets.length > 0) {
const productDetails = getProductById(selectedProduct.productUuid); const fetchAssets = async () => {
const productAssets = productDetails?.eventDatas || []; const grouped: Record<string, any> = {};
const grouped: Record<string, any> = {};
productAssets.forEach((asset: any) => {
if (asset.type === "storageUnit" || asset.type === "human") return;
if (!grouped[asset.modelName]) {
grouped[asset.modelName] = {
id: asset.modelUuid,
name: asset.modelName,
type: asset.type,
model: asset.modelCode || "N/A",
status: asset.status || "Online",
usageRate: asset.usageRate || 15,
level: asset.level || "Level 1",
image: assetImage,
description: asset.description || "No description",
cost: asset.cost || 0,
count: 1,
};
} else {
grouped[asset.modelName].count += 1;
}
});
setAssets(Object.values(grouped)); // Use Promise.all to handle all async operations
await Promise.all(allAssets.map(async (asset: any) => {
if (asset.eventData.type === "Storage" || asset.eventData.type === "Human") return;
const assetImage = await getAsset(asset.assetId);
if (!grouped[asset.assetId]) {
//
grouped[asset.assetId] = {
id: asset.modelUuid,
assetId: asset.assetId,
name: asset.modelName,
type: asset.eventData.type,
model: asset.modelCode ?? "N/A",
status: asset.status ?? "Online",
usageRate: asset.usageRate ?? 15,
level: asset.level ?? "Level 1",
image: assetImage,
description: asset.description ?? "No description",
cost: asset.cost ?? 0,
count: 1,
};
} else {
grouped[asset.assetId].count += 1;
}
}));
setAssets(Object.values(grouped));
}
fetchAssets();
} }
// eslint-disable-next-line }, [allAssets]);
}, [selectedProduct]);
function handleRenameAsset(newName: string) { function handleRenameAsset(newName: string) {
//
// if (expandedAssetId) {
// setAssets(prevAssets =>
// prevAssets.map(asset =>
// asset.id === expandedAssetId ? { ...asset, name: newName } : asset
// )
// );
// }
} }
useEffect(() => { useEffect(() => {
@@ -68,8 +73,6 @@ const AssetManagement = () => {
}, [assets]); }, [assets]);
function handleAssetClick(id: string) { function handleAssetClick(id: string) {
setResourceManagementId(id); setResourceManagementId(id);
} }
@@ -126,7 +129,6 @@ const AssetManagement = () => {
<div className={`assetManagement-card-wrapper ${expandedAssetId === asset.id ? "openViewMore" : ""}`} key={index}> <div className={`assetManagement-card-wrapper ${expandedAssetId === asset.id ? "openViewMore" : ""}`} key={index}>
<header> <header>
<div className="header-wrapper"> <div className="header-wrapper">
{expandedAssetId === asset.id ? {expandedAssetId === asset.id ?
<> <>
<div className="drop-icon" onClick={() => setExpandedAssetId(null)}></div> <div className="drop-icon" onClick={() => setExpandedAssetId(null)}></div>
@@ -173,9 +175,9 @@ const AssetManagement = () => {
</div> </div>
</div> </div>
<div className="asset-estimate__view-button"> <div className="asset-estimate__view-button" onClick={() => handleAssetClick(asset.id)}>
<EyeIcon isClosed={false} /> <EyeIcon isClosed={false} />
<div className="asset-estimate__view-text" onClick={() => handleAssetClick(asset.id)}>View in Scene</div> <div className="asset-estimate__view-text">View in Scene</div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,33 @@
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
export const getAssetThumbnail = async (assetId: String) => {
try {
const response = await fetch(
`${url_Backend_dwinzo}/api/v2/getAssetThumbnail/${assetId}`,
{
method: "GET",
headers: {
Authorization: "Bearer <access_token>",
"Content-Type": "application/json",
token: localStorage.getItem("token") || "",
refresh_token: localStorage.getItem("refreshToken") || "",
},
}
);
const newAccessToken = response.headers.get("x-access-token");
if (newAccessToken) {
localStorage.setItem("token", newAccessToken);
}
if (!response.ok) {
throw new Error("Failed to fetch assets");
}
//
return await response.json();
} catch (error: any) {
echo.error("Failed to get asset image");
}
};

View File

@@ -117,6 +117,12 @@
border-radius: 50%; border-radius: 50%;
background-color: #fff; background-color: #fff;
position: relative; position: relative;
overflow: hidden;
.user-image{
height: 300%;
width: 300%;
transform: translate(-26px, -12px);
}
.status { .status {
border-radius: 50%; border-radius: 50%;