Behaviour Fix on new record addition (#3113)

* Delete record if no company added

* EditMode on First column of new row added

* Fix

* Minor fixes

* Passed scopeId

* Changed FieldInputs to accept onChange handler

* Removed getFieldType

---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Kanav Arora
2024-01-05 22:31:51 +05:30
committed by GitHub
parent 9def3d5b57
commit 8455e15443
28 changed files with 551 additions and 159 deletions

View File

@ -10,12 +10,15 @@ import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObje
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { RecordTableActionBar } from '@/object-record/record-table/action-bar/components/RecordTableActionBar'; import { RecordTableActionBar } from '@/object-record/record-table/action-bar/components/RecordTableActionBar';
import { RecordTableContextMenu } from '@/object-record/record-table/context-menu/components/RecordTableContextMenu'; import { RecordTableContextMenu } from '@/object-record/record-table/context-menu/components/RecordTableContextMenu';
import { useSelectedTableCellEditMode } from '@/object-record/record-table/record-table-cell/hooks/useSelectedTableCellEditMode';
import { DEFAULT_CELL_SCOPE } from '@/object-record/record-table/record-table-cell/hooks/useTableCell';
import { useIcons } from '@/ui/display/icon/hooks/useIcons'; import { useIcons } from '@/ui/display/icon/hooks/useIcons';
import { PageAddButton } from '@/ui/layout/page/PageAddButton'; import { PageAddButton } from '@/ui/layout/page/PageAddButton';
import { PageBody } from '@/ui/layout/page/PageBody'; import { PageBody } from '@/ui/layout/page/PageBody';
import { PageContainer } from '@/ui/layout/page/PageContainer'; import { PageContainer } from '@/ui/layout/page/PageContainer';
import { PageHeader } from '@/ui/layout/page/PageHeader'; import { PageHeader } from '@/ui/layout/page/PageHeader';
import { PageHotkeysEffect } from '@/ui/layout/page/PageHotkeysEffect'; import { PageHotkeysEffect } from '@/ui/layout/page/PageHotkeysEffect';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { RecordTableContainer } from './RecordTableContainer'; import { RecordTableContainer } from './RecordTableContainer';
@ -57,11 +60,19 @@ export const RecordTablePage = () => {
objectNameSingular, objectNameSingular,
}); });
const recordTableId = objectNamePlural ?? '';
const { setSelectedTableCellEditMode } = useSelectedTableCellEditMode({
scopeId: recordTableId,
});
const setHotkeyScope = useSetHotkeyScope();
const handleAddButtonClick = async () => { const handleAddButtonClick = async () => {
await createOneObject?.({}); await createOneObject?.({});
};
const recordTableId = objectNamePlural ?? ''; setSelectedTableCellEditMode(0, 0);
setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes);
};
return ( return (
<PageContainer> <PageContainer>

View File

@ -0,0 +1,22 @@
import { useContext } from 'react';
import { useRecoilValue } from 'recoil';
import { isEntityFieldEditModeEmptyFamilySelector } from '@/object-record/field/states/selectors/isEntityFieldEditModeEmptyFamilySelector';
import { FieldContext } from '../contexts/FieldContext';
export const useIsFieldEditModeValueEmpty = () => {
const { entityId, fieldDefinition } = useContext(FieldContext);
const isFieldEditModeValueEmpty = useRecoilValue(
isEntityFieldEditModeEmptyFamilySelector({
fieldDefinition: {
type: fieldDefinition.type,
},
fieldName: fieldDefinition.metadata.fieldName,
entityId,
}),
);
return isFieldEditModeValueEmpty;
};

View File

@ -0,0 +1,112 @@
import { useContext } from 'react';
import { useRecoilCallback } from 'recoil';
import { entityFieldsEditModeValueFamilySelector } from '@/object-record/field/states/selectors/entityFieldsEditModeValueFamilySelector';
import { isFieldFullName } from '@/object-record/field/types/guards/isFieldFullName';
import { isFieldFullNameValue } from '@/object-record/field/types/guards/isFieldFullNameValue';
import { FieldContext } from '../contexts/FieldContext';
import { isFieldBoolean } from '../types/guards/isFieldBoolean';
import { isFieldBooleanValue } from '../types/guards/isFieldBooleanValue';
import { isFieldCurrency } from '../types/guards/isFieldCurrency';
import { isFieldCurrencyValue } from '../types/guards/isFieldCurrencyValue';
import { isFieldDateTime } from '../types/guards/isFieldDateTime';
import { isFieldDateTimeValue } from '../types/guards/isFieldDateTimeValue';
import { isFieldEmail } from '../types/guards/isFieldEmail';
import { isFieldEmailValue } from '../types/guards/isFieldEmailValue';
import { isFieldLink } from '../types/guards/isFieldLink';
import { isFieldLinkValue } from '../types/guards/isFieldLinkValue';
import { isFieldNumber } from '../types/guards/isFieldNumber';
import { isFieldNumberValue } from '../types/guards/isFieldNumberValue';
import { isFieldPhone } from '../types/guards/isFieldPhone';
import { isFieldPhoneValue } from '../types/guards/isFieldPhoneValue';
import { isFieldRating } from '../types/guards/isFieldRating';
import { isFieldRatingValue } from '../types/guards/isFieldRatingValue';
import { isFieldRelation } from '../types/guards/isFieldRelation';
import { isFieldRelationValue } from '../types/guards/isFieldRelationValue';
import { isFieldText } from '../types/guards/isFieldText';
import { isFieldTextValue } from '../types/guards/isFieldTextValue';
export const useSaveFieldEditModeValue = () => {
const { entityId, fieldDefinition } = useContext(FieldContext);
const saveFieldEditModeValue = useRecoilCallback(
({ set }) =>
(currentValue: unknown) => {
const fieldIsRelation =
isFieldRelation(fieldDefinition) &&
isFieldRelationValue(currentValue);
const fieldIsText =
isFieldText(fieldDefinition) && isFieldTextValue(currentValue);
const fieldIsEmail =
isFieldEmail(fieldDefinition) && isFieldEmailValue(currentValue);
const fieldIsDateTime =
isFieldDateTime(fieldDefinition) &&
isFieldDateTimeValue(currentValue);
const fieldIsLink =
isFieldLink(fieldDefinition) && isFieldLinkValue(currentValue);
const fieldIsBoolean =
isFieldBoolean(fieldDefinition) && isFieldBooleanValue(currentValue);
const fieldIsProbability =
isFieldRating(fieldDefinition) && isFieldRatingValue(currentValue);
const fieldIsNumber =
isFieldNumber(fieldDefinition) && isFieldNumberValue(currentValue);
const fieldIsCurrency =
isFieldCurrency(fieldDefinition) &&
isFieldCurrencyValue(currentValue);
const fieldIsFullName =
isFieldFullName(fieldDefinition) &&
isFieldFullNameValue(currentValue);
const fieldIsPhone =
isFieldPhone(fieldDefinition) && isFieldPhoneValue(currentValue);
if (fieldIsRelation) {
const fieldName = fieldDefinition.metadata.fieldName;
set(
entityFieldsEditModeValueFamilySelector({ entityId, fieldName }),
currentValue,
);
} else if (
fieldIsText ||
fieldIsBoolean ||
fieldIsEmail ||
fieldIsProbability ||
fieldIsNumber ||
fieldIsDateTime ||
fieldIsPhone ||
fieldIsLink ||
fieldIsCurrency ||
fieldIsFullName
) {
const fieldName = fieldDefinition.metadata.fieldName;
set(
entityFieldsEditModeValueFamilySelector({ entityId, fieldName }),
currentValue,
);
} else {
throw new Error(
`Invalid value to save: ${JSON.stringify(
currentValue,
)} for type : ${
fieldDefinition.type
}, type may not be implemented in useSaveFieldEditModeValue.`,
);
}
},
[entityId, fieldDefinition],
);
return saveFieldEditModeValue;
};

View File

@ -1,3 +1,4 @@
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
import { TextInput } from '@/ui/field/input/components/TextInput'; import { TextInput } from '@/ui/field/input/components/TextInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
@ -27,6 +28,8 @@ export const CurrencyFieldInput = ({
persistCurrencyField, persistCurrencyField,
} = useCurrencyField(); } = useCurrencyField();
const saveEditModeValue = useSaveFieldEditModeValue();
const handleEnter = (newValue: string) => { const handleEnter = (newValue: string) => {
onEnter?.(() => { onEnter?.(() => {
persistCurrencyField({ persistCurrencyField({
@ -75,6 +78,13 @@ export const CurrencyFieldInput = ({
); );
}; };
const handleChange = (newValue: string) => {
saveEditModeValue({
amountText: newValue,
currencyCode: initialCurrencyCode,
});
};
return ( return (
<FieldInputOverlay> <FieldInputOverlay>
<TextInput <TextInput
@ -87,6 +97,7 @@ export const CurrencyFieldInput = ({
onShiftTab={handleShiftTab} onShiftTab={handleShiftTab}
onTab={handleTab} onTab={handleTab}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onChange={handleChange}
/> />
</FieldInputOverlay> </FieldInputOverlay>
); );

View File

@ -1,3 +1,4 @@
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
import { DateInput } from '@/ui/field/input/components/DateInput'; import { DateInput } from '@/ui/field/input/components/DateInput';
import { Nullable } from '~/types/Nullable'; import { Nullable } from '~/types/Nullable';
@ -20,6 +21,7 @@ export const DateFieldInput = ({
const { fieldValue, hotkeyScope, clearable } = useDateTimeField(); const { fieldValue, hotkeyScope, clearable } = useDateTimeField();
const persistField = usePersistField(); const persistField = usePersistField();
const saveEditModeValue = useSaveFieldEditModeValue();
const persistDate = (newDate: Nullable<Date>) => { const persistDate = (newDate: Nullable<Date>) => {
if (!newDate) { if (!newDate) {
@ -46,6 +48,10 @@ export const DateFieldInput = ({
onClickOutside?.(() => persistDate(newDate)); onClickOutside?.(() => persistDate(newDate));
}; };
const handleChange = (newDate: Nullable<Date>) => {
saveEditModeValue(newDate);
};
const dateValue = fieldValue ? new Date(fieldValue) : null; const dateValue = fieldValue ? new Date(fieldValue) : null;
return ( return (
@ -56,6 +62,7 @@ export const DateFieldInput = ({
onEscape={handleEscape} onEscape={handleEscape}
value={dateValue} value={dateValue}
clearable={clearable} clearable={clearable}
onChange={handleChange}
/> />
); );
}; };

View File

@ -1,3 +1,4 @@
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
import { TextInput } from '@/ui/field/input/components/TextInput'; import { TextInput } from '@/ui/field/input/components/TextInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
@ -24,6 +25,7 @@ export const EmailFieldInput = ({
const { fieldDefinition, initialValue, hotkeyScope } = useEmailField(); const { fieldDefinition, initialValue, hotkeyScope } = useEmailField();
const persistField = usePersistField(); const persistField = usePersistField();
const saveEditModeValue = useSaveFieldEditModeValue();
const handleEnter = (newText: string) => { const handleEnter = (newText: string) => {
onEnter?.(() => persistField(newText)); onEnter?.(() => persistField(newText));
@ -48,6 +50,10 @@ export const EmailFieldInput = ({
onShiftTab?.(() => persistField(newText)); onShiftTab?.(() => persistField(newText));
}; };
const handleChange = (newText: string) => {
saveEditModeValue(newText);
};
return ( return (
<FieldInputOverlay> <FieldInputOverlay>
<TextInput <TextInput
@ -60,6 +66,7 @@ export const EmailFieldInput = ({
onShiftTab={handleShiftTab} onShiftTab={handleShiftTab}
onTab={handleTab} onTab={handleTab}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onChange={handleChange}
/> />
</FieldInputOverlay> </FieldInputOverlay>
); );

View File

@ -1,3 +1,4 @@
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
import { useFullNameField } from '@/object-record/field/meta-types/hooks/useFullNameField'; import { useFullNameField } from '@/object-record/field/meta-types/hooks/useFullNameField';
import { FieldDoubleText } from '@/object-record/field/types/FieldDoubleText'; import { FieldDoubleText } from '@/object-record/field/types/FieldDoubleText';
import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput'; import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput';
@ -7,6 +8,12 @@ import { usePersistField } from '../../../hooks/usePersistField';
import { FieldInputEvent } from './DateFieldInput'; import { FieldInputEvent } from './DateFieldInput';
const FIRST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS =
'First name';
const LAST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS =
'Last name';
export type FullNameFieldInputProps = { export type FullNameFieldInputProps = {
onClickOutside?: FieldInputEvent; onClickOutside?: FieldInputEvent;
onEnter?: FieldInputEvent; onEnter?: FieldInputEvent;
@ -25,6 +32,8 @@ export const FullNameFieldInput = ({
const { hotkeyScope, initialValue } = useFullNameField(); const { hotkeyScope, initialValue } = useFullNameField();
const persistField = usePersistField(); const persistField = usePersistField();
const saveEditModeValue = useSaveFieldEditModeValue();
const convertToFullName = (newDoubleText: FieldDoubleText) => { const convertToFullName = (newDoubleText: FieldDoubleText) => {
return { return {
firstName: newDoubleText.firstValue, firstName: newDoubleText.firstValue,
@ -55,19 +64,28 @@ export const FullNameFieldInput = ({
onShiftTab?.(() => persistField(convertToFullName(newDoubleText))); onShiftTab?.(() => persistField(convertToFullName(newDoubleText)));
}; };
const handleChange = (newDoubleText: FieldDoubleText) => {
saveEditModeValue(convertToFullName(newDoubleText));
};
return ( return (
<FieldInputOverlay> <FieldInputOverlay>
<DoubleTextInput <DoubleTextInput
firstValue={initialValue.firstName} firstValue={initialValue.firstName}
secondValue={initialValue.lastName} secondValue={initialValue.lastName}
firstValuePlaceholder={'First name'} firstValuePlaceholder={
secondValuePlaceholder={'Last name'} FIRST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS
}
secondValuePlaceholder={
LAST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS
}
onClickOutside={handleClickOutside} onClickOutside={handleClickOutside}
onEnter={handleEnter} onEnter={handleEnter}
onEscape={handleEscape} onEscape={handleEscape}
onShiftTab={handleShiftTab} onShiftTab={handleShiftTab}
onTab={handleTab} onTab={handleTab}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onChange={handleChange}
/> />
</FieldInputOverlay> </FieldInputOverlay>
); );

View File

@ -1,3 +1,4 @@
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
import { TextInput } from '@/ui/field/input/components/TextInput'; import { TextInput } from '@/ui/field/input/components/TextInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
@ -22,6 +23,8 @@ export const LinkFieldInput = ({
}: LinkFieldInputProps) => { }: LinkFieldInputProps) => {
const { initialValue, hotkeyScope, persistLinkField } = useLinkField(); const { initialValue, hotkeyScope, persistLinkField } = useLinkField();
const saveEditModeValue = useSaveFieldEditModeValue();
const handleEnter = (newURL: string) => { const handleEnter = (newURL: string) => {
onEnter?.(() => onEnter?.(() =>
persistLinkField({ persistLinkField({
@ -70,6 +73,13 @@ export const LinkFieldInput = ({
); );
}; };
const handleChange = (newURL: string) => {
saveEditModeValue({
url: newURL,
label: newURL,
});
};
return ( return (
<FieldInputOverlay> <FieldInputOverlay>
<TextInput <TextInput
@ -82,6 +92,7 @@ export const LinkFieldInput = ({
onEscape={handleEscape} onEscape={handleEscape}
onTab={handleTab} onTab={handleTab}
onShiftTab={handleShiftTab} onShiftTab={handleShiftTab}
onChange={handleChange}
/> />
</FieldInputOverlay> </FieldInputOverlay>
); );

View File

@ -1,3 +1,4 @@
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
import { TextInput } from '@/ui/field/input/components/TextInput'; import { TextInput } from '@/ui/field/input/components/TextInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
@ -23,6 +24,8 @@ export const NumberFieldInput = ({
const { fieldDefinition, initialValue, hotkeyScope, persistNumberField } = const { fieldDefinition, initialValue, hotkeyScope, persistNumberField } =
useNumberField(); useNumberField();
const saveEditModeValue = useSaveFieldEditModeValue();
const handleEnter = (newText: string) => { const handleEnter = (newText: string) => {
onEnter?.(() => persistNumberField(newText)); onEnter?.(() => persistNumberField(newText));
}; };
@ -46,6 +49,10 @@ export const NumberFieldInput = ({
onShiftTab?.(() => persistNumberField(newText)); onShiftTab?.(() => persistNumberField(newText));
}; };
const handleChange = (newText: string) => {
saveEditModeValue(newText);
};
return ( return (
<FieldInputOverlay> <FieldInputOverlay>
<TextInput <TextInput
@ -58,6 +65,7 @@ export const NumberFieldInput = ({
onShiftTab={handleShiftTab} onShiftTab={handleShiftTab}
onTab={handleTab} onTab={handleTab}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onChange={handleChange}
/> />
</FieldInputOverlay> </FieldInputOverlay>
); );

View File

@ -1,3 +1,4 @@
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
import { PhoneInput } from '@/ui/field/input/components/PhoneInput'; import { PhoneInput } from '@/ui/field/input/components/PhoneInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
@ -23,6 +24,8 @@ export const PhoneFieldInput = ({
const { fieldDefinition, initialValue, hotkeyScope, persistPhoneField } = const { fieldDefinition, initialValue, hotkeyScope, persistPhoneField } =
usePhoneField(); usePhoneField();
const saveEditModeValue = useSaveFieldEditModeValue();
const handleEnter = (newText: string) => { const handleEnter = (newText: string) => {
onEnter?.(() => persistPhoneField(newText)); onEnter?.(() => persistPhoneField(newText));
}; };
@ -46,6 +49,10 @@ export const PhoneFieldInput = ({
onShiftTab?.(() => persistPhoneField(newText)); onShiftTab?.(() => persistPhoneField(newText));
}; };
const handleChange = (newText: string) => {
saveEditModeValue(newText);
};
return ( return (
<FieldInputOverlay> <FieldInputOverlay>
<PhoneInput <PhoneInput
@ -58,6 +65,7 @@ export const PhoneFieldInput = ({
onShiftTab={handleShiftTab} onShiftTab={handleShiftTab}
onTab={handleTab} onTab={handleTab}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onChange={handleChange}
/> />
</FieldInputOverlay> </FieldInputOverlay>
); );

View File

@ -1,3 +1,4 @@
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
import { TextInput } from '@/ui/field/input/components/TextInput'; import { TextInput } from '@/ui/field/input/components/TextInput';
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay'; import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
@ -24,6 +25,7 @@ export const TextFieldInput = ({
const { fieldDefinition, initialValue, hotkeyScope } = useTextField(); const { fieldDefinition, initialValue, hotkeyScope } = useTextField();
const persistField = usePersistField(); const persistField = usePersistField();
const saveEditModeValue = useSaveFieldEditModeValue();
const handleEnter = (newText: string) => { const handleEnter = (newText: string) => {
onEnter?.(() => persistField(newText)); onEnter?.(() => persistField(newText));
@ -48,6 +50,10 @@ export const TextFieldInput = ({
onShiftTab?.(() => persistField(newText)); onShiftTab?.(() => persistField(newText));
}; };
const handleChange = (newText: string) => {
saveEditModeValue(newText);
};
return ( return (
<FieldInputOverlay> <FieldInputOverlay>
<TextInput <TextInput
@ -60,6 +66,7 @@ export const TextFieldInput = ({
onShiftTab={handleShiftTab} onShiftTab={handleShiftTab}
onTab={handleTab} onTab={handleTab}
hotkeyScope={hotkeyScope} hotkeyScope={hotkeyScope}
onChange={handleChange}
/> />
</FieldInputOverlay> </FieldInputOverlay>
); );

View File

@ -0,0 +1,9 @@
import { atomFamily } from 'recoil';
export const entityFieldsEditModeValueFamilyState = atomFamily<
Record<string, unknown> | null,
string
>({
key: 'entityFieldsEditModeValueFamilyState',
default: null,
});

View File

@ -0,0 +1,18 @@
import { selectorFamily } from 'recoil';
import { entityFieldsEditModeValueFamilyState } from '@/object-record/field/states/entityFieldsEditModeValueFamilyState';
export const entityFieldsEditModeValueFamilySelector = selectorFamily({
key: 'entityFieldsEditModeValueFamilySelector',
get:
<T>({ fieldName, entityId }: { fieldName: string; entityId: string }) =>
({ get }) =>
get(entityFieldsEditModeValueFamilyState(entityId))?.[fieldName] as T,
set:
<T>({ fieldName, entityId }: { fieldName: string; entityId: string }) =>
({ set }, newValue: T) =>
set(entityFieldsEditModeValueFamilyState(entityId), (prevState) => ({
...prevState,
[fieldName]: newValue,
})),
});

View File

@ -0,0 +1,31 @@
import { selectorFamily } from 'recoil';
import { entityFieldsEditModeValueFamilyState } from '@/object-record/field/states/entityFieldsEditModeValueFamilyState';
import { isFieldValueEmpty } from '@/object-record/field/utils/isFieldValueEmpty';
import { FieldDefinition } from '../../types/FieldDefinition';
import { FieldMetadata } from '../../types/FieldMetadata';
export const isEntityFieldEditModeEmptyFamilySelector = selectorFamily({
key: 'isEntityFieldEditModeEmptyFamilySelector',
get: ({
fieldDefinition,
fieldName,
entityId,
}: {
fieldDefinition: Pick<FieldDefinition<FieldMetadata>, 'type'>;
fieldName: string;
entityId: string;
}) => {
return ({ get }) => {
const fieldValue = get(entityFieldsEditModeValueFamilyState(entityId))?.[
fieldName
];
return isFieldValueEmpty({
fieldDefinition,
fieldValue,
});
};
},
});

View File

@ -1,28 +1,10 @@
import { selectorFamily } from 'recoil'; import { selectorFamily } from 'recoil';
import { isFieldFullName } from '@/object-record/field/types/guards/isFieldFullName'; import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
import { isFieldFullNameValue } from '@/object-record/field/types/guards/isFieldFullNameValue'; import { isFieldValueEmpty } from '@/object-record/field/utils/isFieldValueEmpty';
import { isFieldSelect } from '@/object-record/field/types/guards/isFieldSelect';
import { isFieldUuid } from '@/object-record/field/types/guards/isFieldUuid';
import { assertNotNull } from '~/utils/assert';
import { FieldDefinition } from '../../types/FieldDefinition'; import { FieldDefinition } from '../../types/FieldDefinition';
import { FieldMetadata } from '../../types/FieldMetadata'; import { FieldMetadata } from '../../types/FieldMetadata';
import { isFieldBoolean } from '../../types/guards/isFieldBoolean';
import { isFieldCurrency } from '../../types/guards/isFieldCurrency';
import { isFieldCurrencyValue } from '../../types/guards/isFieldCurrencyValue';
import { isFieldDateTime } from '../../types/guards/isFieldDateTime';
import { isFieldEmail } from '../../types/guards/isFieldEmail';
import { isFieldLink } from '../../types/guards/isFieldLink';
import { isFieldLinkValue } from '../../types/guards/isFieldLinkValue';
import { isFieldNumber } from '../../types/guards/isFieldNumber';
import { isFieldRating } from '../../types/guards/isFieldRating';
import { isFieldRelation } from '../../types/guards/isFieldRelation';
import { isFieldRelationValue } from '../../types/guards/isFieldRelationValue';
import { isFieldText } from '../../types/guards/isFieldText';
import { entityFieldsFamilyState } from '../entityFieldsFamilyState';
const isValueEmpty = (value: unknown) => !assertNotNull(value) || value === '';
export const isEntityFieldEmptyFamilySelector = selectorFamily({ export const isEntityFieldEmptyFamilySelector = selectorFamily({
key: 'isEntityFieldEmptyFamilySelector', key: 'isEntityFieldEmptyFamilySelector',
@ -36,57 +18,12 @@ export const isEntityFieldEmptyFamilySelector = selectorFamily({
entityId: string; entityId: string;
}) => { }) => {
return ({ get }) => { return ({ get }) => {
if ( const fieldValue = get(entityFieldsFamilyState(entityId))?.[fieldName];
isFieldUuid(fieldDefinition) ||
isFieldText(fieldDefinition) ||
isFieldDateTime(fieldDefinition) ||
isFieldNumber(fieldDefinition) ||
isFieldRating(fieldDefinition) ||
isFieldEmail(fieldDefinition) ||
isFieldBoolean(fieldDefinition) ||
isFieldSelect(fieldDefinition)
//|| isFieldPhone(fieldDefinition)
) {
const fieldValue = get(entityFieldsFamilyState(entityId))?.[
fieldName
] as string | number | boolean | null;
return isValueEmpty(fieldValue); return isFieldValueEmpty({
} fieldDefinition,
fieldValue,
if (isFieldRelation(fieldDefinition)) { });
const fieldValue = get(entityFieldsFamilyState(entityId))?.[fieldName];
return isFieldRelationValue(fieldValue) && isValueEmpty(fieldValue);
}
if (isFieldCurrency(fieldDefinition)) {
const fieldValue = get(entityFieldsFamilyState(entityId))?.[fieldName];
return (
!isFieldCurrencyValue(fieldValue) ||
isValueEmpty(fieldValue?.amountMicros)
);
}
if (isFieldFullName(fieldDefinition)) {
const fieldValue = get(entityFieldsFamilyState(entityId))?.[fieldName];
return (
!isFieldFullNameValue(fieldValue) ||
isValueEmpty(fieldValue?.firstName + fieldValue?.lastName)
);
}
if (isFieldLink(fieldDefinition)) {
const fieldValue = get(entityFieldsFamilyState(entityId))?.[fieldName];
return !isFieldLinkValue(fieldValue) || isValueEmpty(fieldValue?.url);
}
throw new Error(
`Entity field type not supported in isEntityFieldEmptyFamilySelector : ${fieldDefinition.type}}`,
);
}; };
}, },
}); });

View File

@ -0,0 +1,69 @@
import { FieldDefinition } from '@/object-record/field/types/FieldDefinition';
import { FieldMetadata } from '@/object-record/field/types/FieldMetadata';
import { isFieldBoolean } from '@/object-record/field/types/guards/isFieldBoolean';
import { isFieldCurrency } from '@/object-record/field/types/guards/isFieldCurrency';
import { isFieldCurrencyValue } from '@/object-record/field/types/guards/isFieldCurrencyValue';
import { isFieldDateTime } from '@/object-record/field/types/guards/isFieldDateTime';
import { isFieldEmail } from '@/object-record/field/types/guards/isFieldEmail';
import { isFieldFullName } from '@/object-record/field/types/guards/isFieldFullName';
import { isFieldFullNameValue } from '@/object-record/field/types/guards/isFieldFullNameValue';
import { isFieldLink } from '@/object-record/field/types/guards/isFieldLink';
import { isFieldLinkValue } from '@/object-record/field/types/guards/isFieldLinkValue';
import { isFieldNumber } from '@/object-record/field/types/guards/isFieldNumber';
import { isFieldRating } from '@/object-record/field/types/guards/isFieldRating';
import { isFieldRelation } from '@/object-record/field/types/guards/isFieldRelation';
import { isFieldRelationValue } from '@/object-record/field/types/guards/isFieldRelationValue';
import { isFieldSelect } from '@/object-record/field/types/guards/isFieldSelect';
import { isFieldText } from '@/object-record/field/types/guards/isFieldText';
import { isFieldUuid } from '@/object-record/field/types/guards/isFieldUuid';
import { assertNotNull } from '~/utils/assert';
const isValueEmpty = (value: unknown) => !assertNotNull(value) || value === '';
export const isFieldValueEmpty = ({
fieldDefinition,
fieldValue,
}: {
fieldDefinition: Pick<FieldDefinition<FieldMetadata>, 'type'>;
fieldValue: unknown;
}) => {
if (
isFieldUuid(fieldDefinition) ||
isFieldText(fieldDefinition) ||
isFieldDateTime(fieldDefinition) ||
isFieldNumber(fieldDefinition) ||
isFieldRating(fieldDefinition) ||
isFieldEmail(fieldDefinition) ||
isFieldBoolean(fieldDefinition) ||
isFieldSelect(fieldDefinition)
//|| isFieldPhone(fieldDefinition)
) {
return isValueEmpty(fieldValue);
}
if (isFieldRelation(fieldDefinition)) {
return isFieldRelationValue(fieldValue) && isValueEmpty(fieldValue);
}
if (isFieldCurrency(fieldDefinition)) {
return (
!isFieldCurrencyValue(fieldValue) ||
isValueEmpty(fieldValue?.amountMicros)
);
}
if (isFieldFullName(fieldDefinition)) {
return (
!isFieldFullNameValue(fieldValue) ||
isValueEmpty(fieldValue?.firstName + fieldValue?.lastName)
);
}
if (isFieldLink(fieldDefinition)) {
return !isFieldLinkValue(fieldValue) || isValueEmpty(fieldValue?.url);
}
throw new Error(
`Entity field type not supported in isEntityFieldEditModeEmptyFamilySelector : ${fieldDefinition.type}}`,
);
};

View File

@ -4,9 +4,11 @@ import { useRecoilCallback, useRecoilValue } from 'recoil';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural'; import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { RecordTable } from '@/object-record/record-table/components/RecordTable'; import { RecordTable } from '@/object-record/record-table/components/RecordTable';
import { RecordTableFirstColumnScrollObserver } from '@/object-record/record-table/components/RecordTableFirstColumnScrollObserver'; import { RecordTableFirstColumnScrollObserver } from '@/object-record/record-table/components/RecordTableFirstColumnScrollObserver';
import { RecordTableRefContextWrapper } from '@/object-record/record-table/components/RecordTableRefContext'; import { RecordTableRefContextWrapper } from '@/object-record/record-table/components/RecordTableRefContext';
import { EntityDeleteContext } from '@/object-record/record-table/contexts/EntityDeleteHookContext';
import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates'; import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates';
import { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector'; import { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector';
import { IconPlus } from '@/ui/display/icon'; import { IconPlus } from '@/ui/display/icon';
@ -122,6 +124,8 @@ export const RecordTableWithWrappers = ({
const { persistViewFields } = useViewFields(viewBarId); const { persistViewFields } = useViewFields(viewBarId);
const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular });
return ( return (
<RecordTableScope <RecordTableScope
recordTableScopeId={recordTableId} recordTableScopeId={recordTableId}
@ -129,42 +133,44 @@ export const RecordTableWithWrappers = ({
persistViewFields(mapColumnDefinitionsToViewFields(columns)); persistViewFields(mapColumnDefinitionsToViewFields(columns));
})} })}
> >
<ScrollWrapper> <EntityDeleteContext.Provider value={deleteOneRecord}>
<RecordTableRefContextWrapper> <ScrollWrapper>
<RecordTableFirstColumnScrollObserver /> <RecordTableRefContextWrapper>
<RecordUpdateContext.Provider value={updateRecordMutation}> <RecordTableFirstColumnScrollObserver />
<StyledTableWithHeader> <RecordUpdateContext.Provider value={updateRecordMutation}>
<StyledTableContainer> <StyledTableWithHeader>
<div ref={tableBodyRef}> <StyledTableContainer>
<RecordTable createRecord={createRecord} /> <div ref={tableBodyRef}>
<DragSelect <RecordTable createRecord={createRecord} />
dragSelectable={tableBodyRef} <DragSelect
onDragSelectionStart={resetTableRowSelection} dragSelectable={tableBodyRef}
onDragSelectionChange={setRowSelectedState} onDragSelectionStart={resetTableRowSelection}
/> onDragSelectionChange={setRowSelectedState}
</div>
<RecordTableInternalEffect tableBodyRef={tableBodyRef} />
{!isRecordTableInitialLoading && numberOfTableRows === 0 && (
<StyledObjectEmptyContainer>
<StyledEmptyObjectTitle>
No {foundObjectMetadataItem?.namePlural}
</StyledEmptyObjectTitle>
<StyledEmptyObjectSubTitle>
Create one:
</StyledEmptyObjectSubTitle>
<Button
Icon={IconPlus}
title={`Add a ${foundObjectMetadataItem?.nameSingular}`}
variant={'secondary'}
onClick={createRecord}
/> />
</StyledObjectEmptyContainer> </div>
)} <RecordTableInternalEffect tableBodyRef={tableBodyRef} />
</StyledTableContainer> {!isRecordTableInitialLoading && numberOfTableRows === 0 && (
</StyledTableWithHeader> <StyledObjectEmptyContainer>
</RecordUpdateContext.Provider> <StyledEmptyObjectTitle>
</RecordTableRefContextWrapper> No {foundObjectMetadataItem?.namePlural}
</ScrollWrapper> </StyledEmptyObjectTitle>
<StyledEmptyObjectSubTitle>
Create one:
</StyledEmptyObjectSubTitle>
<Button
Icon={IconPlus}
title={`Add a ${foundObjectMetadataItem?.nameSingular}`}
variant={'secondary'}
onClick={createRecord}
/>
</StyledObjectEmptyContainer>
)}
</StyledTableContainer>
</StyledTableWithHeader>
</RecordUpdateContext.Provider>
</RecordTableRefContextWrapper>
</ScrollWrapper>
</EntityDeleteContext.Provider>
</RecordTableScope> </RecordTableScope>
); );
}; };

View File

@ -0,0 +1,5 @@
import { createContext } from 'react';
export const EntityDeleteContext = createContext<
(idToDelete: string) => Promise<unknown>
>(async () => {});

View File

@ -1,6 +1,10 @@
import { useContext } from 'react';
import { FieldDisplay } from '@/object-record/field/components/FieldDisplay'; import { FieldDisplay } from '@/object-record/field/components/FieldDisplay';
import { FieldInput } from '@/object-record/field/components/FieldInput'; import { FieldInput } from '@/object-record/field/components/FieldInput';
import { useIsFieldEditModeValueEmpty } from '@/object-record/field/hooks/useIsFieldEditModeValueEmpty';
import { FieldInputEvent } from '@/object-record/field/types/FieldInputEvent'; import { FieldInputEvent } from '@/object-record/field/types/FieldInputEvent';
import { ColumnIndexContext } from '@/object-record/record-table/contexts/ColumnIndexContext';
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope'; import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
import { useRecordTable } from '../../hooks/useRecordTable'; import { useRecordTable } from '../../hooks/useRecordTable';
@ -17,14 +21,25 @@ export const RecordTableCell = ({
const { moveLeft, moveRight, moveDown } = useRecordTable(); const { moveLeft, moveRight, moveDown } = useRecordTable();
const isFirstColumnCell = useContext(ColumnIndexContext) === 0;
const isEditModeValueEmpty = useIsFieldEditModeValueEmpty();
const skipFieldPersist = isFirstColumnCell && isEditModeValueEmpty;
const handleEnter: FieldInputEvent = (persistField) => { const handleEnter: FieldInputEvent = (persistField) => {
persistField(); if (!skipFieldPersist) {
persistField();
}
closeTableCell(); closeTableCell();
moveDown(); moveDown();
}; };
const handleSubmit: FieldInputEvent = (persistField) => { const handleSubmit: FieldInputEvent = (persistField) => {
persistField(); if (!skipFieldPersist) {
persistField();
}
closeTableCell(); closeTableCell();
}; };
@ -33,24 +48,35 @@ export const RecordTableCell = ({
}; };
const handleClickOutside: FieldInputEvent = (persistField) => { const handleClickOutside: FieldInputEvent = (persistField) => {
persistField(); if (!skipFieldPersist) {
persistField();
}
closeTableCell(); closeTableCell();
}; };
const handleEscape: FieldInputEvent = (persistField) => { const handleEscape: FieldInputEvent = (persistField) => {
persistField(); if (!skipFieldPersist) {
persistField();
}
closeTableCell(); closeTableCell();
}; };
const handleTab: FieldInputEvent = (persistField) => { const handleTab: FieldInputEvent = (persistField) => {
persistField(); if (!skipFieldPersist) {
persistField();
}
closeTableCell(); closeTableCell();
moveRight(); moveRight();
}; };
const handleShiftTab: FieldInputEvent = (persistField) => { const handleShiftTab: FieldInputEvent = (persistField) => {
persistField(); if (!skipFieldPersist) {
persistField();
}
closeTableCell(); closeTableCell();
moveLeft(); moveLeft();
}; };

View File

@ -33,13 +33,17 @@ export const useMoveSoftFocusToCurrentCellOnHover = () => {
currentTableCellInEditModePositionScopeInjector, currentTableCellInEditModePositionScopeInjector,
); );
const isSomeCellInEditMode = snapshot.getLoadable( const isSomeCellInEditMode = snapshot
isTableCellInEditModeFamilyState(currentTableCellInEditModePosition), .getLoadable(
); isTableCellInEditModeFamilyState(
currentTableCellInEditModePosition,
),
)
.getValue();
const currentHotkeyScope = snapshot const currentHotkeyScope = snapshot
.getLoadable(currentHotkeyScopeState) .getLoadable(currentHotkeyScopeState)
.valueOrThrow(); .getValue();
if ( if (
currentHotkeyScope.scope !== TableHotkeyScope.TableSoftFocus && currentHotkeyScope.scope !== TableHotkeyScope.TableSoftFocus &&
@ -49,7 +53,7 @@ export const useMoveSoftFocusToCurrentCellOnHover = () => {
return; return;
} }
if (!isSomeCellInEditMode.contents) { if (!isSomeCellInEditMode) {
setSoftFocusOnCurrentTableCell(); setSoftFocusOnCurrentTableCell();
} }
}, },

View File

@ -0,0 +1,21 @@
import { useCallback } from 'react';
import { useMoveEditModeToTableCellPosition } from '../../hooks/internal/useMoveEditModeToCellPosition';
export const useSelectedTableCellEditMode = ({
scopeId,
}: {
scopeId: string;
}) => {
const moveEditModeToTableCellPosition =
useMoveEditModeToTableCellPosition(scopeId);
const setSelectedTableCellEditMode = useCallback(
(row: number, column: number) => {
moveEditModeToTableCellPosition({ column, row });
},
[moveEditModeToTableCellPosition],
);
return { setSelectedTableCellEditMode };
};

View File

@ -0,0 +1,35 @@
import { useRecoilCallback } from 'recoil';
import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { TableCellPosition } from '@/object-record/record-table/types/TableCellPosition';
import { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
export const useSetSoftFocus = () => {
const { setSoftFocusPosition } = useRecordTable();
const { isSoftFocusActiveScopeInjector } = getRecordTableScopeInjector();
const { injectStateWithRecordTableScopeId } = useRecordTableScopedStates();
const isSoftFocusActiveState = injectStateWithRecordTableScopeId(
isSoftFocusActiveScopeInjector,
);
const setHotkeyScope = useSetHotkeyScope();
return useRecoilCallback(
({ set }) =>
(newPosition: TableCellPosition) => {
setSoftFocusPosition(newPosition);
set(isSoftFocusActiveState, true);
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
},
[setSoftFocusPosition, isSoftFocusActiveState, setHotkeyScope],
);
};

View File

@ -1,43 +1,13 @@
import { useRecoilCallback } from 'recoil'; import { useSetSoftFocus } from '@/object-record/record-table/record-table-cell/hooks/useSetSoftFocus';
import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector';
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentTableCellPosition } from './useCurrentCellPosition'; import { useCurrentTableCellPosition } from './useCurrentCellPosition';
export const useSetSoftFocusOnCurrentTableCell = () => { export const useSetSoftFocusOnCurrentTableCell = () => {
const { setSoftFocusPosition } = useRecordTable(); const setSoftFocus = useSetSoftFocus();
const { isSoftFocusActiveScopeInjector } = getRecordTableScopeInjector();
const { injectStateWithRecordTableScopeId } = useRecordTableScopedStates();
const isSoftFocusActiveState = injectStateWithRecordTableScopeId(
isSoftFocusActiveScopeInjector,
);
const currentTableCellPosition = useCurrentTableCellPosition(); const currentTableCellPosition = useCurrentTableCellPosition();
const setHotkeyScope = useSetHotkeyScope(); return () => {
setSoftFocus(currentTableCellPosition);
return useRecoilCallback( };
({ set }) =>
() => {
setSoftFocusPosition(currentTableCellPosition);
set(isSoftFocusActiveState, true);
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
},
[
setSoftFocusPosition,
currentTableCellPosition,
isSoftFocusActiveState,
setHotkeyScope,
],
);
}; };

View File

@ -1,11 +1,13 @@
import { useContext } from 'react'; import { useContext } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { useRecoilState, useRecoilValue } from 'recoil'; import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
import { FieldContext } from '@/object-record/field/contexts/FieldContext'; import { FieldContext } from '@/object-record/field/contexts/FieldContext';
import { useIsFieldEditModeValueEmpty } from '@/object-record/field/hooks/useIsFieldEditModeValueEmpty';
import { useIsFieldEmpty } from '@/object-record/field/hooks/useIsFieldEmpty'; import { useIsFieldEmpty } from '@/object-record/field/hooks/useIsFieldEmpty';
import { entityFieldInitialValueFamilyState } from '@/object-record/field/states/entityFieldInitialValueFamilyState'; import { entityFieldInitialValueFamilyState } from '@/object-record/field/states/entityFieldInitialValueFamilyState';
import { FieldInitialValue } from '@/object-record/field/types/FieldInitialValue'; import { FieldInitialValue } from '@/object-record/field/types/FieldInitialValue';
import { EntityDeleteContext } from '@/object-record/record-table/contexts/EntityDeleteHookContext';
import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates'; import { useRecordTableScopedStates } from '@/object-record/record-table/hooks/internal/useRecordTableScopedStates';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable'; import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
import { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector'; import { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector';
@ -20,7 +22,7 @@ import { TableHotkeyScope } from '../../types/TableHotkeyScope';
import { useCurrentTableCellEditMode } from './useCurrentTableCellEditMode'; import { useCurrentTableCellEditMode } from './useCurrentTableCellEditMode';
const DEFAULT_CELL_SCOPE: HotkeyScope = { export const DEFAULT_CELL_SCOPE: HotkeyScope = {
scope: TableHotkeyScope.CellEditMode, scope: TableHotkeyScope.CellEditMode,
}; };
@ -51,9 +53,12 @@ export const useTableCell = () => {
const isFirstColumnCell = useContext(ColumnIndexContext) === 0; const isFirstColumnCell = useContext(ColumnIndexContext) === 0;
const isEmpty = useIsFieldEmpty(); const isEmpty = useIsFieldEmpty();
const isEditModeValueEmpty = useIsFieldEditModeValueEmpty();
const { entityId, fieldDefinition } = useContext(FieldContext); const { entityId, fieldDefinition } = useContext(FieldContext);
const deleteOneRecord = useContext(EntityDeleteContext);
const [, setFieldInitialValue] = useRecoilState( const [, setFieldInitialValue] = useRecoilState(
entityFieldInitialValueFamilyState({ entityFieldInitialValueFamilyState({
entityId, entityId,
@ -61,6 +66,16 @@ export const useTableCell = () => {
}), }),
); );
const { tableRowIdsScopeInjector } = getRecordTableScopeInjector();
const deleteRow = useRecoilCallback(({ snapshot }) => async () => {
const tableRowIds = snapshot
.getLoadable(tableRowIdsScopeInjector(recordTableScopeId))
.getValue();
await deleteOneRecord(tableRowIds[0]);
});
const openTableCell = (options?: { initialValue?: FieldInitialValue }) => { const openTableCell = (options?: { initialValue?: FieldInitialValue }) => {
if (isFirstColumnCell && !isEmpty && basePathToShowPage) { if (isFirstColumnCell && !isEmpty && basePathToShowPage) {
navigate(`${basePathToShowPage}${entityId}`); navigate(`${basePathToShowPage}${entityId}`);
@ -84,11 +99,15 @@ export const useTableCell = () => {
} }
}; };
const closeTableCell = () => { const closeTableCell = async () => {
setDragSelectionStartEnabled(true); setDragSelectionStartEnabled(true);
closeCurrentTableCellInEditMode(); closeCurrentTableCellInEditMode();
setFieldInitialValue(undefined); setFieldInitialValue(undefined);
setHotkeyScope(TableHotkeyScope.TableSoftFocus); setHotkeyScope(TableHotkeyScope.TableSoftFocus);
if (isFirstColumnCell && isEditModeValueEmpty) {
await deleteRow();
}
}; };
return { return {

View File

@ -38,6 +38,7 @@ export type DateInputProps = {
) => void; ) => void;
hotkeyScope: string; hotkeyScope: string;
clearable?: boolean; clearable?: boolean;
onChange?: (newDate: Nullable<Date>) => void;
}; };
export const DateInput = ({ export const DateInput = ({
@ -47,6 +48,7 @@ export const DateInput = ({
onEscape, onEscape,
onClickOutside, onClickOutside,
clearable, clearable,
onChange,
}: DateInputProps) => { }: DateInputProps) => {
const theme = useTheme(); const theme = useTheme();
@ -66,6 +68,7 @@ export const DateInput = ({
const handleChange = (newDate: Date) => { const handleChange = (newDate: Date) => {
setInternalValue(newDate); setInternalValue(newDate);
onChange?.(newDate);
}; };
useEffect(() => { useEffect(() => {

View File

@ -38,6 +38,7 @@ type DoubleTextInputProps = {
event: MouseEvent | TouchEvent, event: MouseEvent | TouchEvent,
newDoubleTextValue: FieldDoubleText, newDoubleTextValue: FieldDoubleText,
) => void; ) => void;
onChange?: (newDoubleTextValue: FieldDoubleText) => void;
}; };
export const DoubleTextInput = ({ export const DoubleTextInput = ({
@ -51,6 +52,7 @@ export const DoubleTextInput = ({
onEscape, onEscape,
onShiftTab, onShiftTab,
onTab, onTab,
onChange,
}: DoubleTextInputProps) => { }: DoubleTextInputProps) => {
const [firstInternalValue, setFirstInternalValue] = useState(firstValue); const [firstInternalValue, setFirstInternalValue] = useState(firstValue);
const [secondInternalValue, setSecondInternalValue] = useState(secondValue); const [secondInternalValue, setSecondInternalValue] = useState(secondValue);
@ -70,6 +72,11 @@ export const DoubleTextInput = ({
): void => { ): void => {
setFirstInternalValue(newFirstValue); setFirstInternalValue(newFirstValue);
setSecondInternalValue(newSecondValue); setSecondInternalValue(newSecondValue);
onChange?.({
firstValue: newFirstValue,
secondValue: newSecondValue,
});
}; };
const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left'); const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left');

View File

@ -54,6 +54,7 @@ export type PhoneInputProps = {
onTab?: (newText: string) => void; onTab?: (newText: string) => void;
onShiftTab?: (newText: string) => void; onShiftTab?: (newText: string) => void;
onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void; onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void;
onChange?: (newText: string) => void;
hotkeyScope: string; hotkeyScope: string;
}; };
@ -66,11 +67,17 @@ export const PhoneInput = ({
onShiftTab, onShiftTab,
onClickOutside, onClickOutside,
hotkeyScope, hotkeyScope,
onChange,
}: PhoneInputProps) => { }: PhoneInputProps) => {
const [internalValue, setInternalValue] = useState<string | undefined>(value); const [internalValue, setInternalValue] = useState<string | undefined>(value);
const wrapperRef = useRef<HTMLDivElement>(null); const wrapperRef = useRef<HTMLDivElement>(null);
const handleChange = (newValue: string) => {
setInternalValue(newValue);
onChange?.(newValue);
};
useEffect(() => { useEffect(() => {
setInternalValue(value); setInternalValue(value);
}, [value]); }, [value]);
@ -92,7 +99,7 @@ export const PhoneInput = ({
autoFocus={autoFocus} autoFocus={autoFocus}
placeholder="Phone number" placeholder="Phone number"
value={value} value={value}
onChange={setInternalValue} onChange={handleChange}
international={true} international={true}
withCountryCallingCode={true} withCountryCallingCode={true}
countrySelectComponent={CountryPickerDropdownButton} countrySelectComponent={CountryPickerDropdownButton}

View File

@ -21,6 +21,7 @@ type TextInputProps = {
onShiftTab?: (newText: string) => void; onShiftTab?: (newText: string) => void;
onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void; onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void;
hotkeyScope: string; hotkeyScope: string;
onChange?: (newText: string) => void;
}; };
export const TextInput = ({ export const TextInput = ({
@ -33,6 +34,7 @@ export const TextInput = ({
onTab, onTab,
onShiftTab, onShiftTab,
onClickOutside, onClickOutside,
onChange,
}: TextInputProps) => { }: TextInputProps) => {
const [internalText, setInternalText] = useState(value); const [internalText, setInternalText] = useState(value);
@ -40,6 +42,7 @@ export const TextInput = ({
const handleChange = (event: ChangeEvent<HTMLInputElement>) => { const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
setInternalText(event.target.value); setInternalText(event.target.value);
onChange?.(event.target.value);
}; };
useEffect(() => { useEffect(() => {