Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import classes from "@ff-client/utils/classes";
import translate from "@ff-client/utils/translations";
import type React from "react";
import { useCallback, useMemo } from "react";
import { FieldSelect } from "./field-select";
import CustomIcon from "./icons/custom";
import ListIcon from "./icons/list";
Expand All @@ -24,38 +25,54 @@ import type { SourceField as SourceFieldType } from "./mapping.types";
type Props = {
sources: SourceFieldType[];
mapping?: FieldMapping;
query?: string;
updateValue: (value: FieldMapping) => void;
};

export const FieldMappingController: React.FC<Props> = ({
sources,
mapping,
query = "",
updateValue,
}) => {
const update = useCallback(
(
sourceId: string | number,
type: TargetFieldType,
value?: string,
): void => {
updateValue({
...mapping,
[sourceId]: {
type,
value: value || "",
},
});
},
[mapping, updateValue],
);

const visibleSources = useMemo(() => {
const searchQuery = query.trim().toLowerCase();
if (!searchQuery) {
return sources;
}

return sources.filter((source) =>
source.label.toLowerCase().includes(searchQuery),
);
}, [sources, query]);

if (!mapping) {
return null;
}

const update = (
sourceId: string | number,
type: TargetFieldType,
value?: string,
): void => {
updateValue({
...mapping,
[sourceId]: {
type,
value,
},
});
};

return (
<MappingContainer>
{sources.length === 0 && (
{visibleSources.length === 0 && (
<HelpText>{translate("No data present")}</HelpText>
)}
{sources.map((source) => {
{visibleSources.map((source) => {
const map = mapping[source.id] ?? {
type: TargetFieldType.Relation,
value: "",
Expand All @@ -68,7 +85,7 @@ export const FieldMappingController: React.FC<Props> = ({
</SourceField>

<TypeButtonGroup>
{source.options?.length > 0 && (
{source.options!.length > 0 && (
<TypeButton
title={translate("Pre-defined options")}
className={classes(
Expand Down Expand Up @@ -110,7 +127,7 @@ export const FieldMappingController: React.FC<Props> = ({
onChange={(fieldUid) => {
update(source.id, TargetFieldType.Preset, fieldUid);
}}
options={source.options.map((opt) => ({
options={source.options!.map((opt) => ({
value: opt.key,
label: opt.label,
}))}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@ export const MappingContainer = styled.div`
${scrollBar};
`;

export const MappingSearchWrapper = styled.div`
max-width: 1000px;
margin-bottom: 0px;
`;

export const SourceField = styled.div`
position: relative;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import { Control } from "@components/form-controls/control";
import type { ControlType } from "@components/form-controls/types";
import { Search } from "@components/search/search";
import type { FieldMapping as FieldMappingType } from "@ff-client/types/integrations";
import type { FieldMappingProperty } from "@ff-client/types/properties";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import cloneDeep from "lodash/cloneDeep";
import type React from "react";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import Skeleton from "react-loading-skeleton";
import { useParams } from "react-router-dom";

import RefreshIcon from "./icons/refresh";
import { FieldMappingController } from "./mapping.controller";
import { RefreshButton } from "./mapping.styles";
import { MappingSearchWrapper, RefreshButton } from "./mapping.styles";
import type { SourceField } from "./mapping.types";
import { extractParameter } from "./mapping.utilities";

const MAX_VISIBLE_SOURCES = 12;

const FieldMapping: React.FC<ControlType<FieldMappingProperty>> = ({
value = {},
property,
Expand All @@ -23,8 +26,9 @@ const FieldMapping: React.FC<ControlType<FieldMappingProperty>> = ({
context,
}) => {
const { formId } = useParams();
const [query, setQuery] = useState("");

const params: Record<string, string> = { formId };
const params: Record<string, string> = { formId: formId as string };
if (property.parameterFields) {
Object.entries(property.parameterFields).forEach(([key, value]) => {
params[value] = extractParameter(context, key);
Expand All @@ -35,7 +39,7 @@ const FieldMapping: React.FC<ControlType<FieldMappingProperty>> = ({
queryKey: ["field-mapping", property.source, params],
queryFn: async () => {
const response = await axios
.get<SourceField[]>(property.source, { params })
.get<SourceField[]>(property.source!, { params })
.then((res) => res.data);

return response;
Expand All @@ -53,9 +57,9 @@ const FieldMapping: React.FC<ControlType<FieldMappingProperty>> = ({

const valueClone = cloneDeep(value);
let modified = false;
Object.keys(value).forEach((key) => {
Object.keys(value!).forEach((key) => {
if (!availableProperties.includes(key)) {
delete valueClone[key];
delete valueClone![key];
modified = true;
}
});
Expand All @@ -78,12 +82,22 @@ const FieldMapping: React.FC<ControlType<FieldMappingProperty>> = ({
>
<RefreshIcon />
</RefreshButton>

{data && (
<FieldMappingController
sources={data}
mapping={value}
updateValue={updateValue}
/>
<>
{data.length > MAX_VISIBLE_SOURCES && (
<MappingSearchWrapper>
<Search query={query} setQuery={setQuery} showShortcut={false} />
</MappingSearchWrapper>
)}

<FieldMappingController
sources={data}
mapping={value as FieldMappingType}
query={query}
updateValue={updateValue}
/>
</>
)}
{!data && isFetching && (
<div>
Expand Down
8 changes: 6 additions & 2 deletions packages/client/src/app/components/search/search.hooks.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import type { RefObject } from "react";
import { useEffect, useRef } from "react";

export const useSearchFocus = (): RefObject<HTMLInputElement> => {
export const useSearchFocus = (enabled = true): RefObject<HTMLInputElement> => {
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
if (!enabled) {
return;
}

const onKeyDown = (event: KeyboardEvent): void => {
if (event.isComposing || event.altKey || event.ctrlKey || event.metaKey) {
return;
Expand Down Expand Up @@ -35,7 +39,7 @@ export const useSearchFocus = (): RefObject<HTMLInputElement> => {
return () => {
window.removeEventListener("keydown", onKeyDown);
};
}, []);
}, [enabled]);

return inputRef;
};
12 changes: 9 additions & 3 deletions packages/client/src/app/components/search/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ type Props = {
placeholder?: string;
query: string;
setQuery?: (value: string) => void;
showShortcut?: boolean;
};

export const Search: React.FC<Props> = ({ placeholder, query, setQuery }) => {
const ref = useSearchFocus();
export const Search: React.FC<Props> = ({
placeholder,
query,
setQuery,
showShortcut = true,
}) => {
const ref = useSearchFocus(showShortcut);

return (
<Wrapper>
Expand All @@ -27,7 +33,7 @@ export const Search: React.FC<Props> = ({ placeholder, query, setQuery }) => {
<SearchIconSVG />
</SearchIcon>

<SearchKeyHelper>{"/"}</SearchKeyHelper>
{showShortcut && <SearchKeyHelper>{"/"}</SearchKeyHelper>}

<SearchBar
ref={ref}
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Loading
Loading