2025-06-03 22:52:40 +00:00
|
|
|
import React, { useState } from 'react';
|
|
|
|
|
import styled from '@emotion/styled';
|
|
|
|
|
import CheckIcon from '@mui/icons-material/Check';
|
|
|
|
|
import DoNotDisturbIcon from '@mui/icons-material/DoNotDisturb';
|
|
|
|
|
import KeyboardArrowLeftIcon from '@mui/icons-material/KeyboardArrowLeft';
|
|
|
|
|
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
|
|
|
|
|
|
2025-06-04 06:29:10 +00:00
|
|
|
const OuterShell = styled('div')`
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
width: 100%;
|
|
|
|
|
justify-content: space-around;
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const TransferShell = styled('div')`
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
width: 100%;
|
|
|
|
|
align-items: center;
|
|
|
|
|
padding: 5px;
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const TransferButtonShell = styled('div')`
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
width: 25%;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const TransferHeader = styled('div')`
|
|
|
|
|
padding-bottom: 10px;
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const TransferOuterBox = styled('div')`
|
|
|
|
|
display: flex;
|
|
|
|
|
width: 200px;
|
|
|
|
|
height: 250px;
|
|
|
|
|
border: 1px solid black;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
background-color: #E8E8E8;
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const TransferInnerBox = styled('div')`
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
padding: 5px;
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const TransferBoxItem = styled('div')`
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
width: 188px;
|
|
|
|
|
padding: 2px 4px;
|
|
|
|
|
background-color: ${({ isSelected }) => (isSelected ? '#D3D3D3' : 'transparent')};
|
|
|
|
|
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
|
|
|
|
color: ${({ disabled }) => (disabled ? '#999' : 'inherit')};
|
|
|
|
|
border-radius: 4px;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
user-select: ${({ disabled }) => (disabled ? 'none' : 'auto')};
|
|
|
|
|
opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const TransferBoxLabel = styled('span')`
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
flex: 1;
|
|
|
|
|
min-width: 0;
|
|
|
|
|
`;
|
|
|
|
|
|
2025-06-03 22:52:40 +00:00
|
|
|
const CenterButton = styled('div')`
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
align-items: center;
|
|
|
|
|
width: 30px;
|
|
|
|
|
height: 30px;
|
|
|
|
|
border: 1px solid black;
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
margin: 5px;
|
|
|
|
|
cursor: ${({ buttonEnabled }) => (buttonEnabled ? 'pointer' : 'not-allowed')};
|
|
|
|
|
transition: background-color 0.3s ease, color 0.3s ease;
|
|
|
|
|
opacity: ${({ buttonEnabled }) => (buttonEnabled ? 1 : 0.5)};
|
|
|
|
|
|
|
|
|
|
${({ color, buttonEnabled }) => buttonEnabled && color && `
|
|
|
|
|
&:hover {
|
|
|
|
|
background-color: ${color};
|
|
|
|
|
color: white;
|
|
|
|
|
}
|
|
|
|
|
`}
|
|
|
|
|
`;
|
|
|
|
|
|
2025-06-04 06:29:10 +00:00
|
|
|
const sortByLastThenFirst = (arr) => {
|
|
|
|
|
return [...arr].sort((a, b) => {
|
|
|
|
|
const [aLast, aFirst] = a.value.split(',').map(s => s.trim());
|
|
|
|
|
const [bLast, bFirst] = b.value.split(',').map(s => s.trim());
|
|
|
|
|
|
|
|
|
|
if (aLast < bLast) return -1;
|
|
|
|
|
if (aLast > bLast) return 1;
|
|
|
|
|
if (aFirst < bFirst) return -1;
|
|
|
|
|
if (aFirst > bFirst) return 1;
|
|
|
|
|
return 0;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export const TransferBox = ({ fields, leftGroup, rightGroup, onSave, user={} }) => {
|
2025-06-03 22:52:40 +00:00
|
|
|
const {
|
|
|
|
|
id,
|
|
|
|
|
exclusionList,
|
|
|
|
|
oldListLabel,
|
|
|
|
|
newListLabel
|
|
|
|
|
} = fields;
|
|
|
|
|
|
|
|
|
|
const [itemSelected, setItemSelected] = useState({});
|
2025-06-04 06:29:10 +00:00
|
|
|
const [leftItems, setLeftItems] = useState(sortByLastThenFirst(leftGroup || []));
|
|
|
|
|
const [rightItems, setRightItems] = useState(sortByLastThenFirst(rightGroup || []));
|
|
|
|
|
|
|
|
|
|
const handleItemClick = (item, side) => {
|
|
|
|
|
setItemSelected({ ...item, from: side });
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const arraysEqual = (a, b) => {
|
|
|
|
|
if (a.length !== b.length) return false;
|
|
|
|
|
const aIds = a.map(i => i.id).sort();
|
|
|
|
|
const bIds = b.map(i => i.id).sort();
|
|
|
|
|
return JSON.stringify(aIds) === JSON.stringify(bIds);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const isLeftArrowEnabled = itemSelected?.from === 'right';
|
|
|
|
|
const isRightArrowEnabled = itemSelected?.from === 'left';
|
|
|
|
|
|
|
|
|
|
const hasChanges = !arraysEqual(leftItems, leftGroup) || !arraysEqual(rightItems, rightGroup);
|
2025-06-03 22:52:40 +00:00
|
|
|
|
|
|
|
|
return (
|
2025-06-04 06:29:10 +00:00
|
|
|
<OuterShell>
|
|
|
|
|
<TransferShell>
|
|
|
|
|
<TransferHeader>
|
2025-06-03 22:52:40 +00:00
|
|
|
<h2>{oldListLabel}</h2>
|
2025-06-04 06:29:10 +00:00
|
|
|
</TransferHeader>
|
|
|
|
|
<TransferOuterBox>
|
|
|
|
|
<TransferInnerBox>
|
|
|
|
|
{leftItems?.filter(leftItem => !rightItems.some(rightItem => rightItem.id === leftItem.id))?.map((j) => {
|
|
|
|
|
const isUser = user?.id === j?.id;
|
|
|
|
|
const isSelected = itemSelected?.id === j?.id && itemSelected?.from === 'left' && !isUser;
|
2025-06-03 22:52:40 +00:00
|
|
|
return (
|
2025-06-04 06:29:10 +00:00
|
|
|
<TransferBoxItem
|
|
|
|
|
key={j?.id}
|
|
|
|
|
isSelected={isSelected}
|
|
|
|
|
disabled={isUser}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
if (isUser) return;
|
|
|
|
|
handleItemClick(j, 'left');
|
2025-06-03 22:52:40 +00:00
|
|
|
}}
|
|
|
|
|
title={j?.value}
|
|
|
|
|
>
|
2025-06-04 06:29:10 +00:00
|
|
|
<TransferBoxLabel>
|
|
|
|
|
{j?.value}
|
|
|
|
|
</TransferBoxLabel>
|
|
|
|
|
</TransferBoxItem>
|
2025-06-03 22:52:40 +00:00
|
|
|
)
|
|
|
|
|
})}
|
2025-06-04 06:29:10 +00:00
|
|
|
</TransferInnerBox>
|
|
|
|
|
</TransferOuterBox>
|
|
|
|
|
</TransferShell>
|
|
|
|
|
<TransferButtonShell>
|
2025-06-03 22:52:40 +00:00
|
|
|
<CenterButton
|
2025-06-04 06:29:10 +00:00
|
|
|
onClick={() => {
|
|
|
|
|
if (!itemSelected) return;
|
|
|
|
|
if (itemSelected.from === 'left') {
|
|
|
|
|
setLeftItems(prev => prev.filter(item => item.id !== itemSelected.id));
|
|
|
|
|
setRightItems(prev => sortByLastThenFirst([
|
|
|
|
|
...rightItems.filter(item => item.id !== itemSelected.id),
|
|
|
|
|
itemSelected
|
|
|
|
|
]));
|
|
|
|
|
}
|
|
|
|
|
setItemSelected({});
|
|
|
|
|
}}
|
2025-06-03 22:52:40 +00:00
|
|
|
color='#A8A8A8'
|
2025-06-04 06:29:10 +00:00
|
|
|
buttonEnabled={isRightArrowEnabled}
|
2025-06-03 22:52:40 +00:00
|
|
|
>
|
|
|
|
|
<KeyboardArrowRightIcon />
|
|
|
|
|
</CenterButton>
|
|
|
|
|
<CenterButton
|
2025-06-04 06:29:10 +00:00
|
|
|
onClick={() => {
|
|
|
|
|
setLeftItems(sortByLastThenFirst(leftGroup));
|
|
|
|
|
setRightItems(sortByLastThenFirst(rightGroup));
|
|
|
|
|
setItemSelected({});
|
|
|
|
|
}}
|
2025-06-03 22:52:40 +00:00
|
|
|
color='#FF6666'
|
2025-06-04 06:29:10 +00:00
|
|
|
buttonEnabled={hasChanges}
|
2025-06-03 22:52:40 +00:00
|
|
|
>
|
|
|
|
|
<DoNotDisturbIcon />
|
|
|
|
|
</CenterButton>
|
|
|
|
|
<CenterButton
|
2025-06-04 06:29:10 +00:00
|
|
|
onClick={() => {
|
|
|
|
|
return onSave(rightItems?.map((item) => { return item?.id }))
|
|
|
|
|
}}
|
2025-06-03 22:52:40 +00:00
|
|
|
color='#00B33C'
|
2025-06-04 06:29:10 +00:00
|
|
|
buttonEnabled={hasChanges}
|
2025-06-03 22:52:40 +00:00
|
|
|
>
|
|
|
|
|
<CheckIcon />
|
|
|
|
|
</CenterButton>
|
|
|
|
|
<CenterButton
|
2025-06-04 06:29:10 +00:00
|
|
|
onClick={() => {
|
|
|
|
|
if (!itemSelected) return;
|
|
|
|
|
if (itemSelected.from === 'right') {
|
|
|
|
|
setRightItems(prev => prev.filter(item => item.id !== itemSelected.id));
|
|
|
|
|
setLeftItems(prev => sortByLastThenFirst([
|
|
|
|
|
...leftItems.filter(item => item.id !== itemSelected.id),
|
|
|
|
|
itemSelected
|
|
|
|
|
]));
|
|
|
|
|
}
|
|
|
|
|
setItemSelected({});
|
|
|
|
|
}}
|
2025-06-03 22:52:40 +00:00
|
|
|
color='#A8A8A8'
|
2025-06-04 06:29:10 +00:00
|
|
|
buttonEnabled={isLeftArrowEnabled}
|
2025-06-03 22:52:40 +00:00
|
|
|
>
|
|
|
|
|
<KeyboardArrowLeftIcon />
|
|
|
|
|
</CenterButton>
|
2025-06-04 06:29:10 +00:00
|
|
|
</TransferButtonShell>
|
|
|
|
|
<TransferShell>
|
|
|
|
|
<TransferHeader>
|
2025-06-03 22:52:40 +00:00
|
|
|
<h2>{newListLabel}</h2>
|
2025-06-04 06:29:10 +00:00
|
|
|
</TransferHeader>
|
|
|
|
|
<TransferOuterBox>
|
|
|
|
|
<TransferInnerBox>
|
|
|
|
|
{rightItems?.map((j) => {
|
|
|
|
|
const isUser = user?.id === j?.id;
|
|
|
|
|
const isSelected = itemSelected?.id === j?.id && itemSelected?.from === 'right' && !isUser;
|
2025-06-03 22:52:40 +00:00
|
|
|
return (
|
2025-06-04 06:29:10 +00:00
|
|
|
<TransferBoxItem
|
|
|
|
|
key={j?.id}
|
|
|
|
|
isSelected={isSelected}
|
|
|
|
|
disabled={isUser}
|
|
|
|
|
onClick={() => {
|
|
|
|
|
if (isUser) return;
|
|
|
|
|
handleItemClick(j, 'right');
|
2025-06-03 22:52:40 +00:00
|
|
|
}}
|
|
|
|
|
title={j?.value}
|
|
|
|
|
>
|
2025-06-04 06:29:10 +00:00
|
|
|
<TransferBoxLabel>
|
|
|
|
|
{j?.value}
|
|
|
|
|
</TransferBoxLabel>
|
|
|
|
|
</TransferBoxItem>
|
2025-06-03 22:52:40 +00:00
|
|
|
)
|
|
|
|
|
})}
|
2025-06-04 06:29:10 +00:00
|
|
|
</TransferInnerBox>
|
|
|
|
|
</TransferOuterBox>
|
|
|
|
|
</TransferShell>
|
|
|
|
|
</OuterShell>
|
2025-06-03 22:52:40 +00:00
|
|
|
);
|
|
|
|
|
}
|