Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TM-1398] Merge tree species epic #744

Merged
merged 18 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
eab3706
[TM-1476] WEB DESIGN: Update the tree species UI to enable users to …
dottyy Dec 4, 2024
e22b5ed
[TM-1402] Wire up the autocomplete search API.
roguenet Dec 5, 2024
f4d53dc
[TM-1402] Remove stray console log.
roguenet Dec 6, 2024
7fecc40
[TM-1402] Tune up the autocomplete search.
roguenet Dec 10, 2024
ffa083b
[TM-1402] Improve auto complete and tree species input UX.
roguenet Dec 10, 2024
adb0d44
[TM-1402] Cleanup some more testing UI.
roguenet Dec 10, 2024
946a71d
[TM-1402] Clear out the auto complete box when a new species is added.
roguenet Dec 11, 2024
2921b52
[TM-1402] Define an entity context for forms.
roguenet Dec 11, 2024
f04a7d5
[TM-1402] Wired up the "new" tag against the new establishment tree d…
roguenet Dec 11, 2024
9a07c46
[TM-1402] Missing parens in hover text.
roguenet Dec 12, 2024
20404f2
[TM-1402] Switch to yarn
roguenet Dec 12, 2024
c22fdd8
[TM-1402] Some cleanup on layout logic and translations.
roguenet Dec 12, 2024
d5f93f9
[TM-1402] Integrate previous planting counts.
roguenet Dec 12, 2024
5740c64
[TM-1402] Run the test suite on all pull requests.
roguenet Dec 13, 2024
64584f4
[TM-1402] Don't try to set the debug method in test envs.
roguenet Dec 13, 2024
f0cae93
[TM-1402] Regenerate storyshot for tree species input.
roguenet Dec 13, 2024
2250727
Merge pull request #739 from wri/feat/TM-1402-tree-species-design-int…
roguenet Dec 13, 2024
448211f
[TM-1398] Merge remote-tracking branch 'origin/staging' into epic/TM-…
roguenet Dec 13, 2024
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
1 change: 0 additions & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
name: pull-request
on:
pull_request:
branches: [main, staging, release/**]
jobs:
test:
runs-on: ubuntu-latest
Expand Down
20 changes: 14 additions & 6 deletions openapi-codegen.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,28 +27,34 @@ type EnvironmentName = (typeof ENVIRONMENT_NAMES)[number];
type Environment = {
apiBaseUrl: string;
userServiceUrl: string;
entityServiceUrl: string;
};

const ENVIRONMENTS: { [Property in EnvironmentName]: Environment } = {
local: {
apiBaseUrl: "http://localhost:8080",
userServiceUrl: "http://localhost:4010"
userServiceUrl: "http://localhost:4010",
entityServiceUrl: "http://localhost:4050"
},
dev: {
apiBaseUrl: "https://api-dev.terramatch.org",
userServiceUrl: "https://api-dev.terramatch.org"
userServiceUrl: "https://api-dev.terramatch.org",
entityServiceUrl: "https://api-dev.terramatch.org"
},
test: {
apiBaseUrl: "https://api-test.terramatch.org",
userServiceUrl: "https://api-test.terramatch.org"
userServiceUrl: "https://api-test.terramatch.org",
entityServiceUrl: "https://api-test.terramatch.org"
},
staging: {
apiBaseUrl: "https://api-staging.terramatch.org",
userServiceUrl: "https://api-staging.terramatch.org"
userServiceUrl: "https://api-staging.terramatch.org",
entityServiceUrl: "https://api-staging.terramatch.org"
},
prod: {
apiBaseUrl: "https://api.terramatch.org",
userServiceUrl: "https://api.terramatch.org"
userServiceUrl: "https://api.terramatch.org",
entityServiceUrl: "https://api.terramatch.org"
}
};

Expand All @@ -60,13 +66,15 @@ if (!ENVIRONMENT_NAMES.includes(declaredEnv as EnvironmentName)) {
const DEFAULTS = ENVIRONMENTS[declaredEnv];
const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL ?? DEFAULTS.apiBaseUrl;
const userServiceUrl = process.env.NEXT_PUBLIC_USER_SERVICE_URL ?? DEFAULTS.userServiceUrl;
const entityServiceUrl = process.env.NEXT_PUBLIC_ENTITY_SERVICE_URL ?? DEFAULTS.entityServiceUrl;

// The services defined in the v3 Node BE codebase. Although the URL path for APIs in the v3 space
// are namespaced by feature set rather than service (a service may contain multiple namespaces), we
// isolate the generated API integration by service to make it easier for a developer to find where
// the associated BE code is for a given FE API integration.
const SERVICES = {
"user-service": userServiceUrl
"user-service": userServiceUrl,
"entity-service": entityServiceUrl
};

const config: Record<string, Config> = {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"build-storybook": "storybook build",
"generate:api": "openapi-codegen gen api",
"generate:userService": "openapi-codegen gen userService",
"generate:services": "npm run generate:userService",
"generate:entityService": "openapi-codegen gen entityService",
"generate:services": "yarn generate:userService && yarn generate:entityService",
"tx:push": "eval $(grep '^TRANSIFEX_TOKEN' .env) && eval $(grep '^TRANSIFEX_SECRET' .env) && txjs-cli push --key-generator=hash src/ --token=$TRANSIFEX_TOKEN --secret=$TRANSIFEX_SECRET",
"tx:pull": "eval $(grep '^TRANSIFEX_TOKEN' .env) && eval $(grep '^TRANSIFEX_SECRET' .env) && txjs-cli pull --token=$TRANSIFEX_TOKEN --secret=$TRANSIFEX_SECRET"
},
Expand Down
53 changes: 28 additions & 25 deletions src/admin/components/EntityEdit/EntityEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useNavigate, useParams } from "react-router-dom";
import modules from "@/admin/modules";
import WizardForm from "@/components/extensive/WizardForm";
import LoadingContainer from "@/components/generic/Loading/LoadingContainer";
import EntityProvider from "@/context/entity.provider";
import FrameworkProvider, { Framework } from "@/context/framework.provider";
import {
GetV2FormsENTITYUUIDResponse,
Expand Down Expand Up @@ -73,31 +74,33 @@ export const EntityEdit = () => {
<div className="mx-auto w-full max-w-7xl">
<LoadingContainer loading={isLoading}>
<FrameworkProvider frameworkKey={framework}>
<WizardForm
steps={formSteps!}
errors={error}
onBackFirstStep={() => navigate("..")}
onChange={data =>
updateEntity({
pathParams: { uuid: entityUUID, entity: entityName },
body: { answers: normalizedFormData(data, formSteps!) }
})
}
formStatus={isSuccess ? "saved" : isUpdating ? "saving" : undefined}
onSubmit={() => navigate(createPath({ resource, id, type: "show" }))}
defaultValues={defaultValues}
title={title}
tabOptions={{
markDone: true,
disableFutureTabs: true
}}
summaryOptions={{
title: "Review Details",
downloadButtonText: "Download"
}}
roundedCorners
hideSaveAndCloseButton
/>
<EntityProvider entityUuid={entityUUID} entityName={entityName}>
<WizardForm
steps={formSteps!}
errors={error}
onBackFirstStep={() => navigate("..")}
onChange={data =>
updateEntity({
pathParams: { uuid: entityUUID, entity: entityName },
body: { answers: normalizedFormData(data, formSteps!) }
})
}
formStatus={isSuccess ? "saved" : isUpdating ? "saving" : undefined}
onSubmit={() => navigate(createPath({ resource, id, type: "show" }))}
defaultValues={defaultValues}
title={title}
tabOptions={{
markDone: true,
disableFutureTabs: true
}}
summaryOptions={{
title: "Review Details",
downloadButtonText: "Download"
}}
roundedCorners
hideSaveAndCloseButton
/>
</EntityProvider>
</FrameworkProvider>
</LoadingContainer>
</div>
Expand Down
9 changes: 9 additions & 0 deletions src/assets/icons/add-button.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/icons/edit-ta.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/icons/new-tag-tree-species.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions src/assets/icons/non-scientific name.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/icons/trash-ta.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
import { Popover, Transition } from "@headlessui/react";
import { useT } from "@transifex/react";
import classNames from "classnames";
import { ChangeEvent, forwardRef, Fragment, Ref, useState } from "react";
import { Else, If, Then } from "react-if";

import { useDebounce } from "@/hooks/useDebounce";
import { useValueChanged } from "@/hooks/useValueChanged";

import Text from "../../Text/Text";
import Input, { InputProps } from "../Input/Input";

export interface AutoCompleteInputProps extends InputProps {
onSearch: (query: string) => Promise<string[]>;
disableAutoComplete?: boolean;
classNameMenu?: string;
}

const SEARCH_RESET = { list: [], query: "" };

//TODO: Bugfix: Users can enter space in this input
const AutoCompleteInput = forwardRef(
({ onSearch, disableAutoComplete, ...inputProps }: AutoCompleteInputProps, ref?: Ref<HTMLInputElement>) => {
(
{ onSearch, disableAutoComplete, classNameMenu, ...inputProps }: AutoCompleteInputProps,
ref?: Ref<HTMLInputElement>
) => {
const t = useT();
const [list, setList] = useState<string[]>([]);
const [searchResult, setSearchResult] = useState<{ list: string[]; query: string }>(SEARCH_RESET);
const [loading, setLoading] = useState(false);

const onSelect = (item: string) => {
Expand All @@ -27,51 +35,53 @@ const AutoCompleteInput = forwardRef(
inputProps.onChange?.({ target: { name: inputProps.name, value: item } } as ChangeEvent<HTMLInputElement>);
}

setList([]);
// Avoid showing the search result list unless the name changes again.
setSearchResult({ list: [], query: item });
};

const search = useDebounce(async (query: string) => {
if (query === searchResult.query) return;

setLoading(true);

onSearch(query)
.then(resp => {
setList(resp);
setLoading(false);
})
.catch(() => {
setList([]);
setLoading(false);
});
try {
setSearchResult({ list: await onSearch(query), query });
setLoading(false);
} catch {
setSearchResult(SEARCH_RESET);
setLoading(false);
}
});

useValueChanged(inputProps.value, () => search(String(inputProps.value ?? "")));

return (
<Popover as="div" className="w-full">
<Popover.Button as={Fragment}>
<Input
{...inputProps}
ref={ref}
onChangeCapture={e => !disableAutoComplete && search(e.currentTarget.value)}
/>
<Input {...inputProps} ref={ref} />
</Popover.Button>

<Transition
show={list.length > 0 || !!loading}
show={searchResult.list.length > 0 || !!loading}
enter="transition duration-100 ease-out"
enterFrom="transform scale-95 opacity-0"
enterTo="transform scale-100 opacity-100"
leave="transition duration-75 ease-out"
leaveFrom="transform scale-100 opacity-100"
leaveTo="transform scale-95 opacity-0"
>
<Popover.Panel as="div" className="border-light mt-2 max-h-[230px] overflow-auto rounded-lg">
<Popover.Panel
as="div"
className={classNames("border-light mt-2 max-h-[230px] overflow-auto rounded-lg", classNameMenu)}
>
<If condition={loading}>
<Then>
<Text variant="text-body-600" className="p-3">
{t("Loading ...")}
</Text>
</Then>
<Else>
{list.map(item => (
{searchResult.list.map(item => (
<Text
key={item}
variant="text-body-600"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ exports[`Storyshots Components/Elements/Inputs/AutoComplete Default 1`] = `
className="w-full outline-none transition-all duration-300 ease-in-out focus:ring-transparent px-3 py-[9px] rounded-lg focus:border-primary-500 border border-neutral-200"
data-headlessui-state=""
id=":r9:"
onChangeCapture={[Function]}
onClick={[Function]}
onKeyUp={[Function]}
onMouseDown={[Function]}
Expand Down
7 changes: 6 additions & 1 deletion src/components/elements/Inputs/Input/Input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export interface InputProps
extends InputWrapperProps,
Omit<DetailedHTMLProps<InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>, "type" | "form"> {
name: string;
variant?: "secondary" | "default" | "login" | "signup" | "monitored";
variant?: "secondary" | "default" | "login" | "signup" | "monitored" | "treePlanted";
formHook?: UseFormReturn<any>;
clearable?: boolean;
iconButtonProps?: IconButtonProps;
Expand Down Expand Up @@ -112,6 +112,11 @@ const Input = forwardRef(
true,
"pl-4": inputProps.type === "number",
"border-neutral-300": !error
},
treePlanted: {
"py-[7.5px] py-1.5 !w-[100px] text-center border border-blueCustom-700 rounded hover:border-primary hover:shadow-blue-border opacity-60 outline-none text-14-light !font-primary":
true,
"text-center": inputProps.type === "number"
}
};

Expand Down
Loading
Loading