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:
@ -10,12 +10,15 @@ import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObje
|
||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||
import { RecordTableActionBar } from '@/object-record/record-table/action-bar/components/RecordTableActionBar';
|
||||
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 { PageAddButton } from '@/ui/layout/page/PageAddButton';
|
||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||
import { PageContainer } from '@/ui/layout/page/PageContainer';
|
||||
import { PageHeader } from '@/ui/layout/page/PageHeader';
|
||||
import { PageHotkeysEffect } from '@/ui/layout/page/PageHotkeysEffect';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
|
||||
import { RecordTableContainer } from './RecordTableContainer';
|
||||
|
||||
@ -57,11 +60,19 @@ export const RecordTablePage = () => {
|
||||
objectNameSingular,
|
||||
});
|
||||
|
||||
const recordTableId = objectNamePlural ?? '';
|
||||
|
||||
const { setSelectedTableCellEditMode } = useSelectedTableCellEditMode({
|
||||
scopeId: recordTableId,
|
||||
});
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
const handleAddButtonClick = async () => {
|
||||
await createOneObject?.({});
|
||||
};
|
||||
|
||||
const recordTableId = objectNamePlural ?? '';
|
||||
setSelectedTableCellEditMode(0, 0);
|
||||
setHotkeyScope(DEFAULT_CELL_SCOPE.scope, DEFAULT_CELL_SCOPE.customScopes);
|
||||
};
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -1,3 +1,4 @@
|
||||
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
|
||||
import { TextInput } from '@/ui/field/input/components/TextInput';
|
||||
|
||||
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
|
||||
@ -27,6 +28,8 @@ export const CurrencyFieldInput = ({
|
||||
persistCurrencyField,
|
||||
} = useCurrencyField();
|
||||
|
||||
const saveEditModeValue = useSaveFieldEditModeValue();
|
||||
|
||||
const handleEnter = (newValue: string) => {
|
||||
onEnter?.(() => {
|
||||
persistCurrencyField({
|
||||
@ -75,6 +78,13 @@ export const CurrencyFieldInput = ({
|
||||
);
|
||||
};
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
saveEditModeValue({
|
||||
amountText: newValue,
|
||||
currencyCode: initialCurrencyCode,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldInputOverlay>
|
||||
<TextInput
|
||||
@ -87,6 +97,7 @@ export const CurrencyFieldInput = ({
|
||||
onShiftTab={handleShiftTab}
|
||||
onTab={handleTab}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</FieldInputOverlay>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
|
||||
import { DateInput } from '@/ui/field/input/components/DateInput';
|
||||
import { Nullable } from '~/types/Nullable';
|
||||
|
||||
@ -20,6 +21,7 @@ export const DateFieldInput = ({
|
||||
const { fieldValue, hotkeyScope, clearable } = useDateTimeField();
|
||||
|
||||
const persistField = usePersistField();
|
||||
const saveEditModeValue = useSaveFieldEditModeValue();
|
||||
|
||||
const persistDate = (newDate: Nullable<Date>) => {
|
||||
if (!newDate) {
|
||||
@ -46,6 +48,10 @@ export const DateFieldInput = ({
|
||||
onClickOutside?.(() => persistDate(newDate));
|
||||
};
|
||||
|
||||
const handleChange = (newDate: Nullable<Date>) => {
|
||||
saveEditModeValue(newDate);
|
||||
};
|
||||
|
||||
const dateValue = fieldValue ? new Date(fieldValue) : null;
|
||||
|
||||
return (
|
||||
@ -56,6 +62,7 @@ export const DateFieldInput = ({
|
||||
onEscape={handleEscape}
|
||||
value={dateValue}
|
||||
clearable={clearable}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
|
||||
import { TextInput } from '@/ui/field/input/components/TextInput';
|
||||
|
||||
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
|
||||
@ -24,6 +25,7 @@ export const EmailFieldInput = ({
|
||||
const { fieldDefinition, initialValue, hotkeyScope } = useEmailField();
|
||||
|
||||
const persistField = usePersistField();
|
||||
const saveEditModeValue = useSaveFieldEditModeValue();
|
||||
|
||||
const handleEnter = (newText: string) => {
|
||||
onEnter?.(() => persistField(newText));
|
||||
@ -48,6 +50,10 @@ export const EmailFieldInput = ({
|
||||
onShiftTab?.(() => persistField(newText));
|
||||
};
|
||||
|
||||
const handleChange = (newText: string) => {
|
||||
saveEditModeValue(newText);
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldInputOverlay>
|
||||
<TextInput
|
||||
@ -60,6 +66,7 @@ export const EmailFieldInput = ({
|
||||
onShiftTab={handleShiftTab}
|
||||
onTab={handleTab}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</FieldInputOverlay>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
|
||||
import { useFullNameField } from '@/object-record/field/meta-types/hooks/useFullNameField';
|
||||
import { FieldDoubleText } from '@/object-record/field/types/FieldDoubleText';
|
||||
import { DoubleTextInput } from '@/ui/field/input/components/DoubleTextInput';
|
||||
@ -7,6 +8,12 @@ import { usePersistField } from '../../../hooks/usePersistField';
|
||||
|
||||
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 = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
@ -25,6 +32,8 @@ export const FullNameFieldInput = ({
|
||||
const { hotkeyScope, initialValue } = useFullNameField();
|
||||
|
||||
const persistField = usePersistField();
|
||||
const saveEditModeValue = useSaveFieldEditModeValue();
|
||||
|
||||
const convertToFullName = (newDoubleText: FieldDoubleText) => {
|
||||
return {
|
||||
firstName: newDoubleText.firstValue,
|
||||
@ -55,19 +64,28 @@ export const FullNameFieldInput = ({
|
||||
onShiftTab?.(() => persistField(convertToFullName(newDoubleText)));
|
||||
};
|
||||
|
||||
const handleChange = (newDoubleText: FieldDoubleText) => {
|
||||
saveEditModeValue(convertToFullName(newDoubleText));
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldInputOverlay>
|
||||
<DoubleTextInput
|
||||
firstValue={initialValue.firstName}
|
||||
secondValue={initialValue.lastName}
|
||||
firstValuePlaceholder={'First name'}
|
||||
secondValuePlaceholder={'Last name'}
|
||||
firstValuePlaceholder={
|
||||
FIRST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS
|
||||
}
|
||||
secondValuePlaceholder={
|
||||
LAST_NAME_PLACEHOLDER_WITH_SPECIAL_CHARACTER_TO_AVOID_PASSWORD_MANAGERS
|
||||
}
|
||||
onClickOutside={handleClickOutside}
|
||||
onEnter={handleEnter}
|
||||
onEscape={handleEscape}
|
||||
onShiftTab={handleShiftTab}
|
||||
onTab={handleTab}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</FieldInputOverlay>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
|
||||
import { TextInput } from '@/ui/field/input/components/TextInput';
|
||||
|
||||
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
|
||||
@ -22,6 +23,8 @@ export const LinkFieldInput = ({
|
||||
}: LinkFieldInputProps) => {
|
||||
const { initialValue, hotkeyScope, persistLinkField } = useLinkField();
|
||||
|
||||
const saveEditModeValue = useSaveFieldEditModeValue();
|
||||
|
||||
const handleEnter = (newURL: string) => {
|
||||
onEnter?.(() =>
|
||||
persistLinkField({
|
||||
@ -70,6 +73,13 @@ export const LinkFieldInput = ({
|
||||
);
|
||||
};
|
||||
|
||||
const handleChange = (newURL: string) => {
|
||||
saveEditModeValue({
|
||||
url: newURL,
|
||||
label: newURL,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldInputOverlay>
|
||||
<TextInput
|
||||
@ -82,6 +92,7 @@ export const LinkFieldInput = ({
|
||||
onEscape={handleEscape}
|
||||
onTab={handleTab}
|
||||
onShiftTab={handleShiftTab}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</FieldInputOverlay>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
|
||||
import { TextInput } from '@/ui/field/input/components/TextInput';
|
||||
|
||||
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
|
||||
@ -23,6 +24,8 @@ export const NumberFieldInput = ({
|
||||
const { fieldDefinition, initialValue, hotkeyScope, persistNumberField } =
|
||||
useNumberField();
|
||||
|
||||
const saveEditModeValue = useSaveFieldEditModeValue();
|
||||
|
||||
const handleEnter = (newText: string) => {
|
||||
onEnter?.(() => persistNumberField(newText));
|
||||
};
|
||||
@ -46,6 +49,10 @@ export const NumberFieldInput = ({
|
||||
onShiftTab?.(() => persistNumberField(newText));
|
||||
};
|
||||
|
||||
const handleChange = (newText: string) => {
|
||||
saveEditModeValue(newText);
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldInputOverlay>
|
||||
<TextInput
|
||||
@ -58,6 +65,7 @@ export const NumberFieldInput = ({
|
||||
onShiftTab={handleShiftTab}
|
||||
onTab={handleTab}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</FieldInputOverlay>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
|
||||
import { PhoneInput } from '@/ui/field/input/components/PhoneInput';
|
||||
|
||||
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
|
||||
@ -23,6 +24,8 @@ export const PhoneFieldInput = ({
|
||||
const { fieldDefinition, initialValue, hotkeyScope, persistPhoneField } =
|
||||
usePhoneField();
|
||||
|
||||
const saveEditModeValue = useSaveFieldEditModeValue();
|
||||
|
||||
const handleEnter = (newText: string) => {
|
||||
onEnter?.(() => persistPhoneField(newText));
|
||||
};
|
||||
@ -46,6 +49,10 @@ export const PhoneFieldInput = ({
|
||||
onShiftTab?.(() => persistPhoneField(newText));
|
||||
};
|
||||
|
||||
const handleChange = (newText: string) => {
|
||||
saveEditModeValue(newText);
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldInputOverlay>
|
||||
<PhoneInput
|
||||
@ -58,6 +65,7 @@ export const PhoneFieldInput = ({
|
||||
onShiftTab={handleShiftTab}
|
||||
onTab={handleTab}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</FieldInputOverlay>
|
||||
);
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useSaveFieldEditModeValue } from '@/object-record/field/hooks/useSaveFieldEditModeValue';
|
||||
import { TextInput } from '@/ui/field/input/components/TextInput';
|
||||
|
||||
import { FieldInputOverlay } from '../../../../../ui/field/input/components/FieldInputOverlay';
|
||||
@ -24,6 +25,7 @@ export const TextFieldInput = ({
|
||||
const { fieldDefinition, initialValue, hotkeyScope } = useTextField();
|
||||
|
||||
const persistField = usePersistField();
|
||||
const saveEditModeValue = useSaveFieldEditModeValue();
|
||||
|
||||
const handleEnter = (newText: string) => {
|
||||
onEnter?.(() => persistField(newText));
|
||||
@ -48,6 +50,10 @@ export const TextFieldInput = ({
|
||||
onShiftTab?.(() => persistField(newText));
|
||||
};
|
||||
|
||||
const handleChange = (newText: string) => {
|
||||
saveEditModeValue(newText);
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldInputOverlay>
|
||||
<TextInput
|
||||
@ -60,6 +66,7 @@ export const TextFieldInput = ({
|
||||
onShiftTab={handleShiftTab}
|
||||
onTab={handleTab}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</FieldInputOverlay>
|
||||
);
|
||||
|
||||
@ -0,0 +1,9 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
export const entityFieldsEditModeValueFamilyState = atomFamily<
|
||||
Record<string, unknown> | null,
|
||||
string
|
||||
>({
|
||||
key: 'entityFieldsEditModeValueFamilyState',
|
||||
default: null,
|
||||
});
|
||||
@ -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,
|
||||
})),
|
||||
});
|
||||
@ -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,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
@ -1,28 +1,10 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import { isFieldFullName } from '@/object-record/field/types/guards/isFieldFullName';
|
||||
import { isFieldFullNameValue } from '@/object-record/field/types/guards/isFieldFullNameValue';
|
||||
import { isFieldSelect } from '@/object-record/field/types/guards/isFieldSelect';
|
||||
import { isFieldUuid } from '@/object-record/field/types/guards/isFieldUuid';
|
||||
import { assertNotNull } from '~/utils/assert';
|
||||
import { entityFieldsFamilyState } from '@/object-record/field/states/entityFieldsFamilyState';
|
||||
import { isFieldValueEmpty } from '@/object-record/field/utils/isFieldValueEmpty';
|
||||
|
||||
import { FieldDefinition } from '../../types/FieldDefinition';
|
||||
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({
|
||||
key: 'isEntityFieldEmptyFamilySelector',
|
||||
@ -36,57 +18,12 @@ export const isEntityFieldEmptyFamilySelector = selectorFamily({
|
||||
entityId: string;
|
||||
}) => {
|
||||
return ({ get }) => {
|
||||
if (
|
||||
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;
|
||||
const fieldValue = get(entityFieldsFamilyState(entityId))?.[fieldName];
|
||||
|
||||
return isValueEmpty(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}}`,
|
||||
);
|
||||
return isFieldValueEmpty({
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
});
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
@ -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}}`,
|
||||
);
|
||||
};
|
||||
@ -4,9 +4,11 @@ import { useRecoilCallback, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||
import { RecordTable } from '@/object-record/record-table/components/RecordTable';
|
||||
import { RecordTableFirstColumnScrollObserver } from '@/object-record/record-table/components/RecordTableFirstColumnScrollObserver';
|
||||
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 { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector';
|
||||
import { IconPlus } from '@/ui/display/icon';
|
||||
@ -122,6 +124,8 @@ export const RecordTableWithWrappers = ({
|
||||
|
||||
const { persistViewFields } = useViewFields(viewBarId);
|
||||
|
||||
const { deleteOneRecord } = useDeleteOneRecord({ objectNameSingular });
|
||||
|
||||
return (
|
||||
<RecordTableScope
|
||||
recordTableScopeId={recordTableId}
|
||||
@ -129,42 +133,44 @@ export const RecordTableWithWrappers = ({
|
||||
persistViewFields(mapColumnDefinitionsToViewFields(columns));
|
||||
})}
|
||||
>
|
||||
<ScrollWrapper>
|
||||
<RecordTableRefContextWrapper>
|
||||
<RecordTableFirstColumnScrollObserver />
|
||||
<RecordUpdateContext.Provider value={updateRecordMutation}>
|
||||
<StyledTableWithHeader>
|
||||
<StyledTableContainer>
|
||||
<div ref={tableBodyRef}>
|
||||
<RecordTable createRecord={createRecord} />
|
||||
<DragSelect
|
||||
dragSelectable={tableBodyRef}
|
||||
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}
|
||||
<EntityDeleteContext.Provider value={deleteOneRecord}>
|
||||
<ScrollWrapper>
|
||||
<RecordTableRefContextWrapper>
|
||||
<RecordTableFirstColumnScrollObserver />
|
||||
<RecordUpdateContext.Provider value={updateRecordMutation}>
|
||||
<StyledTableWithHeader>
|
||||
<StyledTableContainer>
|
||||
<div ref={tableBodyRef}>
|
||||
<RecordTable createRecord={createRecord} />
|
||||
<DragSelect
|
||||
dragSelectable={tableBodyRef}
|
||||
onDragSelectionStart={resetTableRowSelection}
|
||||
onDragSelectionChange={setRowSelectedState}
|
||||
/>
|
||||
</StyledObjectEmptyContainer>
|
||||
)}
|
||||
</StyledTableContainer>
|
||||
</StyledTableWithHeader>
|
||||
</RecordUpdateContext.Provider>
|
||||
</RecordTableRefContextWrapper>
|
||||
</ScrollWrapper>
|
||||
</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>
|
||||
)}
|
||||
</StyledTableContainer>
|
||||
</StyledTableWithHeader>
|
||||
</RecordUpdateContext.Provider>
|
||||
</RecordTableRefContextWrapper>
|
||||
</ScrollWrapper>
|
||||
</EntityDeleteContext.Provider>
|
||||
</RecordTableScope>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
import { createContext } from 'react';
|
||||
|
||||
export const EntityDeleteContext = createContext<
|
||||
(idToDelete: string) => Promise<unknown>
|
||||
>(async () => {});
|
||||
@ -1,6 +1,10 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldDisplay } from '@/object-record/field/components/FieldDisplay';
|
||||
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 { ColumnIndexContext } from '@/object-record/record-table/contexts/ColumnIndexContext';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
|
||||
import { useRecordTable } from '../../hooks/useRecordTable';
|
||||
@ -17,14 +21,25 @@ export const RecordTableCell = ({
|
||||
|
||||
const { moveLeft, moveRight, moveDown } = useRecordTable();
|
||||
|
||||
const isFirstColumnCell = useContext(ColumnIndexContext) === 0;
|
||||
const isEditModeValueEmpty = useIsFieldEditModeValueEmpty();
|
||||
|
||||
const skipFieldPersist = isFirstColumnCell && isEditModeValueEmpty;
|
||||
|
||||
const handleEnter: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
if (!skipFieldPersist) {
|
||||
persistField();
|
||||
}
|
||||
|
||||
closeTableCell();
|
||||
moveDown();
|
||||
};
|
||||
|
||||
const handleSubmit: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
if (!skipFieldPersist) {
|
||||
persistField();
|
||||
}
|
||||
|
||||
closeTableCell();
|
||||
};
|
||||
|
||||
@ -33,24 +48,35 @@ export const RecordTableCell = ({
|
||||
};
|
||||
|
||||
const handleClickOutside: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
if (!skipFieldPersist) {
|
||||
persistField();
|
||||
}
|
||||
|
||||
closeTableCell();
|
||||
};
|
||||
|
||||
const handleEscape: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
if (!skipFieldPersist) {
|
||||
persistField();
|
||||
}
|
||||
|
||||
closeTableCell();
|
||||
};
|
||||
|
||||
const handleTab: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
if (!skipFieldPersist) {
|
||||
persistField();
|
||||
}
|
||||
|
||||
closeTableCell();
|
||||
moveRight();
|
||||
};
|
||||
|
||||
const handleShiftTab: FieldInputEvent = (persistField) => {
|
||||
persistField();
|
||||
if (!skipFieldPersist) {
|
||||
persistField();
|
||||
}
|
||||
|
||||
closeTableCell();
|
||||
moveLeft();
|
||||
};
|
||||
|
||||
@ -33,13 +33,17 @@ export const useMoveSoftFocusToCurrentCellOnHover = () => {
|
||||
currentTableCellInEditModePositionScopeInjector,
|
||||
);
|
||||
|
||||
const isSomeCellInEditMode = snapshot.getLoadable(
|
||||
isTableCellInEditModeFamilyState(currentTableCellInEditModePosition),
|
||||
);
|
||||
const isSomeCellInEditMode = snapshot
|
||||
.getLoadable(
|
||||
isTableCellInEditModeFamilyState(
|
||||
currentTableCellInEditModePosition,
|
||||
),
|
||||
)
|
||||
.getValue();
|
||||
|
||||
const currentHotkeyScope = snapshot
|
||||
.getLoadable(currentHotkeyScopeState)
|
||||
.valueOrThrow();
|
||||
.getValue();
|
||||
|
||||
if (
|
||||
currentHotkeyScope.scope !== TableHotkeyScope.TableSoftFocus &&
|
||||
@ -49,7 +53,7 @@ export const useMoveSoftFocusToCurrentCellOnHover = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isSomeCellInEditMode.contents) {
|
||||
if (!isSomeCellInEditMode) {
|
||||
setSoftFocusOnCurrentTableCell();
|
||||
}
|
||||
},
|
||||
|
||||
@ -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 };
|
||||
};
|
||||
@ -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],
|
||||
);
|
||||
};
|
||||
@ -1,43 +1,13 @@
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
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 { useSetSoftFocus } from '@/object-record/record-table/record-table-cell/hooks/useSetSoftFocus';
|
||||
|
||||
import { useCurrentTableCellPosition } from './useCurrentCellPosition';
|
||||
|
||||
export const useSetSoftFocusOnCurrentTableCell = () => {
|
||||
const { setSoftFocusPosition } = useRecordTable();
|
||||
|
||||
const { isSoftFocusActiveScopeInjector } = getRecordTableScopeInjector();
|
||||
|
||||
const { injectStateWithRecordTableScopeId } = useRecordTableScopedStates();
|
||||
|
||||
const isSoftFocusActiveState = injectStateWithRecordTableScopeId(
|
||||
isSoftFocusActiveScopeInjector,
|
||||
);
|
||||
const setSoftFocus = useSetSoftFocus();
|
||||
|
||||
const currentTableCellPosition = useCurrentTableCellPosition();
|
||||
|
||||
const setHotkeyScope = useSetHotkeyScope();
|
||||
|
||||
return useRecoilCallback(
|
||||
({ set }) =>
|
||||
() => {
|
||||
setSoftFocusPosition(currentTableCellPosition);
|
||||
|
||||
set(isSoftFocusActiveState, true);
|
||||
|
||||
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
|
||||
},
|
||||
[
|
||||
setSoftFocusPosition,
|
||||
currentTableCellPosition,
|
||||
isSoftFocusActiveState,
|
||||
setHotkeyScope,
|
||||
],
|
||||
);
|
||||
return () => {
|
||||
setSoftFocus(currentTableCellPosition);
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import { useContext } from 'react';
|
||||
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 { useIsFieldEditModeValueEmpty } from '@/object-record/field/hooks/useIsFieldEditModeValueEmpty';
|
||||
import { useIsFieldEmpty } from '@/object-record/field/hooks/useIsFieldEmpty';
|
||||
import { entityFieldInitialValueFamilyState } from '@/object-record/field/states/entityFieldInitialValueFamilyState';
|
||||
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 { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||
import { getRecordTableScopeInjector } from '@/object-record/record-table/utils/getRecordTableScopeInjector';
|
||||
@ -20,7 +22,7 @@ import { TableHotkeyScope } from '../../types/TableHotkeyScope';
|
||||
|
||||
import { useCurrentTableCellEditMode } from './useCurrentTableCellEditMode';
|
||||
|
||||
const DEFAULT_CELL_SCOPE: HotkeyScope = {
|
||||
export const DEFAULT_CELL_SCOPE: HotkeyScope = {
|
||||
scope: TableHotkeyScope.CellEditMode,
|
||||
};
|
||||
|
||||
@ -51,9 +53,12 @@ export const useTableCell = () => {
|
||||
const isFirstColumnCell = useContext(ColumnIndexContext) === 0;
|
||||
|
||||
const isEmpty = useIsFieldEmpty();
|
||||
const isEditModeValueEmpty = useIsFieldEditModeValueEmpty();
|
||||
|
||||
const { entityId, fieldDefinition } = useContext(FieldContext);
|
||||
|
||||
const deleteOneRecord = useContext(EntityDeleteContext);
|
||||
|
||||
const [, setFieldInitialValue] = useRecoilState(
|
||||
entityFieldInitialValueFamilyState({
|
||||
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 }) => {
|
||||
if (isFirstColumnCell && !isEmpty && basePathToShowPage) {
|
||||
navigate(`${basePathToShowPage}${entityId}`);
|
||||
@ -84,11 +99,15 @@ export const useTableCell = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const closeTableCell = () => {
|
||||
const closeTableCell = async () => {
|
||||
setDragSelectionStartEnabled(true);
|
||||
closeCurrentTableCellInEditMode();
|
||||
setFieldInitialValue(undefined);
|
||||
setHotkeyScope(TableHotkeyScope.TableSoftFocus);
|
||||
|
||||
if (isFirstColumnCell && isEditModeValueEmpty) {
|
||||
await deleteRow();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
@ -38,6 +38,7 @@ export type DateInputProps = {
|
||||
) => void;
|
||||
hotkeyScope: string;
|
||||
clearable?: boolean;
|
||||
onChange?: (newDate: Nullable<Date>) => void;
|
||||
};
|
||||
|
||||
export const DateInput = ({
|
||||
@ -47,6 +48,7 @@ export const DateInput = ({
|
||||
onEscape,
|
||||
onClickOutside,
|
||||
clearable,
|
||||
onChange,
|
||||
}: DateInputProps) => {
|
||||
const theme = useTheme();
|
||||
|
||||
@ -66,6 +68,7 @@ export const DateInput = ({
|
||||
|
||||
const handleChange = (newDate: Date) => {
|
||||
setInternalValue(newDate);
|
||||
onChange?.(newDate);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -38,6 +38,7 @@ type DoubleTextInputProps = {
|
||||
event: MouseEvent | TouchEvent,
|
||||
newDoubleTextValue: FieldDoubleText,
|
||||
) => void;
|
||||
onChange?: (newDoubleTextValue: FieldDoubleText) => void;
|
||||
};
|
||||
|
||||
export const DoubleTextInput = ({
|
||||
@ -51,6 +52,7 @@ export const DoubleTextInput = ({
|
||||
onEscape,
|
||||
onShiftTab,
|
||||
onTab,
|
||||
onChange,
|
||||
}: DoubleTextInputProps) => {
|
||||
const [firstInternalValue, setFirstInternalValue] = useState(firstValue);
|
||||
const [secondInternalValue, setSecondInternalValue] = useState(secondValue);
|
||||
@ -70,6 +72,11 @@ export const DoubleTextInput = ({
|
||||
): void => {
|
||||
setFirstInternalValue(newFirstValue);
|
||||
setSecondInternalValue(newSecondValue);
|
||||
|
||||
onChange?.({
|
||||
firstValue: newFirstValue,
|
||||
secondValue: newSecondValue,
|
||||
});
|
||||
};
|
||||
|
||||
const [focusPosition, setFocusPosition] = useState<'left' | 'right'>('left');
|
||||
|
||||
@ -54,6 +54,7 @@ export type PhoneInputProps = {
|
||||
onTab?: (newText: string) => void;
|
||||
onShiftTab?: (newText: string) => void;
|
||||
onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void;
|
||||
onChange?: (newText: string) => void;
|
||||
hotkeyScope: string;
|
||||
};
|
||||
|
||||
@ -66,11 +67,17 @@ export const PhoneInput = ({
|
||||
onShiftTab,
|
||||
onClickOutside,
|
||||
hotkeyScope,
|
||||
onChange,
|
||||
}: PhoneInputProps) => {
|
||||
const [internalValue, setInternalValue] = useState<string | undefined>(value);
|
||||
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
setInternalValue(newValue);
|
||||
onChange?.(newValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setInternalValue(value);
|
||||
}, [value]);
|
||||
@ -92,7 +99,7 @@ export const PhoneInput = ({
|
||||
autoFocus={autoFocus}
|
||||
placeholder="Phone number"
|
||||
value={value}
|
||||
onChange={setInternalValue}
|
||||
onChange={handleChange}
|
||||
international={true}
|
||||
withCountryCallingCode={true}
|
||||
countrySelectComponent={CountryPickerDropdownButton}
|
||||
|
||||
@ -21,6 +21,7 @@ type TextInputProps = {
|
||||
onShiftTab?: (newText: string) => void;
|
||||
onClickOutside: (event: MouseEvent | TouchEvent, inputValue: string) => void;
|
||||
hotkeyScope: string;
|
||||
onChange?: (newText: string) => void;
|
||||
};
|
||||
|
||||
export const TextInput = ({
|
||||
@ -33,6 +34,7 @@ export const TextInput = ({
|
||||
onTab,
|
||||
onShiftTab,
|
||||
onClickOutside,
|
||||
onChange,
|
||||
}: TextInputProps) => {
|
||||
const [internalText, setInternalText] = useState(value);
|
||||
|
||||
@ -40,6 +42,7 @@ export const TextInput = ({
|
||||
|
||||
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
setInternalText(event.target.value);
|
||||
onChange?.(event.target.value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user