feat: address composite field (#4492)
Added new Address field input type. --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -8,4 +8,5 @@ export type FilterType =
|
||||
| 'FULL_NAME'
|
||||
| 'LINK'
|
||||
| 'RELATION'
|
||||
| 'ADDRESS'
|
||||
| 'SELECT';
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { FieldContext } from '../contexts/FieldContext';
|
||||
import { AddressFieldDisplay } from '../meta-types/display/components/AddressFieldDisplay';
|
||||
import { ChipFieldDisplay } from '../meta-types/display/components/ChipFieldDisplay';
|
||||
import { CurrencyFieldDisplay } from '../meta-types/display/components/CurrencyFieldDisplay';
|
||||
import { DateFieldDisplay } from '../meta-types/display/components/DateFieldDisplay';
|
||||
@ -13,6 +14,7 @@ import { RelationFieldDisplay } from '../meta-types/display/components/RelationF
|
||||
import { SelectFieldDisplay } from '../meta-types/display/components/SelectFieldDisplay';
|
||||
import { TextFieldDisplay } from '../meta-types/display/components/TextFieldDisplay';
|
||||
import { UuidFieldDisplay } from '../meta-types/display/components/UuidFieldDisplay';
|
||||
import { isFieldAddress } from '../types/guards/isFieldAddress';
|
||||
import { isFieldCurrency } from '../types/guards/isFieldCurrency';
|
||||
import { isFieldDateTime } from '../types/guards/isFieldDateTime';
|
||||
import { isFieldEmail } from '../types/guards/isFieldEmail';
|
||||
@ -55,5 +57,7 @@ export const FieldDisplay = () => {
|
||||
<PhoneFieldDisplay />
|
||||
) : isFieldSelect(fieldDefinition) ? (
|
||||
<SelectFieldDisplay />
|
||||
) : isFieldAddress(fieldDefinition) ? (
|
||||
<AddressFieldDisplay />
|
||||
) : null;
|
||||
};
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { AddressFieldInput } from '@/object-record/record-field/meta-types/input/components/AddressFieldInput';
|
||||
import { FullNameFieldInput } from '@/object-record/record-field/meta-types/input/components/FullNameFieldInput';
|
||||
import { SelectFieldInput } from '@/object-record/record-field/meta-types/input/components/SelectFieldInput';
|
||||
import { RecordFieldInputScope } from '@/object-record/record-field/scopes/RecordFieldInputScope';
|
||||
@ -19,6 +20,7 @@ import { RatingFieldInput } from '../meta-types/input/components/RatingFieldInpu
|
||||
import { RelationFieldInput } from '../meta-types/input/components/RelationFieldInput';
|
||||
import { TextFieldInput } from '../meta-types/input/components/TextFieldInput';
|
||||
import { FieldInputEvent } from '../types/FieldInputEvent';
|
||||
import { isFieldAddress } from '../types/guards/isFieldAddress';
|
||||
import { isFieldBoolean } from '../types/guards/isFieldBoolean';
|
||||
import { isFieldCurrency } from '../types/guards/isFieldCurrency';
|
||||
import { isFieldDateTime } from '../types/guards/isFieldDateTime';
|
||||
@ -127,6 +129,14 @@ export const FieldInput = ({
|
||||
<RatingFieldInput onSubmit={onSubmit} />
|
||||
) : isFieldSelect(fieldDefinition) ? (
|
||||
<SelectFieldInput onSubmit={onSubmit} onCancel={onCancel} />
|
||||
) : isFieldAddress(fieldDefinition) ? (
|
||||
<AddressFieldInput
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
onClickOutside={onClickOutside}
|
||||
onTab={onTab}
|
||||
onShiftTab={onShiftTab}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilCallback } from 'recoil';
|
||||
|
||||
import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress';
|
||||
import { isFieldAddressValue } from '@/object-record/record-field/types/guards/isFieldAddressValue';
|
||||
import { isFieldFullName } from '@/object-record/record-field/types/guards/isFieldFullName';
|
||||
import { isFieldFullNameValue } from '@/object-record/record-field/types/guards/isFieldFullNameValue';
|
||||
import { isFieldSelect } from '@/object-record/record-field/types/guards/isFieldSelect';
|
||||
@ -82,6 +84,10 @@ export const usePersistField = () => {
|
||||
const fieldIsSelect =
|
||||
isFieldSelect(fieldDefinition) && isFieldSelectValue(valueToPersist);
|
||||
|
||||
const fieldIsAddress =
|
||||
isFieldAddress(fieldDefinition) &&
|
||||
isFieldAddressValue(valueToPersist);
|
||||
|
||||
if (
|
||||
fieldIsRelation ||
|
||||
fieldIsText ||
|
||||
@ -94,7 +100,8 @@ export const usePersistField = () => {
|
||||
fieldIsLink ||
|
||||
fieldIsCurrency ||
|
||||
fieldIsFullName ||
|
||||
fieldIsSelect
|
||||
fieldIsSelect ||
|
||||
fieldIsAddress
|
||||
) {
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
set(
|
||||
|
||||
@ -0,0 +1,17 @@
|
||||
import { useAddressField } from '@/object-record/record-field/meta-types/hooks/useAddressField';
|
||||
import { TextDisplay } from '@/ui/field/display/components/TextDisplay';
|
||||
|
||||
export const AddressFieldDisplay = () => {
|
||||
const { fieldValue } = useAddressField();
|
||||
|
||||
const content = [
|
||||
fieldValue?.addressStreet1,
|
||||
fieldValue?.addressStreet2,
|
||||
fieldValue?.addressCity,
|
||||
fieldValue?.addressCountry,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(', ');
|
||||
|
||||
return <TextDisplay text={content} />;
|
||||
};
|
||||
@ -0,0 +1,57 @@
|
||||
import { useContext } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { useRecordFieldInput } from '@/object-record/record-field/hooks/useRecordFieldInput';
|
||||
import { isFieldAddressValue } from '@/object-record/record-field/types/guards/isFieldAddressValue';
|
||||
import { recordStoreFamilySelector } from '@/object-record/record-store/states/selectors/recordStoreFamilySelector';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldContext } from '../../contexts/FieldContext';
|
||||
import { usePersistField } from '../../hooks/usePersistField';
|
||||
import { FieldAddressValue } from '../../types/FieldMetadata';
|
||||
import { assertFieldMetadata } from '../../types/guards/assertFieldMetadata';
|
||||
import { isFieldAddress } from '../../types/guards/isFieldAddress';
|
||||
|
||||
export const useAddressField = () => {
|
||||
const { entityId, fieldDefinition, hotkeyScope } = useContext(FieldContext);
|
||||
|
||||
assertFieldMetadata(
|
||||
FieldMetadataType.Address,
|
||||
isFieldAddress,
|
||||
fieldDefinition,
|
||||
);
|
||||
|
||||
const fieldName = fieldDefinition.metadata.fieldName;
|
||||
|
||||
const [fieldValue, setFieldValue] = useRecoilState<FieldAddressValue>(
|
||||
recordStoreFamilySelector({
|
||||
recordId: entityId,
|
||||
fieldName: fieldName,
|
||||
}),
|
||||
);
|
||||
|
||||
const persistField = usePersistField();
|
||||
|
||||
const persistAddressField = (newValue: FieldAddressValue) => {
|
||||
if (!isFieldAddressValue(newValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
persistField(newValue);
|
||||
};
|
||||
|
||||
const { setDraftValue, getDraftValueSelector } =
|
||||
useRecordFieldInput<FieldAddressValue>(`${entityId}-${fieldName}`);
|
||||
|
||||
const draftValue = useRecoilValue(getDraftValueSelector());
|
||||
|
||||
return {
|
||||
fieldDefinition,
|
||||
fieldValue,
|
||||
setFieldValue,
|
||||
draftValue,
|
||||
setDraftValue,
|
||||
hotkeyScope,
|
||||
persistAddressField,
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,85 @@
|
||||
import { useAddressField } from '@/object-record/record-field/meta-types/hooks/useAddressField';
|
||||
import { FieldAddressDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
|
||||
import { AddressInput } from '@/ui/field/input/components/AddressInput';
|
||||
import { FieldInputOverlay } from '@/ui/field/input/components/FieldInputOverlay';
|
||||
|
||||
import { usePersistField } from '../../../hooks/usePersistField';
|
||||
|
||||
import { FieldInputEvent } from './DateFieldInput';
|
||||
|
||||
export type AddressFieldInputProps = {
|
||||
onClickOutside?: FieldInputEvent;
|
||||
onEnter?: FieldInputEvent;
|
||||
onEscape?: FieldInputEvent;
|
||||
onTab?: FieldInputEvent;
|
||||
onShiftTab?: FieldInputEvent;
|
||||
};
|
||||
|
||||
export const AddressFieldInput = ({
|
||||
onEnter,
|
||||
onEscape,
|
||||
onClickOutside,
|
||||
onTab,
|
||||
onShiftTab,
|
||||
}: AddressFieldInputProps) => {
|
||||
const { hotkeyScope, draftValue, setDraftValue } = useAddressField();
|
||||
|
||||
const persistField = usePersistField();
|
||||
|
||||
const convertToAddress = (
|
||||
newAddress: FieldAddressDraftValue | undefined,
|
||||
): FieldAddressDraftValue => {
|
||||
return {
|
||||
addressStreet1: newAddress?.addressStreet1 ?? '',
|
||||
addressStreet2: newAddress?.addressStreet2 ?? null,
|
||||
addressCity: newAddress?.addressCity ?? null,
|
||||
addressState: newAddress?.addressState ?? null,
|
||||
addressCountry: newAddress?.addressCountry ?? null,
|
||||
addressPostcode: newAddress?.addressPostcode ?? null,
|
||||
addressLat: newAddress?.addressLat ?? null,
|
||||
addressLng: newAddress?.addressLng ?? null,
|
||||
};
|
||||
};
|
||||
|
||||
const handleEnter = (newAddress: FieldAddressDraftValue) => {
|
||||
onEnter?.(() => persistField(convertToAddress(newAddress)));
|
||||
};
|
||||
|
||||
const handleTab = (newAddress: FieldAddressDraftValue) => {
|
||||
onTab?.(() => persistField(convertToAddress(newAddress)));
|
||||
};
|
||||
|
||||
const handleShiftTab = (newAddress: FieldAddressDraftValue) => {
|
||||
onShiftTab?.(() => persistField(convertToAddress(newAddress)));
|
||||
};
|
||||
|
||||
const handleEscape = (newAddress: FieldAddressDraftValue) => {
|
||||
onEscape?.(() => persistField(convertToAddress(newAddress)));
|
||||
};
|
||||
|
||||
const handleClickOutside = (
|
||||
event: MouseEvent | TouchEvent,
|
||||
newAddress: FieldAddressDraftValue,
|
||||
) => {
|
||||
onClickOutside?.(() => persistField(convertToAddress(newAddress)));
|
||||
};
|
||||
|
||||
const handleChange = (newAddress: FieldAddressDraftValue) => {
|
||||
setDraftValue(convertToAddress(newAddress));
|
||||
};
|
||||
|
||||
return (
|
||||
<FieldInputOverlay>
|
||||
<AddressInput
|
||||
value={convertToAddress(draftValue)}
|
||||
onClickOutside={handleClickOutside}
|
||||
onEnter={handleEnter}
|
||||
onEscape={handleEscape}
|
||||
hotkeyScope={hotkeyScope}
|
||||
onChange={handleChange}
|
||||
onTab={handleTab}
|
||||
onShiftTab={handleShiftTab}
|
||||
/>
|
||||
</FieldInputOverlay>
|
||||
);
|
||||
};
|
||||
@ -0,0 +1,137 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Decorator, Meta, StoryObj } from '@storybook/react';
|
||||
import { expect, fn, userEvent, waitFor } from '@storybook/test';
|
||||
|
||||
import { useAddressField } from '@/object-record/record-field/meta-types/hooks/useAddressField';
|
||||
import { FieldAddressDraftValue } from '@/object-record/record-field/types/FieldInputDraftValue';
|
||||
import {
|
||||
AddressInput,
|
||||
AddressInputProps,
|
||||
} from '@/ui/field/input/components/AddressInput';
|
||||
import { useSetHotkeyScope } from '@/ui/utilities/hotkey/hooks/useSetHotkeyScope';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldContextProvider } from '../../../__stories__/FieldContextProvider';
|
||||
|
||||
const AddressValueSetterEffect = ({
|
||||
value,
|
||||
}: {
|
||||
value: FieldAddressDraftValue;
|
||||
}) => {
|
||||
const { setFieldValue } = useAddressField();
|
||||
|
||||
useEffect(() => {
|
||||
setFieldValue(value);
|
||||
}, [setFieldValue, value]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
type AddressInputWithContextProps = AddressInputProps & {
|
||||
value: string;
|
||||
entityId?: string;
|
||||
};
|
||||
|
||||
const AddressInputWithContext = ({
|
||||
entityId,
|
||||
value,
|
||||
onEnter,
|
||||
onEscape,
|
||||
onClickOutside,
|
||||
onTab,
|
||||
onShiftTab,
|
||||
}: AddressInputWithContextProps) => {
|
||||
const setHotKeyScope = useSetHotkeyScope();
|
||||
|
||||
useEffect(() => {
|
||||
setHotKeyScope('hotkey-scope');
|
||||
}, [setHotKeyScope]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FieldContextProvider
|
||||
fieldDefinition={{
|
||||
fieldMetadataId: 'text',
|
||||
label: 'Address',
|
||||
type: FieldMetadataType.Address,
|
||||
iconName: 'IconTag',
|
||||
metadata: {
|
||||
fieldName: 'Address',
|
||||
placeHolder: 'Enter text',
|
||||
},
|
||||
}}
|
||||
entityId={entityId}
|
||||
>
|
||||
<AddressValueSetterEffect value={value} />
|
||||
<AddressInput
|
||||
onEnter={onEnter}
|
||||
onEscape={onEscape}
|
||||
onClickOutside={onClickOutside}
|
||||
value={value}
|
||||
hotkeyScope=""
|
||||
onTab={onTab}
|
||||
onShiftTab={onShiftTab}
|
||||
/>
|
||||
</FieldContextProvider>
|
||||
<div data-testid="data-field-input-click-outside-div" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const enterJestFn = fn();
|
||||
const escapeJestfn = fn();
|
||||
const clickOutsideJestFn = fn();
|
||||
const tabJestFn = fn();
|
||||
const shiftTabJestFn = fn();
|
||||
|
||||
const clearMocksDecorator: Decorator = (Story, context) => {
|
||||
if (context.parameters.clearMocks === true) {
|
||||
enterJestFn.mockClear();
|
||||
escapeJestfn.mockClear();
|
||||
clickOutsideJestFn.mockClear();
|
||||
tabJestFn.mockClear();
|
||||
shiftTabJestFn.mockClear();
|
||||
}
|
||||
return <Story />;
|
||||
};
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'UI/Data/Field/Input/AddressInput',
|
||||
component: AddressInputWithContext,
|
||||
args: {
|
||||
value: 'text',
|
||||
onEnter: enterJestFn,
|
||||
onEscape: escapeJestfn,
|
||||
onClickOutside: clickOutsideJestFn,
|
||||
onTab: tabJestFn,
|
||||
onShiftTab: shiftTabJestFn,
|
||||
},
|
||||
argTypes: {
|
||||
onEnter: { control: false },
|
||||
onEscape: { control: false },
|
||||
onClickOutside: { control: false },
|
||||
onTab: { control: false },
|
||||
onShiftTab: { control: false },
|
||||
},
|
||||
decorators: [clearMocksDecorator],
|
||||
parameters: {
|
||||
clearMocks: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof AddressInputWithContext>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Enter: Story = {
|
||||
play: async () => {
|
||||
expect(enterJestFn).toHaveBeenCalledTimes(0);
|
||||
|
||||
await waitFor(() => {
|
||||
userEvent.keyboard('{enter}');
|
||||
expect(enterJestFn).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
},
|
||||
};
|
||||
@ -1,5 +1,6 @@
|
||||
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
|
||||
import {
|
||||
FieldAddressValue,
|
||||
FieldBooleanValue,
|
||||
FieldCurrencyValue,
|
||||
FieldDateTimeValue,
|
||||
@ -28,6 +29,16 @@ export type FieldCurrencyDraftValue = {
|
||||
amount: string;
|
||||
};
|
||||
export type FieldFullNameDraftValue = { firstName: string; lastName: string };
|
||||
export type FieldAddressDraftValue = {
|
||||
addressStreet1: string;
|
||||
addressStreet2: string | null;
|
||||
addressCity: string | null;
|
||||
addressState: string | null;
|
||||
addressPostcode: string | null;
|
||||
addressCountry: string | null;
|
||||
addressLat: number | null;
|
||||
addressLng: number | null;
|
||||
};
|
||||
|
||||
export type FieldInputDraftValue<FieldValue> = FieldValue extends FieldTextValue
|
||||
? FieldTextDraftValue
|
||||
@ -55,4 +66,6 @@ export type FieldInputDraftValue<FieldValue> = FieldValue extends FieldTextValue
|
||||
? FieldSelectDraftValue
|
||||
: FieldValue extends FieldRelationValue
|
||||
? FieldRelationDraftValue
|
||||
: never;
|
||||
: FieldValue extends FieldAddressValue
|
||||
? FieldAddressDraftValue
|
||||
: never;
|
||||
|
||||
@ -69,6 +69,12 @@ export type FieldRatingMetadata = {
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type FieldAddressMetadata = {
|
||||
objectMetadataNameSingular?: string;
|
||||
placeHolder: string;
|
||||
fieldName: string;
|
||||
};
|
||||
|
||||
export type FieldRawJsonMetadata = {
|
||||
objectMetadataNameSingular?: string;
|
||||
fieldName: string;
|
||||
@ -109,7 +115,8 @@ export type FieldMetadata =
|
||||
| FieldRelationMetadata
|
||||
| FieldSelectMetadata
|
||||
| FieldTextMetadata
|
||||
| FieldUuidMetadata;
|
||||
| FieldUuidMetadata
|
||||
| FieldAddressMetadata;
|
||||
|
||||
export type FieldTextValue = string;
|
||||
export type FieldUUidValue = string;
|
||||
@ -125,6 +132,16 @@ export type FieldCurrencyValue = {
|
||||
amountMicros: number | null;
|
||||
};
|
||||
export type FieldFullNameValue = { firstName: string; lastName: string };
|
||||
export type FieldAddressValue = {
|
||||
addressStreet1: string;
|
||||
addressStreet2: string | null;
|
||||
addressCity: string | null;
|
||||
addressState: string | null;
|
||||
addressPostcode: string | null;
|
||||
addressCountry: string | null;
|
||||
addressLat: number | null;
|
||||
addressLng: number | null;
|
||||
};
|
||||
export type FieldRatingValue = (typeof RATING_VALUES)[number];
|
||||
export type FieldSelectValue = string | null;
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import { FieldDefinition } from '../FieldDefinition';
|
||||
import {
|
||||
FieldAddressMetadata,
|
||||
FieldBooleanMetadata,
|
||||
FieldCurrencyMetadata,
|
||||
FieldDateTimeMetadata,
|
||||
@ -49,9 +50,11 @@ type AssertFieldMetadataFunction = <
|
||||
? FieldTextMetadata
|
||||
: E extends 'UUID'
|
||||
? FieldUuidMetadata
|
||||
: E extends 'RAW_JSON'
|
||||
? FieldRawJsonMetadata
|
||||
: never,
|
||||
: E extends 'ADDRESS'
|
||||
? FieldAddressMetadata
|
||||
: E extends 'RAW_JSON'
|
||||
? FieldRawJsonMetadata
|
||||
: never,
|
||||
>(
|
||||
fieldType: E,
|
||||
fieldTypeGuard: (
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
import { FieldDefinition } from '../FieldDefinition';
|
||||
import { FieldAddressMetadata, FieldMetadata } from '../FieldMetadata';
|
||||
|
||||
export const isFieldAddress = (
|
||||
field: Pick<FieldDefinition<FieldMetadata>, 'type'>,
|
||||
): field is FieldDefinition<FieldAddressMetadata> => field.type === 'ADDRESS';
|
||||
@ -0,0 +1,19 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
import { FieldAddressValue } from '../FieldMetadata';
|
||||
|
||||
const addressSchema = z.object({
|
||||
addressStreet1: z.string(),
|
||||
addressStreet2: z.string().nullable(),
|
||||
addressCity: z.string().nullable(),
|
||||
addressState: z.string().nullable(),
|
||||
addressPostcode: z.string().nullable(),
|
||||
addressCountry: z.string().nullable(),
|
||||
addressLat: z.number().nullable(),
|
||||
addressLng: z.number().nullable(),
|
||||
});
|
||||
|
||||
export const isFieldAddressValue = (
|
||||
fieldValue: unknown,
|
||||
): fieldValue is FieldAddressValue =>
|
||||
addressSchema.safeParse(fieldValue).success;
|
||||
@ -1,5 +1,7 @@
|
||||
import { FieldDefinition } from '@/object-record/record-field/types/FieldDefinition';
|
||||
import { FieldMetadata } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { isFieldAddress } from '@/object-record/record-field/types/guards/isFieldAddress';
|
||||
import { isFieldAddressValue } from '@/object-record/record-field/types/guards/isFieldAddressValue';
|
||||
import { isFieldBoolean } from '@/object-record/record-field/types/guards/isFieldBoolean';
|
||||
import { isFieldCurrency } from '@/object-record/record-field/types/guards/isFieldCurrency';
|
||||
import { isFieldCurrencyValue } from '@/object-record/record-field/types/guards/isFieldCurrencyValue';
|
||||
@ -68,6 +70,18 @@ export const isFieldValueEmpty = ({
|
||||
return !isFieldLinkValue(fieldValue) || isValueEmpty(fieldValue?.url);
|
||||
}
|
||||
|
||||
if (isFieldAddress(fieldDefinition)) {
|
||||
return (
|
||||
!isFieldAddressValue(fieldValue) ||
|
||||
(isValueEmpty(fieldValue?.addressStreet1) &&
|
||||
isValueEmpty(fieldValue?.addressStreet2) &&
|
||||
isValueEmpty(fieldValue?.addressCity) &&
|
||||
isValueEmpty(fieldValue?.addressState) &&
|
||||
isValueEmpty(fieldValue?.addressPostcode) &&
|
||||
isValueEmpty(fieldValue?.addressCountry))
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Entity field type not supported in isFieldValueEmpty : ${fieldDefinition.type}}`,
|
||||
);
|
||||
|
||||
@ -71,6 +71,15 @@ export type FullNameFilter = {
|
||||
lastName?: StringFilter;
|
||||
};
|
||||
|
||||
export type AddressFilter = {
|
||||
addressStreet1?: StringFilter;
|
||||
addressStreet2?: StringFilter;
|
||||
addressCity?: StringFilter;
|
||||
addressState?: StringFilter;
|
||||
addressCountry?: StringFilter;
|
||||
addressPostcode?: StringFilter;
|
||||
};
|
||||
|
||||
export type LeafFilter =
|
||||
| UUIDFilter
|
||||
| StringFilter
|
||||
@ -80,6 +89,7 @@ export type LeafFilter =
|
||||
| URLFilter
|
||||
| FullNameFilter
|
||||
| BooleanFilter
|
||||
| AddressFilter
|
||||
| undefined;
|
||||
|
||||
export type AndObjectRecordFilter = {
|
||||
|
||||
@ -2,6 +2,7 @@ import { isObject } from '@sniptt/guards';
|
||||
|
||||
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import {
|
||||
AddressFilter,
|
||||
AndObjectRecordFilter,
|
||||
BooleanFilter,
|
||||
CurrencyFilter,
|
||||
@ -180,6 +181,30 @@ export const isRecordMatchingFilter = ({
|
||||
}))
|
||||
);
|
||||
}
|
||||
case FieldMetadataType.Address: {
|
||||
const addressFilter = filterValue as AddressFilter;
|
||||
|
||||
const keys = [
|
||||
'addressStreet1',
|
||||
'addressStreet2',
|
||||
'addressCity',
|
||||
'addressState',
|
||||
'addressCountry',
|
||||
'addressPostcode',
|
||||
] as const;
|
||||
|
||||
return keys.some((key) => {
|
||||
const value = addressFilter[key];
|
||||
if (value === undefined) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isMatchingStringFilter({
|
||||
stringFilter: value,
|
||||
value: record[filterKey][key],
|
||||
});
|
||||
});
|
||||
}
|
||||
case FieldMetadataType.DateTime: {
|
||||
return isMatchingDateFilter({
|
||||
dateFilter: filterValue as DateFilter,
|
||||
|
||||
@ -25,6 +25,18 @@ export const generateEmptyFieldValue = (
|
||||
lastName: '',
|
||||
};
|
||||
}
|
||||
case FieldMetadataType.Address: {
|
||||
return {
|
||||
addressStreet1: '',
|
||||
addressStreet2: '',
|
||||
addressCity: '',
|
||||
addressState: '',
|
||||
addressCountry: '',
|
||||
addressPostcode: '',
|
||||
addressLat: null,
|
||||
addressLng: null,
|
||||
};
|
||||
}
|
||||
case FieldMetadataType.DateTime: {
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user