New field currency (#4338)

Closes #4122 
---------

Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
Anoop P
2024-04-02 18:59:57 +05:30
committed by GitHub
parent d694ab1b16
commit bbffde1ca0
12 changed files with 145 additions and 75 deletions

View File

@ -3,7 +3,9 @@ import { ApolloProvider as ApolloProviderBase } from '@apollo/client';
import { useApolloFactory } from '@/apollo/hooks/useApolloFactory';
export const ApolloProvider = ({ children }: React.PropsWithChildren) => {
const apolloClient = useApolloFactory();
const apolloClient = useApolloFactory({
connectToDevTools: true,
});
// This will attach the right apollo client to Apollo Dev Tools
window.__APOLLO_CLIENT__ = apolloClient;

View File

@ -10,6 +10,7 @@ export const ApolloMetadataClientProvider = ({
}) => {
const apolloMetadataClient = useApolloFactory({
uri: `${REACT_APP_SERVER_BASE_URL}/metadata`,
connectToDevTools: false,
});
return (

View File

@ -8,6 +8,7 @@ import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getR
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
import { lowerAndCapitalize } from '~/utils/string/lowerAndCapitalize';
export const getRecordNodeFromRecord = <T extends ObjectRecord>({
objectMetadataItems,
@ -92,46 +93,61 @@ export const getRecordNodeFromRecord = <T extends ObjectRecord>({
];
}
if (field.type === 'RELATION') {
if (
isUndefined(
switch (field.type) {
case FieldMetadataType.Relation: {
if (
isUndefined(
field.relationDefinition?.targetObjectMetadata.nameSingular,
)
) {
return undefined;
}
if (isNull(value)) {
return [fieldName, null];
}
if (isUndefined(value?.id)) {
return undefined;
}
const typeName = getObjectTypename(
field.relationDefinition?.targetObjectMetadata.nameSingular,
)
) {
return undefined;
}
);
if (isNull(value)) {
return [fieldName, null];
}
if (computeReferences) {
return [
fieldName,
{
__ref: `${typeName}:${value.id}`,
},
];
}
if (isUndefined(value?.id)) {
return undefined;
}
const typeName = getObjectTypename(
field.relationDefinition?.targetObjectMetadata.nameSingular,
);
if (computeReferences) {
return [
fieldName,
{
__ref: `${typeName}:${value.id}`,
__typename: typeName,
...value,
},
];
}
return [
fieldName,
{
__typename: typeName,
...value,
},
];
case FieldMetadataType.Link:
case FieldMetadataType.Address:
case FieldMetadataType.FullName:
case FieldMetadataType.Currency: {
return [
fieldName,
{
...value,
__typename: lowerAndCapitalize(field.type),
},
];
}
default: {
return [fieldName, value];
}
}
return [fieldName, value];
})
.filter(isDefined),
) as T; // Todo fix typing once we have investigated apollo edges / nodes removal

View File

@ -5,11 +5,5 @@ import { useCurrencyField } from '../../hooks/useCurrencyField';
export const CurrencyFieldDisplay = () => {
const { fieldValue } = useCurrencyField();
return (
<CurrencyDisplay
amount={
fieldValue?.amountMicros ? fieldValue.amountMicros / 1000000 : null
}
/>
);
return <CurrencyDisplay currencyValue={fieldValue} />;
};

View File

@ -23,7 +23,7 @@ export const SettingsObjectFieldCurrencyForm = ({
<Select
fullWidth
disabled={disabled}
label="Unit"
label="Default Unit"
dropdownId="currency-unit-select"
value={values.currencyCode}
options={Object.entries(SETTINGS_FIELD_CURRENCY_CODES).map(

View File

@ -1,10 +1,51 @@
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { FieldCurrencyValue } from '@/object-record/record-field/types/FieldMetadata';
import { SETTINGS_FIELD_CURRENCY_CODES } from '@/settings/data-model/constants/SettingsFieldCurrencyCodes';
import { formatAmount } from '~/utils/format/formatAmount';
import { isDefined } from '~/utils/isDefined';
import { EllipsisDisplay } from './EllipsisDisplay';
type CurrencyDisplayProps = {
amount?: number | null;
currencyValue: FieldCurrencyValue | null | undefined;
};
// TODO: convert currencyCode to currency symbol
export const CurrencyDisplay = ({ amount }: CurrencyDisplayProps) => {
return <EllipsisDisplay>{amount}</EllipsisDisplay>;
const StyledEllipsisDisplay = styled(EllipsisDisplay)`
align-items: center;
display: flex;
`;
export const CurrencyDisplay = ({ currencyValue }: CurrencyDisplayProps) => {
const theme = useTheme();
const shouldDisplayCurrency = isDefined(currencyValue?.currencyCode);
const CurrencyIcon = isDefined(currencyValue?.currencyCode)
? SETTINGS_FIELD_CURRENCY_CODES[currencyValue?.currencyCode]?.Icon
: null;
const amountToDisplay = isDefined(currencyValue?.amountMicros)
? currencyValue.amountMicros / 1000000
: 0;
if (!shouldDisplayCurrency) {
return <StyledEllipsisDisplay>{0}</StyledEllipsisDisplay>;
}
return (
<StyledEllipsisDisplay>
{isDefined(CurrencyIcon) && (
<>
<CurrencyIcon
color={theme.font.color.primary}
size={theme.icon.size.md}
stroke={theme.icon.stroke.sm}
/>{' '}
</>
)}
{amountToDisplay !== 0 ? formatAmount(amountToDisplay) : ''}
</StyledEllipsisDisplay>
);
};

View File

@ -11,11 +11,15 @@ const StyledEllipsisDisplay = styled.div<{ maxWidth?: number }>`
type EllipsisDisplayProps = {
children: React.ReactNode;
maxWidth?: number;
className?: string;
};
export const EllipsisDisplay = ({
children,
maxWidth,
className,
}: EllipsisDisplayProps) => (
<StyledEllipsisDisplay style={{ maxWidth }}>{children}</StyledEllipsisDisplay>
<StyledEllipsisDisplay style={{ maxWidth }} className={className}>
{children}
</StyledEllipsisDisplay>
);

View File

@ -26,15 +26,18 @@ export const useGetViewFromCache = () => {
const view = {
...viewWithConnections,
viewFilters: viewWithConnections.viewFilters?.edges.map(
(edge: ObjectRecordEdge) => edge.node,
),
viewSorts: viewWithConnections.viewSorts?.edges.map(
(edge: ObjectRecordEdge) => edge.node,
),
viewFields: viewWithConnections.viewFields?.edges.map(
(edge: ObjectRecordEdge) => edge.node,
),
viewFilters:
viewWithConnections.viewFilters?.edges?.map(
(edge: ObjectRecordEdge) => edge.node,
) ?? [],
viewSorts:
viewWithConnections.viewSorts?.edges?.map(
(edge: ObjectRecordEdge) => edge.node,
) ?? [],
viewFields:
viewWithConnections.viewFields?.edges?.map(
(edge: ObjectRecordEdge) => edge.node,
) ?? [],
} as GraphQLView;
return view;

View File

@ -1,28 +1,28 @@
import { amountFormat } from '../amountFormat';
import { formatAmount } from '../formatAmount';
describe('amountFormat', () => {
it('formats numbers less than 1000 correctly', () => {
expect(amountFormat(500)).toBe('500');
expect(amountFormat(123.456)).toBe('123.5');
expect(formatAmount(500)).toBe('500');
expect(formatAmount(123.456)).toBe('123.5');
});
it('formats numbers between 1000 and 999999 correctly', () => {
expect(amountFormat(1500)).toBe('1.5k');
expect(amountFormat(789456)).toBe('789.5k');
expect(formatAmount(1500)).toBe('1.5k');
expect(formatAmount(789456)).toBe('789.5k');
});
it('formats numbers between 1000000 and 999999999 correctly', () => {
expect(amountFormat(2000000)).toBe('2m');
expect(amountFormat(654987654)).toBe('655m');
expect(formatAmount(2000000)).toBe('2m');
expect(formatAmount(654987654)).toBe('655m');
});
it('formats numbers greater than or equal to 1000000000 correctly', () => {
expect(amountFormat(1200000000)).toBe('1.2b');
expect(amountFormat(987654321987)).toBe('987.7b');
expect(formatAmount(1200000000)).toBe('1.2b');
expect(formatAmount(987654321987)).toBe('987.7b');
});
it('handles numbers with decimal places correctly', () => {
expect(amountFormat(123.456)).toBe('123.5');
expect(amountFormat(789.0123)).toBe('789');
expect(formatAmount(123.456)).toBe('123.5');
expect(formatAmount(789.0123)).toBe('789');
});
});

View File

@ -1,11 +0,0 @@
export const amountFormat = (number: number) => {
if (number < 1000) {
return number.toFixed(1).replace(/\.?0+$/, '');
} else if (number < 1000000) {
return (number / 1000).toFixed(1).replace(/\.?0+$/, '') + 'k';
} else if (number < 1000000000) {
return (number / 1000000).toFixed(1).replace(/\.?0+$/, '') + 'm';
} else {
return (number / 1000000000).toFixed(1).replace(/\.?0+$/, '') + 'b';
}
};

View File

@ -0,0 +1,11 @@
export const formatAmount = (amount: number) => {
if (amount < 1000) {
return amount.toFixed(1).replace(/\.?0+$/, '');
} else if (amount < 1000000) {
return (amount / 1000).toFixed(1).replace(/\.?0+$/, '') + 'k';
} else if (amount < 1000000000) {
return (amount / 1000000).toFixed(1).replace(/\.?0+$/, '') + 'm';
} else {
return (amount / 1000000000).toFixed(1).replace(/\.?0+$/, '') + 'b';
}
};

View File

@ -0,0 +1,9 @@
import { isNonEmptyString } from '@sniptt/guards';
export const lowerAndCapitalize = (stringToCapitalize: string) => {
if (!isNonEmptyString(stringToCapitalize)) return '';
const loweredString = stringToCapitalize.toLowerCase();
return loweredString[0].toUpperCase() + loweredString.slice(1);
};