mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-24 09:28:31 +00:00
fix: bugfixes on column data types and table
This commit is contained in:
parent
b79b073b38
commit
a835f9091f
16 changed files with 371 additions and 198 deletions
|
|
@ -3,6 +3,7 @@ import DatePickerComponent from 'react-datepicker';
|
|||
import moment from 'moment-timezone';
|
||||
import cx from 'classnames';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import CustomDatePickerHeader from './_components/CustomDatePickerHeader';
|
||||
import 'react-datepicker/dist/react-datepicker.css';
|
||||
|
||||
const DISABLED_DATE_FORMAT = 'MM/DD/YYYY';
|
||||
|
|
@ -24,14 +25,16 @@ const DatepickerInput = forwardRef(({ value, onClick, styles, readOnly, onInputC
|
|||
onChange={onInputChange}
|
||||
onFocus={onInputFocus}
|
||||
/>
|
||||
<span className="cell-icon-display">
|
||||
<SolidIcon
|
||||
width="16"
|
||||
fill="var(--borders-strong)"
|
||||
name="calender"
|
||||
className="table-column-datepicker-input-icon"
|
||||
/>
|
||||
</span>
|
||||
{!readOnly && (
|
||||
<span className="cell-icon-display">
|
||||
<SolidIcon
|
||||
width="16"
|
||||
fill="var(--borders-strong)"
|
||||
name="calender"
|
||||
className="table-column-datepicker-input-icon"
|
||||
/>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
@ -253,6 +256,7 @@ export const DatepickerColumn = ({
|
|||
dropdownMode="select"
|
||||
excludeDates={excludedDates}
|
||||
showPopperArrow={false}
|
||||
renderCustomHeader={(headerProps) => <CustomDatePickerHeader {...headerProps} />}
|
||||
shouldCloseOnSelect
|
||||
readOnly={readOnly}
|
||||
popperProps={{ strategy: 'fixed' }}
|
||||
|
|
|
|||
|
|
@ -8,20 +8,22 @@ export const LinkColumn = ({ cellValue, linkTarget, underline, underlineColor, l
|
|||
|
||||
return (
|
||||
<div className="h-100 d-flex align-items-center">
|
||||
<a
|
||||
className={underline === 'hover' ? 'table-link-hover' : 'table-link'}
|
||||
href={cellValue}
|
||||
target={linkTarget === '_self' || linkTarget == false ? '_self' : '_blank'}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
color: linkTextColor,
|
||||
textDecoration: underline === 'always' && 'underline',
|
||||
textDecorationColor: underlineColor,
|
||||
}}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{displayText || String(cellValue)}
|
||||
</a>
|
||||
<div className="w-100">
|
||||
<a
|
||||
className={underline === 'hover' ? 'table-link-hover' : 'table-link'}
|
||||
href={cellValue}
|
||||
target={linkTarget === '_self' || linkTarget == false ? '_self' : '_blank'}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
style={{
|
||||
color: linkTextColor,
|
||||
textDecoration: underline === 'always' && 'underline',
|
||||
textDecorationColor: underlineColor,
|
||||
}}
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{displayText || String(cellValue)}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import useStore from '@/AppBuilder/_stores/store';
|
||||
import { shallow } from 'zustand/shallow';
|
||||
import { determineJustifyContentValue } from '@/_helpers/utils';
|
||||
|
|
@ -16,7 +16,25 @@ export const NumberColumn = ({
|
|||
row,
|
||||
searchText,
|
||||
}) => {
|
||||
const [displayValue, setDisplayValue] = useState(cellValue);
|
||||
const validateWidget = useStore((state) => state.validateWidget, shallow);
|
||||
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
|
||||
|
||||
useEffect(() => {
|
||||
setDisplayValue(cellValue);
|
||||
}, [cellValue]);
|
||||
|
||||
const removingExcessDecimalPlaces = (value, allowedDecimalPlaces) => {
|
||||
if (value?.toString()?.includes('.')) {
|
||||
const [integerPart, decimalPart] = value.toString().split('.');
|
||||
const truncatedDecimalPart = decimalPart.slice(0, allowedDecimalPlaces);
|
||||
return Number(`${integerPart}.${truncatedDecimalPart}`);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const allowedDecimalPlaces = getResolvedValue(column?.decimalPlaces) ?? null;
|
||||
cellValue = allowedDecimalPlaces ? removingExcessDecimalPlaces(cellValue, allowedDecimalPlaces) : cellValue;
|
||||
|
||||
const validationData = validateWidget({
|
||||
validationObject: {
|
||||
|
|
@ -55,26 +73,15 @@ export const NumberColumn = ({
|
|||
}
|
||||
};
|
||||
|
||||
const removingExcessDecimalPlaces = (value, allowedDecimalPlaces) => {
|
||||
if (value?.toString()?.includes('.')) {
|
||||
const [integerPart, decimalPart] = value.toString().split('.');
|
||||
const truncatedDecimalPart = decimalPart.slice(0, allowedDecimalPlaces);
|
||||
return Number(`${integerPart}.${truncatedDecimalPart}`);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const handleValueChange = (value) => {
|
||||
if (value === '') return;
|
||||
|
||||
const numValue = Number(value);
|
||||
if (isNaN(numValue)) return;
|
||||
|
||||
const allowedDecimalPlaces = column?.decimalPlaces ?? null;
|
||||
const processedValue = allowedDecimalPlaces
|
||||
? removingExcessDecimalPlaces(numValue, allowedDecimalPlaces)
|
||||
: numValue;
|
||||
|
||||
const processedValue =
|
||||
allowedDecimalPlaces !== null ? removingExcessDecimalPlaces(numValue, allowedDecimalPlaces) : numValue;
|
||||
setDisplayValue(processedValue);
|
||||
handleCellValueChange(row.index, column.key || column.name, processedValue, row.original);
|
||||
};
|
||||
|
||||
|
|
@ -91,17 +98,19 @@ export const NumberColumn = ({
|
|||
paddingRight: '20px',
|
||||
}}
|
||||
className={`table-column-type-input-element input-number h-100 ${!isValid ? 'is-invalid' : ''}`}
|
||||
defaultValue={cellValue}
|
||||
value={displayValue}
|
||||
onChange={(e) => setDisplayValue(e.target.value)}
|
||||
step={allowedDecimalPlaces !== null ? `0.${'0'.repeat(allowedDecimalPlaces - 1)}1` : 'any'}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
if (e.target.defaultValue !== e.target.value) {
|
||||
handleValueChange(e.target.value);
|
||||
if (displayValue !== cellValue) {
|
||||
handleValueChange(displayValue);
|
||||
}
|
||||
}
|
||||
}}
|
||||
onBlur={(e) => {
|
||||
if (e.target.defaultValue !== e.target.value) {
|
||||
handleValueChange(e.target.value);
|
||||
onBlur={() => {
|
||||
if (displayValue !== cellValue) {
|
||||
handleValueChange(displayValue);
|
||||
}
|
||||
}}
|
||||
onFocus={(e) => e.stopPropagation()}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,93 @@
|
|||
import React from 'react';
|
||||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import moment from 'moment';
|
||||
import { range } from 'lodash';
|
||||
|
||||
const CustomDatePickerHeader = ({
|
||||
date,
|
||||
changeYear,
|
||||
changeMonth,
|
||||
decreaseMonth,
|
||||
increaseMonth,
|
||||
prevMonthButtonDisabled,
|
||||
nextMonthButtonDisabled,
|
||||
}) => {
|
||||
const months = [
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
];
|
||||
|
||||
const years = range(1900, 2101);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
style={{
|
||||
marginBottom: 10,
|
||||
marginTop: 10,
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<button
|
||||
className="tj-datepicker-widget-arrows tj-datepicker-widget-left"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
decreaseMonth();
|
||||
}}
|
||||
disabled={prevMonthButtonDisabled}
|
||||
>
|
||||
<SolidIcon name="cheveronleft" width="12" />
|
||||
</button>
|
||||
<div style={{ marginRight: '8px' }}>
|
||||
<select
|
||||
value={months[moment(date).month()]}
|
||||
onChange={({ target: { value } }) => changeMonth(months.indexOf(value))}
|
||||
className="tj-datepicker-widget-month-selector"
|
||||
>
|
||||
{months.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<select
|
||||
value={moment(date).year()}
|
||||
onChange={({ target: { value } }) => changeYear(value)}
|
||||
className="tj-datepicker-widget-year-selector"
|
||||
style={{ padding: '4px 6px' }}
|
||||
>
|
||||
{years.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{option}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="tj-datepicker-widget-arrows tj-datepicker-widget-right "
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
increaseMonth();
|
||||
}}
|
||||
disabled={nextMonthButtonDisabled}
|
||||
>
|
||||
<SolidIcon name="cheveronright" width="12" />
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomDatePickerHeader;
|
||||
|
|
@ -89,6 +89,7 @@ export const Footer = memo(
|
|||
height={height}
|
||||
componentName={componentName}
|
||||
setShowAddNewRowPopup={setShowAddNewRowPopup}
|
||||
fireEvent={fireEvent}
|
||||
columnVisibility={columnVisibility} // Passed to trigger a re-render when columnVisibility changes
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,17 +13,24 @@ export function AddNewRow({ id, hideAddNewRowPopup, darkMode, allColumns, fireEv
|
|||
const addNewRowDetails = useTableStore((state) => state.getAllAddNewRowDetails(id), shallow);
|
||||
const updateAddNewRowDetails = useTableStore((state) => state.updateAddNewRowDetails, shallow);
|
||||
const clearAddNewRowDetails = useTableStore((state) => state.clearAddNewRowDetails, shallow);
|
||||
const updateShouldPersistAddNewRow = useTableStore((state) => state.updateShouldPersistAddNewRow, shallow);
|
||||
|
||||
const addNewRowDetailsLength = addNewRowDetails.size;
|
||||
|
||||
const newEmptyRow = useMemo(() => {
|
||||
return allColumns.reduce((accumulator, column) => {
|
||||
if (column.columnDef?.meta?.skipAddNewRow) return accumulator;
|
||||
const key = column.columnDef.accessorKey;
|
||||
if (column.id !== 'selection') accumulator[key] = '';
|
||||
accumulator[key] = '';
|
||||
return accumulator;
|
||||
}, {});
|
||||
}, [allColumns]);
|
||||
|
||||
useEffect(() => {
|
||||
clearAddNewRowDetails(id);
|
||||
updateShouldPersistAddNewRow(id, false);
|
||||
}, [updateShouldPersistAddNewRow, clearAddNewRowDetails, id]);
|
||||
|
||||
useEffect(() => {
|
||||
function discardNewlyAddedRows() {
|
||||
clearAddNewRowDetails(id);
|
||||
|
|
@ -81,6 +88,11 @@ export function AddNewRow({ id, hideAddNewRowPopup, darkMode, allColumns, fireEv
|
|||
updateAddNewRowDetails(id, addNewRowDetailsLength, newEmptyRow);
|
||||
};
|
||||
|
||||
const closeAddNewRowPopup = () => {
|
||||
hideAddNewRowPopup();
|
||||
updateShouldPersistAddNewRow(id, true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`table-add-new-row card ${darkMode && 'dark-theme'}`}>
|
||||
<div className="card-header row">
|
||||
|
|
@ -90,7 +102,7 @@ export function AddNewRow({ id, hideAddNewRowPopup, darkMode, allColumns, fireEv
|
|||
</h4>
|
||||
</div>
|
||||
<div className="col-auto">
|
||||
<button data-cy={`button-close-filters`} onClick={hideAddNewRowPopup} className="btn btn-light btn-sm">
|
||||
<button data-cy={`button-close-filters`} onClick={closeAddNewRowPopup} className="btn btn-light btn-sm">
|
||||
x
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -178,7 +190,6 @@ export function AddNewRow({ id, hideAddNewRowPopup, darkMode, allColumns, fireEv
|
|||
onClick={async () => {
|
||||
await fireEvent('onNewRowsAdded');
|
||||
hideAddNewRowPopup();
|
||||
clearAddNewRowDetails(id);
|
||||
}}
|
||||
size="sm"
|
||||
customStyles={{ padding: '10px 20px' }}
|
||||
|
|
|
|||
|
|
@ -8,19 +8,20 @@ import IndeterminateCheckbox from '../../IndeterminateCheckbox';
|
|||
import Popover from 'react-bootstrap/Popover';
|
||||
import { exportToCSV, exportToExcel, exportToPDF } from '@/AppBuilder/Widgets/NewTable/_utils/exportData';
|
||||
|
||||
export const ControlButtons = memo(({ id, table, darkMode, height, componentName, setShowAddNewRowPopup }) => {
|
||||
const showAddNewRowButton = useTableStore((state) => state.getTableProperties(id)?.showAddNewRowButton, shallow);
|
||||
const showDownloadButton = useTableStore((state) => state.getTableProperties(id)?.showDownloadButton, shallow);
|
||||
const hideColumnSelectorButton = useTableStore(
|
||||
(state) => state.getTableProperties(id)?.hideColumnSelectorButton,
|
||||
shallow
|
||||
);
|
||||
export const ControlButtons = memo(
|
||||
({ id, table, darkMode, height, componentName, setShowAddNewRowPopup, fireEvent }) => {
|
||||
const showAddNewRowButton = useTableStore((state) => state.getTableProperties(id)?.showAddNewRowButton, shallow);
|
||||
const showDownloadButton = useTableStore((state) => state.getTableProperties(id)?.showDownloadButton, shallow);
|
||||
const hideColumnSelectorButton = useTableStore(
|
||||
(state) => state.getTableProperties(id)?.hideColumnSelectorButton,
|
||||
shallow
|
||||
);
|
||||
const clientSidePagination = useTableStore((state) => state.getTableProperties(id)?.clientSidePagination, shallow);
|
||||
|
||||
const renderOverlay = (id, icon, callBack, tooltipId, tooltipContent) => {
|
||||
return (
|
||||
<>
|
||||
<Tooltip id={tooltipId} className="tooltip" />
|
||||
<OverlayTriggerComponent trigger="click" overlay={callBack()} rootClose={true} placement={'top-end'}>
|
||||
const renderButton = (icon, onClick, tooltipId, tooltipContent) => {
|
||||
return (
|
||||
<>
|
||||
<Tooltip id={tooltipId} className="tooltip" />
|
||||
<ButtonSolid
|
||||
variant="ghostBlack"
|
||||
className={`tj-text-xsm `}
|
||||
|
|
@ -33,125 +34,149 @@ export const ControlButtons = memo(({ id, table, darkMode, height, componentName
|
|||
size="md"
|
||||
data-tooltip-id={tooltipId}
|
||||
data-tooltip-content={tooltipContent}
|
||||
onClick={(e) => {
|
||||
if (document.activeElement === e.currentTarget) {
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
}}
|
||||
></ButtonSolid>
|
||||
</OverlayTriggerComponent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Haven't seperated this into a separate component because of UI issues
|
||||
const hideColumnsPopover = () => (
|
||||
<Popover className={`${darkMode && 'dark-theme'}`} style={{ maxHeight: `${height - 79}px`, overflowY: 'auto' }}>
|
||||
<div
|
||||
data-cy={`dropdown-hide-column`}
|
||||
className={`dropdown-table-column-hide-common ${
|
||||
darkMode ? 'dropdown-table-column-hide-dark-themed dark-theme' : 'dropdown-table-column-hide'
|
||||
} `}
|
||||
placement="top-end"
|
||||
>
|
||||
<div className="dropdown-item cursor-pointer">
|
||||
<IndeterminateCheckbox
|
||||
checked={table.getIsAllColumnsVisible()}
|
||||
onChange={table.getToggleAllColumnsVisibilityHandler()}
|
||||
/>
|
||||
<span className="hide-column-name tj-text-xsm" data-cy={`options-select-all-coloumn`}>
|
||||
Selects All
|
||||
</span>
|
||||
</div>
|
||||
{table.getAllLeafColumns().map((column) => {
|
||||
const header = column?.columnDef?.header;
|
||||
return (
|
||||
typeof header === 'string' && (
|
||||
<div key={column.id}>
|
||||
<div>
|
||||
<label className="dropdown-item d-flex cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
data-cy={`checkbox-coloumn-${String(header).toLowerCase().replace(/\s+/g, '-')}`}
|
||||
checked={column.getIsVisible()}
|
||||
onChange={column.getToggleVisibilityHandler()}
|
||||
/>
|
||||
<span
|
||||
className="hide-column-name tj-text-xsm"
|
||||
data-cy={`options-coloumn-${String(header).toLowerCase().replace(/\s+/g, '-')}`}
|
||||
>
|
||||
{` ${header}`}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
// Haven't seperated this into a separate component because of UI issues
|
||||
const downlaodPopover = () => (
|
||||
<Popover
|
||||
id="popover-basic"
|
||||
data-cy="popover-card"
|
||||
className={`${darkMode && 'dark-theme'} shadow table-widget-download-popup`}
|
||||
placement="top-end"
|
||||
>
|
||||
<Popover.Body className="p-0">
|
||||
<div className="table-download-option cursor-pointer">
|
||||
<span
|
||||
data-cy={`option-download-CSV`}
|
||||
className="cursor-pointer"
|
||||
onClick={() => exportToCSV(table, componentName)}
|
||||
>
|
||||
Download as CSV
|
||||
</span>
|
||||
<span
|
||||
data-cy={`option-download-execel`}
|
||||
className="pt-2 cursor-pointer"
|
||||
onClick={() => exportToExcel(table, componentName)}
|
||||
>
|
||||
Download as Excel
|
||||
</span>
|
||||
<span
|
||||
data-cy={`option-download-pdf`}
|
||||
className="pt-2 cursor-pointer"
|
||||
onClick={() => exportToPDF(table, componentName)}
|
||||
>
|
||||
Download as PDF
|
||||
</span>
|
||||
</div>
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="col d-flex justify-content-end ">
|
||||
{showAddNewRowButton && (
|
||||
<>
|
||||
<Tooltip id="tooltip-for-add-new-row" className="tooltip" />
|
||||
<ButtonSolid
|
||||
variant="ghostBlack"
|
||||
fill={`var(--icons-default)`}
|
||||
className={'tj-text-xsm'}
|
||||
customStyles={{ minWidth: '32px' }}
|
||||
leftIcon="plus"
|
||||
iconWidth="16"
|
||||
onClick={() => {
|
||||
setShowAddNewRowPopup(true);
|
||||
}}
|
||||
size="md"
|
||||
data-tooltip-id="tooltip-for-add-new-row"
|
||||
data-tooltip-content="Add new row"
|
||||
onClick={onClick}
|
||||
></ButtonSolid>
|
||||
</>
|
||||
)}
|
||||
{showDownloadButton && renderOverlay(id, 'filedownload', downlaodPopover, 'tooltip-for-download', 'Download')}
|
||||
{!hideColumnSelectorButton &&
|
||||
renderOverlay(id, 'eye1', hideColumnsPopover, 'tooltip-for-manage-columns', 'Manage columns')}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
const renderOverlay = (id, icon, callBack, tooltipId, tooltipContent) => {
|
||||
const onClick = (e) => {
|
||||
if (document.activeElement === e.currentTarget) {
|
||||
e.currentTarget.blur();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tooltip id={tooltipId} className="tooltip" />
|
||||
<OverlayTriggerComponent trigger="click" overlay={callBack()} rootClose={true} placement={'top-end'}>
|
||||
{renderButton(icon, onClick, tooltipId, tooltipContent)}
|
||||
</OverlayTriggerComponent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// Haven't seperated this into a separate component because of UI issues
|
||||
const hideColumnsPopover = () => (
|
||||
<Popover className={`${darkMode && 'dark-theme'}`} style={{ maxHeight: `${height - 79}px`, overflowY: 'auto' }}>
|
||||
<div
|
||||
data-cy={`dropdown-hide-column`}
|
||||
className={`dropdown-table-column-hide-common ${
|
||||
darkMode ? 'dropdown-table-column-hide-dark-themed dark-theme' : 'dropdown-table-column-hide'
|
||||
} `}
|
||||
placement="top-end"
|
||||
>
|
||||
<div className="dropdown-item cursor-pointer">
|
||||
<IndeterminateCheckbox
|
||||
checked={table.getIsAllColumnsVisible()}
|
||||
onChange={table.getToggleAllColumnsVisibilityHandler()}
|
||||
/>
|
||||
<span className="hide-column-name tj-text-xsm" data-cy={`options-select-all-coloumn`}>
|
||||
Selects All
|
||||
</span>
|
||||
</div>
|
||||
{table.getAllLeafColumns().map((column) => {
|
||||
const header = column?.columnDef?.header;
|
||||
return (
|
||||
typeof header === 'string' && (
|
||||
<div key={column.id}>
|
||||
<div>
|
||||
<label className="dropdown-item d-flex cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
data-cy={`checkbox-coloumn-${String(header).toLowerCase().replace(/\s+/g, '-')}`}
|
||||
checked={column.getIsVisible()}
|
||||
onChange={column.getToggleVisibilityHandler()}
|
||||
/>
|
||||
<span
|
||||
className="hide-column-name tj-text-xsm"
|
||||
data-cy={`options-coloumn-${String(header).toLowerCase().replace(/\s+/g, '-')}`}
|
||||
>
|
||||
{` ${header}`}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
// Haven't seperated this into a separate component because of UI issues
|
||||
const downlaodPopover = () => (
|
||||
<Popover
|
||||
id="popover-basic"
|
||||
data-cy="popover-card"
|
||||
className={`${darkMode && 'dark-theme'} shadow table-widget-download-popup`}
|
||||
placement="top-end"
|
||||
>
|
||||
<Popover.Body className="p-0">
|
||||
<div className="table-download-option cursor-pointer">
|
||||
<span
|
||||
data-cy={`option-download-CSV`}
|
||||
className="cursor-pointer"
|
||||
onClick={() => exportToCSV(table, componentName)}
|
||||
>
|
||||
Download as CSV
|
||||
</span>
|
||||
<span
|
||||
data-cy={`option-download-execel`}
|
||||
className="pt-2 cursor-pointer"
|
||||
onClick={() => exportToExcel(table, componentName)}
|
||||
>
|
||||
Download as Excel
|
||||
</span>
|
||||
<span
|
||||
data-cy={`option-download-pdf`}
|
||||
className="pt-2 cursor-pointer"
|
||||
onClick={() => exportToPDF(table, componentName)}
|
||||
>
|
||||
Download as PDF
|
||||
</span>
|
||||
</div>
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
);
|
||||
|
||||
const renderDownloadButton = () => {
|
||||
if (!clientSidePagination) {
|
||||
const onClick = () => {
|
||||
fireEvent('onTableDataDownload');
|
||||
};
|
||||
return renderButton('filedownload', onClick, 'tooltip-for-download-serverside-pagingation', 'Download');
|
||||
}
|
||||
|
||||
return renderOverlay(id, 'filedownload', downlaodPopover, 'tooltip-for-download', 'Download');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="col d-flex justify-content-end ">
|
||||
{showAddNewRowButton && (
|
||||
<>
|
||||
<Tooltip id="tooltip-for-add-new-row" className="tooltip" />
|
||||
<ButtonSolid
|
||||
variant="ghostBlack"
|
||||
fill={`var(--icons-default)`}
|
||||
className={'tj-text-xsm'}
|
||||
customStyles={{ minWidth: '32px' }}
|
||||
leftIcon="plus"
|
||||
iconWidth="16"
|
||||
onClick={() => {
|
||||
setShowAddNewRowPopup(true);
|
||||
}}
|
||||
size="md"
|
||||
data-tooltip-id="tooltip-for-add-new-row"
|
||||
data-tooltip-content="Add new row"
|
||||
></ButtonSolid>
|
||||
</>
|
||||
)}
|
||||
{renderDownloadButton()}
|
||||
{!hideColumnSelectorButton &&
|
||||
renderOverlay(id, 'eye1', hideColumnsPopover, 'tooltip-for-manage-columns', 'Manage columns')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
import React from 'react';
|
||||
|
||||
export const HighLightSearch = React.memo(({ text, searchTerm }) => {
|
||||
if (searchTerm === '' || !text.toString()?.toLowerCase().includes(searchTerm?.toLowerCase())) return text;
|
||||
if (text === '') return null;
|
||||
|
||||
if (searchTerm === '' || !text.toString()?.toLowerCase().includes(searchTerm?.toLowerCase()))
|
||||
return <span>{text}</span>;
|
||||
|
||||
const parts = String(text).split(new RegExp(`(${searchTerm})`, 'gi'));
|
||||
|
||||
return (
|
||||
<span>
|
||||
{parts.map((part, index) =>
|
||||
part.toLowerCase() === searchTerm.toLowerCase() ? (
|
||||
part?.toLowerCase() === searchTerm?.toLowerCase() ? (
|
||||
<span key={index}>
|
||||
<mark>{part}</mark>
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ export const TableContainer = ({
|
|||
useTableStore();
|
||||
|
||||
const columnProperties = getColumnProperties(id);
|
||||
|
||||
// Table properties
|
||||
const showBulkSelector = useTableStore((state) => state.getTableProperties(id)?.showBulkSelector, shallow);
|
||||
const enableSorting = useTableStore((state) => state.getTableProperties(id)?.enabledSort, shallow);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ const DraggableHeader = ({ header, darkMode, id }) => {
|
|||
});
|
||||
|
||||
const columnHeaderWrap = useTableStore((state) => state.getTableStyles(id)?.columnHeaderWrap, shallow);
|
||||
const headerCasing = useTableStore((state) => state.getTableStyles(id)?.headerCasing, shallow);
|
||||
|
||||
const getResolvedValue = useStore.getState().getResolvedValue;
|
||||
|
||||
|
|
@ -82,6 +83,7 @@ const DraggableHeader = ({ header, darkMode, id }) => {
|
|||
'text-truncate': getResolvedValue(columnHeaderWrap) === 'fixed',
|
||||
'wrap-wrapper': getResolvedValue(columnHeaderWrap) === 'wrap',
|
||||
})}
|
||||
style={{ textTransform: headerCasing === 'uppercase' ? 'uppercase' : 'none' }}
|
||||
>
|
||||
{header.isPlaceholder ? null : flexRender(header.column.columnDef.header, header.getContext())}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -51,9 +51,6 @@ export const TableRow = ({
|
|||
fireEvent('onRowHovered');
|
||||
}
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
hasHoveredEvent && setExposedVariables({ hoveredRowId: '', hoveredRow: '' });
|
||||
}}
|
||||
>
|
||||
{row.getVisibleCells().map((cell) => {
|
||||
const cellStyles = {
|
||||
|
|
@ -65,6 +62,7 @@ export const TableRow = ({
|
|||
justifyContent: determineJustifyContentValue(cell.column.columnDef?.meta?.horizontalAlignment),
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
textAlign: cell.column.columnDef?.meta?.horizontalAlignment,
|
||||
};
|
||||
|
||||
const isEditable = getResolvedValue(cell.column.columnDef?.meta?.isEditable ?? false, {
|
||||
|
|
@ -76,9 +74,13 @@ export const TableRow = ({
|
|||
<td
|
||||
key={cell.id}
|
||||
style={cellStyles}
|
||||
className={cx('table-cell table-text-align-left td', {
|
||||
className={cx('table-cell td', {
|
||||
'table-text-align-center': cell.column.columnDef?.meta?.horizontalAlignment === 'center',
|
||||
'table-text-align-right': cell.column.columnDef?.meta?.horizontalAlignment === 'right',
|
||||
'table-text-align-left': cell.column.columnDef?.meta?.horizontalAlignment === 'left',
|
||||
'wrap-wrapper': contentWrap,
|
||||
'has-text': cell.column.columnDef?.meta?.columnType === 'text' || isEditable,
|
||||
'has-datepicker': cell.column.columnDef?.meta?.columnType === 'datepicker',
|
||||
'has-number': cell.column.columnDef?.meta?.columnType === 'number',
|
||||
'has-badge': ['badge', 'badges'].includes(cell.column.columnDef?.meta?.columnType),
|
||||
[cellHeight]: true,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const TableExposedVariables = ({
|
|||
fireEvent,
|
||||
table,
|
||||
componentName,
|
||||
pageIndex,
|
||||
pageIndex = 1,
|
||||
lastClickedRow,
|
||||
}) => {
|
||||
const editedRows = useTableStore((state) => state.getAllEditedRows(id), shallow);
|
||||
|
|
@ -63,6 +63,8 @@ export const TableExposedVariables = ({
|
|||
columnSizing: table.getState().columnSizing,
|
||||
};
|
||||
|
||||
const prevSortingLength = useRef(null);
|
||||
|
||||
const getColumnName = useCallback(
|
||||
(columnId) => {
|
||||
const column = table.getColumn(columnId);
|
||||
|
|
@ -147,8 +149,11 @@ export const TableExposedVariables = ({
|
|||
const sortApplied = [{ column: getColumnName(sorting[0].id), direction: sorting[0].desc ? 'desc' : 'asc' }];
|
||||
setExposedVariables({ sortApplied });
|
||||
fireEvent('onSort');
|
||||
prevSortingLength.current = sorting.length;
|
||||
} else {
|
||||
setExposedVariables({ sortApplied: undefined });
|
||||
prevSortingLength.current && fireEvent('onSort');
|
||||
prevSortingLength.current = null;
|
||||
}
|
||||
}, [sorting, getColumnName, setExposedVariables, fireEvent]);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { utilityForNestedNewRow } from '../helper';
|
|||
import { deepClone } from '@/_helpers/utilities/utils.helpers';
|
||||
|
||||
export const createInitSlice = (set, get) => ({
|
||||
initializeComponent: (id) =>
|
||||
initializeComponent: (id) => {
|
||||
set(
|
||||
(state) => {
|
||||
if (!state.components[id]) {
|
||||
|
|
@ -12,6 +12,7 @@ export const createInitSlice = (set, get) => ({
|
|||
styles: {},
|
||||
filters: {},
|
||||
addNewRow: new Map(),
|
||||
shouldPersistAddNewRow: false,
|
||||
editedRowDetails: {
|
||||
editedRows: new Map(),
|
||||
editedFields: new Map(),
|
||||
|
|
@ -31,7 +32,8 @@ export const createInitSlice = (set, get) => ({
|
|||
},
|
||||
false,
|
||||
{ type: 'initializeComponent', payload: { id } }
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
setTableProperties: (id, properties) =>
|
||||
set(
|
||||
|
|
@ -105,10 +107,9 @@ export const createInitSlice = (set, get) => ({
|
|||
maxRowHeight = 'auto',
|
||||
maxRowHeightValue = 80,
|
||||
columnHeaderWrap = 'fixed',
|
||||
headerCasing = 'uppercase',
|
||||
} = styles;
|
||||
|
||||
console.log('style--- cellSize--- ', cellSize);
|
||||
|
||||
state.components[id].styles.borderRadius = Number.parseFloat(borderRadius);
|
||||
state.components[id].styles.boxShadow = boxShadow;
|
||||
state.components[id].styles.borderColor = borderColor;
|
||||
|
|
@ -120,6 +121,7 @@ export const createInitSlice = (set, get) => ({
|
|||
state.components[id].styles.isMaxRowHeightAuto = maxRowHeight === 'auto';
|
||||
state.components[id].styles.maxRowHeightValue = maxRowHeightValue;
|
||||
state.components[id].styles.columnHeaderWrap = columnHeaderWrap;
|
||||
state.components[id].styles.headerCasing = headerCasing;
|
||||
},
|
||||
false,
|
||||
{ type: 'setStyles', payload: { id, styles } }
|
||||
|
|
@ -239,11 +241,21 @@ export const createInitSlice = (set, get) => ({
|
|||
clearAddNewRowDetails: (id) =>
|
||||
set(
|
||||
(state) => {
|
||||
state.components[id].addNewRow.clear();
|
||||
if (!state.components[id].shouldPersistAddNewRow) {
|
||||
state.components[id].addNewRow.clear();
|
||||
}
|
||||
},
|
||||
false,
|
||||
{ type: 'clearNewRow', payload: { id } }
|
||||
),
|
||||
updateShouldPersistAddNewRow: (id, value) =>
|
||||
set(
|
||||
(state) => {
|
||||
state.components[id].shouldPersistAddNewRow = value;
|
||||
},
|
||||
false,
|
||||
{ type: 'updateShouldPersistAddNewRow', payload: { id, value } }
|
||||
),
|
||||
getTableComponentEvents: (id) => {
|
||||
return get().components[id]?.events?.tableComponentEvents || [];
|
||||
},
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export const buildTableColumn = (
|
|||
fireEvent,
|
||||
tableRef: tableBodyRef,
|
||||
handleCellValueChange,
|
||||
searchText: serverSideSearch ? '' : globalFilter,
|
||||
searchText: globalFilter,
|
||||
}).filter(Boolean),
|
||||
|
||||
...generateActionColumns({
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export const generateActionColumns = ({ actions, fireEvent, setExposedVariables,
|
|||
id: `${position}Actions`,
|
||||
accessorKey: 'actions',
|
||||
enableResizing: false,
|
||||
meta: { columnType: 'action', position, skipFilter: true },
|
||||
meta: { columnType: 'action', position, skipFilter: true, skipAddNewRow: true },
|
||||
size: 90,
|
||||
header: 'Actions',
|
||||
cell: ({ row, cell }) => (
|
||||
|
|
|
|||
|
|
@ -79,6 +79,9 @@ export default function generateColumnsData({
|
|||
}
|
||||
|
||||
const isEditable = getResolvedValue(column.isEditable);
|
||||
const isVisible = getResolvedValue(column.columnVisibility) ?? true;
|
||||
|
||||
if (!isVisible) return null;
|
||||
|
||||
const columnDef = {
|
||||
id: column.id || uuidv4(),
|
||||
|
|
@ -91,7 +94,7 @@ export default function generateColumnsData({
|
|||
filterFn: 'applyFilters',
|
||||
size: columnSize || defaultColumn.width,
|
||||
minSize: 60,
|
||||
show: column?.columnVisibility ?? true,
|
||||
show: isVisible,
|
||||
meta: {
|
||||
columnType,
|
||||
isEditable: isEditable,
|
||||
|
|
@ -100,7 +103,7 @@ export default function generateColumnsData({
|
|||
horizontalAlignment: column?.horizontalAlignment ?? 'left',
|
||||
transformation: column.transformation,
|
||||
validation: column.validation,
|
||||
columnVisibility: column.columnVisibility,
|
||||
columnVisibility: isVisible,
|
||||
...column,
|
||||
},
|
||||
|
||||
|
|
@ -108,9 +111,9 @@ export default function generateColumnsData({
|
|||
const changeSet = columnForAddNewRow
|
||||
? getAddNewRowDetailFromIndex(id, row.index)
|
||||
: getEditedRowFromIndex(id, row.index);
|
||||
const cellValue = changeSet
|
||||
? changeSet[cell.column.columnDef?.meta?.name] ?? cell.getValue()
|
||||
: cell.getValue();
|
||||
|
||||
let cellValue = changeSet ? changeSet[cell.column.columnDef?.meta?.name] ?? cell.getValue() : cell.getValue();
|
||||
cellValue = cellValue === undefined || cellValue === null ? '' : cellValue;
|
||||
const rowData = tableData?.[row.index];
|
||||
|
||||
switch (columnType) {
|
||||
|
|
@ -221,6 +224,8 @@ export default function generateColumnsData({
|
|||
className="select-search table-select-search"
|
||||
column={column}
|
||||
isNewRow={columnForAddNewRow}
|
||||
horizontalAlignment={column?.horizontalAlignment}
|
||||
textColor={getResolvedValue(column.textColor, { cellValue, rowData })}
|
||||
/>
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue