fix: added json data type and bugfixes on table crash, text colour etc

This commit is contained in:
Kavin Venkatachalam 2025-04-02 12:18:08 +05:30
parent ba147ec897
commit 749743eed8
17 changed files with 311 additions and 33 deletions

View file

@ -96,8 +96,8 @@ export const Table = memo(
// Set styles to the table store
useEffect(() => {
setTableStyles(id, styles);
}, [id, styles, setTableStyles]);
setTableStyles(id, styles, darkMode);
}, [id, styles, darkMode, setTableStyles]);
// Set events to the table store
useEffect(() => {

View file

@ -8,6 +8,7 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import { isArray } from 'lodash';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import useTextColor from '../DataTypes/_hooks/useTextColor';
const { MenuList } = components;
@ -136,12 +137,14 @@ export const CustomSelectColumn = ({
isEditable,
column,
isNewRow,
id,
}) => {
const validateWidget = useStore((state) => state.validateWidget, shallow);
const [isFocused, setIsFocused] = useState(false);
const containerRef = useRef(null);
const inputRef = useRef(null);
const cellTextColor = useTextColor(id, textColor);
const validationData = validateWidget({
validationObject: {
@ -184,7 +187,7 @@ export const CustomSelectColumn = ({
padding: '2px 6px',
background: 'var(--surfaces-surface-03)',
borderRadius: '6px',
color: textColor || 'var(--text-primary)',
color: cellTextColor || 'var(--text-primary)',
fontSize: '12px',
}),
}),
@ -211,11 +214,11 @@ export const CustomSelectColumn = ({
padding: '2px 6px',
background: 'var(--surfaces-surface-03)',
borderRadius: '6px',
color: textColor || 'var(--text-primary)',
color: cellTextColor || 'var(--text-primary)',
fontSize: '12px',
}),
}),
[darkMode, isMulti, horizontalAlignment, textColor]
[darkMode, isMulti, horizontalAlignment, cellTextColor]
);
const defaultValue = useMemo(
@ -275,7 +278,7 @@ export const CustomSelectColumn = ({
>
<>
<div
className="w-100 h-100 d-flex align-items-center"
className="w-100 d-flex align-items-center"
ref={containerRef}
onClick={() => {
if (isNewRow && isEditable) {

View file

@ -5,13 +5,16 @@ import cx from 'classnames';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import CustomDatePickerHeader from './_components/CustomDatePickerHeader';
import 'react-datepicker/dist/react-datepicker.css';
import useTextColor from '../DataTypes/_hooks/useTextColor';
const DISABLED_DATE_FORMAT = 'MM/DD/YYYY';
const DatepickerInput = forwardRef(({ value, onClick, styles, readOnly, onInputChange, onInputFocus }, ref) => (
<div className="table-column-datepicker-input-container">
{readOnly ? (
<div className="h-100 w-100 overflow-hidden">{value}</div>
<div className="h-100 w-100 overflow-hidden" style={{ color: styles?.color }}>
{value}
</div>
) : (
<>
<input
@ -83,14 +86,16 @@ export const DatepickerColumn = ({
disabledDates,
unixTimestamp = 'seconds',
parseInUnixTimestamp,
cellStyles,
darkMode,
textColor,
id,
}) => {
const [date, setDate] = useState(null);
const [excludedDates, setExcludedDates] = useState([]);
const [isInputFocused, setIsInputFocused] = useState(false);
const [inputValue, setInputValue] = useState('');
const dateInputRef = useRef(null);
const cellTextColor = useTextColor(id, textColor);
const computeDateString = useCallback(
(date) => {
@ -241,7 +246,7 @@ export const DatepickerColumn = ({
<DatepickerInput
ref={dateInputRef}
readOnly={readOnly}
styles={{ color: cellStyles?.color }}
styles={{ color: cellTextColor }}
onInputChange={(e) => {
setIsInputFocused(true);
setInputValue(e.target.value);

View file

@ -0,0 +1,189 @@
/* eslint-disable no-undef */
import React, { useState, useEffect } from 'react';
import { determineJustifyContentValue } from '@/_helpers/utils';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import useTextColor from '../DataTypes/_hooks/useTextColor';
import { getMaxHeight } from '../../_utils/helper';
import useTableStore from '../../_stores/tableStore';
import { shallow } from 'zustand/shallow';
export const JsonColumn = ({
isEditable,
jsonIndentation,
darkMode,
handleCellValueChange,
textColor,
cellValue,
column,
containerWidth,
horizontalAlignment,
id,
}) => {
const cellTextColor = useTextColor(id, textColor, 'json');
const cellHeight = useTableStore((state) => state.getTableStyles(id)?.cellHeight, shallow);
const isMaxRowHeightAuto = useTableStore((state) => state.getTableStyles(id)?.isMaxRowHeightAuto, shallow);
const maxRowHeightValue = useTableStore((state) => state.getTableStyles(id)?.maxRowHeightValue, shallow);
const ref = React.useRef(null);
const [hovered, setHovered] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const cellStyles = {
color: cellTextColor ?? 'inherit',
};
useEffect(() => {
if (!isEditable && isEditing) {
setIsEditing(false);
}
}, [isEditable, isEditing]);
function format(obj) {
if (typeof obj !== 'object' || obj === null) {
return typeof obj === 'string' ? `"${obj}"` : obj;
}
if (Array.isArray(obj)) {
return `[ ${obj.map(format).join(', ')} ]`;
}
return `{ ${Object.entries(obj)
.map(([key, value]) => `"${key}": ${format(value)}`)
.join(', ')} }`;
}
const formatCellValue = (value, overlay = false) => {
try {
if (typeof value === 'object') {
if (jsonIndentation === true && !overlay) {
return JSON.stringify(value, null, 4).replace(/":/g, '": ');
}
const formattedJSON = format(value);
return formattedJSON;
} else {
if (jsonIndentation === true && !overlay) {
return JSON.stringify(JSON.parse(value), null, 4).replace(/":/g, '": ');
}
const formattedJSON = format(JSON.parse(value));
return formattedJSON;
}
} catch (error) {
return value;
}
};
const _renderString = () => (
<div
ref={ref}
contentEditable={'true'}
className={`h-100 text-container long-text-input d-flex align-items-center ${
darkMode ? ' textarea-dark-theme' : ''
} justify-content-${determineJustifyContentValue(horizontalAlignment)}`}
tabIndex={-1}
style={{
color: cellTextColor ? cellTextColor : 'inherit',
outline: 'none',
border: 'none',
background: 'inherit',
position: 'relative',
height: '100%',
}}
readOnly={!isEditable}
onBlur={(e) => {
setIsEditing(false);
if (cellValue !== e.target.textContent) {
const value = JSON.stringify(JSON.parse(e.target.textContent.replace(/\n/g, '')));
handleCellValueChange(row.index, column.key || column.name, value, row.original);
}
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
ref.current.blur();
if (cellValue !== e.target.textContent) {
const value = JSON.stringify(JSON.parse(e.target.textContent.replace(/\n/g, '')));
handleCellValueChange(row.index, column.key || column.name, value, row.original);
}
}
}}
onFocus={(e) => {
setIsEditing(true);
e.stopPropagation();
}}
>
{String(formatCellValue(cellValue))}
</div>
);
const getOverlay = () => {
return (
<div
className={`overlay-cell-table ${darkMode && 'dark-theme'}`}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
style={{ color: 'var(--text-primary)' }}
>
<span
style={{
width: `${containerWidth}px`,
whiteSpace: 'pre-wrap',
}}
>
{String(formatCellValue(cellValue, true))}
</span>
</div>
);
};
const _showOverlay =
ref?.current &&
(ref?.current?.clientWidth < ref?.current?.children[0]?.offsetWidth ||
ref?.current?.clientHeight < ref?.current?.children[0]?.offsetHeight);
return (
<>
<OverlayTrigger
placement="bottom"
overlay={_showOverlay ? getOverlay() : <div />}
trigger={_showOverlay && ['hover', 'focus']}
rootClose={true}
show={_showOverlay && hovered && !isEditing}
>
{!isEditable ? (
<div
className={`d-flex align-items-center h-100 w-100 justify-content-${determineJustifyContentValue(
horizontalAlignment
)}`}
style={cellStyles}
onMouseMove={() => {
if (!hovered) setHovered(true);
}}
onMouseLeave={() => {
setHovered(false);
}}
ref={ref}
>
<span
style={{
maxHeight: getMaxHeight(isMaxRowHeightAuto, maxRowHeightValue, cellHeight),
whiteSpace: 'pre-wrap',
}}
>
{String(formatCellValue(cellValue))}
</span>
</div>
) : (
<div className="h-100 d-flex flex-column justify-content-center position-relative">
<div
onMouseMove={() => {
if (!hovered) setHovered(true);
}}
onMouseLeave={() => setHovered(false)}
className={`${isEditing ? 'h-100 content-editing' : ''} h-100`}
>
{_renderString()}
</div>
</div>
)}
</OverlayTrigger>
</>
);
};

View file

@ -1,9 +1,21 @@
import React, { useMemo } from 'react';
import useTextColor from '../DataTypes/_hooks/useTextColor';
export const LinkColumn = ({ cellValue, linkTarget, underline, underlineColor, linkColor, displayText, darkMode }) => {
export const LinkColumn = ({
cellValue,
linkTarget,
underline,
underlineColor,
textColor,
displayText,
darkMode,
id,
}) => {
const cellTextColor = useTextColor(id, textColor);
const linkTextColor = useMemo(
() => (linkColor !== '#1B1F24' ? linkColor : darkMode && linkColor === '#1B1F24' ? '#FFFFFF' : linkColor),
[linkColor, darkMode]
() =>
cellTextColor !== '#1B1F24' ? cellTextColor : darkMode && cellTextColor === '#1B1F24' ? '#FFFFFF' : cellTextColor,
[cellTextColor, darkMode]
);
return (

View file

@ -4,11 +4,13 @@ import { shallow } from 'zustand/shallow';
import { determineJustifyContentValue } from '@/_helpers/utils';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import HighLightSearch from '@/AppBuilder/Widgets/NewTable/_components/HighLightSearch';
import useTextColor from '../DataTypes/_hooks/useTextColor';
export const NumberColumn = ({
id,
isEditable,
handleCellValueChange,
cellTextColor,
textColor,
horizontalAlignment,
cellValue,
column,
@ -19,6 +21,7 @@ export const NumberColumn = ({
const [displayValue, setDisplayValue] = useState(cellValue);
const validateWidget = useStore((state) => state.validateWidget, shallow);
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
const cellTextColor = useTextColor(id, textColor);
useEffect(() => {
setDisplayValue(cellValue);

View file

@ -6,12 +6,14 @@ import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import useTableStore from '@/AppBuilder/Widgets/NewTable/_stores/tableStore';
import HighLightSearch from '@/AppBuilder/Widgets/NewTable/_components/HighLightSearch';
import { getMaxHeight } from '../../_utils/helper';
import useTextColor from '../DataTypes/_hooks/useTextColor';
import useValidationStyle from '../DataTypes/_hooks/useValidationStyle';
export const StringColumn = ({
isEditable,
darkMode,
handleCellValueChange,
cellTextColor,
textColor,
horizontalAlignment,
cellValue,
column,
@ -26,6 +28,7 @@ export const StringColumn = ({
const cellHeight = useTableStore((state) => state.getTableStyles(id)?.cellHeight, shallow);
const isMaxRowHeightAuto = useTableStore((state) => state.getTableStyles(id)?.isMaxRowHeightAuto, shallow);
const maxRowHeightValue = useTableStore((state) => state.getTableStyles(id)?.maxRowHeightValue, shallow);
const cellTextColor = useTextColor(id, textColor);
const ref = useRef(null);
const [showOverlay, setShowOverlay] = useState(false);
@ -43,6 +46,7 @@ export const StringColumn = ({
customResolveObjects: { cellValue },
});
const { isValid, validationError } = validationData;
useValidationStyle(id, row, validationError);
useEffect(() => {
setShowOverlay(hovered);

View file

@ -7,13 +7,14 @@ import { shallow } from 'zustand/shallow';
import HighLightSearch from '@/AppBuilder/Widgets/NewTable/_components/HighLightSearch';
import useTableStore from '@/AppBuilder/Widgets/NewTable/_stores/tableStore';
import { getMaxHeight } from '../../_utils/helper';
import useTextColor from '../DataTypes/_hooks/useTextColor';
export const TextColumn = ({
id,
isEditable,
darkMode,
handleCellValueChange,
cellTextColor,
textColor,
cellValue,
column,
containerWidth,
@ -24,6 +25,7 @@ export const TextColumn = ({
const cellHeight = useTableStore((state) => state.getTableStyles(id)?.cellHeight, shallow);
const isMaxRowHeightAuto = useTableStore((state) => state.getTableStyles(id)?.isMaxRowHeightAuto, shallow);
const maxRowHeightValue = useTableStore((state) => state.getTableStyles(id)?.maxRowHeightValue, shallow);
const cellTextColor = useTextColor(id, textColor);
const [showOverlay, setShowOverlay] = useState(false);
const [isEditing, setIsEditing] = useState(false);
@ -99,7 +101,7 @@ export const TextColumn = ({
contentEditable="true"
className={`${!isValid ? 'is-invalid' : ''} h-100 long-text-input text-container ${
darkMode ? 'textarea-dark-theme' : ''
}`}
} justify-content-${determineJustifyContentValue(horizontalAlignment)}`}
style={{
color: cellTextColor || 'inherit',
maxWidth: containerWidth,

View file

@ -0,0 +1,11 @@
import useTableStore from '../../../_stores/tableStore';
import { shallow } from 'zustand/shallow';
export default function useTextColor(id, cellTextColor) {
const textColor = useTableStore((state) => state.getTableStyles(id)?.textColor, shallow);
if (!cellTextColor || cellTextColor === '#11181C') {
return textColor;
}
return cellTextColor;
}

View file

@ -0,0 +1,10 @@
export default function useValidationStyle(id, row, validationError) {
if (validationError) {
const elem = document.getElementById(id)?.querySelector(`[data-index="${row.index}"]`);
if (elem) {
elem.style.maxHeight = '';
elem.style.height = '';
}
}
return null;
}

View file

@ -12,3 +12,4 @@ export { CustomDropdownColumn } from './CustomDropdown';
// export { MultiSelectColumn } from './MultiSelect';
// export { SelectColumn } from './Select';
export { TextColumn } from './Text';
export { JsonColumn } from './JSON';

View file

@ -25,6 +25,7 @@ export const Footer = memo(
fireEvent,
setExposedVariables,
pageCount,
dataLength,
columnVisibility, // Passed to trigger a re-render when columnVisibility changes
}) => {
const isFooterVisible = useTableStore((state) => state.getFooterVisibility(id), shallow);
@ -37,8 +38,6 @@ export const Footer = memo(
shallow
);
const dataLength = table.getFilteredRowModel().rows.length;
const [showAddNewRowPopup, setShowAddNewRowPopup] = useState(false);
// Hide footer if the properties are not enabled

View file

@ -10,7 +10,17 @@ import Loader from '../Loader';
import { Filter } from './_components/Filter/Filter';
export const Header = memo(
({ id, darkMode, fireEvent, setExposedVariables, setGlobalFilter, globalFilter, table, setFilters }) => {
({
id,
darkMode,
fireEvent,
setExposedVariables,
setGlobalFilter,
globalFilter,
table,
setFilters,
appliedFiltersLength,
}) => {
const displaySearchBox = useTableStore((state) => state.getTableProperties(id)?.displaySearchBox, shallow);
const showFilterButton = useTableStore((state) => state.getTableProperties(id)?.showFilterButton, shallow);
@ -61,7 +71,7 @@ export const Header = memo(
data-tooltip-id="tooltip-for-filter-data"
data-tooltip-content="Filter data"
></ButtonSolid>
{appliedFilters.length > 0 && (
{appliedFiltersLength > 0 && (
<div className="filter-applied-state position-absolute">
<svg
className="filter-applied-svg"

View file

@ -151,6 +151,7 @@ export const TableContainer = ({
globalFilter={globalFilter}
table={table}
setFilters={handleFilterChange}
appliedFiltersLength={table.getState().columnFilters.length}
/>
<TableData
id={id}
@ -178,6 +179,7 @@ export const TableContainer = ({
setExposedVariables={setExposedVariables}
fireEvent={fireEvent}
pageCount={table.getPageCount()}
dataLength={table.getFilteredRowModel().rows.length}
columnVisibility={columnVisibility} // Passed to trigger a re-render when columnVisibility changes
/>
</>

View file

@ -47,6 +47,7 @@ export const TableExposedVariables = ({
tableData,
columns,
columnSizing,
resetRowSelection,
} = {
selectedRows: table.getFilteredSelectedRowModel()?.rows,
sorting: table.getState()?.sorting,
@ -61,6 +62,7 @@ export const TableExposedVariables = ({
tableData: table.getRowModel().rows,
columns: table.getAllColumns(),
columnSizing: table.getState().columnSizing,
resetRowSelection: table.resetRowSelection,
};
const prevSortingLength = useRef(null);
@ -209,10 +211,12 @@ export const TableExposedVariables = ({
}, [setPageIndex, setExposedVariables, clientSidePagination]);
useEffect(() => {
resetRowSelection();
function selectRow(key, value) {
const item = data.find((item) => item[key] == value);
const index = data.findIndex((item) => item[key] == value);
const item = index !== -1 ? data[index] : null;
if (item) {
setRowSelection({ [item.id - 1]: true });
setRowSelection({ [index]: true });
}
setExposedVariables({
selectedRow: item,
@ -231,7 +235,7 @@ export const TableExposedVariables = ({
selectedRowId: null,
});
}
}, [data, defaultSelectedRow, setExposedVariables, setRowSelection]);
}, [data, defaultSelectedRow, setExposedVariables, setRowSelection, resetRowSelection]);
useEffect(() => {
if (lastClickedRow) {

View file

@ -87,12 +87,12 @@ export const createInitSlice = (set, get) => ({
} else {
state.components[id].properties.enablePagination = properties.enablePagination;
}
}
// false,
// { type: 'setProperties', payload: { id, properties } }
},
false,
{ type: 'setProperties', payload: { id, properties } }
),
setTableStyles: (id, styles) =>
setTableStyles: (id, styles, darkMode) =>
set(
(state) => {
const {
@ -114,7 +114,7 @@ export const createInitSlice = (set, get) => ({
state.components[id].styles.boxShadow = boxShadow;
state.components[id].styles.borderColor = borderColor;
state.components[id].styles.contentWrap = contentWrap;
state.components[id].styles.textColor = textColor;
state.components[id].styles.textColor = textColor !== '#000' ? textColor : darkMode && '#fff';
state.components[id].styles.rowStyle = tableType;
state.components[id].styles.cellHeight = cellSize;
state.components[id].styles.actionButtonRadius = parseFloat(actionButtonRadius);
@ -171,7 +171,9 @@ export const createInitSlice = (set, get) => ({
removeComponent: (id) =>
set(
(state) => {
if (state.components[id]) {
// if the component is not present in the DOM, then remove it - this is to handle the case where the component is removed from the DOM and then added back
// Like when the component is moved from one container to another
if (state.components[id] && !document.querySelector(`[id="${id}"]`)) {
delete state.components[id];
}
},

View file

@ -18,6 +18,7 @@ import {
CustomSelectColumn,
CustomDropdownColumn,
TextColumn,
JsonColumn,
} from '../_components/DataTypes';
import useTableStore from '../_stores/tableStore';
@ -125,7 +126,7 @@ export default function generateColumnsData({
isEditable={isEditable}
darkMode={darkMode}
handleCellValueChange={handleCellValueChange}
cellTextColor={getResolvedValue(column.textColor, { cellValue, rowData })}
textColor={getResolvedValue(column.textColor, { cellValue, rowData })}
horizontalAlignment={column?.horizontalAlignment}
cellValue={cellValue}
column={column}
@ -143,7 +144,7 @@ export default function generateColumnsData({
isEditable={isEditable}
darkMode={darkMode}
handleCellValueChange={handleCellValueChange}
cellTextColor={getResolvedValue(column.textColor, { cellValue, rowData })}
textColor={getResolvedValue(column.textColor, { cellValue, rowData })}
horizontalAlignment={column?.horizontalAlignment}
cellValue={cellValue}
column={column}
@ -160,7 +161,7 @@ export default function generateColumnsData({
<NumberColumn
isEditable={isEditable}
handleCellValueChange={handleCellValueChange}
cellTextColor={getResolvedValue(column.textColor, { cellValue, rowData })}
textColor={getResolvedValue(column.textColor, { cellValue, rowData })}
horizontalAlignment={column?.horizontalAlignment}
cellValue={cellValue}
column={column}
@ -226,6 +227,7 @@ export default function generateColumnsData({
isNewRow={columnForAddNewRow}
horizontalAlignment={column?.horizontalAlignment}
textColor={getResolvedValue(column.textColor, { cellValue, rowData })}
id={id}
/>
);
@ -291,6 +293,8 @@ export default function generateColumnsData({
getResolvedValue(column?.isTwentyFourHrFormatEnabled, { cellValue, rowData }) ?? false
}
darkMode={darkMode}
textColor={getResolvedValue(column.textColor, { cellValue, rowData })}
id={id}
/>
);
@ -299,11 +303,12 @@ export default function generateColumnsData({
<LinkColumn
cellValue={cellValue}
linkTarget={getResolvedValue(column?.linkTarget, { cellValue, rowData })}
linkColor={getResolvedValue(column?.linkColor ?? '#1B1F24', { cellValue, rowData })}
textColor={getResolvedValue(column?.linkColor ?? '#1B1F24', { cellValue, rowData })}
underlineColor={getResolvedValue(column?.underlineColor, { cellValue, rowData })}
underline={column.underline}
displayText={getResolvedValue(column?.displayText, { cellValue, rowData })}
darkMode={darkMode}
id={id}
/>
);
@ -318,6 +323,22 @@ export default function generateColumnsData({
/>
);
case 'json':
return (
<JsonColumn
isEditable={isEditable}
jsonIndentation={getResolvedValue(column?.jsonIndentation, { cellValue, rowData })}
darkMode={darkMode}
handleCellValueChange={handleCellValueChange}
textColor={getResolvedValue(column.textColor, { cellValue, rowData })}
horizontalAlignment={column?.horizontalAlignment}
cellValue={cellValue}
column={column}
containerWidth={columnSize}
id={id}
/>
);
default:
return cellValue || '';
}