Compare commits

...

6 Commits

Author SHA1 Message Date
David B.M.
f539713e10 feat: add set to null edit button 2025-03-16 23:46:07 +01:00
David B.M.
9b150c786e fix: correct types when there is no actual default value 2025-03-16 23:46:07 +01:00
David B.M.
3d917b6e12 chore: asd 2025-03-16 23:46:07 +01:00
David B.M.
7574504cd5 chore: add changeset 2025-03-16 23:46:07 +01:00
David B.M.
be9f6b6967 feat: create rows with default empty string value 2025-03-16 23:46:07 +01:00
David B.M.
affe9db42a feat: add empty string default in table creation 2025-03-16 23:46:07 +01:00
7 changed files with 94 additions and 39 deletions

View File

@@ -0,0 +1,5 @@
---
'@nhost/dashboard': minor
---
feat: add empty string as default value for text in databases

View File

@@ -44,7 +44,10 @@ export default function BaseRecordForm({
(accumulator, column) => {
if (
column.isPrimary ||
(!column.isNullable && !column.defaultValue && !column.isIdentity)
(!column.isNullable &&
!column.defaultValue &&
!column.isDefaultValueCustom &&
!column.isIdentity)
) {
return {
...accumulator,
@@ -96,7 +99,12 @@ export default function BaseRecordForm({
const gridColumn = gridColumnMap.get(columnId);
const value = columnValues[columnId];
if (!value && (gridColumn?.defaultValue || gridColumn?.isIdentity)) {
if (
!value &&
(gridColumn?.defaultValue ||
gridColumn?.defaultValue === '' ||
gridColumn?.isIdentity)
) {
return {
...options,
[columnId]: {
@@ -147,7 +155,7 @@ export default function BaseRecordForm({
{optionalColumns.length > 0 && (
<DatabaseRecordInputGroup
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}
autoFocusFirstInput={requiredColumns.length === 0}
sx={{ borderTopWidth: requiredColumns.length > 0 ? 1 : 0 }}

View File

@@ -18,7 +18,7 @@ import {
} from '@/features/orgs/projects/database/dataGrid/utils/postgresqlConstants';
import clsx from 'clsx';
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 { 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(() => {
if (!defaultValue) {
setInputValue('');
@@ -176,7 +183,7 @@ function DefaultValueAutocomplete({ index }: FieldArrayInputProps) {
placeholder="NULL"
error={Boolean(errors?.columns?.[index]?.defaultValue)}
helperText={errors?.columns?.[index]?.defaultValue?.message}
inputValue={isIdentity ? '' : inputValue}
inputValue={formattedInputValue}
onInputChange={(_event, value) => setInputValue(value)}
onBlur={(event) => {
if (event.target instanceof HTMLInputElement && !event.target.value) {

View File

@@ -53,10 +53,10 @@ export default function CreateRecordForm({
return (
<FormProvider {...form}>
{error && error instanceof Error && (
<div className="-mt-3 mb-4 px-6">
<div className="px-6 mb-4 -mt-3">
<Alert
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">
<strong>Error:</strong> {error.message}

View File

@@ -34,6 +34,7 @@ export interface DatabaseRecordInputGroupProps extends BoxProps {
function getPlaceholder(
defaultValue?: string,
isDefaultValueCustom?: boolean,
isIdentity?: boolean,
isNullable?: boolean,
) {
@@ -45,6 +46,10 @@ function getPlaceholder(
return 'NULL';
}
if (defaultValue === '' && isDefaultValueCustom) {
return `Automatically generated value: ''`;
}
if (!defaultValue) {
return '';
}
@@ -102,6 +107,7 @@ export default function DatabaseRecordInputGroup({
specificType,
maxLength,
defaultValue,
isDefaultValueCustom,
isPrimary,
isNullable,
isIdentity,
@@ -118,6 +124,7 @@ export default function DatabaseRecordInputGroup({
const placeholder = getPlaceholder(
defaultValue,
isDefaultValueCustom,
isIdentity,
isNullable,
);

View File

@@ -186,7 +186,7 @@ export const dateFunctions = [
* Relevant functions for PostgreSQL types.
*/
export const postgresFunctions = {
text: ['version()', 'timeofday()'],
text: ['version()', 'timeofday()', "''::text"],
json: ['json_build_object()', 'json_build_array()'],
jsonb: ['jsonb_build_object()', 'jsonb_build_array()'],
date: dateFunctions,

View File

@@ -1,3 +1,4 @@
import { Box } from '@/components/ui/v2/Box';
import { Button } from '@/components/ui/v2/Button';
import { CopyIcon } from '@/components/ui/v2/icons/CopyIcon';
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>) {
if (
event.key === 'ArrowLeft' ||
@@ -122,36 +131,58 @@ export default function DataGridTextCell<TData extends object>({
if (isEditing && isMultiline) {
return (
<Input
multiline
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}
<Box
className="absolute top-0 z-10 -mx-0.5 h-full min-h-36 w-full"
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',
},
backgroundColor: (theme) =>
theme.palette.mode === 'dark'
? `${theme.palette.secondary[100]} !important`
: `${theme.palette.common.white} !important`,
}}
slotProps={{
inputRoot: {
className:
'resize-none outline-none focus:outline-none !text-xs focus:ring-0',
},
}}
/>
>
<Input
multiline
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.common.white} !important`,
},
[`& .${inputClasses.input}`]: {
backgroundColor: 'transparent',
},
}}
slotProps={{
inputWrapper: { className: 'h-full' },