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

feat(partner): Deal with Fetching Search Partners results #22

Merged
merged 1 commit into from
Jan 2, 2024
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
3 changes: 2 additions & 1 deletion components/Partner/Parnter.styled.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { Box } from '@mui/material';
export const StyledWrapper = styled.div`
position: relative;
margin: 70px auto 0;
padding-bottom: 14px;
width: 100%;
max-width: 1024px;
min-height: 100vh;
margin-top: -80px;

@media (max-width: 900px) {
padding: 0 16px;
padding: 0 16px 44px;
margin-top: -50px;
}
`;
Expand Down
1 change: 0 additions & 1 deletion components/Partner/PartnerList/PartnerCard/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ function PartnerCard({

return (
<StyledCard>
{/* TODO: href redirect */}
<StyledCardContainer>
<FlexAlignCenter mb="8px">
<PartnerCardAvator image={image} />
Expand Down
26 changes: 12 additions & 14 deletions components/Partner/PartnerList/index.jsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,14 @@
import { useEffect, Fragment } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchAllPartners } from '@/redux/actions/partners';
import { Fragment } from 'react';
import { useRouter } from 'next/router';
import { useSelector } from 'react-redux';
import useMediaQuery from '@mui/material/useMediaQuery';
import { Grid, Box } from '@mui/material';
import PartnerCard from './PartnerCard';

function PartnerList() {
const partners = useSelector((state) => state.partners);
const dispatch = useDispatch();

// TODO: ADD PAGE
const handleFetchData = () => {
dispatch(fetchAllPartners());
};
const router = useRouter();

useEffect(() => {
handleFetchData();
}, []);
const partners = useSelector((state) => state.partners);

const lists = partners.items || [];
const mobileScreen = useMediaQuery('(max-width: 900px)');
Expand All @@ -34,7 +26,13 @@ function PartnerList() {
>
{lists.map((item, idx) => (
<Fragment key={`${item._id}`}>
<Grid item width="100%" md={6} mb={mobileScreen && '12px'}>
<Grid
onClick={() => router.push(`partner/${item._id}`)}
item
width="100%"
md={6}
mb={mobileScreen && '12px'}
>
<PartnerCard
image={item.photoURL}
date={item.date}
Expand Down
17 changes: 6 additions & 11 deletions components/Partner/SearchParamsList/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,14 @@ import {
StyledClosed,
} from './SearchParamsList.styles';

const SearchParamsList = ({ paramsKey = [], keySelections = {} }) => {
const [getSearchParams, pushState] = useSearchParamsManager();
const SearchParamsList = ({ paramsKey = [], paramsKeyOptions = {} }) => {
const [getSearchParams, pushState, genParamsItems] = useSearchParamsManager();

const handleGenerateParams = (params) => {
return params.reduce((acc, param) => {
const values = getSearchParams(param).filter((value) =>
keySelections[param]?.includes(value),
);
return [...acc, { key: param, values }];
}, []);
};
const params = useMemo(
() => (Array.isArray(paramsKey) ? handleGenerateParams(paramsKey) : []),
() =>
Array.isArray(paramsKey)
? genParamsItems(paramsKey, paramsKeyOptions)
: [],
[getSearchParams],
);

Expand Down
90 changes: 80 additions & 10 deletions components/Partner/index.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { useSelector } from 'react-redux';
import { useEffect, useMemo } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { Box, Button } from '@mui/material';
import useMediaQuery from '@mui/material/useMediaQuery';
import { AREAS } from '@/constants/areas';
import { fetchPartners } from '@/redux/actions/partners';
import { EDUCATION_STEP, ROLE } from '@/constants/member';
import { SEARCH_TAGS } from '@/constants/category';
import useSearchParamsManager from '@/hooks/useSearchParamsManager';

import PartnerList from './PartnerList';
import SearchField from './SearchField';
import SearchParamsList from './SearchParamsList';
Expand All @@ -13,11 +18,79 @@ import {
StyledSearchWrapper,
} from './Parnter.styled';

// utils
const _compose =
(...fns) =>
(x) =>
fns.reduceRight((v, f) => f(v), x);
const _map = (arr, key) => arr.map((item) => item[key]);
const mapValues = (values, mapFn) => values.map(mapFn).join(',');

const createObjFromArrary = (arr, keyProp = 'label', valueProp = 'label') => {
return arr.reduce(
(obj, item) => ({
...obj,
[item[keyProp]]: item[valueProp],
}),
{},
);
};

// constants
const keySelections = {
area: _map(AREAS, 'name'),
edu: _map(EDUCATION_STEP, 'label'),
role: _map(ROLE, 'label'),
tag: SEARCH_TAGS['全部'],
q: 'PASS_STRING',
};

const eduObj = createObjFromArrary(EDUCATION_STEP);
const roleObj = createObjFromArrary(ROLE);

function Partner() {
const dispatch = useDispatch();
const mobileScreen = useMediaQuery('(max-width: 767px)');

// main data
const partners = useSelector((state) => state.partners);
const { page: current = 1, totalPages } = partners.pagination;

// queryStr
const [getSearchParams, _, generateParamsItems] = useSearchParamsManager();
const searchParamsItems = useMemo(
() =>
generateParamsItems(['area', 'role', 'edu', 'tag', 'q'], keySelections),
[getSearchParams],
);

// fetch api - params
const findValues = (params, key) =>
params.find((item) => item.key === key)?.values;
const prepareData = _compose(
([location, educationStage, roleList, tag, search]) => ({
location,
educationStage,
roleList,
tag,
search,
}),
(arg) => [
findValues(arg, 'area').join(','),
mapValues(findValues(arg, 'edu'), (item) => eduObj[item]),
mapValues(findValues(arg, 'role'), (item) => roleObj[item]),
findValues(arg, 'tag').join(','),
findValues(arg, 'q').join(','),
],
);

const handleFetchData = (page = 1) => {
dispatch(fetchPartners({ page, ...prepareData(searchParamsItems) }));
};

useEffect(() => {
handleFetchData();
}, [getSearchParams]);

return (
<>
Expand All @@ -28,24 +101,21 @@ function Partner() {
</StyledSearchWrapper>
<StyledContent>
<SearchParamsList
paramsKey={['area', 'role', 'edu']}
keySelections={{
area: _map(AREAS, 'name'),
edu: _map(EDUCATION_STEP, 'label'),
role: _map(ROLE, 'label'),
}}
paramsKey={['area', 'role', 'edu', 'tag', 'q']}
keySelections={keySelections}
/>
<PartnerList />
</StyledContent>
{partners.items.length > 0 && (
{partners.items.length > 0 && current < totalPages && (
<Box
sx={
mobileScreen
? { textAlign: 'center', padding: '32px 0 80px' }
: { textAlign: 'center', padding: '72px 0 100px' }
? { textAlign: 'center', padding: '32px 0' }
: { textAlign: 'center', padding: '72px 0' }
}
>
<Button
onClick={() => handleFetchData(current + 1)}
variant="outlined"
sx={{
fontSize: '16px',
Expand Down
17 changes: 16 additions & 1 deletion hooks/useSearchParamsManager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,20 @@ export default function useSearchParamsManager() {
[push, searchParams],
);

return [getSearchParams, pushState];
const generateParamsItems = useCallback(
(arr, keyObj = {}) => {
if (!Array.isArray(arr)) return [];
return arr.reduce((acc, param) => {
const values = getSearchParams(param).filter((value) =>
keyObj[param] === 'PASS_STRING'
? value
: keyObj[param]?.includes(value),
);
return [...acc, { key: param, values }];
}, []);
},
[searchParams],
);

return [getSearchParams, pushState, generateParamsItems];
}
9 changes: 7 additions & 2 deletions redux/actions/partners.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
export function fetchAllPartners() {
export function fetchPartners({ pageSize = 10, page = 1, ...rest } = {}) {
return {
type: 'FETCH_ALL_PARTNERS',
type: 'FETCH_PARTNERS',
payload: {
page,
pageSize,
...rest,
},
};
}
21 changes: 17 additions & 4 deletions redux/reducers/partners.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
const initialState = {
items: [],
pagination: {
pageSize: 10,
page: 1,
totalCount: 0,
totalPages: 0,
},
};

const reducer = (state = initialState, action) => {
switch (action.type) {
case 'FETCH_ALL_PARTNERS_SUCCESS': {
case 'FETCH_PARTNERS_MORE_SUCCESS': {
return {
...state,
items: action.payload,
items: [...state.items, ...action.payload.data],
pagination: action.payload.pagination,
};
}
case 'FETCH_ALL_PARTNERS_FAILURE': {
case 'FETCH_PARTNERS_SUCCESS': {
return {
...initialState,
items: action.payload.data,
pagination: action.payload.pagination,
};
}
case 'FETCH_PARTNERS_FAILURE': {
return {
...initialState,
};
Expand Down
35 changes: 28 additions & 7 deletions redux/sagas/partnersSaga.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,40 @@
import { put, takeEvery } from 'redux-saga/effects';

function* fetchAllPartners() {
function* fetchPartnersResource(action) {
const { pageSize = 10, page = 1, ...rest } = action.payload;

const startParams = `page=${page}&pageSize=${pageSize}`;
const searchKey = ['educationStage', 'roleList', 'location', 'tag', 'search'];

const queryStr = Object.entries(rest).reduce((acc, [key, val]) => {
return val && searchKey.includes(key) ? `${acc}&${key}=${val}` : acc;
}, startParams);

try {
const baseUrl =
process.env.NEXT_PUBLIC_API_URL || 'https://daodao-server.onrender.com';
const URL = `${baseUrl}/user/all_User`;
const URL = `${baseUrl}/user?${queryStr}`;
const result = yield fetch(URL).then((res) => res.json());
yield put({ type: 'FETCH_ALL_PARTNERS_SUCCESS', payload: result });
yield put({
type:
page !== 1 ? 'FETCH_PARTNERS_MORE_SUCCESS' : 'FETCH_PARTNERS_SUCCESS',
payload: {
data: result.data,
pagination: {
pageSize: +result.pageSize || 10,
page: +result.page,
totalPages: +result.totalPages,
totalCount: +result.totalCount,
},
},
});
} catch (error) {
yield put({ type: 'FETCH_ALL_PARTNERS_FAILURE' });
yield put({ type: 'FETCH_PARTNERS_FAILURE' });
}
}

function* userSaga() {
yield takeEvery('FETCH_ALL_PARTNERS', fetchAllPartners);
function* partnerSaga() {
yield takeEvery('FETCH_PARTNERS', fetchPartnersResource);
}

export default userSaga;
export default partnerSaga;
Loading