feat: implement simulation dashboard element data management with new input components and styles
This commit is contained in:
@@ -107,23 +107,17 @@ const AnalyzerManager: React.FC = () => {
|
|||||||
|
|
||||||
const assetAnalysis = getAssetAnalysis(assetId);
|
const assetAnalysis = getAssetAnalysis(assetId);
|
||||||
if (assetAnalysis) {
|
if (assetAnalysis) {
|
||||||
const timeLabel = new Date().toLocaleTimeString();
|
const newGraphData = dataKeys.map((key) => {
|
||||||
const newPoint: GraphDataPoint = { name: timeLabel, value: 0 };
|
|
||||||
|
|
||||||
let hasValidData = false;
|
|
||||||
dataKeys.forEach((key) => {
|
|
||||||
const val = resolvePath(assetAnalysis, key);
|
const val = resolvePath(assetAnalysis, key);
|
||||||
if (typeof val === "number") {
|
return {
|
||||||
newPoint["value"] = val;
|
// Make the key readable or just use it as name
|
||||||
hasValidData = true;
|
name: key.split(".").pop() || key,
|
||||||
}
|
value: typeof val === "number" ? val : 0,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
if (hasValidData) {
|
// Deep check to avoid unnecessary updates
|
||||||
const currentGraphData = element.graphData || [];
|
if (JSON.stringify(newGraphData) !== JSON.stringify(element.graphData)) {
|
||||||
const newGraphData = [...currentGraphData, newPoint].slice(-10);
|
|
||||||
|
|
||||||
// Always update for single-machine as we are appending time-series data
|
|
||||||
updateGraphData(block.blockUuid, element.elementUuid, newGraphData);
|
updateGraphData(block.blockUuid, element.elementUuid, newGraphData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ const ElementData: React.FC<ElementDataProps> = ({
|
|||||||
const totalAssetOptions = assetDropdownItems.length;
|
const totalAssetOptions = assetDropdownItems.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="data-details">
|
<div className="fields-wrapper data-details">
|
||||||
{element?.type === "label-value" && (
|
{element?.type === "label-value" && (
|
||||||
<>
|
<>
|
||||||
<div className="design-section">
|
<div className="design-section">
|
||||||
@@ -99,7 +99,7 @@ const ElementData: React.FC<ElementDataProps> = ({
|
|||||||
updateElementData(selectedBlock, selectedElement, { label: value });
|
updateElementData(selectedBlock, selectedElement, { label: value });
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="data">
|
<div className="datas">
|
||||||
<DataDetailedDropdown
|
<DataDetailedDropdown
|
||||||
title="Data Source"
|
title="Data Source"
|
||||||
placeholder="Select assets"
|
placeholder="Select assets"
|
||||||
@@ -134,9 +134,10 @@ const ElementData: React.FC<ElementDataProps> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="data">
|
<div className="datas">
|
||||||
<DataDetailedDropdown
|
<DataDetailedDropdown
|
||||||
title="Data Value"
|
title="Data Value"
|
||||||
|
className="fill-width"
|
||||||
placeholder="Select Value"
|
placeholder="Select Value"
|
||||||
sections={getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)}
|
sections={getLableValueDropdownItems(element.dataBinding?.dataSource as string | undefined)}
|
||||||
value={
|
value={
|
||||||
@@ -163,8 +164,8 @@ const ElementData: React.FC<ElementDataProps> = ({
|
|||||||
|
|
||||||
{/* Data Mapping */}
|
{/* Data Mapping */}
|
||||||
{element?.type === "graph" && (
|
{element?.type === "graph" && (
|
||||||
<div className="data-mapping">
|
<div className="data-mapping design-section">
|
||||||
<div className="heading">Data Mapping</div>
|
<div className="sub-header">Data Mapping</div>
|
||||||
|
|
||||||
<div className="type-switch">
|
<div className="type-switch">
|
||||||
<div className={`type ${selectDataMapping === "singleMachine" ? "active" : ""}`} onClick={() => handleDataTypeSwitch("singleMachine")}>
|
<div className={`type ${selectDataMapping === "singleMachine" ? "active" : ""}`} onClick={() => handleDataTypeSwitch("singleMachine")}>
|
||||||
@@ -243,6 +244,7 @@ const ElementData: React.FC<ElementDataProps> = ({
|
|||||||
{multipleValueFields.map((field) => (
|
{multipleValueFields.map((field) => (
|
||||||
<DataSourceSelector
|
<DataSourceSelector
|
||||||
key={field.id}
|
key={field.id}
|
||||||
|
className="fill-width"
|
||||||
label={field.label}
|
label={field.label}
|
||||||
options={field.options}
|
options={field.options}
|
||||||
selected={field.options.find((o) => o.id === element.dataBinding?.commonValue)?.label ?? ""}
|
selected={field.options.find((o) => o.id === element.dataBinding?.commonValue)?.label ?? ""}
|
||||||
@@ -258,6 +260,7 @@ const ElementData: React.FC<ElementDataProps> = ({
|
|||||||
{multipleSourceFields.map((field, index) => (
|
{multipleSourceFields.map((field, index) => (
|
||||||
<DataSourceSelector
|
<DataSourceSelector
|
||||||
key={field.id}
|
key={field.id}
|
||||||
|
className="dual-buttons"
|
||||||
label={field.label}
|
label={field.label}
|
||||||
options={getFilteredOptions(field.options, element.dataBinding?.dataSource, index)}
|
options={getFilteredOptions(field.options, element.dataBinding?.dataSource, index)}
|
||||||
selected={getEventByModelUuid(selectedProduct.productUuid, (element.dataBinding?.dataSource as string[])?.[index] || "")?.modelName ?? ""}
|
selected={getEventByModelUuid(selectedProduct.productUuid, (element.dataBinding?.dataSource as string[])?.[index] || "")?.modelName ?? ""}
|
||||||
|
|||||||
@@ -450,7 +450,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
|||||||
|
|
||||||
return dataValues.map((value, index) => ({
|
return dataValues.map((value, index) => ({
|
||||||
id: `data-value-${index}`,
|
id: `data-value-${index}`,
|
||||||
label: `Data Value ${index + 1}`,
|
label: `Value ${index + 1}`,
|
||||||
showEyeDropper: false,
|
showEyeDropper: false,
|
||||||
options: valueOptions,
|
options: valueOptions,
|
||||||
}));
|
}));
|
||||||
@@ -459,7 +459,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
id: "data-value",
|
id: "data-value",
|
||||||
label: "Data Value",
|
label: "Value",
|
||||||
showEyeDropper: false,
|
showEyeDropper: false,
|
||||||
options: [],
|
options: [],
|
||||||
},
|
},
|
||||||
@@ -472,7 +472,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
|||||||
|
|
||||||
return dataSources.map((_, index) => ({
|
return dataSources.map((_, index) => ({
|
||||||
id: `data-source-${index}`,
|
id: `data-source-${index}`,
|
||||||
label: `Data Source ${index + 1}`,
|
label: `Source ${index + 1}`,
|
||||||
showEyeDropper: true,
|
showEyeDropper: true,
|
||||||
options: getAssetDropdownItems().map((item) => ({
|
options: getAssetDropdownItems().map((item) => ({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
@@ -485,7 +485,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
|||||||
}, [element, getAssetDropdownItems]);
|
}, [element, getAssetDropdownItems]);
|
||||||
|
|
||||||
const multipleValueFields = useMemo(
|
const multipleValueFields = useMemo(
|
||||||
() => [{ id: "data-value", label: "Data Value", showEyeDropper: false, options: getCommonValueDropdownItems().map((item) => ({ id: item.id, label: item.label })) }],
|
() => [{ id: "data-value", label: "Value", showEyeDropper: false, options: getCommonValueDropdownItems().map((item) => ({ id: item.id, label: item.label })) }],
|
||||||
[getCommonValueDropdownItems]
|
[getCommonValueDropdownItems]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -524,7 +524,7 @@ const ElementEditor: React.FC<ElementEditorProps> = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="panel element-editor-panel"
|
className="panel element-editor-panel"
|
||||||
style={{ position: "fixed", left: position.x, top: position.y, zIndex: 1000, width: panelWidth }}
|
style={{ position: "fixed", left: position.x, top: position.y, zIndex: 1000, width: panelWidth }}
|
||||||
>
|
>
|
||||||
<div className="free-move-button" onPointerDown={startDrag} onDoubleClick={resetPosition}>
|
<div className="free-move-button" onPointerDown={startDrag} onDoubleClick={resetPosition}>
|
||||||
<ResizeHeightIcon />
|
<ResizeHeightIcon />
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export type DropdownItem = {
|
|||||||
|
|
||||||
type DataDetailedDropdownProps = {
|
type DataDetailedDropdownProps = {
|
||||||
title: string;
|
title: string;
|
||||||
|
className?: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
sections: {
|
sections: {
|
||||||
title?: string;
|
title?: string;
|
||||||
@@ -22,7 +23,18 @@ type DataDetailedDropdownProps = {
|
|||||||
onEyeDrop?: () => void;
|
onEyeDrop?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DataDetailedDropdown: React.FC<DataDetailedDropdownProps> = ({ title, placeholder = "Select value", sections, value, onChange, dropDownHeader, eyedroper, eyeDropperActive, onEyeDrop }) => {
|
const DataDetailedDropdown: React.FC<DataDetailedDropdownProps> = ({
|
||||||
|
title,
|
||||||
|
className,
|
||||||
|
placeholder = "Select value",
|
||||||
|
sections,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
dropDownHeader,
|
||||||
|
eyedroper,
|
||||||
|
eyeDropperActive,
|
||||||
|
onEyeDrop,
|
||||||
|
}) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const [isEyeDroperActiveLocal, setIsEyeDroperActiveLocal] = useState(false);
|
const [isEyeDroperActiveLocal, setIsEyeDroperActiveLocal] = useState(false);
|
||||||
@@ -62,7 +74,7 @@ const DataDetailedDropdown: React.FC<DataDetailedDropdownProps> = ({ title, plac
|
|||||||
}, [search, sections]);
|
}, [search, sections]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="data-detailed-dropdown" ref={containerRef}>
|
<div className={`data-detailed-dropdown ${className ?? ""}`} ref={containerRef}>
|
||||||
<div className="title">{title}</div>
|
<div className="title">{title}</div>
|
||||||
<div className="input-container">
|
<div className="input-container">
|
||||||
<div className="input-wrapper">
|
<div className="input-wrapper">
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { DeleteIcon } from "../../icons/ContextMenuIcons";
|
|||||||
|
|
||||||
type DataSourceSelectorProps = {
|
type DataSourceSelectorProps = {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
className?: string;
|
||||||
options: {
|
options: {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -18,7 +19,18 @@ type DataSourceSelectorProps = {
|
|||||||
onEyeDrop?: () => void;
|
onEyeDrop?: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DataSourceSelector: React.FC<DataSourceSelectorProps> = ({ label = "Data Source", options, selected, onSelect, showEyeDropper = true, showDeleteBtn, onDelete, eyeDropperActive, onEyeDrop }) => {
|
const DataSourceSelector: React.FC<DataSourceSelectorProps> = ({
|
||||||
|
label = "Data Source",
|
||||||
|
className,
|
||||||
|
options,
|
||||||
|
selected,
|
||||||
|
onSelect,
|
||||||
|
showEyeDropper = true,
|
||||||
|
showDeleteBtn,
|
||||||
|
onDelete,
|
||||||
|
eyeDropperActive,
|
||||||
|
onEyeDrop,
|
||||||
|
}) => {
|
||||||
// Local state fallback if no external control provided
|
// Local state fallback if no external control provided
|
||||||
const [isEyeActiveLocal, setIsEyeActiveLocal] = useState(false);
|
const [isEyeActiveLocal, setIsEyeActiveLocal] = useState(false);
|
||||||
|
|
||||||
@@ -33,7 +45,7 @@ const DataSourceSelector: React.FC<DataSourceSelectorProps> = ({ label = "Data S
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="datas">
|
<div className={`datas ${className ?? ""}`}>
|
||||||
<div className="datas__label">{label}</div>
|
<div className="datas__label">{label}</div>
|
||||||
|
|
||||||
<div className="datas__class">
|
<div className="datas__class">
|
||||||
|
|||||||
@@ -2,145 +2,123 @@ import React, { useState, useEffect, useRef } from "react";
|
|||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
|
|
||||||
interface DropdownProps {
|
interface DropdownProps {
|
||||||
header: string;
|
header: string;
|
||||||
options: string[];
|
options: string[];
|
||||||
onSelect: (option: string) => void;
|
onSelect: (option: string) => void;
|
||||||
search?: boolean;
|
search?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
onChange?: () => void;
|
onChange?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RegularDropDown: React.FC<DropdownProps> = ({
|
const RegularDropDown: React.FC<DropdownProps> = ({ header, options, onSelect, search = true }) => {
|
||||||
header,
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
options,
|
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||||
onSelect,
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
search = true,
|
const [filteredOptions, setFilteredOptions] = useState<string[]>(options);
|
||||||
}) => {
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [position, setPosition] = useState<{
|
||||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
top: number;
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
left: number;
|
||||||
const [filteredOptions, setFilteredOptions] = useState<string[]>(options);
|
width: number;
|
||||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
}>({
|
||||||
const [position, setPosition] = useState<{
|
top: 0,
|
||||||
top: number;
|
left: 0,
|
||||||
left: number;
|
width: 0,
|
||||||
width: number;
|
});
|
||||||
}>({
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
width: 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset when closed
|
// Reset when closed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
setSelectedOption(null);
|
setSelectedOption(null);
|
||||||
setSearchTerm("");
|
setSearchTerm("");
|
||||||
setFilteredOptions(options);
|
setFilteredOptions(options);
|
||||||
}
|
}
|
||||||
}, [isOpen, options]);
|
}, [isOpen, options]);
|
||||||
|
|
||||||
// Reset when header changes
|
// Reset when header changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedOption(null);
|
setSelectedOption(null);
|
||||||
setSearchTerm("");
|
setSearchTerm("");
|
||||||
setFilteredOptions(options);
|
setFilteredOptions(options);
|
||||||
}, [header, options]);
|
}, [header, options]);
|
||||||
|
|
||||||
// Close if clicked outside
|
// Close if clicked outside
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
if (
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
||||||
dropdownRef.current &&
|
setIsOpen(false);
|
||||||
!dropdownRef.current.contains(event.target as Node)
|
}
|
||||||
) {
|
};
|
||||||
|
document.addEventListener("click", handleClickOutside);
|
||||||
|
return () => document.removeEventListener("click", handleClickOutside);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Recalculate position when opening
|
||||||
|
useEffect(() => {
|
||||||
|
if (isOpen && dropdownRef.current) {
|
||||||
|
const rect = dropdownRef.current.getBoundingClientRect();
|
||||||
|
setPosition({
|
||||||
|
top: rect.bottom + window.scrollY,
|
||||||
|
left: rect.left + window.scrollX,
|
||||||
|
width: rect.width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
const toggleDropdown = () => setIsOpen((prev) => !prev);
|
||||||
|
|
||||||
|
const handleOptionClick = (option: string) => {
|
||||||
|
setSelectedOption(option);
|
||||||
|
onSelect(option);
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
}
|
|
||||||
};
|
};
|
||||||
document.addEventListener("click", handleClickOutside);
|
|
||||||
return () => document.removeEventListener("click", handleClickOutside);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Recalculate position when opening
|
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
useEffect(() => {
|
const term = event.target.value;
|
||||||
if (isOpen && dropdownRef.current) {
|
setSearchTerm(term);
|
||||||
const rect = dropdownRef.current.getBoundingClientRect();
|
setFilteredOptions(options.filter((option) => option.toLowerCase().includes(term.toLowerCase())));
|
||||||
setPosition({
|
};
|
||||||
top: rect.bottom + window.scrollY,
|
|
||||||
left: rect.left + window.scrollX,
|
|
||||||
width: rect.width,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [isOpen]);
|
|
||||||
|
|
||||||
const toggleDropdown = () => setIsOpen((prev) => !prev);
|
return (
|
||||||
|
<div className="regularDropdown-container" ref={dropdownRef}>
|
||||||
|
{/* Header */}
|
||||||
|
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
|
||||||
|
<div className="key">{selectedOption || header}</div>
|
||||||
|
<div className="icon">▾</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
const handleOptionClick = (option: string) => {
|
{/* Options rendered in portal */}
|
||||||
setSelectedOption(option);
|
{isOpen &&
|
||||||
onSelect(option);
|
createPortal(
|
||||||
setIsOpen(false);
|
<div
|
||||||
};
|
className="dropdown-options"
|
||||||
|
style={{
|
||||||
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
position: "absolute",
|
||||||
const term = event.target.value;
|
top: position.top,
|
||||||
setSearchTerm(term);
|
left: position.left,
|
||||||
setFilteredOptions(
|
width: position.width,
|
||||||
options.filter((option) =>
|
zIndex: 9999,
|
||||||
option.toLowerCase().includes(term.toLowerCase())
|
}}
|
||||||
)
|
>
|
||||||
|
{search && (
|
||||||
|
<div className="dropdown-search">
|
||||||
|
<input type="text" placeholder="Search..." value={searchTerm} onChange={handleSearchChange} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{filteredOptions.length > 0 ? (
|
||||||
|
filteredOptions.map((option, index) => (
|
||||||
|
<div className="option" key={index} onClick={() => handleOptionClick(option)} title={option}>
|
||||||
|
{option}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="no-options">No options found</div>
|
||||||
|
)}
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="regularDropdown-container" ref={dropdownRef}>
|
|
||||||
{/* Header */}
|
|
||||||
<div className="dropdown-header flex-sb" onClick={toggleDropdown}>
|
|
||||||
<div className="key">{selectedOption || header}</div>
|
|
||||||
<div className="icon">▾</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Options rendered in portal */}
|
|
||||||
{isOpen &&
|
|
||||||
createPortal(
|
|
||||||
<div
|
|
||||||
className="dropdown-options"
|
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
top: position.top,
|
|
||||||
left: position.left,
|
|
||||||
width: position.width,
|
|
||||||
zIndex: 9999,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{search && (
|
|
||||||
<div className="dropdown-search">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Search..."
|
|
||||||
value={searchTerm}
|
|
||||||
onChange={handleSearchChange}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{filteredOptions.length > 0 ? (
|
|
||||||
filteredOptions.map((option, index) => (
|
|
||||||
<div
|
|
||||||
className="option"
|
|
||||||
key={index}
|
|
||||||
onClick={() => handleOptionClick(option)}
|
|
||||||
title={option}
|
|
||||||
>
|
|
||||||
{option}
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="no-options">No options found</div>
|
|
||||||
)}
|
|
||||||
</div>,
|
|
||||||
document.body
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default RegularDropDown;
|
export default RegularDropDown;
|
||||||
|
|||||||
@@ -15,12 +15,7 @@ interface DropdownProps {
|
|||||||
onChange?: () => void;
|
onChange?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RegularDropDownID: React.FC<DropdownProps> = ({
|
const RegularDropDownID: React.FC<DropdownProps> = ({ header, options, onSelect, search = true }) => {
|
||||||
header,
|
|
||||||
options,
|
|
||||||
onSelect,
|
|
||||||
search = true,
|
|
||||||
}) => {
|
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
const [selectedOption, setSelectedOption] = useState<string | null>(null);
|
||||||
const [searchTerm, setSearchTerm] = useState("");
|
const [searchTerm, setSearchTerm] = useState("");
|
||||||
@@ -70,7 +65,7 @@ const RegularDropDownID: React.FC<DropdownProps> = ({
|
|||||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const term = e.target.value;
|
const term = e.target.value;
|
||||||
setSearchTerm(term);
|
setSearchTerm(term);
|
||||||
setFilteredOptions(options.filter(o => o.label.toLowerCase().includes(term.toLowerCase())));
|
setFilteredOptions(options.filter((o) => o.label.toLowerCase().includes(term.toLowerCase())));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -80,27 +75,28 @@ const RegularDropDownID: React.FC<DropdownProps> = ({
|
|||||||
<div className="icon">▾</div>
|
<div className="icon">▾</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isOpen && createPortal(
|
{isOpen &&
|
||||||
<div className="dropdown-options" style={{ position: "absolute", top: position.top, left: position.left, width: position.width, zIndex: 9999 }}>
|
createPortal(
|
||||||
{search && (
|
<div className="dropdown-options" style={{ position: "absolute", top: position.top, left: position.left, width: position.width, zIndex: 9999 }}>
|
||||||
<div className="dropdown-search">
|
{search && (
|
||||||
<input type="text" placeholder="Search..." value={searchTerm} onChange={handleSearchChange} />
|
<div className="dropdown-search">
|
||||||
</div>
|
<input type="text" placeholder="Search..." value={searchTerm} onChange={handleSearchChange} />
|
||||||
)}
|
|
||||||
{filteredOptions.length > 0 ? (
|
|
||||||
filteredOptions.map((opt) => (
|
|
||||||
<div className="option" key={opt.id} onClick={() => handleOptionClick(opt)} title={opt.label}>
|
|
||||||
{opt.label}
|
|
||||||
</div>
|
</div>
|
||||||
))
|
)}
|
||||||
) : (
|
{filteredOptions.length > 0 ? (
|
||||||
<div className="no-options">No options found</div>
|
filteredOptions.map((opt) => (
|
||||||
)}
|
<div className="option" key={opt.id} onClick={() => handleOptionClick(opt)} title={opt.label}>
|
||||||
</div>,
|
{opt.label}
|
||||||
document.body
|
</div>
|
||||||
)}
|
))
|
||||||
|
) : (
|
||||||
|
<div className="no-options">No options found</div>
|
||||||
|
)}
|
||||||
|
</div>,
|
||||||
|
document.body
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
export default RegularDropDownID
|
export default RegularDropDownID;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -337,6 +337,11 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sub-header {
|
||||||
|
padding: 4px 6px 8px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
.design-section {
|
.design-section {
|
||||||
padding: 4px;
|
padding: 4px;
|
||||||
outline: 1px solid var(--border-color);
|
outline: 1px solid var(--border-color);
|
||||||
@@ -353,6 +358,10 @@
|
|||||||
.value-field-container {
|
.value-field-container {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
.label {
|
||||||
|
width: 90px;
|
||||||
|
max-width: 90px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-type {
|
.select-type {
|
||||||
@@ -534,18 +543,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.datas {
|
|
||||||
// width: 100% ;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.datas__label,
|
|
||||||
.datas__class {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-switch {
|
.type-switch {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
@@ -594,6 +591,101 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
|
||||||
|
.datas {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
// padding: 6px 12px;
|
||||||
|
|
||||||
|
&__label,
|
||||||
|
&__class,
|
||||||
|
.input-container,
|
||||||
|
.title {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label,
|
||||||
|
.title {
|
||||||
|
flex: 0.8;
|
||||||
|
max-width: 90px;
|
||||||
|
min-width: 90px;
|
||||||
|
width: 90px;
|
||||||
|
line-height: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__class,
|
||||||
|
.input-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
.input-wrapper {
|
||||||
|
max-width: 126px;
|
||||||
|
width: 126px;
|
||||||
|
.icon {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-icon,
|
||||||
|
.delete {
|
||||||
|
display: flex;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--background-color-input);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: var(--background-color-button);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete {
|
||||||
|
cursor: pointer;
|
||||||
|
&:hover {
|
||||||
|
background: var(--log-error-background-color);
|
||||||
|
path {
|
||||||
|
stroke: var(--log-error-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.regularDropdown-container {
|
||||||
|
max-width: 112px;
|
||||||
|
width: 112px;
|
||||||
|
.icon {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fields-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.add-field {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
svg path {
|
||||||
|
stroke: #ccacff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label {
|
||||||
|
color: #ccacff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.data-wrapper {
|
.data-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -633,97 +725,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 9px;
|
gap: 9px;
|
||||||
background: var(--background-color);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
box-shadow: var(--box-shadow-medium);
|
|
||||||
padding: 15px 12px;
|
|
||||||
border-radius: 25px;
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
padding: 4px 6px 8px;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fields-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 6px;
|
|
||||||
|
|
||||||
.datas {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
// padding: 6px 12px;
|
|
||||||
|
|
||||||
.datas__label,
|
|
||||||
.datas__class {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datas__label {
|
|
||||||
flex: 0.8;
|
|
||||||
min-width: 96px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datas__class {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
.add-icon,
|
|
||||||
.delete {
|
|
||||||
display: flex;
|
|
||||||
height: 24px;
|
|
||||||
width: 24px;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 6px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: var(--background-color-input);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
background: var(--background-color-button);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete {
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
background: var(--log-error-background-color);
|
|
||||||
path {
|
|
||||||
stroke: var(--log-error-text-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.regularDropdown-container {
|
|
||||||
max-width: 106px;
|
|
||||||
width: 106px;
|
|
||||||
.icon {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-field {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
svg path {
|
|
||||||
stroke: #ccacff !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.label {
|
|
||||||
color: #ccacff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1106,3 +1107,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.fill-width {
|
||||||
|
.input-wrapper,
|
||||||
|
.regularDropdown-container {
|
||||||
|
max-width: none !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dual-buttons {
|
||||||
|
.regularDropdown-container {
|
||||||
|
max-width: 70px !important;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user