Skip to content

Commit

Permalink
added enhancements in new look (#185)
Browse files Browse the repository at this point in the history
* updated selected tags font size

* fixed icon transparent background

* clear search text after selection

* show more records message
  • Loading branch information
khoait authored Oct 24, 2024
1 parent 8b68483 commit 412aff0
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export default function PolyLookupControlClassic({
async (filterText: string, selectedTags?: ITag[]): Promise<ITag[]> => {
try {
const results = await filterQueryAsync({ searchText: filterText, pageSizeParam: pageSize });
const options = results.filter(
const options = results.records.filter(
(r) =>
!selectedTags?.some((t) => {
const data = (t as ITagWithData).data;
Expand All @@ -176,7 +176,7 @@ export default function PolyLookupControlClassic({
searchText: filterText,
pageSizeParam: (pageSize ?? 50) * 2 + 1,
});
const options = results.filter(
const options = results.records.filter(
(r) =>
!selectedTags?.some((t) => {
const data = (t as ITagWithData).data;
Expand All @@ -196,7 +196,7 @@ export default function PolyLookupControlClassic({
async (selectedTags?: ITag[]): Promise<ITag[]> => {
try {
const results = await filterQueryAsync({ searchText: "", pageSizeParam: pageSize });
const options = results.filter(
const options = results.records.filter(
(r) =>
!selectedTags?.some((t) => {
const data = (t as ITagWithData).data;
Expand Down
137 changes: 98 additions & 39 deletions PolyLookupComponent/PolyLookup/components/PolyLookupControlNewLook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
tokens,
Text,
Button,
mergeClasses,
Divider,
} from "@fluentui/react-components";
import { useQueryClient } from "@tanstack/react-query";
import React, { useEffect, useState } from "react";
Expand Down Expand Up @@ -58,6 +60,21 @@ const useStyle = makeStyles({
},
listBox: {
maxHeight: "50vh",
overflowX: "hidden",
overflowY: "auto",
padding: tokens.spacingVerticalXS,
},
optionListFooter: {
padding: tokens.spacingVerticalS,
},
tagFontSize: {
fontSize: tokens.fontSizeBase300,
},
iconFontSize: {
fontSize: tokens.fontSizeBase200,
},
transparentBackground: {
backgroundColor: tokens.colorTransparentBackground,
},
});

Expand All @@ -83,7 +100,24 @@ export default function PolyLookupControlNewLook({
onQuickCreate,
}: PolyLookupProps) {
const queryClient = useQueryClient();
const { tagGroup, marginLeft, underline, borderTransparent, listBox } = useStyle();
const {
tagGroup,
marginLeft,
underline,
borderTransparent,
listBox,
optionListFooter,
tagFontSize,
iconFontSize,
transparentBackground,
} = useStyle();

const tagStyle = mergeClasses(!!tagAction && underline);
const iconStyle = mergeClasses(borderTransparent, iconFontSize);
const imageStyle = mergeClasses(
showIcon === ShowIconOptions.EntityIcon && marginLeft,
showIcon === ShowIconOptions.EntityIcon && transparentBackground
);

const [searchText, setSearchText] = useState<string>("");

Expand Down Expand Up @@ -142,7 +176,7 @@ export default function PolyLookupControlNewLook({

const selectedOptions = formType === XrmEnum.FormType.Create ? selectedEntitiesCreate : selectedItems;

const optionList = entityOptions?.filter(
const optionList = entityOptions?.records.filter(
(option) => !selectedOptions?.some((i) => i.associatedId === option.associatedId)
);

Expand Down Expand Up @@ -216,7 +250,7 @@ export default function PolyLookupControlNewLook({
}

if (formType === XrmEnum.FormType.Create) {
const selectedEntity = entityOptions?.find((i) => i.id === value);
const selectedEntity = entityOptions?.records.find((i) => i.id === value);
if (!selectedEntity) {
return;
}
Expand All @@ -234,6 +268,8 @@ export default function PolyLookupControlNewLook({
} else if (formType === XrmEnum.FormType.Update) {
associateQuery(value);
}

setSearchText("");
};

const handleOnOptionDismiss: TagGroupProps["onDismiss"] = (_e, { value }) => {
Expand Down Expand Up @@ -313,6 +349,57 @@ export default function PolyLookupControlNewLook({

const placeholder = getPlaceholder();

const renderOptionList = () => {
if (!optionList?.length) {
return (
<div className={optionListFooter}>
<Text>{isLoadingEntityOptions ? languagePack.LoadingMessage : languagePack.EmptyListDefaultMessage}</Text>
</div>
);
}

return (
<div>
<div className={listBox}>
{optionList?.map((option) => (
<TagPickerOption
media={
showIcon ? (
<Avatar
className={transparentBackground}
size={showIcon === ShowIconOptions.EntityIcon ? 16 : 28}
shape="square"
name={showIcon === ShowIconOptions.EntityIcon ? "" : option.optionText}
image={{
className: transparentBackground,
src:
showIcon === ShowIconOptions.EntityIcon
? metadata?.associatedEntity.EntityIconUrl
: option.iconSrc,
}}
color={showIcon === ShowIconOptions.EntityIcon ? "neutral" : "colorful"}
aria-hidden
/>
) : undefined
}
key={option.id}
value={option.id}
text={option.optionText}
>
<SuggestionInfo data={option} columns={lookupViewConfig?.columns ?? []} />
</TagPickerOption>
))}
</div>
<Divider />
<div className={optionListFooter}>
<Text>
{entityOptions?.moreRecords ? languagePack.SuggestionListFullMessage : languagePack.NoMoreRecordsMessage}
</Text>
</div>
</div>
);
};

return (
<FluentProvider style={{ width: "100%" }} theme={fluentDesign?.tokenTheme}>
<TagPicker
Expand All @@ -325,7 +412,7 @@ export default function PolyLookupControlNewLook({
>
<TagPickerControl
secondaryAction={
onQuickCreate && !entityOptions?.length && !isFetchingEntityOptions && !isDataLoading ? (
onQuickCreate && !entityOptions?.records.length && !isFetchingEntityOptions && !isDataLoading ? (
<Button appearance="transparent" size="small" shape="rounded" onClick={handleQuickCreate}>
{languagePack.AddNewLabel}
</Button>
Expand All @@ -342,15 +429,16 @@ export default function PolyLookupControlNewLook({
value={i.id}
>
<InteractionTagPrimary
className={tagAction ? underline : undefined}
className={tagStyle}
hasSecondaryAction={!disabled}
media={
showIcon ? (
<Avatar
className={showIcon === ShowIconOptions.EntityIcon ? marginLeft : undefined}
className={imageStyle}
size={showIcon === ShowIconOptions.EntityIcon ? 16 : 20}
name={i.selectedOptionText}
name={showIcon === ShowIconOptions.EntityIcon ? "" : i.selectedOptionText}
image={{
className: transparentBackground,
src:
showIcon === ShowIconOptions.EntityIcon
? metadata?.associatedEntity.EntityIconUrl
Expand All @@ -363,9 +451,9 @@ export default function PolyLookupControlNewLook({
}
onClick={() => handleOnItemClick(i)}
>
{i.selectedOptionText}
<span className={tagFontSize}>{i.selectedOptionText}</span>
</InteractionTagPrimary>
{disabled ? null : <InteractionTagSecondary className={borderTransparent} aria-label="remove" />}
{disabled ? null : <InteractionTagSecondary className={iconStyle} aria-label="remove" />}
</InteractionTag>
))}
</TagGroup>
Expand All @@ -386,36 +474,7 @@ export default function PolyLookupControlNewLook({
{disabled || !isSupported || (itemLimit !== undefined && (selectedOptions?.length ?? 0) >= itemLimit) ? (
<></>
) : (
<TagPickerList className={listBox}>
{optionList?.length && isSuccessEntityOptions
? optionList.map((option) => (
<TagPickerOption
media={
showIcon ? (
<Avatar
size={showIcon === ShowIconOptions.EntityIcon ? 16 : 28}
shape="square"
name={option.optionText}
image={{
src:
showIcon === ShowIconOptions.EntityIcon
? metadata?.associatedEntity.EntityIconUrl
: option.iconSrc,
}}
color={showIcon === ShowIconOptions.EntityIcon ? "neutral" : "colorful"}
aria-hidden
/>
) : undefined
}
key={option.id}
value={option.id}
text={option.optionText}
>
<SuggestionInfo data={option} columns={lookupViewConfig?.columns ?? []} />
</TagPickerOption>
))
: "No options available"}
</TagPickerList>
<TagPickerList>{renderOptionList()}</TagPickerList>
)}
</TagPicker>
</FluentProvider>
Expand Down
82 changes: 50 additions & 32 deletions PolyLookupComponent/PolyLookup/services/DataverseService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const DEFAULT_HEADERS = {
Accept: "application/json",
"Content-Type": "application/json; charset=utf-8",
Prefer:
'odata.include-annotations="OData.Community.Display.V1.FormattedValue,Microsoft.Dynamics.CRM.associatednavigationproperty,Microsoft.Dynamics.CRM.lookuplogicalname"',
'odata.include-annotations="OData.Community.Display.V1.FormattedValue,Microsoft.Dynamics.CRM.associatednavigationproperty,Microsoft.Dynamics.CRM.lookuplogicalname,Microsoft.Dynamics.CRM.morerecords"',
};

const parser = new DOMParser();
Expand Down Expand Up @@ -207,7 +207,7 @@ export async function getEntityOptions(
searchText?: string,
pageSize?: number
) {
if (!metadata || !lookupViewConfig) return Promise.resolve([]);
if (!metadata || !lookupViewConfig) return Promise.resolve({ records: [], moreRecords: false });

let fetchXml = lookupViewConfig.fetchXml;
let shouldDefaultSearch = false;
Expand Down Expand Up @@ -272,25 +272,33 @@ export async function getEntityOptions(
fetchXml = serializer.serializeToString(doc);
}

const records = await retrieveMultipleFetch(metadata.associatedEntity.EntitySetName, fetchXml, 1, pageSize);
return records.map((r) => {
const iconSrc = metadata.associatedEntity.PrimaryImageAttribute
? `/api/data/v${apiVersion}/${metadata.associatedEntity.EntitySetName}(${
r[metadata.associatedEntity.PrimaryIdAttribute]
})/${metadata.associatedEntity.PrimaryImageAttribute}/$value`
: "";

return {
id: r[metadata.associatedEntity.PrimaryIdAttribute],
intersectId: "",
associatedId: r[metadata.associatedEntity.PrimaryIdAttribute],
associatedName: r[metadata.associatedEntity.PrimaryNameAttribute],
optionText: r[metadata.associatedEntity.PrimaryNameAttribute],
selectedOptionText: r[metadata.associatedEntity.PrimaryNameAttribute],
iconSrc,
entity: r,
} as EntityOption;
});
const { records, moreRecords } = await retrieveMultipleFetch(
metadata.associatedEntity.EntitySetName,
fetchXml,
1,
pageSize
);
return {
records: records.map((r) => {
const iconSrc = metadata.associatedEntity.PrimaryImageAttribute
? `/api/data/v${apiVersion}/${metadata.associatedEntity.EntitySetName}(${
r[metadata.associatedEntity.PrimaryIdAttribute]
})/${metadata.associatedEntity.PrimaryImageAttribute}/$value`
: "";

return {
id: r[metadata.associatedEntity.PrimaryIdAttribute],
intersectId: "",
associatedId: r[metadata.associatedEntity.PrimaryIdAttribute],
associatedName: r[metadata.associatedEntity.PrimaryNameAttribute],
optionText: r[metadata.associatedEntity.PrimaryNameAttribute],
selectedOptionText: r[metadata.associatedEntity.PrimaryNameAttribute],
iconSrc,
entity: r,
} as EntityOption;
}),
moreRecords,
};
}

function getLanguagePack(webResourceUrl: string | undefined, defaultLanguagePack: LanguagePack): Promise<LanguagePack> {
Expand Down Expand Up @@ -719,17 +727,27 @@ export async function retrieveMultipleFetch(
}
const newFetchXml = new XMLSerializer().serializeToString(doc);

const { data } = await axios.get<{ value: ComponentFramework.WebApi.Entity[] }>(
`/api/data/v${apiVersion}/${entitySetName}`,
{
headers: DEFAULT_HEADERS,
params: {
fetchXml: encodeURIComponent(newFetchXml),
},
}
);
const { data } = await axios.get<{
value: ComponentFramework.WebApi.Entity[];
"@Microsoft.Dynamics.CRM.morerecords": boolean;
}>(`/api/data/v${apiVersion}/${entitySetName}`, {
headers: DEFAULT_HEADERS,
params: {
fetchXml: encodeURIComponent(newFetchXml),
},
});

return data.value ?? [];
if (data.value.length === 0) {
return {
records: [],
moreRecords: false,
};
}

return {
records: data.value,
moreRecords: data["@Microsoft.Dynamics.CRM.morerecords"],
};
} catch (error) {
// handle serialization error
if (error instanceof DOMException || error instanceof TypeError || error instanceof SyntaxError) {
Expand Down Expand Up @@ -768,7 +786,7 @@ export async function retrieveAssociatedRecords(
</entity>
</fetch>`;

const results = await retrieveMultipleFetch(intersectEntity.EntitySetName, fetchXml);
const { records: results } = await retrieveMultipleFetch(intersectEntity.EntitySetName, fetchXml);
return results.map((r) => {
const intersectId = r[intersectEntity.PrimaryIdAttribute];
const associatedId = r[`aLink.${associatedEntity.PrimaryIdAttribute}`];
Expand Down

0 comments on commit 412aff0

Please sign in to comment.