From f9a990573a0c4eabf27400b7f86f4d1c33249e02 Mon Sep 17 00:00:00 2001 From: Onitoxan Date: Tue, 16 Apr 2024 14:42:01 +0200 Subject: [PATCH] Refactors, Fixes and minor updates --- .../filters/filter-organization-table.tsx | 8 ++- .../filters/filter-pending-flows-table.tsx | 30 ++++----- .../app/pages/organizations/organization.tsx | 14 ++--- .../hpc-ftsadmin/src/app/utils/fn-promises.ts | 3 +- .../src/app/utils/map-functions.ts | 3 +- .../src/app/utils/parse-filters.ts | 61 +++++++++++++++---- libs/hpc-data/src/index.ts | 2 + libs/hpc-data/src/lib/util.ts | 44 ++++++++++++- libs/hpc-live/src/lib/utils.ts | 6 +- .../form-fields/multi-text-field.tsx | 9 ++- .../components/form-fields/number-field.tsx | 4 +- .../src/lib/components/form-fields/switch.tsx | 1 - .../lib/components/form-fields/types/types.ts | 1 - .../src/lib/components/search-filter.tsx | 5 +- 14 files changed, 131 insertions(+), 60 deletions(-) delete mode 100644 libs/hpc-ui/src/lib/components/form-fields/types/types.ts diff --git a/apps/hpc-ftsadmin/src/app/components/filters/filter-organization-table.tsx b/apps/hpc-ftsadmin/src/app/components/filters/filter-organization-table.tsx index d3edc175b..ac6f1a004 100644 --- a/apps/hpc-ftsadmin/src/app/components/filters/filter-organization-table.tsx +++ b/apps/hpc-ftsadmin/src/app/components/filters/filter-organization-table.tsx @@ -1,7 +1,8 @@ import { Form, Formik, FormikState } from 'formik'; import tw from 'twin.macro'; -import { C, FormObjectValue } from '@unocha/hpc-ui'; +import { C } from '@unocha/hpc-ui'; +import { util as codecs, FormObjectValue } from '@unocha/hpc-data'; import { Environment } from '../../../environments/interface'; import { decodeFilters, encodeFilters } from '../../utils/parse-filters'; import { LanguageKey, t } from '../../../i18n'; @@ -52,7 +53,7 @@ export const FilterOrganizationsTable = (props: Props) => { ); const FORM_VALIDATION = io.partial({ - organization: io.string, + date: codecs.VALID_DAYJS_DATE, }); const handleSubmit = (values: OrganizationFilterValues) => { @@ -125,6 +126,7 @@ export const FilterOrganizationsTable = (props: Props) => { )} name="organizationType" fnPromise={() => fnCategories('organizationType', environment)} + isAutocompleteAPI={false} /> { (s) => s.components.organizationsFilter.filters.date )} /> - void; } export interface PendingFlowsFilterValues { - status?: string; - dataProvider?: string; + status?: FormObjectValue | null; + dataProvider?: FormObjectValue | null; reporterRefCode?: string; sourceOrganizations?: Array; sourceCountries?: Array; @@ -33,8 +31,8 @@ export interface PendingFlowsFilterValues { } export const PENDING_FLOWS_FILTER_INITIAL_VALUES: PendingFlowsFilterValues = { - status: '', - dataProvider: '', + status: null, + dataProvider: null, reporterRefCode: '', sourceOrganizations: [], sourceCountries: [], @@ -55,10 +53,6 @@ export const FilterPendingFlowsTable = (props: Props) => { const { lang, env } = useContext(AppContext); const environment = env(); - const FORM_VALIDATION = io.partial({ - reporterRefCode: codecs.POSITIVE_INTEGER_FROM_STRING, - }); - const handleSubmit = (values: PendingFlowsFilterValues) => { const encodedFilters = encodeFilters( values, @@ -100,7 +94,6 @@ export const FilterPendingFlowsTable = (props: Props) => { query.filters, PENDING_FLOWS_FILTER_INITIAL_VALUES )} - validate={(values) => validateForm(values, FORM_VALIDATION)} onSubmit={handleSubmit} > {({ resetForm }) => ( @@ -125,21 +118,21 @@ export const FilterPendingFlowsTable = (props: Props) => { (s) => s.components.pendingFlowsFilter.filters.details )} > - s.components.pendingFlowsFilter.filters.status )} name="status" options={[ - { displayLabel: 'New', value: 'New' }, - { displayLabel: 'Update', value: 'Update' }, + { displayLabel: 'New', value: 'new' }, + { displayLabel: 'Update', value: 'updated' }, ]} /> - s.components.pendingFlowsFilter.filters.flowType + (s) => s.components.pendingFlowsFilter.filters.dataProvider )} name="dataProvider" fnPromise={async () => { @@ -151,6 +144,7 @@ export const FilterPendingFlowsTable = (props: Props) => { }; }); }} + isAutocompleteAPI={false} /> { {t.t( lang, - (s) => s.components.organizationUpdateCreate.update + (s) => s.components.organizationUpdateCreate.text.edit )} { {t.t( lang, - (s) => s.components.organizationUpdateCreate.create + (s) => s.components.organizationUpdateCreate.text.create )} diff --git a/apps/hpc-ftsadmin/src/app/utils/fn-promises.ts b/apps/hpc-ftsadmin/src/app/utils/fn-promises.ts index 1041ec2d9..8e093d742 100644 --- a/apps/hpc-ftsadmin/src/app/utils/fn-promises.ts +++ b/apps/hpc-ftsadmin/src/app/utils/fn-promises.ts @@ -1,6 +1,5 @@ -import { categories } from '@unocha/hpc-data'; +import { categories, FormObjectValue } from '@unocha/hpc-data'; import { Environment } from '../../environments/interface'; -import { FormObjectValue } from '@unocha/hpc-ui'; /** Functions to pass to fnPromise prop */ diff --git a/apps/hpc-ftsadmin/src/app/utils/map-functions.ts b/apps/hpc-ftsadmin/src/app/utils/map-functions.ts index 65126fe8b..9aecea0bc 100644 --- a/apps/hpc-ftsadmin/src/app/utils/map-functions.ts +++ b/apps/hpc-ftsadmin/src/app/utils/map-functions.ts @@ -1,7 +1,6 @@ -import { organizations } from '@unocha/hpc-data'; +import { organizations, FormObjectValue } from '@unocha/hpc-data'; import { LanguageKey } from '../../i18n'; import dayjs from 'dayjs'; -import { FormObjectValue } from '@unocha/hpc-ui'; export const valueToInteger = (value: string | number) => { return typeof value === 'number' ? value : parseInt(value); diff --git a/apps/hpc-ftsadmin/src/app/utils/parse-filters.ts b/apps/hpc-ftsadmin/src/app/utils/parse-filters.ts index ecc08bf89..13d269b4e 100644 --- a/apps/hpc-ftsadmin/src/app/utils/parse-filters.ts +++ b/apps/hpc-ftsadmin/src/app/utils/parse-filters.ts @@ -1,11 +1,15 @@ -import { categories, flows, organizations } from '@unocha/hpc-data'; +import { + categories, + flows, + organizations, + FormObjectValue, +} from '@unocha/hpc-data'; import { PendingFlowsFilterValues } from '../components/filters/filter-pending-flows-table'; import { Strings } from '../../i18n/iface'; import { OrganizationFilterValues } from '../components/filters/filter-organization-table'; import { Dayjs } from 'dayjs'; import { FlowsFilterValues } from '../components/filters/filter-flows-table'; import { valueToInteger } from './map-functions'; -import { FormObjectValue } from '@unocha/hpc-ui'; /** * The whole idea of this filtering system is to parse every Filter Form to a common type, in this case Filter @@ -43,7 +47,7 @@ export type FlowStatusType = | 'commitment' | 'carryover' | 'paid' - | 'pledged' + | 'pledge' | 'parked' | 'pass_through' | 'standard'; @@ -52,7 +56,7 @@ export type FlowStatusType = * Type guard functions */ -const filterValueIsString = (value: FilterValues): value is string => { +const filterValueIsString = (value: FilterValues | number): value is string => { return typeof value === 'string'; }; @@ -92,13 +96,13 @@ const filterValueIsDayJS = (value: FilterValues): value is Dayjs => { }; const filterValueIsFlowStatusType = ( - value: FilterValues + value: FilterValues | number ): value is FlowStatusType => { const values = [ 'commitment', 'carryover', 'paid', - 'pledged', + 'pledge', 'parked', 'pass_through', 'standard', @@ -252,7 +256,10 @@ const parseActiveStatus = (activeStatus: string): boolean | undefined => { }; export const parseFlowFilters = ( - filters: Filter, + filters: Filter< + | keyof Strings['components']['flowsFilter']['filters'] + | keyof Strings['components']['pendingFlowsFilter']['filters'] + >, pending?: boolean ): flows.SearchFlowsParams => { const res: flows.SearchFlowsParams = { @@ -346,11 +353,13 @@ export const parseFlowFilters = ( case 'flowType': case 'flowStatus': { const filterValue = filters[key]?.value; + console.log(filterValue); if (!filterValue) { break; } - if (filterValueIsFlowStatusType(filterValue)) { - res[filterValue] = true; + if (filterValueIsFormObjectValue(filterValue)) { + const statusType = filterValue.value; + if (filterValueIsFlowStatusType(statusType)) res[statusType] = true; } break; } @@ -407,6 +416,33 @@ export const parseFlowFilters = ( } break; } + case 'dataProvider': { + const dataProvider = filters.dataProvider?.value; + if (!dataProvider) { + break; + } + if ( + filterValueIsFormObjectValue(dataProvider) && + filterValueIsString(dataProvider.value) + ) { + res.nestedFlowFilters.systemID = dataProvider.value; + } + break; + } + case 'status': { + const status = filters.status?.value; + if (!status) { + break; + } + if ( + filterValueIsFormObjectValue(status) && + filterValueIsString(status.value) && + (status.value === 'new' || status.value === 'updated') + ) { + res.status = status.value; + } + break; + } } } } @@ -478,8 +514,11 @@ export const parseOrganizationFilters = ( if (!value) { break; } - if (filterValueIsString(value)) { - res.search[key] = value; + if ( + filterValueIsFormObjectValue(value) && + filterValueIsString(value.value) + ) { + res.search[key] = value.value; } break; } diff --git a/libs/hpc-data/src/index.ts b/libs/hpc-data/src/index.ts index bab1ce099..011a16528 100644 --- a/libs/hpc-data/src/index.ts +++ b/libs/hpc-data/src/index.ts @@ -14,6 +14,7 @@ import * as projects from './lib/projects'; import * as reportingWindows from './lib/reporting-windows'; import * as usageYears from './lib/usageYears'; import * as util from './lib/util'; +import { FormObjectValue } from './lib/util'; export interface Model { access: access.Model; @@ -48,4 +49,5 @@ export { usageYears, util, systems, + FormObjectValue, }; diff --git a/libs/hpc-data/src/lib/util.ts b/libs/hpc-data/src/lib/util.ts index e09fe7609..ad384ef62 100644 --- a/libs/hpc-data/src/lib/util.ts +++ b/libs/hpc-data/src/lib/util.ts @@ -1,4 +1,7 @@ import * as t from 'io-ts'; +import { Dayjs, isDayjs } from 'dayjs'; + +export type FormObjectValue = { displayLabel: string; value: string | number }; export const resultWithPermissions = ( data: t.Type, @@ -203,8 +206,8 @@ export const ARRAY_BUFFER = new t.Type( * Takes date string and verifies that * it is a valid date in YYYY-MM-DD format */ -const isDateValid = (date: string) => { - if (!ISO_DATE.test(date)) { +const isDateValid = (date: string | undefined) => { + if (!date || !ISO_DATE.test(date)) { return false; } @@ -269,3 +272,40 @@ export const DATE_FROM_STRING = new t.Type( }, t.identity ); + +const isFormObjectValue = (v: unknown): v is FormObjectValue => + typeof v === 'object' && + !Array.isArray(v) && + v !== null && + Object.keys(v).includes('displayLabel') && + Object.keys(v).includes('value'); + +/** + * Accepts a FormObjectValue. + */ +export const FORM_OBJECT_VALUE = new t.Type( + 'FORM_OBJECT_VALUE', + isFormObjectValue, + (v, c) => { + if (isFormObjectValue(v)) { + return t.success(v); + } + return t.failure(v, c); + }, + t.identity +); + +export const VALID_DAYJS_DATE = new t.Type( + 'VALID_DAYJS_DATE', + (u): u is Dayjs => u instanceof Dayjs, + (v, c) => { + if (isDayjs(v)) { + if (isDateValid(v.toISOString().split('T').at(0))) { + return t.success(v); + } + return t.failure(v, c); + } + return t.failure(v, c); + }, + t.identity +); diff --git a/libs/hpc-live/src/lib/utils.ts b/libs/hpc-live/src/lib/utils.ts index 165a9d0c2..fb79352a6 100644 --- a/libs/hpc-live/src/lib/utils.ts +++ b/libs/hpc-live/src/lib/utils.ts @@ -41,9 +41,7 @@ function parseFilterToGraphQL(obj: Record): string { return `{${keyValuePairs.join(', ')}}`; } -/** - * Also valid for TotalAmountUSD() params - */ + export const searchFlowsParams = (params: flows.SearchFlowsParams): string => { let queryParams = ''; let key: keyof typeof params; @@ -57,7 +55,7 @@ export const searchFlowsParams = (params: flows.SearchFlowsParams): string => { case 'standard': case 'pass_through': case 'pending': - case 'pledged': + case 'pledge': case 'includeChildrenOfParkedFlows': if (params[unmutableKey]) queryParams = queryParams.concat(unmutableKey, ': true '); diff --git a/libs/hpc-ui/src/lib/components/form-fields/multi-text-field.tsx b/libs/hpc-ui/src/lib/components/form-fields/multi-text-field.tsx index ce3c0b765..b3461724b 100644 --- a/libs/hpc-ui/src/lib/components/form-fields/multi-text-field.tsx +++ b/libs/hpc-ui/src/lib/components/form-fields/multi-text-field.tsx @@ -20,12 +20,14 @@ const MultiTextField = ({ label, placeholder, type, + errorMessage, ...otherProps }: { name: string; label: string; placeholder?: string; type?: 'text' | 'currency'; + errorMessage?: string; }) => { const { setFieldValue } = useFormikContext(); const [field, meta] = useField(name); @@ -56,7 +58,12 @@ const MultiTextField = ({ } : { ...params.InputProps }, error: meta && meta.touched && meta.error ? true : false, - helperText: meta && meta.touched && meta.error ? meta.error : undefined, + helperText: + meta && meta.touched && meta.error + ? errorMessage + ? meta.error.replace('{validationError}', errorMessage) + : meta.error + : undefined, }; }; diff --git a/libs/hpc-ui/src/lib/components/form-fields/number-field.tsx b/libs/hpc-ui/src/lib/components/form-fields/number-field.tsx index 172fd0000..542cad5ca 100644 --- a/libs/hpc-ui/src/lib/components/form-fields/number-field.tsx +++ b/libs/hpc-ui/src/lib/components/form-fields/number-field.tsx @@ -9,12 +9,14 @@ interface NumberFieldProps { label: string; placeholder?: string; required?: boolean; + allowNegative?: boolean; } const NumberField = ({ type, name, label, placeholder, + allowNegative, required, ...otherProps }: NumberFieldProps) => { @@ -34,7 +36,7 @@ const NumberField = ({ valueIsNumericString size="small" decimalScale={type === 'number' ? 0 : undefined} // 0 means no decimals - allowNegative={false} + allowNegative={allowNegative} customInput={StyledTextField} InputProps={{ startAdornment: diff --git a/libs/hpc-ui/src/lib/components/form-fields/switch.tsx b/libs/hpc-ui/src/lib/components/form-fields/switch.tsx index 97b9507f1..403498104 100644 --- a/libs/hpc-ui/src/lib/components/form-fields/switch.tsx +++ b/libs/hpc-ui/src/lib/components/form-fields/switch.tsx @@ -4,7 +4,6 @@ import { Switch as SwitchMUI, } from '@mui/material'; import { useField } from 'formik'; -import React from 'react'; type SwitchSize = 'small' | 'medium'; type SwitchColor = 'primary' | 'error' | 'success'; diff --git a/libs/hpc-ui/src/lib/components/form-fields/types/types.ts b/libs/hpc-ui/src/lib/components/form-fields/types/types.ts deleted file mode 100644 index a051b8c98..000000000 --- a/libs/hpc-ui/src/lib/components/form-fields/types/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type FormObjectValue = { displayLabel: string; value: string | number }; diff --git a/libs/hpc-ui/src/lib/components/search-filter.tsx b/libs/hpc-ui/src/lib/components/search-filter.tsx index 2591e81d9..4a946a3c9 100644 --- a/libs/hpc-ui/src/lib/components/search-filter.tsx +++ b/libs/hpc-ui/src/lib/components/search-filter.tsx @@ -3,7 +3,6 @@ import { styled } from '../theme'; import tw from 'twin.macro'; import ChevronLeftIcon from '@mui/icons-material/ChevronLeft'; import { Drawer, IconButton, Tooltip } from '@mui/material'; -import { organizations } from '@unocha/hpc-data'; interface Props { className?: string; title?: string; @@ -20,9 +19,7 @@ export interface SearchFields { interface FieldConfig { isMulti?: boolean; hasAutocomplete?: boolean; - fnPromise?: ({ - query, - }: organizations.GetOrganizationsAutocompleteParams) => Promise; + fnPromise?: ({ query }: { query: string }) => Promise; selectFields?: { value: string | number; name: string }[]; }