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:
Shaurya Sharma 2024-07-09 12:25:06 +05:30 committed by GitHub
parent c63abeb168
commit 4f1beef160
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 429 additions and 78 deletions

View file

@ -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>
);
};

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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) ? (

View file

@ -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) ? (

View file

@ -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>
) : (

View file

@ -9,6 +9,12 @@
padding: 20px;
}
.header-label-timestamp {
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
}
.drawer-card-wrapper {
.tj-base-btn {

View file

@ -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) => {

View file

@ -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

View file

@ -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 && (

View file

@ -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;

View file

@ -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',

View file

@ -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"

View file

@ -210,7 +210,8 @@ export class PostgrestTableColumnDto {
@IsOptional()
constraints_type: ConstraintTypeDto;
configurations: any;
@IsOptional()
configurations: Record<string, any>;
}
export class EditTableColumnsDto {

View file

@ -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,