Implement select v1 (#3312)
* Implement select v1 * Implement select v1
This commit is contained in:
@ -28,6 +28,7 @@ export const useMapFieldMetadataToGraphQLQuery = () => {
|
|||||||
'EMAIL',
|
'EMAIL',
|
||||||
'NUMBER',
|
'NUMBER',
|
||||||
'BOOLEAN',
|
'BOOLEAN',
|
||||||
|
'SELECT',
|
||||||
] as FieldType[]
|
] as FieldType[]
|
||||||
).includes(fieldType);
|
).includes(fieldType);
|
||||||
|
|
||||||
|
|||||||
@ -40,6 +40,7 @@ export const formatFieldMetadataItemAsColumnDefinition = ({
|
|||||||
relationObjectMetadataNamePlural:
|
relationObjectMetadataNamePlural:
|
||||||
relationObjectMetadataItem?.namePlural ?? '',
|
relationObjectMetadataItem?.namePlural ?? '',
|
||||||
objectMetadataNameSingular: objectMetadataItem.nameSingular ?? '',
|
objectMetadataNameSingular: objectMetadataItem.nameSingular ?? '',
|
||||||
|
options: field.options,
|
||||||
},
|
},
|
||||||
iconName: field.icon ?? 'Icon123',
|
iconName: field.icon ?? 'Icon123',
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
|
|||||||
@ -15,7 +15,6 @@ import { RecordInlineCell } from '@/object-record/record-inline-cell/components/
|
|||||||
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
import { PropertyBox } from '@/object-record/record-inline-cell/property-box/components/PropertyBox';
|
||||||
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
import { InlineCellHotkeyScope } from '@/object-record/record-inline-cell/types/InlineCellHotkeyScope';
|
||||||
import { RecordRelationFieldCardSection } from '@/object-record/record-relation-card/components/RecordRelationFieldCardSection';
|
import { RecordRelationFieldCardSection } from '@/object-record/record-relation-card/components/RecordRelationFieldCardSection';
|
||||||
import { useRelationPicker } from '@/object-record/relation-picker/hooks/useRelationPicker';
|
|
||||||
import { isFieldMetadataItemAvailable } from '@/object-record/utils/isFieldMetadataItemAvailable';
|
import { isFieldMetadataItemAvailable } from '@/object-record/utils/isFieldMetadataItemAvailable';
|
||||||
import { IconBuildingSkyscraper } from '@/ui/display/icon';
|
import { IconBuildingSkyscraper } from '@/ui/display/icon';
|
||||||
import { PageBody } from '@/ui/layout/page/PageBody';
|
import { PageBody } from '@/ui/layout/page/PageBody';
|
||||||
@ -51,12 +50,13 @@ export const RecordShowPage = () => {
|
|||||||
throw new Error(`Object name is not defined`);
|
throw new Error(`Object name is not defined`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { objectMetadataItem, labelIdentifierFieldMetadata } =
|
const {
|
||||||
useObjectMetadataItem({
|
objectMetadataItem,
|
||||||
objectNameSingular,
|
labelIdentifierFieldMetadata,
|
||||||
});
|
mapToObjectRecordIdentifier,
|
||||||
|
} = useObjectMetadataItem({
|
||||||
const { identifiersMapper } = useRelationPicker();
|
objectNameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
const { favorites, createFavorite, deleteFavorite } = useFavorites();
|
const { favorites, createFavorite, deleteFavorite } = useFavorites();
|
||||||
|
|
||||||
@ -107,11 +107,6 @@ export const RecordShowPage = () => {
|
|||||||
? record?.name.firstName + ' ' + record?.name.lastName
|
? record?.name.firstName + ' ' + record?.name.lastName
|
||||||
: record?.name;
|
: record?.name;
|
||||||
|
|
||||||
const recordIdentifiers = identifiersMapper?.(
|
|
||||||
record,
|
|
||||||
objectMetadataItem?.nameSingular ?? '',
|
|
||||||
);
|
|
||||||
|
|
||||||
const onUploadPicture = async (file: File) => {
|
const onUploadPicture = async (file: File) => {
|
||||||
if (objectNameSingular !== 'person') {
|
if (objectNameSingular !== 'person') {
|
||||||
return;
|
return;
|
||||||
@ -201,8 +196,12 @@ export const RecordShowPage = () => {
|
|||||||
<>
|
<>
|
||||||
<ShowPageSummaryCard
|
<ShowPageSummaryCard
|
||||||
id={record.id}
|
id={record.id}
|
||||||
logoOrAvatar={recordIdentifiers?.avatarUrl}
|
logoOrAvatar={
|
||||||
avatarPlaceholder={recordIdentifiers?.name ?? ''}
|
mapToObjectRecordIdentifier(record).avatarUrl ?? ''
|
||||||
|
}
|
||||||
|
avatarPlaceholder={
|
||||||
|
mapToObjectRecordIdentifier(record).name ?? ''
|
||||||
|
}
|
||||||
date={record.createdAt ?? ''}
|
date={record.createdAt ?? ''}
|
||||||
title={
|
title={
|
||||||
<FieldContext.Provider
|
<FieldContext.Provider
|
||||||
@ -232,7 +231,10 @@ export const RecordShowPage = () => {
|
|||||||
<RecordInlineCell />
|
<RecordInlineCell />
|
||||||
</FieldContext.Provider>
|
</FieldContext.Provider>
|
||||||
}
|
}
|
||||||
avatarType={recordIdentifiers?.avatarType ?? 'rounded'}
|
avatarType={
|
||||||
|
mapToObjectRecordIdentifier(record).avatarType ??
|
||||||
|
'rounded'
|
||||||
|
}
|
||||||
onUploadPicture={
|
onUploadPicture={
|
||||||
objectNameSingular === 'person'
|
objectNameSingular === 'person'
|
||||||
? onUploadPicture
|
? onUploadPicture
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
|
|
||||||
import { FullNameFieldInput } from '@/object-record/field/meta-types/input/components/FullNameFieldInput';
|
import { FullNameFieldInput } from '@/object-record/field/meta-types/input/components/FullNameFieldInput';
|
||||||
|
import { SelectFieldInput } from '@/object-record/field/meta-types/input/components/SelectFieldInput';
|
||||||
import { isFieldFullName } from '@/object-record/field/types/guards/isFieldFullName';
|
import { isFieldFullName } from '@/object-record/field/types/guards/isFieldFullName';
|
||||||
|
import { isFieldSelect } from '@/object-record/field/types/guards/isFieldSelect';
|
||||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||||
|
|
||||||
import { FieldContext } from '../contexts/FieldContext';
|
import { FieldContext } from '../contexts/FieldContext';
|
||||||
@ -120,6 +122,8 @@ export const FieldInput = ({
|
|||||||
<BooleanFieldInput onSubmit={onSubmit} />
|
<BooleanFieldInput onSubmit={onSubmit} />
|
||||||
) : isFieldRating(fieldDefinition) ? (
|
) : isFieldRating(fieldDefinition) ? (
|
||||||
<RatingFieldInput onSubmit={onSubmit} />
|
<RatingFieldInput onSubmit={onSubmit} />
|
||||||
|
) : isFieldSelect(fieldDefinition) ? (
|
||||||
|
<SelectFieldInput onSubmit={onSubmit} />
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -3,6 +3,8 @@ import { useRecoilCallback } from 'recoil';
|
|||||||
|
|
||||||
import { isFieldFullName } from '@/object-record/field/types/guards/isFieldFullName';
|
import { isFieldFullName } from '@/object-record/field/types/guards/isFieldFullName';
|
||||||
import { isFieldFullNameValue } from '@/object-record/field/types/guards/isFieldFullNameValue';
|
import { isFieldFullNameValue } from '@/object-record/field/types/guards/isFieldFullNameValue';
|
||||||
|
import { isFieldSelect } from '@/object-record/field/types/guards/isFieldSelect';
|
||||||
|
import { isFieldSelectValue } from '@/object-record/field/types/guards/isFieldSelectValue';
|
||||||
|
|
||||||
import { FieldContext } from '../contexts/FieldContext';
|
import { FieldContext } from '../contexts/FieldContext';
|
||||||
import { entityFieldsFamilySelector } from '../states/selectors/entityFieldsFamilySelector';
|
import { entityFieldsFamilySelector } from '../states/selectors/entityFieldsFamilySelector';
|
||||||
@ -77,6 +79,9 @@ export const usePersistField = () => {
|
|||||||
const fieldIsPhone =
|
const fieldIsPhone =
|
||||||
isFieldPhone(fieldDefinition) && isFieldPhoneValue(valueToPersist);
|
isFieldPhone(fieldDefinition) && isFieldPhoneValue(valueToPersist);
|
||||||
|
|
||||||
|
const fieldIsSelect =
|
||||||
|
isFieldSelect(fieldDefinition) && isFieldSelectValue(valueToPersist);
|
||||||
|
|
||||||
if (fieldIsRelation) {
|
if (fieldIsRelation) {
|
||||||
const fieldName = fieldDefinition.metadata.fieldName;
|
const fieldName = fieldDefinition.metadata.fieldName;
|
||||||
|
|
||||||
@ -104,7 +109,8 @@ export const usePersistField = () => {
|
|||||||
fieldIsPhone ||
|
fieldIsPhone ||
|
||||||
fieldIsLink ||
|
fieldIsLink ||
|
||||||
fieldIsCurrency ||
|
fieldIsCurrency ||
|
||||||
fieldIsFullName
|
fieldIsFullName ||
|
||||||
|
fieldIsSelect
|
||||||
) {
|
) {
|
||||||
const fieldName = fieldDefinition.metadata.fieldName;
|
const fieldName = fieldDefinition.metadata.fieldName;
|
||||||
set(
|
set(
|
||||||
|
|||||||
@ -3,7 +3,15 @@ import { Tag } from '@/ui/display/tag/components/Tag';
|
|||||||
import { useSelectField } from '../../hooks/useSelectField';
|
import { useSelectField } from '../../hooks/useSelectField';
|
||||||
|
|
||||||
export const SelectFieldDisplay = () => {
|
export const SelectFieldDisplay = () => {
|
||||||
const { fieldValue } = useSelectField();
|
const { fieldValue, fieldDefinition } = useSelectField();
|
||||||
|
|
||||||
return <Tag color={fieldValue.color} text={fieldValue.label} />;
|
const selectedOption = fieldDefinition.metadata.options.find(
|
||||||
|
(option) => option.value === fieldValue,
|
||||||
|
);
|
||||||
|
|
||||||
|
return selectedOption ? (
|
||||||
|
<Tag color={selectedOption.color} text={selectedOption.label} />
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { useRecoilState } from 'recoil';
|
import { useRecoilState } from 'recoil';
|
||||||
|
|
||||||
import { ThemeColor } from '@/ui/theme/constants/colors';
|
import { usePersistField } from '@/object-record/field/hooks/usePersistField';
|
||||||
import { FieldMetadataType } from '~/generated/graphql';
|
import { FieldMetadataType } from '~/generated/graphql';
|
||||||
|
|
||||||
import { FieldContext } from '../../contexts/FieldContext';
|
import { FieldContext } from '../../contexts/FieldContext';
|
||||||
@ -25,23 +25,18 @@ export const useSelectField = () => {
|
|||||||
fieldName: fieldName,
|
fieldName: fieldName,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
const fieldSelectValue = isFieldSelectValue(fieldValue)
|
|
||||||
? fieldValue
|
const fieldSelectValue = isFieldSelectValue(fieldValue) ? fieldValue : null;
|
||||||
: { color: 'green' as ThemeColor, label: '' };
|
|
||||||
|
|
||||||
const fieldInitialValue = useFieldInitialValue();
|
const fieldInitialValue = useFieldInitialValue();
|
||||||
|
|
||||||
const initialValue = {
|
const persistField = usePersistField();
|
||||||
color: 'green' as ThemeColor,
|
|
||||||
label: fieldInitialValue?.isEmpty
|
|
||||||
? ''
|
|
||||||
: fieldInitialValue?.value ?? fieldSelectValue?.label ?? '',
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fieldDefinition,
|
fieldDefinition,
|
||||||
|
persistField,
|
||||||
fieldValue: fieldSelectValue,
|
fieldValue: fieldSelectValue,
|
||||||
initialValue,
|
initialValue: fieldInitialValue,
|
||||||
setFieldValue,
|
setFieldValue,
|
||||||
hotkeyScope,
|
hotkeyScope,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,38 @@
|
|||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { MenuItem } from 'tsup.ui.index';
|
||||||
|
|
||||||
|
import { useSelectField } from '@/object-record/field/meta-types/hooks/useSelectField';
|
||||||
|
import { FieldInputEvent } from '@/object-record/field/types/FieldInputEvent';
|
||||||
|
import { DropdownMenu } from '@/ui/layout/dropdown/components/DropdownMenu';
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
|
||||||
|
const StyledRelationPickerContainer = styled.div`
|
||||||
|
left: -1px;
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export type SelectFieldInputProps = {
|
||||||
|
onSubmit?: FieldInputEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SelectFieldInput = ({ onSubmit }: SelectFieldInputProps) => {
|
||||||
|
const { persistField, fieldDefinition } = useSelectField();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledRelationPickerContainer>
|
||||||
|
<DropdownMenu data-select-disable>
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
{fieldDefinition.metadata.options.map((option) => {
|
||||||
|
return (
|
||||||
|
<MenuItem
|
||||||
|
text={option.label}
|
||||||
|
onClick={() => onSubmit?.(() => persistField(option.value))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</DropdownMenu>
|
||||||
|
</StyledRelationPickerContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -87,6 +87,7 @@ export type FieldRelationMetadata = {
|
|||||||
export type FieldSelectMetadata = {
|
export type FieldSelectMetadata = {
|
||||||
objectMetadataNameSingular?: string;
|
objectMetadataNameSingular?: string;
|
||||||
fieldName: string;
|
fieldName: string;
|
||||||
|
options: { label: string; color: ThemeColor; value: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type FieldMetadata =
|
export type FieldMetadata =
|
||||||
@ -119,6 +120,6 @@ export type FieldCurrencyValue = {
|
|||||||
};
|
};
|
||||||
export type FieldFullNameValue = { firstName: string; lastName: string };
|
export type FieldFullNameValue = { firstName: string; lastName: string };
|
||||||
export type FieldRatingValue = '1' | '2' | '3' | '4' | '5';
|
export type FieldRatingValue = '1' | '2' | '3' | '4' | '5';
|
||||||
export type FieldSelectValue = { color: ThemeColor; label: string };
|
export type FieldSelectValue = string | null;
|
||||||
|
|
||||||
export type FieldRelationValue = EntityForSelect | null;
|
export type FieldRelationValue = EntityForSelect | null;
|
||||||
|
|||||||
@ -1,13 +1,7 @@
|
|||||||
import { z } from 'zod';
|
import { isString } from '@sniptt/guards';
|
||||||
|
|
||||||
import { themeColorSchema } from '@/ui/theme/utils/themeColorSchema';
|
import { FieldSelectValue } from '@/object-record/field/types/FieldMetadata';
|
||||||
|
|
||||||
const selectValueSchema = z.object({
|
|
||||||
color: themeColorSchema,
|
|
||||||
label: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const isFieldSelectValue = (
|
export const isFieldSelectValue = (
|
||||||
fieldValue: unknown,
|
fieldValue: unknown,
|
||||||
): fieldValue is z.infer<typeof selectValueSchema> =>
|
): fieldValue is FieldSelectValue => isString(fieldValue);
|
||||||
selectValueSchema.safeParse(fieldValue).success;
|
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import { isFieldRating } from '@/object-record/field/types/guards/isFieldRating'
|
|||||||
import { isFieldRelation } from '@/object-record/field/types/guards/isFieldRelation';
|
import { isFieldRelation } from '@/object-record/field/types/guards/isFieldRelation';
|
||||||
import { isFieldRelationValue } from '@/object-record/field/types/guards/isFieldRelationValue';
|
import { isFieldRelationValue } from '@/object-record/field/types/guards/isFieldRelationValue';
|
||||||
import { isFieldSelect } from '@/object-record/field/types/guards/isFieldSelect';
|
import { isFieldSelect } from '@/object-record/field/types/guards/isFieldSelect';
|
||||||
|
import { isFieldSelectValue } from '@/object-record/field/types/guards/isFieldSelectValue';
|
||||||
import { isFieldText } from '@/object-record/field/types/guards/isFieldText';
|
import { isFieldText } from '@/object-record/field/types/guards/isFieldText';
|
||||||
import { isFieldUuid } from '@/object-record/field/types/guards/isFieldUuid';
|
import { isFieldUuid } from '@/object-record/field/types/guards/isFieldUuid';
|
||||||
import { assertNotNull } from '~/utils/assert';
|
import { assertNotNull } from '~/utils/assert';
|
||||||
@ -34,8 +35,7 @@ export const isFieldValueEmpty = ({
|
|||||||
isFieldNumber(fieldDefinition) ||
|
isFieldNumber(fieldDefinition) ||
|
||||||
isFieldRating(fieldDefinition) ||
|
isFieldRating(fieldDefinition) ||
|
||||||
isFieldEmail(fieldDefinition) ||
|
isFieldEmail(fieldDefinition) ||
|
||||||
isFieldBoolean(fieldDefinition) ||
|
isFieldBoolean(fieldDefinition)
|
||||||
isFieldSelect(fieldDefinition)
|
|
||||||
//|| isFieldPhone(fieldDefinition)
|
//|| isFieldPhone(fieldDefinition)
|
||||||
) {
|
) {
|
||||||
return isValueEmpty(fieldValue);
|
return isValueEmpty(fieldValue);
|
||||||
@ -45,6 +45,10 @@ export const isFieldValueEmpty = ({
|
|||||||
return isFieldRelationValue(fieldValue) && isValueEmpty(fieldValue);
|
return isFieldRelationValue(fieldValue) && isValueEmpty(fieldValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isFieldSelect(fieldDefinition)) {
|
||||||
|
return isFieldSelectValue(fieldValue) && !assertNotNull(fieldValue);
|
||||||
|
}
|
||||||
|
|
||||||
if (isFieldCurrency(fieldDefinition)) {
|
if (isFieldCurrency(fieldDefinition)) {
|
||||||
return (
|
return (
|
||||||
!isFieldCurrencyValue(fieldValue) ||
|
!isFieldCurrencyValue(fieldValue) ||
|
||||||
|
|||||||
@ -41,7 +41,7 @@ class Support {
|
|||||||
|
|
||||||
@ObjectType()
|
@ObjectType()
|
||||||
class Sentry {
|
class Sentry {
|
||||||
@Field(() => String)
|
@Field(() => String, { nullable: true })
|
||||||
dsn: string | undefined;
|
dsn: string | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user