ToolJet/frontend/src/TooljetDatabase/Menu/CellEditMenu/index.jsx
2024-06-06 14:05:03 +05:30

381 lines
12 KiB
JavaScript

import React, { useEffect, useState } from 'react';
import Popover from 'react-bootstrap/Popover';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import DropDownSelect from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/DropDownSelect';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import Information from '@/_ui/Icon/solidIcons/Information';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import LeftNav from '../../Icons/LeftNav.svg';
import RightNav from '../../Icons/RightNav.svg';
import cx from 'classnames';
import './styles.scss';
import styles from './styles.module.scss';
import Skeleton from 'react-loading-skeleton';
export const CellEditMenu = ({
darkMode = false,
children,
show,
close,
columnDetails,
saveFunction,
setCellValue,
cellValue,
previousCellValue,
setDefaultValue,
defaultValue,
setNullValue,
nullValue,
isBoolean,
referencedColumnDetails = [],
referenceColumnName = '',
isForeignKey = false,
scrollEventForColumnValues,
organizationId,
foreignKeys,
setReferencedColumnDetails,
cellHeader,
cachedOptions,
dataType = '',
}) => {
// below state is used only for boolean cell
const [selectedValue, setSelectedValue] = useState(cellValue);
const [shouldCloseFkMenu, setShouldCloseFKMenu] = useState(0);
const [selectedForeignKeyValue, setSelectedForeignKeyValue] = useState({
value: previousCellValue === 'Null' ? null : previousCellValue,
label: previousCellValue === 'Null' ? null : previousCellValue,
});
const handleDefaultChange = (defaultColumnValue, defaultBooleanValue) => {
if (defaultBooleanValue === true) {
setCellValue(defaultColumnValue);
setSelectedForeignKeyValue({
label: defaultColumnValue,
value: defaultColumnValue,
});
} else {
setCellValue(previousCellValue);
}
setDefaultValue(defaultBooleanValue);
setNullValue(false);
};
const handleNullChange = (nullVal) => {
if (nullVal === true) {
setCellValue(null);
setSelectedForeignKeyValue({
label: null,
value: null,
});
} else {
if (previousCellValue === null) {
setCellValue('');
setSelectedForeignKeyValue({
label: '',
value: '',
});
} else {
setCellValue(previousCellValue);
setSelectedForeignKeyValue({
label: previousCellValue,
value: previousCellValue,
});
}
}
setNullValue(nullVal);
setDefaultValue(false);
};
const handleSelectedState = (value) => {
setSelectedValue(value);
setCellValue(value);
};
const closePopover = () => {
setReferencedColumnDetails([]);
setSelectedValue(previousCellValue);
close();
};
const closeFKMenu = () => {
setShouldCloseFKMenu((prev) => prev + 1);
};
const saveFKValue = () => {
saveFunction(cellValue);
closeFKMenu();
};
const handleKeyDown = (e) => {
if (e.key === 'ArrowRight' && isBoolean) {
e.preventDefault();
if (selectedValue === false) handleSelectedState(true);
if (selectedValue === true) handleSelectedState(null);
}
if (e.key === 'ArrowLeft' && isBoolean) {
e.preventDefault();
if (selectedValue === null) handleSelectedState(true);
if (selectedValue === true) handleSelectedState(false);
}
if (e.key === 'Escape') {
closePopover();
}
if (e.key === 'Enter' && cellValue != previousCellValue && show) {
saveFunction(cellValue);
}
if (e.key === 'Backspace') {
if (selectedValue === null) {
if (isBoolean) {
setSelectedValue(true);
setCellValue(true);
} else {
setSelectedValue('');
setCellValue('');
}
setNullValue(false);
setDefaultValue(false);
document.getElementById('edit-input-blur').focus();
}
}
e.stopPropagation();
};
const referencedFKDataList = referencedColumnDetails.map((item) => {
const [key, _value] = Object.entries(item);
return {
label: key[1] === null ? 'Null' : key[1],
value: key[1] === null ? 'Null' : key[1],
};
});
useEffect(() => {
if (show) {
document.addEventListener('keydown', handleKeyDown);
return () => {
document.removeEventListener('keydown', handleKeyDown);
};
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [show, isBoolean, selectedValue, cellValue]);
useEffect(() => {
if (selectedValue !== cellValue) setSelectedValue(cellValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cellValue]);
const SaveChangesSection = () => {
return (
<div className="d-flex justify-content-between align-items-center">
<div className="d-flex flex-column align-items-start gap-1">
<div className="d-flex align-items-center gap-1">
<div className={`fw-500 ${styles.tjdbCellMenuShortcutsInfo}`}>
<SolidIcon name="enterbutton" />
</div>
<div className={`fw-400 ${styles.tjdbCellMenuShortcutsText}`}>Save Changes</div>
</div>
<div className="d-flex align-items-center gap-1">
<div className={`fw-500 ${styles.tjdbCellMenuShortcutsInfo}`}>Esc</div>
<div className={`fw-400 ${styles.tjdbCellMenuShortcutsText}`}>Discard Changes</div>
</div>
</div>
<div className="d-flex flex-column align-items-end gap-1">
{columnDetails?.constraints_type.is_not_null === false && (
<div className="d-flex align-items-center gap-2">
<div className="d-flex flex-column">
<span className="fw-400 fs-12">Set to null</span>
</div>
<div>
<label className={`form-switch`}>
<input
className="form-check-input"
type="checkbox"
checked={nullValue}
onChange={() => handleNullChange(!nullValue)}
/>
</label>
</div>
</div>
)}
{columnDetails?.column_default !== null && (
<div className="d-flex align-items-center gap-2">
<div className="d-flex flex-column">
<span className="fw-400 fs-12">Set to default</span>
</div>
<div>
<label className={`form-switch`}>
<input
className="form-check-input"
type="checkbox"
checked={defaultValue}
onChange={() => handleDefaultChange(columnDetails?.column_default, !defaultValue)}
/>
</label>
</div>
</div>
)}
</div>
</div>
);
};
const SaveChangesFooter = ({ isForeignKeyInEditCell }) => {
return (
<div
className={cx('d-flex align-items-center gap-2', {
'justify-content-between': isBoolean,
'justify-content-end': !isBoolean,
})}
>
{isBoolean ? (
<div className="cell-editmenu-keyActions">
<div className="leftNav-parent-container">
<LeftNav style={{ verticalAlign: 'baseline' }} width={8} height={8} />
</div>
<div className="rightNav-parent-container">
<RightNav style={{ verticalAlign: 'baseline' }} width={8} height={8} />
</div>
<div className="navigate-title fs-10">Navigate</div>
</div>
) : null}
<div className="d-flex" style={{ gap: '8px' }}>
<ButtonSolid
onClick={isForeignKeyInEditCell ? closeFKMenu : closePopover}
variant="tertiary"
size="sm"
className="fs-12 p-2"
>
Cancel
</ButtonSolid>
<ButtonSolid
onClick={isForeignKeyInEditCell ? saveFKValue : () => saveFunction(selectedValue)}
disabled={cellValue == previousCellValue ? true : false}
variant="primary"
size="sm"
className="fs-12 p-2"
>
Save
</ButtonSolid>
</div>
</div>
);
};
const popover = (
<Popover
className={`${darkMode && 'dark-theme'} tjdb-table-cell-edit-popover`}
onClick={(event) => {
if (event.target.closest('.react-select-container')) {
event.preventDefault(); // Prevent the Popover from closing
}
}}
>
<Popover.Body className={`${darkMode && 'dark-theme'}`}>
<div className={`d-flex flex-column ${isBoolean ? 'gap-4' : 'gap-3'}`}>
{/* Boolean View */}
{isBoolean && (
<div className="d-flex align-items-start gap-2">
<span
className={`boolean-state-${
selectedValue === false ? 'selected' : ''
} d-flex align-items-center gap-2 fw-500 tjdb-bool-cell-menu-badge-default`}
tabIndex="0"
onClick={() => handleSelectedState(false)}
>
False
</span>
<span
className={`boolean-state-${
selectedValue === true ? 'selected' : ''
} d-flex align-items-center gap-2 fw-500 tjdb-bool-cell-menu-badge-default`}
tabIndex="1"
onClick={() => handleSelectedState(true)}
>
True
</span>
{columnDetails?.constraints_type.is_not_null === false && (
<span
className={`boolean-state-${
selectedValue === null ? 'selected' : ''
} d-flex align-items-center gap-2 fw-500 tjdb-bool-cell-menu-badge-default`}
tabIndex="2"
onClick={() => handleSelectedState(null)}
>
Null
</span>
)}
</div>
)}
{!isBoolean && <SaveChangesSection />}
{/* Footer */}
<SaveChangesFooter />
</div>
</Popover.Body>
</Popover>
);
return (
<OverlayTrigger show={show} trigger="click" placement="bottom-start" rootclose overlay={popover} defaultShow>
{isForeignKey ? (
<DropDownSelect
buttonClasses="border border-end-1 foreignKeyAcces-container"
showPlaceHolder={true}
loader={
<div className="tjdb-cellmenu-loader mx-2">
<Skeleton height={18} width={176} className="skeleton" style={{ margin: '15px 50px 7px 7px' }} />
<Skeleton height={18} width={212} className="skeleton" style={{ margin: '7px 14px 7px 7px' }} />
<Skeleton height={18} width={176} className="skeleton" style={{ margin: '7px 50px 15px 7px' }} />
</div>
}
isLoading={true}
options={referencedFKDataList}
darkMode={darkMode}
emptyError={
<div className="tjdb-cellmenu-error">
<div className="dd-select-alert-error m-2 d-flex align-items-center">
<Information />
No data found
</div>
</div>
}
value={selectedForeignKeyValue}
onChange={(value) => {
setSelectedForeignKeyValue({
label: value.value === 'Null' ? null : value.value,
value: value.value === 'Null' ? null : value.value,
});
setCellValue(value.value === 'Null' ? null : value.value);
setNullValue(value.value === 'Null' ? true : false);
}}
onAdd={true}
closeFKMenu={closeFKMenu}
saveFKValue={saveFKValue}
addBtnLabel={'Open referenced table'}
isCellEdit={true}
scrollEventForColumnValues={scrollEventForColumnValues}
organizationId={organizationId}
foreignKeys={foreignKeys}
setReferencedColumnDetails={setReferencedColumnDetails}
cellColumnName={cellHeader}
customChildren={
<div className={`d-flex flex-column gap-3`} style={{ padding: '10px' }}>
<SaveChangesSection />
<SaveChangesFooter isForeignKeyInEditCell={true} />
</div>
}
isForeignKeyInEditCell={true}
shouldCloseFkMenu={shouldCloseFkMenu}
cachedOptions={cachedOptions}
columnDataType={dataType}
/>
) : (
children
)}
</OverlayTrigger>
);
};