Newer
Older
import { faChevronDown } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { insertMarkTags } from "@helper/text";
import DOMPurify from 'dompurify';
import { RefObject, useCallback, useRef, useState } from 'react';
import { Collapse, OverlayTrigger, Tooltip } from 'react-bootstrap';
import { useTranslation } from "react-i18next";
import { Device } from "../reducer/device.reducer";
import { useDeviceTableViewModel } from "../view_model/device.table.viewmodel";
import { DeviceListCollapsable } from "./subcomponent/device.view.list-detail";
const cropUUID = (uuid: string): string => {
return uuid.substring(0, 3) + "..." + uuid.substring(uuid.length - 3, uuid.length);
export const DeviceList = ({ searchRef }: { searchRef: RefObject<HTMLInputElement> }) => {
const { devices, pnds, selected: selectedDevice } = useAppSelector(state => state.device);
const [expandedId, setExpandedId] = useState<string | null | undefined>(null);
const listRef = useRef<HTMLDivElement>(null);
const { dispatchDevice } = useDeviceTableViewModel(searchRef, listRef);
const handleItemClick = useCallback((device: Device) => {
dispatchDevice(device)
const { id } = device
setExpandedId(expandedId === id ? null : id);
}, [expandedId]);
const getDeviceList = useCallback(() => {
const search = searchRef?.current?.value;
let filtered = devices;
if (search) {
filtered = devices.filter((device) => {
const user = pnds.find(pnd => pnd.id === device.pid);
return device.id?.includes(search) ||
device.name?.includes(search) ||
user?.name?.includes(search);
return filtered.map((device) => {
const user = pnds.find(pnd => pnd.id === device.pid);
const username = user?.name || '';
const croppedId = cropUUID(deviceId);
const devicename = device.name || '';
const isExpanded = expandedId === deviceId;
const isSelected = selectedDevice?.device.id === deviceId;
<div
key={deviceId}
className={`border-bottom border-primary p-2 transitions ${isSelected && 'bg-gradient-fade py-2'} ${!isSelected && 'text-disabled disabled-hover'}`}
onClick={() => !isExpanded && handleItemClick(device)}
>
<div
aria-expanded={isExpanded}
className="d-flex justify-content-between py-2 clickable"
onClick={() => isExpanded && handleItemClick(device)}
>
<FontAwesomeIcon icon={faChevronDown} rotation={isExpanded ? undefined : 270} />
<span dangerouslySetInnerHTML={{
__html: search ? insertMarkTags(devicename, search) : DOMPurify.sanitize(devicename)
}} />
<OverlayTrigger overlay={<Tooltip id={deviceId}>{deviceId}</Tooltip>}>
<span className="text-gray-500" dangerouslySetInnerHTML={{
__html: search ? insertMarkTags(croppedId, search) : DOMPurify.sanitize(croppedId)
}} />
</OverlayTrigger>
<span className="text-gray-500" dangerouslySetInnerHTML={{
__html: search ? insertMarkTags(username, search) : DOMPurify.sanitize(username)
}} />
</div>
<Collapse in={isExpanded}>
<div>
<DeviceListCollapsable deviceId={deviceId} username={username} search={search} />
</div>
</Collapse>
</div>
);
});
}, [devices, searchRef, pnds, selectedDevice, expandedId, handleItemClick]);
<div className="rounded border border-primary mt-2">
<div className="border-bottom border-primary d-flex justify-content-between px-4 py-2 clickable">
<FontAwesomeIcon icon={faChevronDown} className="opacity-0" />
<span className="font-medium">{t('device.table.header.name')}</span>
<span className="font-medium">{t('device.table.header.uuid')}</span>
<span className="font-medium">{t('device.table.header.user')}</span>
</div>
<div ref={listRef}>{getDeviceList()}</div>
</div>
);
};