mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 08:58:26 +00:00
Fixed issues related to date time data type for TJDB (#10253)
* Fixed some issues * Added error borders and fixed some more issues * Added Tooltip for datatype label and added pill for create/edit row * Resolved few more issues * Resolved datetime input text colliding with clear button & create/edit row css collapsing * Import/export for configurations added * Minor Fixes * Fixed calendar not being updated on keydown * Fixed date formatting
This commit is contained in:
parent
c63abeb168
commit
4f1beef160
16 changed files with 429 additions and 78 deletions
|
|
@ -9,7 +9,8 @@ import { getLocalTimeZone, convertToDateType, formatDate } from '../util';
|
|||
export const DateTimePicker = ({
|
||||
enableDate = true,
|
||||
enableTime = true,
|
||||
format = 'dd/MM/yyyy, h:mm aa',
|
||||
format = 'dd/MM/yyyy, hh:mm aa',
|
||||
timeFormat = 'hh:mm aa',
|
||||
isOpenOnStart = false,
|
||||
timestamp = null,
|
||||
setTimestamp,
|
||||
|
|
@ -19,18 +20,28 @@ export const DateTimePicker = ({
|
|||
saveFunction = () => {},
|
||||
timezone = getLocalTimeZone(),
|
||||
isClearable = false,
|
||||
isPlaceholderEnabled = false,
|
||||
isDisabled = false,
|
||||
errorMessage,
|
||||
}) => {
|
||||
const transformedTimestamp = timestamp ? convertToDateType(timestamp, timezone) : null;
|
||||
const startValue = useRef(timestamp);
|
||||
const timestampRef = useRef(timestamp);
|
||||
const prevTimestampRef = useRef(timestamp || new Date().toISOString());
|
||||
const transformedTimestamp = timestamp ? convertToDateType(timestamp, timezone) : null;
|
||||
const [triggeredKeyPress, setTriggeredKeyPress] = useState(0);
|
||||
const minDate = new Date(1800, 0, 1);
|
||||
const maxDate = new Date(2200, 11, 31);
|
||||
const [isOpen, setIsOpen] = useState(isOpenOnStart);
|
||||
|
||||
const handleKeyDown = (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
handleCancel();
|
||||
} else if (e.key === 'Enter') {
|
||||
handleSave();
|
||||
setIsOpen(false);
|
||||
if (isEditCell) {
|
||||
if (e.key === 'Escape') {
|
||||
handleCancel();
|
||||
timestampRef.current = startValue.current;
|
||||
setTimestamp(startValue.current);
|
||||
} else if (e.key === 'Enter') {
|
||||
handleSave();
|
||||
}
|
||||
}
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
|
@ -39,8 +50,12 @@ export const DateTimePicker = ({
|
|||
setIsOpen(false);
|
||||
};
|
||||
|
||||
const handleSave = () => {
|
||||
const handleSave = (e) => {
|
||||
if (e) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
saveFunction(timestampRef.current);
|
||||
startValue.current = timestampRef.current;
|
||||
setIsOpen(false);
|
||||
};
|
||||
|
||||
|
|
@ -56,6 +71,12 @@ export const DateTimePicker = ({
|
|||
setTimestamp(stringifiedTimestamp, isTimeSelect);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen === false && isEditCell) {
|
||||
setIsOpen(true);
|
||||
}
|
||||
}, [triggeredKeyPress]);
|
||||
|
||||
useEffect(() => {
|
||||
const currentTimeInMilliseconds = new Date(timestampRef.current).getTime();
|
||||
const defaultValueInMilliseconds = new Date(defaultValue).getTime();
|
||||
|
|
@ -64,6 +85,13 @@ export const DateTimePicker = ({
|
|||
}
|
||||
}, [timestampRef.current]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const SaveChangesSection = () => {
|
||||
const [isNull, setIsNull] = useState(timestampRef.current === null);
|
||||
const [isDefault, setIsDefault] = useState(timestampRef.current === defaultValue);
|
||||
|
|
@ -189,7 +217,7 @@ export const DateTimePicker = ({
|
|||
Cancel
|
||||
</ButtonSolid>
|
||||
<ButtonSolid
|
||||
onClick={handleSave}
|
||||
onClick={(e) => handleSave(e)}
|
||||
disabled={timestamp == timestampRef.current ? true : false}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
|
|
@ -283,25 +311,45 @@ export const DateTimePicker = ({
|
|||
}}
|
||||
>
|
||||
<DatePickerComponent
|
||||
className={`input-field form-control validation-without-icon px-2 ${
|
||||
darkMode ? 'bg-dark color-white' : 'bg-light'
|
||||
} ${!isEditCell && 'tjdb-datepicker-wrapper '}`}
|
||||
className={cx('input-field', 'validation-without-icon', 'px-2', {
|
||||
'bg-dark color-white': darkMode,
|
||||
'bg-light': !darkMode,
|
||||
'tjdb-datepicker-wrapper': !isEditCell,
|
||||
'tjdb-datepicker-celledit': isEditCell,
|
||||
'form-control': !isDisabled,
|
||||
'form-control-disabled': isDisabled && !darkMode,
|
||||
'dark-form-control-disabled': isDisabled && darkMode,
|
||||
'null-value-padding': !transformedTimestamp && !isEditCell,
|
||||
'input-value-padding': transformedTimestamp,
|
||||
})}
|
||||
popperPlacement={'bottom-start'}
|
||||
popperClassName={`${!isEditCell && 'tjdb-datepicker-reset'}`}
|
||||
popperClassName={cx({
|
||||
'tjdb-datepicker-reset': !isEditCell,
|
||||
'tjdb-datepicker-celledit-reset': isEditCell,
|
||||
})}
|
||||
onInputClick={() => {
|
||||
setIsOpen(true);
|
||||
}}
|
||||
isClearable={isClearable}
|
||||
value={transformedTimestamp}
|
||||
onClickOutside={() => setIsOpen(false)}
|
||||
placeholderText="DD/MM/YYYY, 12:00pm"
|
||||
placeholderText="dd/mm/yyyy, 12:00am/pm"
|
||||
selected={transformedTimestamp}
|
||||
minDate={minDate}
|
||||
maxDate={maxDate}
|
||||
onChange={(newTimestamp, event) => {
|
||||
if (isEditCell) {
|
||||
handleCellEditChange(newTimestamp);
|
||||
if (event?.type === 'keydown') {
|
||||
setTriggeredKeyPress((prev) => prev + 1);
|
||||
setIsOpen(false);
|
||||
}
|
||||
} else {
|
||||
if (event) {
|
||||
handleDefaultChange(newTimestamp);
|
||||
setIsOpen(false);
|
||||
if (event.type === 'click') {
|
||||
setIsOpen(false);
|
||||
}
|
||||
} else {
|
||||
handleDefaultChange(newTimestamp, true);
|
||||
}
|
||||
|
|
@ -314,10 +362,11 @@ export const DateTimePicker = ({
|
|||
showTimeSelectOnly={enableDate ? false : true}
|
||||
showMonthDropdown
|
||||
showYearDropdown
|
||||
locale="en-GB"
|
||||
fixedHeight
|
||||
dropdownMode="select"
|
||||
customInput={
|
||||
transformedTimestamp ? (
|
||||
transformedTimestamp || isPlaceholderEnabled ? (
|
||||
<input
|
||||
onFocus={'auto'}
|
||||
style={{
|
||||
|
|
@ -326,6 +375,7 @@ export const DateTimePicker = ({
|
|||
alignItems: 'center',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
className={cx({ 'tjdb-datepicker-celledit-input': isEditCell, 'input-error-border': errorMessage })}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
|
|
@ -334,14 +384,22 @@ export const DateTimePicker = ({
|
|||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
border: 'none',
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
className={cx('null-container', {
|
||||
'tjdb-datepicker-celledit-input': isEditCell,
|
||||
'bg-dark color-white': darkMode,
|
||||
'bg-white': !darkMode,
|
||||
'input-error-border': errorMessage,
|
||||
})}
|
||||
tabindex="0"
|
||||
className="null-container"
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
position: 'static',
|
||||
margin: isEditCell ? '8px 0px 8px 0px' : '6px 0px 6px 0px',
|
||||
margin: isEditCell ? '8px 0px 8px 0px' : '5px 0px 5px 0px',
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
className={cx({ 'cell-text-null': isEditCell, 'null-tag': !isEditCell })}
|
||||
>
|
||||
|
|
@ -352,9 +410,22 @@ export const DateTimePicker = ({
|
|||
}
|
||||
timeInputLabel={<div className={`${darkMode && 'theme-dark'}`}>Time</div>}
|
||||
dateFormat={format}
|
||||
{...(isEditCell && { calendarContainer: memoizedCustomCalendarContainer, onKeyDown: handleKeyDown })}
|
||||
timeFormat={timeFormat}
|
||||
{...(isEditCell && { calendarContainer: memoizedCustomCalendarContainer })}
|
||||
{...(!isEditCell && { calendarContainer: memoizedDefaultCalendarContainer })}
|
||||
/>
|
||||
{errorMessage && (
|
||||
<small
|
||||
className="tj-input-error"
|
||||
style={{
|
||||
fontSize: '10px',
|
||||
color: '#DB4324',
|
||||
}}
|
||||
data-cy="app-name-error-label"
|
||||
>
|
||||
{errorMessage}
|
||||
</small>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,10 +32,6 @@
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.datepicker-widget.theme-tjdb {
|
||||
.react-datepicker-wrapper{
|
||||
width:100%;
|
||||
|
|
@ -126,20 +122,26 @@
|
|||
margin-left:20px
|
||||
}
|
||||
|
||||
.react-datepicker-wrapper {
|
||||
position: relative;
|
||||
padding-left: 8px;
|
||||
margin-left:8px;
|
||||
right:16px;
|
||||
|
||||
.tjdb-datepicker-celledit-input{
|
||||
padding:0px !important;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tjdb-datepicker-reset {
|
||||
inset: 0px auto 0px auto;
|
||||
transform: none;
|
||||
margin-top: 0px;
|
||||
margin-left:14px;
|
||||
}
|
||||
|
||||
.tjdb-datepicker-celledit-reset {
|
||||
inset: 0px auto 0px auto;
|
||||
transform: none;
|
||||
margin-top: 0px;
|
||||
margin-left:-10px;
|
||||
}
|
||||
|
||||
|
||||
.tjdb-datepicker-wrapper {
|
||||
width: 101.7%;
|
||||
}
|
||||
|
|
@ -161,3 +163,55 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
.input-error-border {
|
||||
border: 1px solid #DB4324 !important;
|
||||
}
|
||||
|
||||
.form-control-disabled {
|
||||
gap: 16px !important;
|
||||
background: #f4f6fa !important;
|
||||
border: 1px solid var(--slate7) !important;
|
||||
border-radius: 6px !important;
|
||||
margin-bottom: 4px !important;
|
||||
color: var(--slate11) !important;
|
||||
transition: none;
|
||||
width: 100% !important;
|
||||
padding: 7px 9px !important;
|
||||
|
||||
&:hover {
|
||||
background: #F1F3F5 !important;
|
||||
border: 1px solid var(--slate7) !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dark-form-control-disabled {
|
||||
gap: 16px !important;
|
||||
background: var(--base) !important;
|
||||
border: 1px solid var(--slate7) !important;
|
||||
border-radius: 6px !important;
|
||||
margin-bottom: 4px !important;
|
||||
color: var(--slate12) !important;
|
||||
transition: none;
|
||||
width: 100% !important;
|
||||
padding: 7px 9px !important;
|
||||
|
||||
&:hover {
|
||||
background: var(--base) !important;
|
||||
border: 1px solid var(--slate8) !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.null-value-padding {
|
||||
padding: 0 9px !important;
|
||||
}
|
||||
|
||||
.input-value-padding {
|
||||
box-sizing: border-box;
|
||||
padding-right: 30px !important;
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import './styles.scss';
|
|||
import Skeleton from 'react-loading-skeleton';
|
||||
import DateTimePicker from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker';
|
||||
import { getLocalTimeZone, timeZonesWithOffsets } from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/util';
|
||||
import defaultStyles from '@/_ui/Select/styles';
|
||||
|
||||
const ColumnForm = ({
|
||||
onCreate,
|
||||
|
|
@ -302,7 +303,7 @@ const ColumnForm = ({
|
|||
setTimezone(option.value);
|
||||
}}
|
||||
components={{ Option: CustomSelectOption, IndicatorSeparator: () => null }}
|
||||
styles={customStyles}
|
||||
styles={defaultStyles(darkMode, '100%')}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -323,6 +324,7 @@ const ColumnForm = ({
|
|||
setTimestamp={setDefaultValue}
|
||||
timezone={timezone}
|
||||
isClearable={true}
|
||||
isPlaceholderEnabled={true}
|
||||
/>
|
||||
) : !foreignKeyDetails?.length > 0 && !isForeignKey ? (
|
||||
<input
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import React, { useState, useContext, useEffect, useMemo } from 'react';
|
||||
import Select, { components } from 'react-select';
|
||||
import DrawerFooter from '@/_ui/Drawer/DrawerFooter';
|
||||
import defaultStyles from '@/_ui/Select/styles';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { tooljetDatabaseService } from '@/_services';
|
||||
import { TooljetDatabaseContext } from '../index';
|
||||
|
|
@ -156,17 +157,6 @@ const ColumnForm = ({
|
|||
const currentReferencedTableName = targetTable?.value;
|
||||
const currentReferencedColumnName = targetColumn?.value;
|
||||
|
||||
const checkIfButtonShouldBeDisabled = () => {
|
||||
if (isTimestamp) {
|
||||
return ![
|
||||
timezone === columnConfigurations?.timezone,
|
||||
defaultValue === selectedColumn?.column_default,
|
||||
columnName === selectedColumn?.Header,
|
||||
].some((item) => item === false);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handleCreateForeignKeyinEditMode = async () => {
|
||||
const data = [
|
||||
{
|
||||
|
|
@ -579,8 +569,8 @@ const ColumnForm = ({
|
|||
onChange={(option) => {
|
||||
setTimezone(option.value);
|
||||
}}
|
||||
styles={defaultStyles(darkMode, '100%')}
|
||||
components={{ Option: CustomSelectOption, IndicatorSeparator: () => null }}
|
||||
styles={customStyles}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
|
@ -602,6 +592,7 @@ const ColumnForm = ({
|
|||
setTimestamp={setDefaultValue}
|
||||
timezone={timezone}
|
||||
isClearable={true}
|
||||
isPlaceholderEnabled={true}
|
||||
/>
|
||||
) : !isMatchingForeignKeyColumn(selectedColumn?.Header) ? (
|
||||
<input
|
||||
|
|
@ -912,7 +903,6 @@ const ColumnForm = ({
|
|||
shouldDisableCreateBtn={columnName === ''}
|
||||
showToolTipForFkOnReadDocsSection={true}
|
||||
initiator={initiator}
|
||||
isButtonDisabledForTimestamp={checkIfButtonShouldBeDisabled()}
|
||||
/>
|
||||
</div>
|
||||
<ConfirmDialog
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import Information from '@/_ui/Icon/solidIcons/Information';
|
|||
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
||||
import { ToolTip } from '@/_components/ToolTip';
|
||||
import './styles.scss';
|
||||
import cx from 'classnames';
|
||||
// import Maximize from '@/TooljetDatabase/Icons/maximize.svg';
|
||||
// import { Link } from 'react-router-dom';
|
||||
// import { getPrivateRoute } from '@/_helpers/routes';
|
||||
|
|
@ -18,6 +19,7 @@ import ForeignKeyIndicator from '../Icons/ForeignKeyIndicator.svg';
|
|||
import ArrowRight from '../Icons/ArrowRight.svg';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import DateTimePicker from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker';
|
||||
import { getLocalTimeZone, getUTCOffset } from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/util';
|
||||
|
||||
const EditRowForm = ({
|
||||
onEdit,
|
||||
|
|
@ -28,7 +30,8 @@ const EditRowForm = ({
|
|||
initiator,
|
||||
}) => {
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
const { organizationId, selectedTable, columns, foreignKeys } = useContext(TooljetDatabaseContext);
|
||||
const { organizationId, selectedTable, columns, foreignKeys, getConfigurationProperty } =
|
||||
useContext(TooljetDatabaseContext);
|
||||
const inputRefs = useRef({});
|
||||
const [fetching, setFetching] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState(Array.isArray(columns) ? columns.map(() => 'Custom') : []);
|
||||
|
|
@ -96,7 +99,7 @@ const EditRowForm = ({
|
|||
editRowColumns.forEach(({ accessor }) => {
|
||||
if (rowData[accessor] != '') {
|
||||
const inputElement = inputRefs.current[accessor];
|
||||
inputElement?.style?.setProperty('background-color', darkMode ? '#1f2936' : '#FFFFFF', 'important');
|
||||
inputElement?.style?.setProperty('background-color', darkMode ? '#1f2936' : '#FFFFFF');
|
||||
setErrorMap((prev) => {
|
||||
return { ...prev, [accessor]: '' };
|
||||
});
|
||||
|
|
@ -214,7 +217,7 @@ const EditRowForm = ({
|
|||
acc.newErrorMap[accessor] = 'Cannot be empty';
|
||||
|
||||
const inputElement = inputRefs.current?.[accessor];
|
||||
inputElement?.style?.setProperty('background-color', darkMode ? '#1f2936' : '#FFF8F7', 'important');
|
||||
inputElement?.style?.setProperty('background-color', darkMode ? '#1f2936' : '#FFF8F7');
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
|
|
@ -248,7 +251,14 @@ const EditRowForm = ({
|
|||
return { ...prev, [columnName]: 'Value already exists' };
|
||||
});
|
||||
const inputElement = inputRefs.current?.[columnName];
|
||||
inputElement?.style?.setProperty('background-color', darkMode ? '#1f2936' : '#FFF8F7', 'important');
|
||||
inputElement?.style?.setProperty('background-color', darkMode ? '#1f2936' : '#FFF8F7');
|
||||
} else if (error?.code === postgresErrorCode.NotNullViolation) {
|
||||
const columnName = error?.message.split('.')[1];
|
||||
setErrorMap((prev) => {
|
||||
return { ...prev, [columnName]: 'Cannot be Null' };
|
||||
});
|
||||
const inputElement = inputRefs.current?.[columnName];
|
||||
inputElement?.style?.setProperty('background-color', '#FFF8F7');
|
||||
} else if (error?.code === postgresErrorCode.DataTypeMismatch) {
|
||||
const errorMessageSplit = error?.message.split(':');
|
||||
const columnValue = errorMessageSplit[1]?.slice(2, -1);
|
||||
|
|
@ -263,7 +273,7 @@ const EditRowForm = ({
|
|||
return { ...prev, [accessor]: `Data type mismatch` };
|
||||
});
|
||||
const inputElement = inputRefs.current?.[accessor];
|
||||
inputElement?.style?.setProperty('background-color', darkMode ? '#1f2936' : '#FFF8F7', 'important');
|
||||
inputElement?.style?.setProperty('background-color', darkMode ? '#1f2936' : '#FFF8F7');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -397,13 +407,42 @@ const EditRowForm = ({
|
|||
|
||||
case 'timestamp with time zone':
|
||||
return (
|
||||
<div onClick={() => handleTabClick(index, 'Custom', column_default, isNullable, columnName, dataType)}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<DateTimePicker
|
||||
timestamp={inputValues[index]?.value}
|
||||
setTimestamp={(value) => handleInputChange(index, value, columnName)}
|
||||
isOpenOnStart={false}
|
||||
isClearable={activeTab[index] === 'Custom'}
|
||||
isPlaceholderEnabled={activeTab[index] === 'Custom'}
|
||||
errorMessage={errorMap[columnName]}
|
||||
timezone={getConfigurationProperty(columnName, 'timezone', getLocalTimeZone())}
|
||||
isDisabled={inputValues[index]?.disabled || shouldInputBeDisabled}
|
||||
/>
|
||||
{(inputValues[index]?.disabled || shouldInputBeDisabled) && (
|
||||
<div
|
||||
onClick={() =>
|
||||
handleDisabledInputClick(
|
||||
index,
|
||||
'Custom',
|
||||
column_default,
|
||||
isNullable,
|
||||
columnName,
|
||||
dataType,
|
||||
currentValue[columnName]
|
||||
)
|
||||
}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: 4,
|
||||
cursor: 'pointer',
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
@ -495,12 +534,41 @@ const EditRowForm = ({
|
|||
<span style={{ width: '24px' }}>
|
||||
{renderDatatypeIcon(isSerialDataTypeColumn ? 'serial' : dataType)}
|
||||
</span>
|
||||
<span style={{ marginRight: '5px' }}>{headerText}</span>
|
||||
<ToolTip
|
||||
message={<span>{headerText}</span>}
|
||||
show={dataType === 'timestamp with time zone' && headerText.length >= 20}
|
||||
placement="top"
|
||||
tooltipClassName="tjdb-table-tooltip"
|
||||
>
|
||||
<span
|
||||
style={{ marginRight: '5px' }}
|
||||
className={cx({
|
||||
'header-label-timestamp': dataType === 'timestamp with time zone',
|
||||
})}
|
||||
>
|
||||
{headerText}
|
||||
</span>
|
||||
</ToolTip>
|
||||
{constraints_type?.is_primary_key === true && (
|
||||
<span style={{ marginRight: '3px' }}>
|
||||
<SolidIcon name="primarykey" />
|
||||
</span>
|
||||
)}
|
||||
{dataType === 'timestamp with time zone' && (
|
||||
<div
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: '2px',
|
||||
marginTop: '1px',
|
||||
}}
|
||||
>
|
||||
<span className="tjdb-display-time-pill">{`UTC ${getUTCOffset(
|
||||
getConfigurationProperty(accessor, 'timezone', getLocalTimeZone())
|
||||
)}`}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ToolTip
|
||||
message={
|
||||
isMatchingForeignKeyColumn(Header) ? (
|
||||
|
|
|
|||
|
|
@ -11,11 +11,10 @@ import Information from '@/_ui/Icon/solidIcons/Information';
|
|||
import ForeignKeyIndicator from '../Icons/ForeignKeyIndicator.svg';
|
||||
import ArrowRight from '../Icons/ArrowRight.svg';
|
||||
import cx from 'classnames';
|
||||
|
||||
import './styles.scss';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import DateTimePicker from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/DateTimePicker';
|
||||
import { getLocalTimeZone } from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/util';
|
||||
import { getLocalTimeZone, getUTCOffset } from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/util';
|
||||
|
||||
const RowForm = ({
|
||||
onCreate,
|
||||
|
|
@ -56,6 +55,9 @@ const RowForm = ({
|
|||
const inputValuesDefaultValues = () => {
|
||||
return Array.isArray(rowColumns)
|
||||
? rowColumns.map((item, _index) => {
|
||||
if (item.dataType === 'timestamp with time zone' && !item.column_default) {
|
||||
return { value: new Date().toISOString(), checkboxValue: false, disabled: false, label: '' };
|
||||
}
|
||||
if (item.accessor === 'id') {
|
||||
return { value: '', checkboxValue: false, disabled: false, label: '' };
|
||||
}
|
||||
|
|
@ -119,8 +121,18 @@ const RowForm = ({
|
|||
return matchingColumn;
|
||||
}
|
||||
|
||||
const handleDisabledInputClick = (index, tabData, defaultValue, nullValue, columnName, dataType, currentValue) => {
|
||||
handleTabClick(index, tabData, defaultValue, nullValue, columnName, dataType, currentValue);
|
||||
if (inputRefs.current[columnName]) {
|
||||
setTimeout(() => {
|
||||
inputRefs.current[columnName].focus();
|
||||
}, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const handleTabClick = (index, tabData, defaultValue, nullValue, columnName, dataType) => {
|
||||
const newActiveTabs = [...activeTab];
|
||||
const oldActiveTab = [...activeTab];
|
||||
newActiveTabs[index] = tabData;
|
||||
setActiveTab(newActiveTabs);
|
||||
const newInputValues = [...inputValues];
|
||||
|
|
@ -141,6 +153,7 @@ const RowForm = ({
|
|||
} else if (tabData === 'Custom' && dataType === 'character varying') {
|
||||
newInputValues[index] = { value: '', checkboxValue: false, disabled: false, label: '' };
|
||||
} else if (tabData === 'Custom' && dataType === 'timestamp with time zone') {
|
||||
if (oldActiveTab[index] === 'Custom') return;
|
||||
newInputValues[index] = { value: new Date().toISOString(), checkboxValue: false, disabled: false, label: '' };
|
||||
} else {
|
||||
newInputValues[index] = { value: '', checkboxValue: false, disabled: false, label: '' };
|
||||
|
|
@ -204,6 +217,11 @@ const RowForm = ({
|
|||
return result;
|
||||
}
|
||||
|
||||
if (column.dataType === 'timestamp with time zone') {
|
||||
result[column.accessor] = column_default ? column_default : new Date().toISOString();
|
||||
return result;
|
||||
}
|
||||
|
||||
result[column.accessor] = column_default ? column_default : '';
|
||||
return result;
|
||||
}, {});
|
||||
|
|
@ -263,6 +281,13 @@ const RowForm = ({
|
|||
});
|
||||
const inputElement = inputRefs.current?.[columnName];
|
||||
inputElement?.style?.setProperty('background-color', '#FFF8F7');
|
||||
} else if (error?.code === postgresErrorCode.NotNullViolation) {
|
||||
const columnName = error?.message.split('.')[1];
|
||||
setErrorMap((prev) => {
|
||||
return { ...prev, [columnName]: 'Cannot be Null' };
|
||||
});
|
||||
const inputElement = inputRefs.current?.[columnName];
|
||||
inputElement?.style?.setProperty('background-color', '#FFF8F7');
|
||||
} else if (error?.message.includes('Invalid input syntax for type')) {
|
||||
const errorMessageSplit = error?.message.split(':');
|
||||
const columnValue = errorMessageSplit[1]?.slice(2, -1);
|
||||
|
|
@ -368,6 +393,31 @@ const RowForm = ({
|
|||
ref={(el) => (inputRefs.current[columnName] = el)}
|
||||
/>
|
||||
)}
|
||||
{inputValues[index]?.disabled && (
|
||||
<div
|
||||
onClick={() =>
|
||||
handleDisabledInputClick(
|
||||
index,
|
||||
'Custom',
|
||||
inputValues[index]?.column_default,
|
||||
isNullable,
|
||||
columnName,
|
||||
dataType,
|
||||
inputValues[index]?.value
|
||||
)
|
||||
}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: 1,
|
||||
cursor: 'pointer',
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{inputValues[index].value === null && (
|
||||
<p className={darkMode === true ? 'null-tag-dark' : 'null-tag'}>Null</p>
|
||||
)}
|
||||
|
|
@ -405,14 +455,42 @@ const RowForm = ({
|
|||
|
||||
case 'timestamp with time zone':
|
||||
return (
|
||||
<div onClick={() => handleTabClick(index, 'Custom', defaultValue, isNullable, columnName, dataType)}>
|
||||
<div style={{ position: 'relative' }}>
|
||||
<DateTimePicker
|
||||
timestamp={inputValues[index]?.value}
|
||||
setTimestamp={(value) => handleInputChange(index, value, columnName)}
|
||||
isOpenOnStart={false}
|
||||
timezone={getConfigurationProperty(columnName, 'timezone', getLocalTimeZone())}
|
||||
isClearable={activeTab[index] === 'Custom'}
|
||||
isPlaceholderEnabled={activeTab[index] === 'Custom'}
|
||||
errorMessage={errorMap[columnName]}
|
||||
isDisabled={inputValues[index]?.disabled}
|
||||
/>
|
||||
{inputValues[index]?.disabled && (
|
||||
<div
|
||||
onClick={() =>
|
||||
handleDisabledInputClick(
|
||||
index,
|
||||
'Custom',
|
||||
inputValues[index]?.column_default,
|
||||
isNullable,
|
||||
columnName,
|
||||
dataType,
|
||||
inputValues[index]?.value
|
||||
)
|
||||
}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: 1,
|
||||
cursor: 'pointer',
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
|
|
@ -456,12 +534,40 @@ const RowForm = ({
|
|||
<span style={{ width: '24px' }}>
|
||||
{renderDatatypeIcon(isSerialDataTypeColumn ? 'serial' : dataType)}
|
||||
</span>
|
||||
<span style={{ marginRight: '5px' }}>{headerText}</span>
|
||||
<ToolTip
|
||||
message={<span>{headerText}</span>}
|
||||
show={dataType === 'timestamp with time zone' && headerText.length >= 20}
|
||||
placement="top"
|
||||
tooltipClassName="tjdb-table-tooltip"
|
||||
>
|
||||
<span
|
||||
style={{ marginRight: '5px' }}
|
||||
className={cx({
|
||||
'header-label-timestamp': dataType === 'timestamp with time zone',
|
||||
})}
|
||||
>
|
||||
{headerText}
|
||||
</span>
|
||||
</ToolTip>
|
||||
{constraints_type?.is_primary_key === true && (
|
||||
<span style={{ marginRight: '3px' }}>
|
||||
<SolidIcon name="primarykey" />
|
||||
</span>
|
||||
)}
|
||||
{dataType === 'timestamp with time zone' && (
|
||||
<div
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
marginLeft: '2px',
|
||||
marginTop: '1px',
|
||||
}}
|
||||
>
|
||||
<span className="tjdb-display-time-pill">{`UTC ${getUTCOffset(
|
||||
getConfigurationProperty(accessor, 'timezone', getLocalTimeZone())
|
||||
)}`}</span>
|
||||
</div>
|
||||
)}
|
||||
<ToolTip
|
||||
message={
|
||||
isMatchingForeignKeyColumn(Header) ? (
|
||||
|
|
|
|||
|
|
@ -77,7 +77,6 @@ function TableSchema({
|
|||
});
|
||||
return dict;
|
||||
}, []);
|
||||
|
||||
const CustomSelectOption = (props) => {
|
||||
return (
|
||||
<Option {...props}>
|
||||
|
|
@ -224,11 +223,20 @@ function TableSchema({
|
|||
</ToolTip>
|
||||
|
||||
<ToolTip
|
||||
message="Primary key data type cannot be modified"
|
||||
message={
|
||||
columnDetails[index]?.constraints_type?.is_primary_key === true
|
||||
? 'Primary key data type cannot be modified'
|
||||
: columnDetails[index]?.data_type === 'timestamp with time zone'
|
||||
? 'Date with time'
|
||||
: null
|
||||
}
|
||||
placement="top"
|
||||
tooltipClassName="tootip-table"
|
||||
style={getToolTipPlacementStyle(index, isEditMode, columnDetails)}
|
||||
show={isEditMode && columnDetails[index]?.constraints_type?.is_primary_key === true ? true : false}
|
||||
show={
|
||||
(isEditMode && columnDetails[index]?.constraints_type?.is_primary_key === true ? true : false) ||
|
||||
columnDetails[index]?.data_type === 'timestamp with time zone'
|
||||
}
|
||||
>
|
||||
<div className="p-0 datatype-dropdown" data-cy="type-dropdown-field">
|
||||
<Select
|
||||
|
|
@ -401,6 +409,8 @@ function TableSchema({
|
|||
}}
|
||||
isOpenOnStart={columnDetails[index]?.isOpenOnStart}
|
||||
isClearable={true}
|
||||
isPlaceholderEnabled={true}
|
||||
// format="dd/MM/yyyy"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@
|
|||
padding: 20px;
|
||||
}
|
||||
|
||||
.header-label-timestamp {
|
||||
max-width: 180px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.drawer-card-wrapper {
|
||||
|
||||
.tj-base-btn {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import Select, { components } from 'react-select';
|
|||
import { formatOptionLabel } from '@/TooljetDatabase/constants';
|
||||
import { getLocalTimeZone } from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/util';
|
||||
import './styles.scss';
|
||||
import defaultStyles from '@/_ui/Select/styles';
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
export const UniqueConstraintPopOver = ({
|
||||
disabled,
|
||||
|
|
@ -77,6 +78,33 @@ export const UniqueConstraintPopOver = ({
|
|||
<Select
|
||||
placeholder="Select Timezone"
|
||||
value={tzDictionary[columns[index].configurations.timezone]}
|
||||
styles={{
|
||||
control: (provided, state) => ({
|
||||
...provided,
|
||||
backgroundColor: darkMode ? '#2b3547' : state.menuIsOpen ? '#F1F3F5' : '#fff',
|
||||
}),
|
||||
singleValue: (provided) => ({
|
||||
...provided,
|
||||
color: darkMode ? '#fff' : '#232e3c',
|
||||
}),
|
||||
input: (provided) => ({
|
||||
...provided,
|
||||
color: darkMode ? '#fff' : '#232e3c',
|
||||
}),
|
||||
menu: (provided) => ({
|
||||
...provided,
|
||||
backgroundColor: darkMode ? 'rgb(31,40,55)' : 'white',
|
||||
}),
|
||||
option: (provided) => ({
|
||||
...provided,
|
||||
backgroundColor: darkMode ? '#2b3547' : '#fff',
|
||||
color: darkMode ? '#fff' : '#232e3c',
|
||||
cursor: 'pointer',
|
||||
':hover': {
|
||||
backgroundColor: darkMode ? '#323C4B' : '#d8dce9',
|
||||
},
|
||||
}),
|
||||
}}
|
||||
formatOptionLabel={formatOptionLabel}
|
||||
options={tzOptions}
|
||||
onChange={(option) => {
|
||||
|
|
|
|||
|
|
@ -166,7 +166,7 @@ const Header = ({
|
|||
disabled={false}
|
||||
onClick={() => toggleAddNewDataMenu(true)}
|
||||
size="sm"
|
||||
className="px-1 pe-3 ps-2 gap-0"
|
||||
className="px-1 pe-3 ps-2 gap-1"
|
||||
>
|
||||
<Plus fill="#697177" style={{ height: '16px' }} />
|
||||
Add new data
|
||||
|
|
|
|||
|
|
@ -587,7 +587,7 @@ const Table = ({ collapseSidebar }) => {
|
|||
cellValue === null ? setNullValue(true) : setNullValue(false);
|
||||
setDefaultValue(isCellValueDefault);
|
||||
setEditPopover(true);
|
||||
document.getElementById('edit-input-blur').focus();
|
||||
document?.getElementById('edit-input-blur').focus();
|
||||
} else if (e.key === 'Backspace' && !editPopover && shouldOpenCellEditMenu(selectedCellRef.current.columnIndex)) {
|
||||
const cellValue = rows[selectedCellRef.current.rowIndex].cells[selectedCellRef.current.columnIndex]?.value;
|
||||
const cellDataType =
|
||||
|
|
@ -616,7 +616,8 @@ const Table = ({ collapseSidebar }) => {
|
|||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!editPopover) {
|
||||
const selectedDatatype = headerGroups[0]?.headers?.[selectedCellRef.current.columnIndex]?.dataType;
|
||||
if (!editPopover && selectedDatatype !== 'timestamp with time zone') {
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
}
|
||||
return () => {
|
||||
|
|
@ -1630,21 +1631,22 @@ const Table = ({ collapseSidebar }) => {
|
|||
</>
|
||||
);
|
||||
})}
|
||||
{rows.length > 0 && (
|
||||
<div
|
||||
onClick={() => {
|
||||
resetCellAndRowSelection();
|
||||
setIsCreateRowDrawerOpen(true);
|
||||
}}
|
||||
className={darkMode ? 'add-icon-row-dark' : 'add-icon-row'}
|
||||
style={{
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
+
|
||||
</div>
|
||||
)}
|
||||
<div />
|
||||
</tbody>
|
||||
{rows.length > 0 && (
|
||||
<div
|
||||
onClick={() => {
|
||||
resetCellAndRowSelection();
|
||||
setIsCreateRowDrawerOpen(true);
|
||||
}}
|
||||
className={darkMode ? 'add-icon-row-dark' : 'add-icon-row'}
|
||||
style={{
|
||||
zIndex: 3,
|
||||
}}
|
||||
>
|
||||
+
|
||||
</div>
|
||||
)}
|
||||
</table>
|
||||
)}
|
||||
{rows.length === 0 && !loadingState && (
|
||||
|
|
|
|||
|
|
@ -15,11 +15,18 @@
|
|||
// margin-right: 6px !important;
|
||||
// }
|
||||
|
||||
.tj-database-column-header:hover{
|
||||
.primaryKeyTooltip{
|
||||
width: 88% !important;
|
||||
}
|
||||
}
|
||||
|
||||
.primaryKeyTooltip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
|
||||
.table-header-container {
|
||||
white-space: nowrap;
|
||||
|
|
|
|||
|
|
@ -264,7 +264,8 @@ export default function tjdbDropdownStyles(
|
|||
backgroundColor:
|
||||
state.isSelected && !darkMode ? '#F0F4FF' : state.isSelected && darkMode ? '#323C4B' : 'transparent',
|
||||
':hover': {
|
||||
backgroundColor: state.isFocused && !darkMode ? '#F0F4FF' : '#323C4B',
|
||||
backgroundColor:
|
||||
state.isFocused && !darkMode ? '#F0F4FF' : state.isFocused && darkMode ? '#323C4B' : 'transparent',
|
||||
},
|
||||
color: darkMode ? '#fff' : '#232e3c',
|
||||
cursor: 'pointer',
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ function DrawerFooter({
|
|||
showToolTipForFkOnReadDocsSection = false,
|
||||
foreignKeyDetails = [],
|
||||
initiator,
|
||||
isButtonDisabledForTimestamp = false,
|
||||
}) {
|
||||
useEffect(() => {
|
||||
const keyboardShortcutStore = useKeyboardShortcutStore.getState();
|
||||
|
|
@ -222,7 +221,7 @@ function DrawerFooter({
|
|||
<>
|
||||
{isEditMode && (
|
||||
<ButtonSolid
|
||||
disabled={shouldDisableCreateBtn || fetching || isButtonDisabledForTimestamp}
|
||||
disabled={shouldDisableCreateBtn || fetching}
|
||||
data-cy={`save-changes-button`}
|
||||
onClick={onEdit}
|
||||
fill="#fff"
|
||||
|
|
|
|||
|
|
@ -210,7 +210,8 @@ export class PostgrestTableColumnDto {
|
|||
@IsOptional()
|
||||
constraints_type: ConstraintTypeDto;
|
||||
|
||||
configurations: any;
|
||||
@IsOptional()
|
||||
configurations: Record<string, any>;
|
||||
}
|
||||
|
||||
export class EditTableColumnsDto {
|
||||
|
|
|
|||
|
|
@ -26,10 +26,16 @@ export class TooljetDbImportExportService {
|
|||
|
||||
if (!internalTable) throw new NotFoundException('Tooljet database table not found');
|
||||
|
||||
const { configurations = {} } = internalTable;
|
||||
const { columns, foreign_keys } = await this.tooljetDbService.perform(organizationId, 'view_table', {
|
||||
id: tjDbDto.table_id,
|
||||
});
|
||||
|
||||
columns.forEach((column) => {
|
||||
const columnUuid = configurations?.columns?.column_names?.[column.column_name];
|
||||
column.configurations = configurations?.columns?.configurations?.[columnUuid];
|
||||
});
|
||||
|
||||
return {
|
||||
id: internalTable.id,
|
||||
table_name: internalTable.tableName,
|
||||
|
|
|
|||
Loading…
Reference in a new issue