Compare commits
6 Commits
feat/cron-
...
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) => {
|
||||
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 }}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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' },
|
||||
|
||||
Reference in New Issue
Block a user