Skip to content

Commit

Permalink
react-boilerplate#32: done show category
Browse files Browse the repository at this point in the history
  • Loading branch information
Thinh922001 committed Oct 8, 2024
1 parent 3e58b8a commit 756afe0
Show file tree
Hide file tree
Showing 18 changed files with 287 additions and 47 deletions.
2 changes: 1 addition & 1 deletion src/app/components/Card/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ interface Props {
export const Card: React.FC<Props> = ({ data, imgWidth, imgHeight }) => {
return (
<CardWrapper>
{data.labels?.length && mapCardLabel(data.labels)}
{data.labels?.length ? mapCardLabel(data.labels) : null}
<CardImgWrapper>
<A to="/chi-tiet-san-pham">
<CardImg imgWidth={imgWidth} imgHeight={imgHeight} src={data.img} />
Expand Down
5 changes: 2 additions & 3 deletions src/app/components/Header/Features/LocationBox/slice/saga.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import { call, put, select, takeEvery, takeLatest } from 'redux-saga/effects';
import { District, Province, Ward } from './type';
import { Province, Ward } from './type';
import { request } from 'utils/request';
import { LocationBoxActions } from '.';
import { data } from 'app/components/Card/data/card-data';
import {
selectDistrictId,
selectDistricts,
selectProvince,
selectProVinceId,
} from './selectors';

const BASE_URL = 'https://kltn-huit-production.up.railway.app';
export const BASE_URL = 'https://kltn-huit-production.up.railway.app';

export function* getProvince() {
try {
Expand Down
5 changes: 3 additions & 2 deletions src/app/components/Header/sub-menu.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import styled from 'styled-components/macro';
import { SubMenuType } from './data/menu';
import React from 'react';
import { Link } from 'react-router-dom';

interface SubMenuProps {
data: SubMenuType[];
Expand All @@ -15,7 +16,7 @@ export const SubMenu: React.FC<SubMenuProps> = ({ data = [], title = '' }) => {
{data?.length &&
data.map((e, index) => {
return (
<A href="#!" key={index}>
<A to="/danh-muc/1" key={index}>
<SubMenuItem>
<ItemImg src={e.img} alt={e.desc} loading="lazy" />
<ItemDesc>{e.desc}</ItemDesc>
Expand Down Expand Up @@ -62,7 +63,7 @@ const ItemDesc = styled.p`
text-align: center;
`;

const A = styled.a``;
const A = styled(Link)``;

const SubMenuTitle = styled.h3`
font-size: 1.5rem;
Expand Down
13 changes: 9 additions & 4 deletions src/app/components/LoadingCenter/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import styled from 'styled-components';
import { LoadingIndicator } from '../LoadingIndicator';
import React from 'react';

export const CenteredLoading = () => {
interface Props {
minHeight?: string;
}

export const CenteredLoading: React.FC<Props> = ({ minHeight }) => {
return (
<CenteredWrapper>
<CenteredWrapper minHeight={minHeight}>
<LoadingIndicator />
</CenteredWrapper>
);
};

const CenteredWrapper = styled.div`
const CenteredWrapper = styled.div<{ minHeight?: string }>`
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
min-height: ${({ minHeight }) => (minHeight ? minHeight : '200px')};
`;
5 changes: 3 additions & 2 deletions src/app/components/SeeMore/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import styled from 'styled-components';

interface Props {
text: string;
onClick?: () => void;
}

export const SeeMore: React.FC<Props> = ({ text }) => {
export const SeeMore: React.FC<Props> = ({ text, onClick }) => {
return (
<SeeMoreWrapper>
<SeeMoreWrapper onClick={onClick}>
<LoadMore>{text}</LoadMore>
</SeeMoreWrapper>
);
Expand Down
65 changes: 43 additions & 22 deletions src/app/pages/Category/components/ProductList.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,50 @@
import { Card } from 'app/components/Card';
import styled from 'styled-components';
import { ProductData } from './data';
import { ProductCateActions, useProductCateSlice } from '../slice';
import { useDispatch, useSelector } from 'react-redux';
import {
selectIsLoadingPage,
selectProductCate,
selectSkip,
selectTake,
selectTotal,
} from '../slice/selector';
import { SeeMore } from 'app/components/SeeMore';
import { CenteredLoading } from 'app/components/LoadingCenter';

export const ProductList = () => {
useProductCateSlice();

const dispatch = useDispatch();

const product = useSelector(selectProductCate);
const isLoading = useSelector(selectIsLoadingPage);
const skip = useSelector(selectSkip);
const take = useSelector(selectTake);
const total = useSelector(selectTotal);

const nextPage = () => {
dispatch(ProductCateActions.nextPage());
dispatch(ProductCateActions.loadingPage());
};

return (
<Wrapper>
<ProductWrapper>
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
<Card imgWidth="200px" imgHeight="200px" data={ProductData[0]} />
{product.length > 0 &&
product.map(e => (
<Card key={e.id} imgWidth="200px" imgHeight="200px" data={e} />
))}
</ProductWrapper>

{isLoading ? (
<CenteredLoading />
) : skip + take < total ? (
<SeeMore
onClick={nextPage}
text={`Xem thêm ${total - (skip + take)} sản phẩm`}
/>
) : null}
</Wrapper>
);
};
Expand All @@ -42,3 +59,7 @@ const ProductWrapper = styled.div`
grid-template-columns: repeat(5, 1fr);
gap: 10px;
`;

const WrapperSeeMore = styled.div`
width: 100%;
`;
46 changes: 38 additions & 8 deletions src/app/pages/Category/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,27 @@ import { Container } from 'app/components/container';
import { Banner } from './components/Banner';
import { Filter } from './components/Filter';
import { ProductList } from './components/ProductList';
import { useParams } from 'react-router-dom';
import { useEffect } from 'react';
import { ProductCateActions, useProductCateSlice } from './slice';
import { useDispatch, useSelector } from 'react-redux';
import { CenteredLoading } from 'app/components/LoadingCenter';
import { selectIsLoading } from './slice/selector';

export function Category() {
useProductCateSlice();

const dispatch = useDispatch();

const { id } = useParams<{ id: string }>();

const isLoading = useSelector(selectIsLoading);

useEffect(() => {
dispatch(ProductCateActions.setCateId(id));
dispatch(ProductCateActions.loadProduct());
}, [id]);

const BreakCumData: IBreakCum[] = [
{
name: 'Trang chủ',
Expand All @@ -24,14 +43,20 @@ export function Category() {
<meta name="description" content="login" />
</Helmet>
<Wrapper>
<Container>
<BreakCum data={BreakCumData} />
<Banner />
<ProductionContainer>
<Filter />
<ProductList />
</ProductionContainer>
</Container>
{isLoading ? (
<WrapperLoading>
<CenteredLoading minHeight="100%" />
</WrapperLoading>
) : (
<Container>
<BreakCum data={BreakCumData} />
<Banner />
<ProductionContainer>
<Filter />
<ProductList />
</ProductionContainer>
</Container>
)}
</Wrapper>
</>
);
Expand All @@ -45,3 +70,8 @@ const ProductionContainer = styled.div`
background: rgba(255, 255, 255, 1);
border-radius: 8px;
`;

const WrapperLoading = styled.div`
width: 100vw;
height: calc(100vh - 157px);
`;
58 changes: 58 additions & 0 deletions src/app/pages/Category/slice/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { PayloadAction } from '@reduxjs/toolkit';
import { ProductCateState } from './type';
import { createSlice } from 'utils/@reduxjs/toolkit';
import { useInjectReducer, useInjectSaga } from 'utils/redux-injectors';
import { ICard } from 'types/Card';
import { ProductCateFromSaga } from './saga';

export const initialState: ProductCateState = {
isLoading: false,
isPageLoading: false,
cateId: '0',
take: 10,
skip: 0,
total: 0,
isNext: true,
itemPerPage: 10,
products: [],
};

const slice = createSlice({
name: 'productCateState',
initialState,
reducers: {
loadProduct(state) {
state.isLoading = true;
},
productLoaded(state, actions: PayloadAction<ICard[]>) {
state.isLoading = false;
state.products = [...state.products, ...actions.payload];
},
nextPage(state) {
state.skip = state.skip + state.itemPerPage;
},
loadingPage(state) {
state.isPageLoading = true;
},
pageLoaded(state, actions: PayloadAction<ICard[]>) {
state.isPageLoading = false;
state.products = [...state.products, ...actions.payload];
},
setCateId(state, actions: PayloadAction<string | undefined>) {
if (actions.payload) {
state.cateId = actions.payload;
}
},
setTotal(state, action: PayloadAction<number>) {
state.total = action.payload;
},
},
});

export const { actions: ProductCateActions, reducer } = slice;

export const useProductCateSlice = () => {
useInjectReducer({ key: slice.name, reducer: slice.reducer });
useInjectSaga({ key: slice.name, saga: ProductCateFromSaga });
return { actions: slice.actions };
};
49 changes: 49 additions & 0 deletions src/app/pages/Category/slice/saga.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { call, put, select, takeLatest } from 'redux-saga/effects';
import { selectCateId, selectSkip, selectTake } from './selector';
import { request } from 'utils/request';
import { BASE_URL } from 'utils/url';
import { createQueryString } from 'utils/string';
import { ProductCateActions } from '.';

export function* getProductCate() {
try {
const cateId = yield select(selectCateId);
const take = yield select(selectTake);
const skip = yield select(selectSkip);

const queryString = createQueryString({ cateId, take, skip });

const data = yield call(request, `${BASE_URL}/category?${queryString}`);

if (data.data.data.length > 0) {
yield put(ProductCateActions.productLoaded(data.data.data));
yield put(ProductCateActions.setTotal(data.data.paging.total));
}
} catch (error) {
console.log(error);
}
}

export function* getProductPageCate() {
try {
const cateId = yield select(selectCateId);
const take = yield select(selectTake);
const skip = yield select(selectSkip);

const queryString = createQueryString({ cateId, take, skip });

const data = yield call(request, `${BASE_URL}/category?${queryString}`);

if (data.data.data.length > 0) {
yield put(ProductCateActions.pageLoaded(data.data.data));
yield put(ProductCateActions.setTotal(data.data.paging.total));
}
} catch (error) {
console.log(error);
}
}

export function* ProductCateFromSaga() {
yield takeLatest(ProductCateActions.loadProduct, getProductCate);
yield takeLatest(ProductCateActions.loadingPage, getProductPageCate);
}
38 changes: 38 additions & 0 deletions src/app/pages/Category/slice/selector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'types';
import { initialState } from '.';

export const selectCateId = createSelector(
[(state: RootState) => state.productCateState || initialState],
state => state.cateId,
);

export const selectIsLoading = createSelector(
[(state: RootState) => state.productCateState || initialState],
state => state.isLoading,
);

export const selectTake = createSelector(
[(state: RootState) => state.productCateState || initialState],
state => state.take,
);

export const selectSkip = createSelector(
[(state: RootState) => state.productCateState || initialState],
state => state.skip,
);

export const selectProductCate = createSelector(
[(state: RootState) => state.productCateState || initialState],
state => state.products,
);

export const selectIsLoadingPage = createSelector(
[(state: RootState) => state.productCateState || initialState],
state => state.isPageLoading,
);

export const selectTotal = createSelector(
[(state: RootState) => state.productCateState || initialState],
state => state.total,
);
Loading

0 comments on commit 756afe0

Please sign in to comment.