Compare commits
6 Commits
@nhost/das
...
feat/add-e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f539713e10 | ||
|
|
9b150c786e | ||
|
|
3d917b6e12 | ||
|
|
7574504cd5 | ||
|
|
be9f6b6967 | ||
|
|
affe9db42a |
5
.changeset/dry-kids-chew.md
Normal file
5
.changeset/dry-kids-chew.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
'@nhost/dashboard': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: add empty string as default value for text in databases
|
||||||
@@ -44,7 +44,10 @@ export default function BaseRecordForm({
|
|||||||
(accumulator, column) => {
|
(accumulator, column) => {
|
||||||
if (
|
if (
|
||||||
column.isPrimary ||
|
column.isPrimary ||
|
||||||
(!column.isNullable && !column.defaultValue && !column.isIdentity)
|
(!column.isNullable &&
|
||||||
|
!column.defaultValue &&
|
||||||
|
!column.isDefaultValueCustom &&
|
||||||
|
!column.isIdentity)
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
...accumulator,
|
...accumulator,
|
||||||
@@ -96,7 +99,12 @@ export default function BaseRecordForm({
|
|||||||
const gridColumn = gridColumnMap.get(columnId);
|
const gridColumn = gridColumnMap.get(columnId);
|
||||||
const value = columnValues[columnId];
|
const value = columnValues[columnId];
|
||||||
|
|
||||||
if (!value && (gridColumn?.defaultValue || gridColumn?.isIdentity)) {
|
if (
|
||||||
|
!value &&
|
||||||
|
(gridColumn?.defaultValue ||
|
||||||
|
gridColumn?.defaultValue === '' ||
|
||||||
|
gridColumn?.isIdentity)
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
...options,
|
...options,
|
||||||
[columnId]: {
|
[columnId]: {
|
||||||
@@ -147,7 +155,7 @@ export default function BaseRecordForm({
|
|||||||
{optionalColumns.length > 0 && (
|
{optionalColumns.length > 0 && (
|
||||||
<DatabaseRecordInputGroup
|
<DatabaseRecordInputGroup
|
||||||
title="Optional columns"
|
title="Optional columns"
|
||||||
description="These columns are nullable and don't require a value."
|
description="These columns are nullable or have a default value."
|
||||||
columns={optionalColumns}
|
columns={optionalColumns}
|
||||||
autoFocusFirstInput={requiredColumns.length === 0}
|
autoFocusFirstInput={requiredColumns.length === 0}
|
||||||
sx={{ borderTopWidth: requiredColumns.length > 0 ? 1 : 0 }}
|
sx={{ borderTopWidth: requiredColumns.length > 0 ? 1 : 0 }}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
} from '@/features/orgs/projects/database/dataGrid/utils/postgresqlConstants';
|
} from '@/features/orgs/projects/database/dataGrid/utils/postgresqlConstants';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import type { PropsWithoutRef } from 'react';
|
import type { PropsWithoutRef } from 'react';
|
||||||
import { memo, useEffect, useState } from 'react';
|
import { memo, useEffect, useMemo, useState } from 'react';
|
||||||
import type { FieldError } from 'react-hook-form';
|
import type { FieldError } from 'react-hook-form';
|
||||||
import { useFormContext, useFormState, useWatch } from 'react-hook-form';
|
import { useFormContext, useFormState, useWatch } from 'react-hook-form';
|
||||||
|
|
||||||
@@ -150,6 +150,13 @@ function DefaultValueAutocomplete({ index }: FieldArrayInputProps) {
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const formattedInputValue = useMemo(() => {
|
||||||
|
if (defaultValue?.value === '') {
|
||||||
|
return "''::text";
|
||||||
|
}
|
||||||
|
return isIdentity ? '' : inputValue;
|
||||||
|
}, [isIdentity, defaultValue?.value, inputValue]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!defaultValue) {
|
if (!defaultValue) {
|
||||||
setInputValue('');
|
setInputValue('');
|
||||||
@@ -176,7 +183,7 @@ function DefaultValueAutocomplete({ index }: FieldArrayInputProps) {
|
|||||||
placeholder="NULL"
|
placeholder="NULL"
|
||||||
error={Boolean(errors?.columns?.[index]?.defaultValue)}
|
error={Boolean(errors?.columns?.[index]?.defaultValue)}
|
||||||
helperText={errors?.columns?.[index]?.defaultValue?.message}
|
helperText={errors?.columns?.[index]?.defaultValue?.message}
|
||||||
inputValue={isIdentity ? '' : inputValue}
|
inputValue={formattedInputValue}
|
||||||
onInputChange={(_event, value) => setInputValue(value)}
|
onInputChange={(_event, value) => setInputValue(value)}
|
||||||
onBlur={(event) => {
|
onBlur={(event) => {
|
||||||
if (event.target instanceof HTMLInputElement && !event.target.value) {
|
if (event.target instanceof HTMLInputElement && !event.target.value) {
|
||||||
|
|||||||
@@ -53,10 +53,10 @@ export default function CreateRecordForm({
|
|||||||
return (
|
return (
|
||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
{error && error instanceof Error && (
|
{error && error instanceof Error && (
|
||||||
<div className="-mt-3 mb-4 px-6">
|
<div className="px-6 mb-4 -mt-3">
|
||||||
<Alert
|
<Alert
|
||||||
severity="error"
|
severity="error"
|
||||||
className="grid grid-flow-col items-center justify-between px-4 py-3"
|
className="grid items-center justify-between grid-flow-col px-4 py-3"
|
||||||
>
|
>
|
||||||
<span className="text-left">
|
<span className="text-left">
|
||||||
<strong>Error:</strong> {error.message}
|
<strong>Error:</strong> {error.message}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ export interface DatabaseRecordInputGroupProps extends BoxProps {
|
|||||||
|
|
||||||
function getPlaceholder(
|
function getPlaceholder(
|
||||||
defaultValue?: string,
|
defaultValue?: string,
|
||||||
|
isDefaultValueCustom?: boolean,
|
||||||
isIdentity?: boolean,
|
isIdentity?: boolean,
|
||||||
isNullable?: boolean,
|
isNullable?: boolean,
|
||||||
) {
|
) {
|
||||||
@@ -45,6 +46,10 @@ function getPlaceholder(
|
|||||||
return 'NULL';
|
return 'NULL';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (defaultValue === '' && isDefaultValueCustom) {
|
||||||
|
return `Automatically generated value: ''`;
|
||||||
|
}
|
||||||
|
|
||||||
if (!defaultValue) {
|
if (!defaultValue) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -102,6 +107,7 @@ export default function DatabaseRecordInputGroup({
|
|||||||
specificType,
|
specificType,
|
||||||
maxLength,
|
maxLength,
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
isDefaultValueCustom,
|
||||||
isPrimary,
|
isPrimary,
|
||||||
isNullable,
|
isNullable,
|
||||||
isIdentity,
|
isIdentity,
|
||||||
@@ -118,6 +124,7 @@ export default function DatabaseRecordInputGroup({
|
|||||||
|
|
||||||
const placeholder = getPlaceholder(
|
const placeholder = getPlaceholder(
|
||||||
defaultValue,
|
defaultValue,
|
||||||
|
isDefaultValueCustom,
|
||||||
isIdentity,
|
isIdentity,
|
||||||
isNullable,
|
isNullable,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ export const dateFunctions = [
|
|||||||
* Relevant functions for PostgreSQL types.
|
* Relevant functions for PostgreSQL types.
|
||||||
*/
|
*/
|
||||||
export const postgresFunctions = {
|
export const postgresFunctions = {
|
||||||
text: ['version()', 'timeofday()'],
|
text: ['version()', 'timeofday()', "''::text"],
|
||||||
json: ['json_build_object()', 'json_build_array()'],
|
json: ['json_build_object()', 'json_build_array()'],
|
||||||
jsonb: ['jsonb_build_object()', 'jsonb_build_array()'],
|
jsonb: ['jsonb_build_object()', 'jsonb_build_array()'],
|
||||||
date: dateFunctions,
|
date: dateFunctions,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Box } from '@/components/ui/v2/Box';
|
||||||
import { Button } from '@/components/ui/v2/Button';
|
import { Button } from '@/components/ui/v2/Button';
|
||||||
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
|
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
|
||||||
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
import { Input, inputClasses } from '@/components/ui/v2/Input';
|
||||||
@@ -57,6 +58,14 @@ export default function DataGridTextCell<TData extends object>({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleSetToNull() {
|
||||||
|
if (onSave) {
|
||||||
|
await onSave(null);
|
||||||
|
// Unfocus the cell
|
||||||
|
cancelEditCell();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleInputKeyDown(event: KeyboardEvent<HTMLInputElement>) {
|
async function handleInputKeyDown(event: KeyboardEvent<HTMLInputElement>) {
|
||||||
if (
|
if (
|
||||||
event.key === 'ArrowLeft' ||
|
event.key === 'ArrowLeft' ||
|
||||||
@@ -122,36 +131,58 @@ export default function DataGridTextCell<TData extends object>({
|
|||||||
|
|
||||||
if (isEditing && isMultiline) {
|
if (isEditing && isMultiline) {
|
||||||
return (
|
return (
|
||||||
<Input
|
<Box
|
||||||
multiline
|
className="absolute top-0 z-10 -mx-0.5 h-full min-h-36 w-full"
|
||||||
ref={inputRef as Ref<HTMLInputElement>}
|
|
||||||
value={(normalizedTemporaryValue || '').replace(/\\n/gi, `\n`)}
|
|
||||||
onChange={handleChange}
|
|
||||||
onKeyDown={handleTextAreaKeyDown}
|
|
||||||
fullWidth
|
|
||||||
className="absolute top-0 z-10 -mx-0.5 h-full min-h-38"
|
|
||||||
rows={5}
|
|
||||||
sx={{
|
sx={{
|
||||||
[`&.${inputClasses.focused}`]: {
|
backgroundColor: (theme) =>
|
||||||
boxShadow: `inset 0 0 0 1.5px rgba(0, 82, 205, 1)`,
|
theme.palette.mode === 'dark'
|
||||||
borderColor: 'transparent !important',
|
? `${theme.palette.secondary[100]} !important`
|
||||||
borderRadius: 0,
|
: `${theme.palette.common.white} !important`,
|
||||||
backgroundColor: (theme) =>
|
|
||||||
theme.palette.mode === 'dark'
|
|
||||||
? `${theme.palette.secondary[100]} !important`
|
|
||||||
: `${theme.palette.common.white} !important`,
|
|
||||||
},
|
|
||||||
[`& .${inputClasses.input}`]: {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
slotProps={{
|
>
|
||||||
inputRoot: {
|
<Input
|
||||||
className:
|
multiline
|
||||||
'resize-none outline-none focus:outline-none !text-xs focus:ring-0',
|
ref={inputRef as Ref<HTMLInputElement>}
|
||||||
},
|
value={(normalizedTemporaryValue || '').replace(/\\n/gi, `\n`)}
|
||||||
}}
|
onChange={handleChange}
|
||||||
/>
|
onKeyDown={handleTextAreaKeyDown}
|
||||||
|
fullWidth
|
||||||
|
autoFocus
|
||||||
|
className="z-10"
|
||||||
|
rows={5}
|
||||||
|
sx={{
|
||||||
|
[`&.${inputClasses.focused}`]: {
|
||||||
|
boxShadow: `inset 0 0 0 1.5px rgba(0, 82, 205, 1)`,
|
||||||
|
borderColor: 'transparent !important',
|
||||||
|
borderRadius: 0,
|
||||||
|
backgroundColor: (theme) =>
|
||||||
|
theme.palette.mode === 'dark'
|
||||||
|
? `${theme.palette.secondary[100]} !important`
|
||||||
|
: `${theme.palette.common.white} !important`,
|
||||||
|
},
|
||||||
|
[`& .${inputClasses.input}`]: {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
slotProps={{
|
||||||
|
inputRoot: {
|
||||||
|
className:
|
||||||
|
'resize-none outline-none focus:outline-none !text-xs focus:ring-0',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="my-0 flex flex-1 items-center justify-end p-2">
|
||||||
|
<Button
|
||||||
|
className="z-10"
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
color="secondary"
|
||||||
|
onClick={handleSetToNull}
|
||||||
|
>
|
||||||
|
Set to NULL
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,9 +205,6 @@ export default function DataGridTextCell<TData extends object>({
|
|||||||
? `${theme.palette.secondary[100]} !important`
|
? `${theme.palette.secondary[100]} !important`
|
||||||
: `${theme.palette.common.white} !important`,
|
: `${theme.palette.common.white} !important`,
|
||||||
},
|
},
|
||||||
[`& .${inputClasses.input}`]: {
|
|
||||||
backgroundColor: 'transparent',
|
|
||||||
},
|
|
||||||
}}
|
}}
|
||||||
slotProps={{
|
slotProps={{
|
||||||
inputWrapper: { className: 'h-full' },
|
inputWrapper: { className: 'h-full' },
|
||||||
|
|||||||
Reference in New Issue
Block a user