Skip to content

Commit

Permalink
feat(odd-platform-ui): owner association wip (#1622)
Browse files Browse the repository at this point in the history
  • Loading branch information
anatolii-yemets committed Apr 9, 2024
1 parent 938f49e commit 445cea2
Show file tree
Hide file tree
Showing 21 changed files with 800 additions and 310 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import React from 'react';
import React, { useMemo } from 'react';
import { Grid } from '@mui/material';
import { Navigate, Route, Routes, useLocation, useMatch } from 'react-router-dom';
import { useAtom } from 'jotai';
import { fetchOwnerAssociationRequestList } from 'redux/thunks';
import {
getNewOwnerAssociationRequestsPageInfo,
getResolvedOwnerAssociationRequestsPageInfo,
} from 'redux/selectors';
import { useAppDispatch, useAppSelector } from 'redux/lib/hooks';
import { Navigate, Route, Routes } from 'react-router-dom';
import { useGetOwnerAssociationRequestList } from 'lib/hooks/api/ownerAssociationRequest';
import { OwnerAssociationRequestStatusParam } from 'generated-sources';
import OwnerAssociationsTabs from './OwnerAssociationsTabs/OwnerAssociationsTabs';
import OwnerAssociationsHeader from './OwnerAssociationsHeader/OwnerAssociationsHeader';
import { queryAtom } from './OwnerAssociationsStore/OwnerAssociationsAtoms';
import OwnerAssociationsAtomProvider from './OwnerAssociationsStore/OwnerAssociationsProvider';

const OwnerAssociationsNew = React.lazy(
Expand All @@ -20,53 +14,49 @@ const OwnerAssociationsResolved = React.lazy(
() =>
import('./OwnerAssociationsList/OwnerAssociationsResolved/OwnerAssociationsResolved')
);
const OwnerAssociationsActive = React.lazy(
() => import('./OwnerAssociationsList/OwnerAssociationsActive/OwnerAssociationsActive')
);

const OwnerAssociations: React.FC = () => {
const dispatch = useAppDispatch();
const match = useMatch(useLocation().pathname);

const [query] = useAtom(queryAtom);

const size = 30;

const active = React.useMemo(() => {
if (match) {
return match.pathname.includes('new');
}
return false;
}, [match?.pathname]);
const { data: activeRequests } = useGetOwnerAssociationRequestList({
query: '',
status: OwnerAssociationRequestStatusParam.APPROVED,
size,
});
const { data: newRequests } = useGetOwnerAssociationRequestList({
query: '',
status: OwnerAssociationRequestStatusParam.PENDING,
size,
});

const newRequestsPageInfo = useAppSelector(getNewOwnerAssociationRequestsPageInfo);
const resolvedRequestsPageInfo = useAppSelector(
getResolvedOwnerAssociationRequestsPageInfo
const totalActive = useMemo(
() => activeRequests?.pages[0].pageInfo.total,
[activeRequests?.pages]
);

const total = React.useMemo(
() => newRequestsPageInfo.total + resolvedRequestsPageInfo.total,
[newRequestsPageInfo.total + resolvedRequestsPageInfo.total]
const totalNew = useMemo(
() => newRequests?.pages[0].pageInfo.total,
[newRequests?.pages]
);

React.useEffect(() => {
if (!query) {
dispatch(fetchOwnerAssociationRequestList({ page: 1, size, active: true }));
dispatch(fetchOwnerAssociationRequestList({ page: 1, size, active: false }));
}
}, []);

return (
<OwnerAssociationsAtomProvider>
<Grid container flexDirection='column' alignItems='center'>
<OwnerAssociationsHeader total={total} size={size} active={active} />
<OwnerAssociationsHeader />
<Grid sx={{ width: '100%' }}>
<OwnerAssociationsTabs
size={size}
newRequestsTabHint={newRequestsPageInfo.total}
newRequestsTabHint={totalNew}
activeAssociationsTabHint={totalActive}
/>
</Grid>
<Routes>
<Route path='' element={<Navigate to='new' />} />
<Route path='active' element={<OwnerAssociationsActive size={size} />} />
<Route path='new' element={<OwnerAssociationsNew size={size} />} />
<Route path='resolved' element={<OwnerAssociationsResolved size={size} />} />
<Route path='history' element={<OwnerAssociationsResolved size={size} />} />
</Routes>
</Grid>
</OwnerAssociationsAtomProvider>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React from 'react';
import { Controller, useForm } from 'react-hook-form';
import { Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';
import type { Collector, UserOwnerMappingFormData } from 'generated-sources';
import { registerCollector, updateCollector } from 'redux/thunks';
import { useAppDispatch, useAppSelector } from 'redux/lib/hooks';
import {
getCollectorCreatingStatuses,
getCollectorsUpdatingStatuses,
} from 'redux/selectors';
import {
Button,
DialogWrapper,
Input,
OwnerIdAutocomplete,
} from 'components/shared/elements';
import Asterisk from 'components/shared/styled-components/asterisk';
import { useCreateUserOwnerMapping } from 'lib/hooks';

interface OwnerAssociationFormProps {
btnCreateEl: React.JSX.Element;
}

const OwnerAssociationForm: React.FC<OwnerAssociationFormProps> = ({ btnCreateEl }) => {
const { t } = useTranslation();

const {
mutateAsync: createUserOwnerMapping,
isPending: isAssociationCreating,
isSuccess,
} = useCreateUserOwnerMapping();
// const dispatch = useAppDispatch();
// const { isLoading: isCollectorCreating, isLoaded: isCollectorCreated } = useAppSelector(
// getCollectorCreatingStatuses
// );
// const { isLoading: isCollectorUpdating, isLoaded: isCollectorUpdated } = useAppSelector(
// getCollectorsUpdatingStatuses
// );

const {
handleSubmit,
control,
reset,
formState: { isValid },
} = useForm<UserOwnerMappingFormData>({
mode: 'all',
reValidateMode: 'onChange',
defaultValues: {},
});

const clearState = () => {
reset();
};

const onSubmit = async (data: UserOwnerMappingFormData) => {
console.log('UserOwnerMappingFormData', data);
// await createUserOwnerMapping({
// userOwnerMappingFormData: data,
// });
// (collector
// ? dispatch(
// updateCollector({
// collectorId: collector.id,
// collectorFormData: parsedData,
// })
// )
// : dispatch(registerCollector({ collectorFormData: parsedData }))
// ).then(() => {
// clearState();
// });
};

const ownerAssociationFormTitle = (
<Typography variant='h4' component='span'>
Create association
</Typography>
);

const ownerAssociationFormContent = () => (
<form id='owner-association-create-form' onSubmit={handleSubmit(onSubmit)}>
<Typography variant='subtitle2' fontSize='0.73rem' sx={{ mb: 1.5 }}>
{t('Fields with the')} <Asterisk>*</Asterisk>
{' symbol are required to save the Association'}
</Typography>
<Controller
name='ownerId'
control={control}
rules={{ required: true }}
render={({ field }) => <OwnerIdAutocomplete field={field} />}
/>
<Controller
name='oidcUsername'
control={control}
render={({ field }) => (
<Input {...field} variant='main-m' sx={{ my: 1.5 }} label='User' />
)}
/>
<Controller
name='provider'
control={control}
rules={{ required: false }}
render={({ field }) => (
// <OwnerAutocomplete field={field} disableOwnerCreating={!createOwner} />
<div>provider autocomplete</div>
)}
/>
</form>
);

const ownerAssociationFormActionButtons = () => (
<Button
text={t('Save')}
type='submit'
form='owner-association-create-form'
buttonType='main-lg'
fullWidth
disabled={!isValid}
/>
);

return (
<DialogWrapper
renderOpenBtn={({ handleOpen }) =>
React.cloneElement(btnCreateEl, { onClick: handleOpen })
}
title={ownerAssociationFormTitle}
renderContent={ownerAssociationFormContent}
renderActions={ownerAssociationFormActionButtons}
handleCloseSubmittedForm={isSuccess}
isLoading={isAssociationCreating}
clearState={clearState}
confirmOnClose
/>
);
};

export default OwnerAssociationForm;
Original file line number Diff line number Diff line change
@@ -1,69 +1,50 @@
import React, { useCallback } from 'react';
import { Typography } from '@mui/material';
import { useAtom } from 'jotai';
import { useDebouncedCallback } from 'use-debounce';
import { useTranslation } from 'react-i18next';
import { Input, NumberFormatted } from 'components/shared/elements';
import { useAppDispatch } from 'redux/lib/hooks';
import { fetchOwnerAssociationRequestList } from 'redux/thunks';
import { Input, Button } from 'components/shared/elements';
import { WithPermissions } from 'components/shared/contexts';
import { Permission } from 'generated-sources';
import { AddIcon } from 'components/shared/icons';
import { queryAtom } from '../OwnerAssociationsStore/OwnerAssociationsAtoms';
import * as S from './OwnerAssociationsHeaderStyles';
import OwnerAssociationForm from './OwnerAssociationForm/OwnerAssociationForm';

interface OwnerAssociationsHeaderProps {
total: number;
size: number;
active: boolean;
}

const OwnerAssociationsHeader: React.FC<OwnerAssociationsHeaderProps> = ({
total,
size,
active,
}) => {
const OwnerAssociationsHeader: React.FC = () => {
const { t } = useTranslation();
const dispatch = useAppDispatch();

const [query, setQuery] = useAtom(queryAtom);

const handleSearch = useCallback(
useDebouncedCallback(() => {
dispatch(fetchOwnerAssociationRequestList({ page: 1, size, query, active }));
}, 500),
[query, active, size]
);

const handleInputChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
setQuery(event.target.value);
handleSearch();
},
[handleSearch, setQuery]
);

const handleKeyDown = useCallback(
(event: React.KeyboardEvent) => {
if (event.key === 'Enter') handleSearch();
},
[handleSearch]
[setQuery]
);

return (
<>
<S.Caption container sx={{ mb: 1 }}>
<Typography variant='h1'>{t('Owner associations')}</Typography>
<Typography variant='subtitle1' color='texts.info'>
<NumberFormatted value={total} /> {t('requests overall')}
</Typography>
<WithPermissions permissionTo={Permission.OWNER_ASSOCIATION_MANAGE}>
<OwnerAssociationForm
btnCreateEl={
<Button
text='Create association'
buttonType='secondary-m'
startIcon={<AddIcon />}
/>
}
/>
</WithPermissions>
</S.Caption>
<S.Caption container sx={{ mb: 2 }}>
<Input
variant='search-m'
placeholder={t('Search requests')}
maxWidth={340}
onKeyDown={handleKeyDown}
onChange={handleInputChange}
value={query}
handleSearchClick={handleSearch}
/>
</S.Caption>
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from 'react';
import { Grid, Typography } from '@mui/material';
import type { OwnerAssociationRequest } from 'generated-sources';
import { useAppDateTime } from 'lib/hooks';
import * as S from '../../OwnerAssociationsSharedStyles';

interface ActiveAssociationRequestProps {
ownerName: OwnerAssociationRequest['ownerName'];
username: OwnerAssociationRequest['username'];
provider?: OwnerAssociationRequest['provider'];
// TODO: fix type
role?: string;
statusUpdatedBy: OwnerAssociationRequest['statusUpdatedBy'];
statusUpdatedAt: OwnerAssociationRequest['statusUpdatedAt'];
}

const ActiveAssociationRequest: React.FC<ActiveAssociationRequestProps> = ({
provider,
username,
role,
statusUpdatedBy,
statusUpdatedAt,
ownerName,
}) => {
const { associationRequestFormattedDateTime } = useAppDateTime();

return (
<S.AssociationsItemContainer container>
<Grid item lg={2.5}>
<Typography variant='body1' noWrap title={username}>
{username}
</Typography>
</Grid>
<Grid item lg={2.5}>
<Typography variant='body1' noWrap title={ownerName}>
{ownerName}
</Typography>
</Grid>
<Grid item lg={1}>
{role}
</Grid>
<Grid item lg={2}>
<Typography variant='body1' noWrap title={provider}>
{provider}
</Typography>
</Grid>
<Grid item lg={2}>
<Typography
variant='body1'
noWrap
title={statusUpdatedBy?.owner?.name || statusUpdatedBy?.identity.username}
>
{statusUpdatedBy?.owner?.name || statusUpdatedBy?.identity.username}
</Typography>
</Grid>
<Grid item lg={2}>
<Typography variant='body1' noWrap>
{statusUpdatedAt &&
associationRequestFormattedDateTime(statusUpdatedAt?.getTime())}
</Typography>
</Grid>
</S.AssociationsItemContainer>
);
};

export default ActiveAssociationRequest;
Loading

0 comments on commit 445cea2

Please sign in to comment.