瀏覽代碼

Merge branch 'feature/3754' of https://git.dilig.net/selenaaasi/trampa-frontend

bugfix/4515
jovan.cirkovic 2 年之前
父節點
當前提交
bb79396368
共有 42 個檔案被更改,包括 1486 行新增192 行删除
  1. 100
    26
      src/components/Cards/CategoryCard/CategoryCard.js
  2. 8
    3
      src/components/Cards/CategoryCard/CategoryDetail/CategoryDetail.js
  3. 5
    4
      src/components/Cards/CategoryCard/CategoryDetail/CategoryDetail.styled.js
  4. 23
    12
      src/components/Cards/FilterCard/FilterCard.js
  5. 8
    8
      src/components/Cards/FilterCard/FilterCard.styled.js
  6. 11
    4
      src/components/Cards/FilterCard/FilterFooter/FilterFooter.js
  7. 14
    5
      src/components/Cards/FilterCard/FilterHeader/FilterHeader.js
  8. 2
    2
      src/components/MUI/DrawerComponent.js
  9. 53
    0
      src/components/MarketPlace/Header/Drawer/Drawer.js
  10. 9
    0
      src/components/MarketPlace/Header/Drawer/Drawer.styled.js
  11. 21
    1
      src/components/MarketPlace/Header/Header.js
  12. 34
    0
      src/components/MarketPlace/Header/Header.styled.js
  13. 271
    0
      src/components/Modals/CreatePayment/CreatePayment.js
  14. 155
    0
      src/components/Modals/CreatePayment/CreatePayment.styled.js
  15. 32
    12
      src/components/Modals/DeleteCategory/DeleteCategory.js
  16. 5
    0
      src/components/Modals/Modal.js
  17. 42
    10
      src/components/UserReviews/UserReviews.js
  18. 88
    71
      src/enums/sortEnum.js
  19. 3
    3
      src/hooks/useOffers/useOffers.js
  20. 29
    0
      src/i18n/resources/rs.js
  21. 7
    0
      src/initialValues/paymentInitialValues.js
  22. 115
    9
      src/pages/AdminHomePage/AdminPaymentPage/AdminPaymentPage.js
  23. 83
    2
      src/pages/AdminHomePage/AdminPaymentPage/AdminPaymentPage.styled.js
  24. 6
    0
      src/request/apiEndpoints.js
  25. 28
    0
      src/request/paymentsRequest.js
  26. 1
    0
      src/store/actions/filters/filtersActionConstants.js
  27. 5
    0
      src/store/actions/filters/filtersActions.js
  28. 3
    0
      src/store/actions/modal/modalActionConstants.js
  29. 15
    0
      src/store/actions/modal/modalActions.js
  30. 28
    0
      src/store/actions/payment/paymentActionConstants.js
  31. 60
    0
      src/store/actions/payment/paymentActions.js
  32. 14
    0
      src/store/reducers/filters/filtersReducer.js
  33. 9
    2
      src/store/reducers/index.js
  34. 22
    2
      src/store/reducers/modal/modalReducer.js
  35. 20
    0
      src/store/reducers/payment/paymentReducer.js
  36. 17
    15
      src/store/saga/index.js
  37. 82
    0
      src/store/saga/paymentSaga.js
  38. 1
    0
      src/store/saga/profileSaga.js
  39. 4
    0
      src/store/selectors/filtersSelectors.js
  40. 8
    0
      src/store/selectors/paymentSelector.js
  41. 29
    1
      src/util/helpers/adminSortHelper.js
  42. 16
    0
      src/validations/createPaymentValidation.js

+ 100
- 26
src/components/Cards/CategoryCard/CategoryCard.js 查看文件

@@ -1,4 +1,4 @@
import React from "react";
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import {
CategoryCardContainer,
@@ -16,15 +16,47 @@ import history from "../../../store/utils/history";
import { replaceInRoute } from "../../../util/helpers/routeHelpers";
import { ADMIN_SUBCATEGORIES_PAGE } from "../../../constants/pages";
import { useMemo } from "react";
import { useDispatch } from "react-redux";
import { useDispatch, useSelector } from "react-redux";
import {
toggleDeleteCategoryModal,
toggleEditCategoryModal,
toggleEditPaymentModal,
} from "../../../store/actions/modal/modalActions";
import { selectAllProfiles } from "../../../store/selectors/profileSelectors";
import { fetchAllProfiles } from "../../../store/actions/profile/profileActions";
import { selectOffers } from "../../../store/selectors/offersSelectors";
import { fetchOffers } from "../../../store/actions/offers/offersActions";
import { formatDateLocale } from "../../../util/helpers/dateHelpers";

const CategoryCard = (props) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const profiles = useSelector(selectAllProfiles);
const offers = useSelector(selectOffers);
useEffect(() => {
dispatch(fetchAllProfiles());
dispatch(fetchOffers({ queryString: "" }));
}, []);
const company = useMemo(() => {
if (profiles) {
return profiles?.filter?.(
(profile) => profile?._id === props?.category?.user?._id
);
} else {
return [];
}
}, [profiles]);

const offer = useMemo(() => {
if (offers) {
return offers?.filter?.(
(offer) => offer?._id === props?.category?.offer?._id
);
} else {
return [];
}
}, [offers]);

const navigateToCategory = () => {
if (!props.hideCheckButton) {
history.push(
@@ -35,53 +67,94 @@ const CategoryCard = (props) => {
}
};
const showEditCategoryModal = () => {
dispatch(
toggleEditCategoryModal({
hideImagePicker: props.type !== "categories",
category: props.category,
categoryId: props.categoryId,
subcategory: props.subcategory,
type: props.type,
method: "edit",
firstOutlined: false,
})
);
if (!props.payments) {
dispatch(
toggleEditCategoryModal({
hideImagePicker: props.type !== "categories",
category: props.category,
categoryId: props.categoryId,
subcategory: props.subcategory,
type: props.type,
method: "edit",
firstOutlined: false,
})
);
} else {
dispatch(
toggleEditPaymentModal({
paymentInfo: {
id: props.category._id,
payerName: props.category.payerName,
companyName: company[0].companyName,
type: props.category.type,
date: props.category.date,
offerName: offer[0].name,
},
})
);
}
};

const showDeleteCategoryModal = () => {
dispatch(
toggleDeleteCategoryModal({
categoryId: props.categoryId,
subcategory: props.subcategory,
category: props.category,
type: props.type,
})
);
if (!props.payments) {
dispatch(
toggleDeleteCategoryModal({
categoryId: props.categoryId,
subcategory: props.subcategory,
category: props.category,
type: props.type,
})
);
} else {
dispatch(
toggleDeleteCategoryModal({
id: props.category._id,
category: props.category,
type: props.type,
})
);
}
};
const isDisabledDelete = useMemo(() => {
return (
props?.category?.name === "Ostalo" || props?.category?.city === "Ostalo"
);
}, [props.category]);

const date = formatDateLocale(new Date(props?.category?.date));

return (
<>
<CategoryCardContainer className={props.className}>
<CategoryCardLeftContainer>
<CategoryCardName
image={props?.category?.image}
categoryName={props?.category?.name || props?.category?.city}
categoryName={
props?.category?.name ||
props?.category?.city ||
props?.category?.payerName
}
onClick={navigateToCategory}
/>
<CategoryCardDetailsContainer>
<CategoryDetail
label={t("admin.categories.noOfOffers")}
value={props?.category?.offerCount}
payments={props.payments}
label={
!props.payments
? t("admin.categories.noOfOffers")
: t("admin.payment.typeOfPayment")
}
value={props?.category?.offerCount || props?.category?.type}
/>
{!props.hideSecondLabel && (
<CategoryDetail
payments={props.payments}
label={props?.secondLabel}
value={
props?.category?.subcategories?.length ||
props?.category?.offerCount
!props.payments
? props?.category?.subcategories?.length ||
props?.category?.offerCount
: date
}
/>
)}
@@ -115,6 +188,7 @@ CategoryCard.propTypes = {
subcategory: PropTypes.bool,
type: PropTypes.string,
categoryId: PropTypes.string,
payments: PropTypes.bool,
};

export default CategoryCard;

+ 8
- 3
src/components/Cards/CategoryCard/CategoryDetail/CategoryDetail.js 查看文件

@@ -8,9 +8,13 @@ import {

const CategoryDetail = (props) => {
return (
<CategoryDetailContainer>
<CategoryDetailLabel>{props.label}</CategoryDetailLabel>
<CategoryDetailValue>{props.value}</CategoryDetailValue>
<CategoryDetailContainer payments={props.payments}>
<CategoryDetailLabel payments={props.payments}>
{props.label}
</CategoryDetailLabel>
<CategoryDetailValue payments={props.payments}>
{props.value}
</CategoryDetailValue>
</CategoryDetailContainer>
);
};
@@ -18,6 +22,7 @@ const CategoryDetail = (props) => {
CategoryDetail.propTypes = {
label: PropTypes.string,
value: PropTypes.string,
payments: PropTypes.bool,
};

export default CategoryDetail;

+ 5
- 4
src/components/Cards/CategoryCard/CategoryDetail/CategoryDetail.styled.js 查看文件

@@ -8,9 +8,9 @@ export const CategoryDetailContainer = styled(Box)`
flex-direction: row; */
margin-top: 20px;
margin-bottom: 20px;
min-width: 150px;
text-align: center;
min-width: ${(props) => (props.payments ? "220px" : "150px")};
border-left: 1px solid ${hexToRGB(selectedTheme.colors.primaryText, 0.13)};
${(props) => !props.payments && ` text-align: center; `}
@media (max-width: 600px) {
margin: 0;
min-width: 123px;
@@ -18,7 +18,7 @@ export const CategoryDetailContainer = styled(Box)`
border-left: 0;
}
&:nth-child(2) {
padding-left: 18px;
${(props) => !props.payments && ` padding-left: 18px; `}
}
}
`;
@@ -29,11 +29,11 @@ export const CategoryDetailLabel = styled(Typography)`
letter-spacing: 0.01rem;
padding-top: 14px;
padding-right: 3px;
${(props) => props.payments && ` padding-left: 18px; `}
@media (max-width: 600px) {
position: relative;
bottom: 2.5px;
padding-top: 0;
}
`;
export const CategoryDetailValue = styled(Typography)`
@@ -49,5 +49,6 @@ export const CategoryDetailValue = styled(Typography)`
padding: 0;
font-size: 12px;
font-weight: 600;
${(props) => props.payments && ` padding-right: 18px; `}
}
`;

+ 23
- 12
src/components/Cards/FilterCard/FilterCard.js 查看文件

@@ -19,8 +19,8 @@ const FilterCard = (props) => {
const locationRef = useRef(null);
const companyRef = useRef(null);
const closeAllSections = () => {
categoryRef.current.closeSection();
subcategoryRef.current.closeSection();
categoryRef?.current?.closeSection();
subcategoryRef?.current?.closeSection();
locationRef?.current?.closeSection();
companyRef?.current?.closeSection();
};
@@ -30,6 +30,7 @@ const FilterCard = (props) => {
filtersOpened={props.filtersOpened}
myOffers={props.myOffers}
skeleton={props.skeleton}
payments={props.payments}
>
{props?.skeleton && <SkeletonFilterCard skeleton={props.skeleton} />}
{/* Header title for my offers */}
@@ -40,24 +41,30 @@ const FilterCard = (props) => {
closeAllSections={closeAllSections}
isMyOffers={props.myOffers}
toggleFilters={props.toggleFilters}
toggleDrawer={props.toggleDrawer}
payments={props.payments}
/>

<ContentContainer>
{/* Categories */}
<CategoryChoser filters={filters} ref={categoryRef} offers={offers} />
{!props.payments && (
<CategoryChoser filters={filters} ref={categoryRef} offers={offers} />
)}

{/* Subcategories */}
<SubcategoryChoser
filters={filters}
queryStringHook={offers.queryStringHook}
ref={subcategoryRef}
categoryOpened={categoryRef.current?.isOpened}
myOffers={props.myOffers}
offers={offers}
/>
{!props.payments && (
<SubcategoryChoser
filters={filters}
queryStringHook={offers.queryStringHook}
ref={subcategoryRef}
categoryOpened={categoryRef.current?.isOpened}
myOffers={props.myOffers}
offers={offers}
/>
)}

{/* Locations */}
{!props.myOffers && (
{!props.myOffers && !props.payments && (
<LocationChoser filters={filters} ref={locationRef} offers={offers} />
)}

@@ -72,6 +79,8 @@ const FilterCard = (props) => {
filters={offers}
closeAllSections={closeAllSections}
isMyOffers={props.myOffers}
toggleDrawer={props.toggleDrawer}
payments={props.payments}
/>
</FilterCardContainer>
);
@@ -87,6 +96,8 @@ FilterCard.propTypes = {
skeleton: PropTypes.bool,
filtersOpened: PropTypes.bool,
toggleFilters: PropTypes.func,
payments: PropTypes.bool,
toggleDrawer: PropTypes.func,
};

FilterCard.defaultProps = {

+ 8
- 8
src/components/Cards/FilterCard/FilterCard.styled.js 查看文件

@@ -10,8 +10,9 @@ export const FilterCardContainer = styled(Box)`
props.myOffers ? `calc(100% - 153px)` : `calc(100% - 90px)`};
padding: ${(props) => (props.skeleton ? "0" : "36px")};
background-color: white;
width: calc(100% / 12 * 3.5);
left: 0;
width: ${(props) => (!props.payments ? "calc(100% / 12 * 3.5)" : "209px")};
${(props) => !props.payments && `left: 0; margin-top: -24px;`}
${(props) => props.payments && "top: 0;"}
bottom: 0;
max-width: 360px;
display: ${(props) =>
@@ -22,7 +23,6 @@ export const FilterCardContainer = styled(Box)`
min-width: fit-content;
min-width: 285px !important;
z-index: 9;
margin-top: -24px;
transition: all ease-in-out 1s;
transition: padding 0s;

@@ -31,18 +31,18 @@ export const FilterCardContainer = styled(Box)`
top: -73px;
}
@media (max-width: 900px) {
margin-left: -400px;
${(props) => !props.payments && `margin-left: -400px;`}

${(props) =>
props.filtersOpened
? `
props.filtersOpened &&
`
display: "flex";
margin-left: 0;
max-width: 100vw;
width: 100vw;
bottom: 0;
height: calc(100% - 50px);
`
: "display: none"};
`};
transition: all ease-in-out 0.36s;
}
& * {

+ 11
- 4
src/components/Cards/FilterCard/FilterFooter/FilterFooter.js 查看文件

@@ -17,13 +17,18 @@ const FilterFooter = (props) => {
// props.toggleFilters();
// };
const clearFilters = () => {
if (props.isMyOffers) {
filters.clear();
if (!props.payments) {
if (props.isMyOffers) {
filters.clear();
} else {
filters.clearOnlyFiltersAndApply();
}
props.toggleFilters();
props.closeAllSections();
} else {
props.toggleDrawer();
filters.clearOnlyFiltersAndApply();
}
props.toggleFilters();
props.closeAllSections();
};
return (
<FilterFooterContainer responsiveOpen={isMobile}>
@@ -68,6 +73,8 @@ const FilterFooter = (props) => {
filters: PropTypes.any,
isMyOffers: PropTypes.bool,
closeAllSections: PropTypes.func,
toggleDrawer: PropTypes.func,
payments: PropTypes.bool,
}),
(FilterFooter.defaultProps = {
responsiveOpen: false,

+ 14
- 5
src/components/Cards/FilterCard/FilterHeader/FilterHeader.js 查看文件

@@ -11,19 +11,26 @@ const FilterHeader = (props) => {
const { t } = useTranslation();
const { isMobile } = useIsMobile();
const clearFilters = () => {
if (props.isMyOffers) {
filters.clear();
if (!props.payments) {
if (props.isMyOffers) {
filters.clear();
} else {
filters.clearOnlyFiltersAndApply();
}
props.toggleFilters();
props.closeAllSections();
} else {
filters.clearOnlyFiltersAndApply();
props.toggleDrawer();
}
props.toggleFilters();
props.closeAllSections();
};
return (
<FilterHeaderContainer>
<Title>{t("filters.title")}</Title>
{isMobile ? (
<CloseIcon onClick={props.toggleFilters} />
<CloseIcon
onClick={props.payments ? props.toggleDrawer : props.toggleFilters}
/>
) : (
<Link
to="#"
@@ -44,6 +51,8 @@ FilterHeader.propTypes = {
closeAllSections: PropTypes.func,
isMyOffers: PropTypes.bool,
toggleFilters: PropTypes.func,
toggleDrawer: PropTypes.func,
payments: PropTypes.bool,
};

export default FilterHeader;

+ 2
- 2
src/components/MUI/DrawerComponent.js 查看文件

@@ -5,9 +5,9 @@ import { Drawer } from "@mui/material";
const DrawerComponent = ({ open, toggleOpen, content, anchor = "right" }) => (
<Drawer
sx={{
minWidth: 250,
minWidth: 280,
"& .MuiDrawer-paper": {
minWidth: 250,
minWidth: 280,
},
}}
anchor={anchor}

+ 53
- 0
src/components/MarketPlace/Header/Drawer/Drawer.js 查看文件

@@ -0,0 +1,53 @@
import React from "react";
import PropTypes from "prop-types";
// import useIsMobile from "../../../hooks/useIsMobile";
// import { Drawer as HeaderDrawer } from "../Drawer/Drawer";
import Drawer from "../../../MUI/DrawerComponent";
import { useState } from "react";
import { forwardRef } from "react";
import { useImperativeHandle } from "react";
import FilterCard from "../../../Cards/FilterCard/FilterCard";
import useOffers from "../../../../hooks/useOffers/useOffers";

const DrawerContainer = forwardRef((props, ref) => {
const [openDrawer, setOpenDrawer] = useState(false);
const [filtersOpened, setFiltersOpened] = useState(false);
const offers = useOffers();
// const { isMobile } = useIsMobile();

const toggleFilters = () => {
setFiltersOpened((prevFiltersOpened) => !prevFiltersOpened);
};

useImperativeHandle(ref, () => ({
handleToggleDrawer,
}));

const handleToggleDrawer = () => {
setOpenDrawer((prevOpenDrawer) => !prevOpenDrawer);
};
// if (!isMobile) return <></>;
return (
<Drawer
open={openDrawer}
toggleOpen={handleToggleDrawer}
content={
<FilterCard
payments
offers={offers}
filtersOpened={filtersOpened}
toggleFilters={toggleFilters}
toggleDrawer={handleToggleDrawer}
/>
}
/>
);
});

DrawerContainer.displayName = "DrawerContainer";

DrawerContainer.propTypes = {
showCreateOfferModal: PropTypes.func,
};

export default DrawerContainer;

+ 9
- 0
src/components/MarketPlace/Header/Drawer/Drawer.styled.js 查看文件

@@ -0,0 +1,9 @@
import styled from "styled-components";
import { Box } from "@mui/material";

export const DrawerContainer = styled(Box)`
width: 100vw;
position: relative;
height: 100%;
overflow: hidden;
`;

+ 21
- 1
src/components/MarketPlace/Header/Header.js 查看文件

@@ -1,12 +1,15 @@
import React, { useMemo } from "react";
import React, { useMemo, useRef } from "react";
import PropTypes from "prop-types";
import {
CategoryIcon,
FilterButtonContainer,
FilterButtonIcon,
HeaderContainer,
HeaderOptions,
HeaderWrapperContainer,
LocationIcon,
PageTitleContainer,
PaymentsIcon,
SubcategoryIcon,
SwapsHeaderIcon,
SwapsIcon,
@@ -22,9 +25,12 @@ import useIsTablet from "../../../hooks/useIsTablet";
import TooltipHeader from "./TooltipHeader/TooltipHeader";
import GridButtons from "./GridButtons/GridButtons";
import HeaderSelect from "./HeaderSelect/HeaderSelect";
// import DrawerContainer from "../../Header/DrawerContainer/DrawerContainer";
// import HeaderTitle from "../../Profile/ProfileOffers/HeaderTitle/HeaderTitle";
import Drawer from "./Drawer/Drawer";

const Header = (props) => {
const drawerRef = useRef(null);
const { t } = useTranslation();
const sorting = props?.sorting;
const { isTablet } = useIsTablet();
@@ -53,6 +59,8 @@ const Header = (props) => {
? t("admin.subcategories.headerTitle")
: props.location
? t("admin.locations.headerTitle")
: props.payments
? t("admin.payment.headerTitle")
: !isTablet
? t("header.myOffers")
: props.myOffers
@@ -68,6 +76,8 @@ const Header = (props) => {
<SubcategoryIcon />
) : props.location ? (
<LocationIcon />
) : props.payments ? (
<PaymentsIcon />
) : isTablet ? (
<SwapsIcon />
) : (
@@ -113,6 +123,13 @@ const Header = (props) => {
hideSorting={props?.hideSorting}
/>
)}
{props.payments && (
<FilterButtonContainer
onClick={drawerRef.current?.handleToggleDrawer}
>
<FilterButtonIcon />
</FilterButtonContainer>
)}
{/* ^^^^^^ */}
</HeaderOptions>
</HeaderContainer>
@@ -123,6 +140,8 @@ const Header = (props) => {
</PageTitleContainer>
)}
</HeaderWrapperContainer>
<Drawer ref={drawerRef} />
{/* <DrawerContainer ref={drawerRef} /> */}
</>
);
};
@@ -145,6 +164,7 @@ Header.propTypes = {
subcategories: PropTypes.bool,
hideSorting: PropTypes.bool,
location: PropTypes.bool,
payments: PropTypes.bool,
};
Header.defaultProps = {
isGrid: false,

+ 34
- 0
src/components/MarketPlace/Header/Header.styled.js 查看文件

@@ -6,6 +6,8 @@ import { ReactComponent as User } from "../../../assets/images/svg/user.svg";
import { ReactComponent as Category } from "../../../assets/images/svg/category.svg";
import { ReactComponent as Subcategory } from "../../../assets/images/svg/subcategory.svg";
import { ReactComponent as Location } from "../../../assets/images/svg/location.svg";
import { ReactComponent as Payment } from "../../../assets/images/svg/dollar-sign.svg";
import { ReactComponent as Filter } from "../../../assets/images/svg/filter.svg";

export const HeaderWrapperContainer = styled(Box)`
display: ${(props) => (props.skeleton ? "none" : "block")};
@@ -109,3 +111,35 @@ export const LocationIcon = styled(Location)`
stroke: ${selectedTheme.colors.primaryText};
}
`;

export const PaymentsIcon = styled(Payment)`
position: relative;
top: 3px;
margin-right: 5px;
& path {
stroke: ${selectedTheme.colors.primaryText};
}
`;

export const FilterButtonContainer = styled(Box)`
background-color: #e4e4e4;
border-radius: 100%;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
margin-left: 18px;
cursor: pointer;

@media (max-width: 600px) {
margin-top: 25px;
}
`;

export const FilterButtonIcon = styled(Filter)`
width: 20px;
path {
stroke-width: 2px;
}
`;

+ 271
- 0
src/components/Modals/CreatePayment/CreatePayment.js 查看文件

@@ -0,0 +1,271 @@
import React, { useEffect } from "react";
import PropTypes from "prop-types";
import BackdropComponent from "../../MUI/BackdropComponent";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useFormik } from "formik";
// import { useMemo } from "react";
import { useDispatch, useSelector } from "react-redux";
// import { fetchAdminMethod } from "../../../store/actions/admin/adminActions";
import { useRef } from "react";
import { closeModal } from "../../../store/actions/modal/modalActions";
import {
CreatePaymentContainer,
CreatePaymentTitle,
PaymentInputField,
XIcon,
FieldLabel,
InputContainer,
SaveButton,
ButtonsContainer,
StyledWrapper,
ErrorMessage,
} from "./CreatePayment.styled";
import paymentInitialValues from "../../../initialValues/paymentInitialValues";
import { selectAllProfiles } from "../../../store/selectors/profileSelectors";
import { fetchAllProfiles } from "../../../store/actions/profile/profileActions";
import AutoSuggestTextField from "../../TextFields/AutoSuggestTextField/AutoSuggestTextField";
import { InputFieldLabelLocation } from "../../Cards/ProfileCard/EditProfile/LocationField/LocationField.styled";
import {
addPayment,
editPayment,
fetchPayments,
} from "../../../store/actions/payment/paymentActions";
import { selectOffers } from "../../../store/selectors/offersSelectors";
// import { selectQueryString } from "../../../store/selectors/filtersSelectors";
import { fetchOffers } from "../../../store/actions/offers/offersActions";
import createPaymentValidation from "../../../validations/createPaymentValidation";

const CreatePayment = (props) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const [clickedOnNext, setClickedOnNext] = useState(false);
const [offersToShow, setOffersToShow] = useState([]);
const [userId, setUserId] = useState("");
const [offerId, setOfferId] = useState("");
const inputRef = useRef(null);
// const queryString = useSelector(selectQueryString);
const profiles = useSelector(selectAllProfiles);
const offers = useSelector(selectOffers);
useEffect(() => {
dispatch(fetchAllProfiles());
dispatch(fetchOffers({ queryString: "" }));
}, []);

const closeModalHandler = () => {
dispatch(closeModal());
};

const userIdHandler = (value) => {
if (value) {
let userId = profiles?.filter?.(
(profile) => profile.companyName === value
)[0]?._id;
setUserId(userId);
let filterdOffers = offers?.filter?.(
(offer) => offer.user._id === userId
);
setOffersToShow(filterdOffers);
}
};

const offerIdHandler = (value) => {
if (value) {
let offerId = offers?.filter?.((offer) => offer.name === value)[0]?._id;
setOfferId(offerId);
}
};

const handleApiResponseSuccess = () => {
closeModalHandler();
dispatch(fetchPayments());
};

const handleSubmit = (values) => {
if (!props.editPayment) {
dispatch(
addPayment({
type: values.type,
payerName: values.payerName,
userId: userId,
offerId: offerId,
date: values.date,
handleApiResponseSuccess,
})
);
} else {
const editUserId = profiles?.filter(
(profile) => profile.companyName === values.companyName
)[0]._id;
const editOfferId = offers?.filter(
(offer) => offer.name === values.offerName
)[0]._id;
dispatch(
editPayment({
id: props.paymentInfo.id,
type: values.type,
payerName: values.payerName,
userId: editUserId,
offerId: editOfferId,
date: values.date,
handleApiResponseSuccess,
})
);
}
};
const formik = useFormik({
initialValues: paymentInitialValues(props.paymentInfo),
validationSchema: createPaymentValidation(profiles, offers),
onSubmit: handleSubmit,
});
const handleClick = (next) => {
if (next !== clickedOnNext) setClickedOnNext(next);
formik.handleSubmit();
};

return (
<>
<BackdropComponent
isLoading
handleClose={closeModalHandler}
position="fixed"
/>
<CreatePaymentContainer hideImagePicker={props.hideImagePicker}>
<XIcon onClick={closeModalHandler} />
<CreatePaymentTitle>
{!props.editPayment
? t("admin.payment.modal.newTitle")
: t("admin.payment.modal.editTitle")}
</CreatePaymentTitle>
<InputContainer hideImagePicker={props.hideImagePicker}>
<FieldLabel leftText={t("admin.payment.modal.name")} />
<PaymentInputField
name="payerName"
ref={inputRef}
// placeholder={placeholder}
italicPlaceholder
margin="normal"
value={formik.values.payerName}
onChange={formik.handleChange}
error={formik.touched.payerName && formik.errors.payerName}
helperText={formik.touched.payerName && formik.errors.payerName}
autoFocus
fullWidth
/>
<InputFieldLabelLocation
leftText={t("admin.payment.modal.company").toUpperCase()}
/>
<AutoSuggestTextField
data={profiles?.map((item) => ({
name: item.companyName,
}))}
value={formik.values.companyName}
error={formik.errors.companyName}
onChange={(event, { newValue }) => {
formik.setFieldValue("companyName", newValue);
userIdHandler(newValue);
if (newValue === "") {
formik.setFieldValue("offerName", "");
setOffersToShow([]);
}
}}
/>
<InputFieldLabelLocation
leftText={t("admin.payment.modal.offer").toUpperCase()}
/>
<StyledWrapper>
<AutoSuggestTextField
data={
userId
? offersToShow?.map((item) => ({
name: item.name,
}))
: []
}
value={formik.values.offerName}
error={formik.errors.offerName}
onChange={(event, { newValue }) => {
formik.setFieldValue("offerName", newValue);
offerIdHandler(newValue);
}}
/>
</StyledWrapper>

<FieldLabel leftText={t("admin.payment.modal.type")} />
<PaymentInputField
name="type"
ref={inputRef}
// placeholder={placeholder}
italicPlaceholder
margin="normal"
value={formik.values.type}
onChange={formik.handleChange}
error={formik.touched.type && formik.errors.type}
helperText={formik.touched.type && formik.errors.type}
fullWidth
/>
<FieldLabel leftText={t("admin.payment.modal.date")} />
<PaymentInputField
name="date"
ref={inputRef}
type="date"
italicPlaceholder
margin="normal"
value={formik.values.date}
onChange={formik.handleChange}
error={formik.touched.date && formik.errors.date}
helperText={formik.touched.date && formik.errors.date}
fullWidth
/>
{formik.errors.payerName && formik.touched.payerName ? (
<ErrorMessage>{formik.errors.payerName}</ErrorMessage>
) : formik.errors.companyName && formik.touched.companyName ? (
<ErrorMessage>{formik.errors.companyName}</ErrorMessage>
) : formik.errors.offerName && formik.touched.offerName ? (
<ErrorMessage>{formik.errors.offerName}</ErrorMessage>
) : formik.errors.type && formik.touched.type ? (
<ErrorMessage>{formik.errors.type}</ErrorMessage>
) : formik.errors.date && formik.touched.date ? (
<ErrorMessage>{formik.errors.date}</ErrorMessage>
) : (
<></>
)}
</InputContainer>

<ButtonsContainer>
<SaveButton
showSecondButton={props.showSecondButton}
variant="contained"
height="48px"
onClick={() => handleClick(true)}
>
{!props.editPayment
? t("admin.payment.modal.add")
: t("admin.payment.modal.change")}
</SaveButton>
</ButtonsContainer>
</CreatePaymentContainer>
</>
);
};

CreatePayment.propTypes = {
category: PropTypes.any,
setOpenedEditModal: PropTypes.func,
hideImagePicker: PropTypes.bool,
type: PropTypes.string,
showSecondButton: PropTypes.bool,
method: PropTypes.string,
firstOutlined: PropTypes.bool,
secondOutlined: PropTypes.bool,
categoryId: PropTypes.string,
editPayment: PropTypes.bool,
paymentInfo: PropTypes.any,
id: PropTypes.string,
};

CreatePayment.defaultProps = {
firstOutlined: true,
};

export default CreatePayment;

+ 155
- 0
src/components/Modals/CreatePayment/CreatePayment.styled.js 查看文件

@@ -0,0 +1,155 @@
import { Box, Typography } from "@mui/material";
import styled from "styled-components";
import selectedTheme from "../../../themes";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { Label } from "../../CheckBox/Label";
import { TextField } from "../../TextFields/TextField/TextField";
import { ReactComponent as X } from "../../../assets/images/svg/plus.svg";

export const CreatePaymentContainer = styled(Box)`
position: fixed;
width: 623px;
top: calc(50vh - 326.5px);
/* height: ${(props) => (props.hideImagePicker ? "296px" : "510px")}; */
/* top: ${(props) =>
props.hideImagePicker ? "calc(50vh - 148px)" : "calc(50vh - 146.5px)"}; */
left: calc(50vw - 311px);

background: white;
z-index: 150;
& > * {
margin-left: auto;
margin-right: auto;
}
@media (max-width: 600px) {
height: 100vh;
max-height: 100vh;
min-height: 90vh;
width: 100vw;
top: 0;
left: 0;
padding: 0 18px;
}
`;
export const CreatePaymentTitle = styled(Typography)`
display: block;
font-family: ${selectedTheme.fonts.textFont};
font-weight: 700;
font-size: 24px;
line-height: 31px;
text-align: center;
margin-top: 36px;
width: 256px;
color: ${selectedTheme.colors.primaryPurple};
@media (max-width: 600px) {
font-size: 18px;
line-height: 24px;
margin-top: 18px;
}
`;

export const PaymentInputField = styled(TextField)`
width: 375px;
/* margin-bottom: 36px; */

@media (max-width: 600px) {
margin-bottom: 0;
& div div input {
font-size: 12px !important;
}
& div {
height: 40px;
width: 314px;
}
}
`;
export const FieldLabel = styled(Label)`
position: relative;
bottom: -14px;
width: 376px;
& label {
font-size: 12px;
font-weight: 600;
line-height: 20px;
color: ${selectedTheme.colors.primaryGrayText};
cursor: auto;
letter-spacing: 0.2px;
}
@media (max-width: 600px) {
& label {
font-size: 9px;
}
}
`;
export const InputContainer = styled(Box)`
display: block;
width: fit-content;
margin-top: 22px;

@media (max-width: 600px) {
width: 314px;
height: 56px;
margin-top: 27px;
position: relative;
top: -18px;
${(props) =>
!props.hideImagePicker &&
`
margin-top: 25px;
`}
}
`;
export const SaveButton = styled(PrimaryButton)`
max-width: 376px;
width: ${(props) => (props.showSecondButton ? "180px" : "376px")};
height: 48px;
& button {
letter-spacing: 1.5px;
}
@media (max-width: 600px) {
height: 45px;
width: ${(props) => (props.showSecondButton ? "149px" : "314px")};
}
`;
export const XIcon = styled(X)`
transform: rotate(45deg);
position: absolute;
top: 36px;
right: 36px;
cursor: pointer;
width: 26px;
height: 26px;
& path {
stroke: ${selectedTheme.colors.messageText};
stroke-width: 2;
}
@media (max-width: 600px) {
top: 18px;
right: 18px;
}
`;
export const ButtonsContainer = styled(Box)`
display: flex;
justify-content: center;
flex-direction: row;
gap: 18px;
margin: 36px auto;
@media (max-width: 600px) {
position: absolute;
bottom: 18px;
margin: auto;
left: 28px;
}
`;

export const StyledWrapper = styled(Box)`
& .react-autosuggest__container {
z-index: 10;
}
`;

export const ErrorMessage = styled(Typography)`
color: red;
font-family: ${selectedTheme.fonts.textFont};
font-size: 14px;
`;

+ 32
- 12
src/components/Modals/DeleteCategory/DeleteCategory.js 查看文件

@@ -21,6 +21,10 @@ import { dynamicRouteMatches } from "../../../util/helpers/routeHelpers";
import { ADMIN_SINGLE_USER_PAGE } from "../../../constants/pages";
import { fetchProfile } from "../../../store/actions/profile/profileActions";
import { closeModal } from "../../../store/actions/modal/modalActions";
import {
deletePayment,
fetchPayments,
} from "../../../store/actions/payment/paymentActions";

const DeleteCategory = (props) => {
const dispatch = useDispatch();
@@ -38,18 +42,31 @@ const DeleteCategory = (props) => {
}
closeModalHandler();
};

const handleApiDeletePaymentResponseSuccess = () => {
closeModalHandler();
dispatch(fetchPayments());
};
console.log(props);
const handleSubmit = () => {
dispatch(
fetchAdminMethod({
type: props.type,
method: DELETE_TYPE,
name: props.category?.name,
id: props.customId || props.category?._id,
categoryId: props.categoryId,
handleApiResponseSuccess,
})
);
if (!props.type === "payment") {
dispatch(
fetchAdminMethod({
type: props.type,
method: DELETE_TYPE,
name: props.category?.name,
id: props.customId || props.category?._id,
categoryId: props.categoryId,
handleApiResponseSuccess,
})
);
} else {
dispatch(
deletePayment({
id: props.id,
handleApiDeletePaymentResponseSuccess,
})
);
}
};
return (
<>
@@ -66,7 +83,9 @@ const DeleteCategory = (props) => {
>
{props.customLabeledCard || (
<CategoryName>
{props.category?.name || props.category?.city}
{props.category?.name ||
props.category?.city ||
props.category.payerName}
</CategoryName>
)}
</LabeledCard>
@@ -102,6 +121,7 @@ DeleteCategory.propTypes = {
customLabeledCardIcon: PropTypes.node,
categoryId: PropTypes.string,
customId: PropTypes.string,
id: PropTypes.string,
};

export default DeleteCategory;

+ 5
- 0
src/components/Modals/Modal.js 查看文件

@@ -9,6 +9,7 @@ import CreateReview from "../CreateReview/CreateReview";
import EditProfile from "../Cards/ProfileCard/EditProfile/EditProfile";
import DeleteReview from "./DeleteReview/DeleteReview";
import DeleteOffer from "../Cards/OfferCard/DeleteOffer/DeleteOffer";
import CreatePayment from "./CreatePayment/CreatePayment";

const Modal = () => {
const modals = useSelector(selectModalValues);
@@ -36,6 +37,10 @@ const Modal = () => {
{modals?.editCategoryModal && (
<EditCategory method="edit" {...modals?.props} />
)}
{modals?.createPaymentModal && <CreatePayment {...modals?.props} />}
{modals?.editPaymentModal && (
<CreatePayment editPayment {...modals?.props} />
)}
{modals?.deleteCategoryModal && <DeleteCategory {...modals?.props} />}
{modals?.createReviewModal && <CreateReview {...modals?.props} />}
{modals?.deleteReviewModal && <DeleteReview {...modals?.props} />}

+ 42
- 10
src/components/UserReviews/UserReviews.js 查看文件

@@ -37,6 +37,7 @@ const UserReviews = (props) => {
const [positiveReviews, setPositiveReviews] = useState(false);
const [negativeReviews, setNegativeReviews] = useState(false);
const [filteredReviews, setFilteredReviews] = useState();
const [recivedReviews, setRecivedReviews] = useState();
const { t } = useTranslation();
const offer = useSelector(selectOffer);
const reviews = useSelector(selectSelectedReviews);
@@ -50,24 +51,38 @@ const UserReviews = (props) => {
props.givingReview ? ONE_OFFER_SCOPE : REVIEW_GET_SCOPE
)
);
useEffect(() => {
setRecivedReviews(
reviews.filter(
(review) =>
review.exchange.buyer.user._id !== routeMatch.params.profileId
)
);
}, [reviews]);

useEffect(() => {
if (!positiveReviews && !negativeReviews) setFilteredReviews(reviews);
if (!positiveReviews && !negativeReviews)
setFilteredReviews(recivedReviews);
if (positiveReviews)
setFilteredReviews(
reviews.filter(
(review) =>
review.succeeded === reviewEnum.YES.backendText &&
review.communication === reviewEnum.YES.backendTextSecond
)
recivedReviews
.filter(
(review) =>
review.exchange.buyer.user._id !== routeMatch.params.profileId
)
.filter(
(review) =>
review.succeeded === reviewEnum.YES.backendText &&
review.communication === reviewEnum.YES.backendTextSecond
)
);
if (negativeReviews)
setFilteredReviews(
reviews.filter(
recivedReviews.filter(
(review) => review.succeeded === reviewEnum.NO.backendText
)
);
}, [reviews, positiveReviews, negativeReviews]);
}, [recivedReviews, positiveReviews, negativeReviews]);

const userId = useMemo(() => {
if (
@@ -88,7 +103,6 @@ const UserReviews = (props) => {
}
}, [props.givingReview, userId]);

console.log(props);
const lastThreeReviews = useMemo(() => {
console.log("profile Reviews", reviews);
if (props.givingReview) return [props.givingReview];
@@ -190,7 +204,25 @@ const UserReviews = (props) => {
</ReviewsHeader>
)}
<ReviewList ref={listRef} isProfileReviews={props.isProfileReviews}>
{filteredReviews?.length > 0 ? (
{dynamicRouteMatches(ADMIN_SINGLE_USER_PAGE) ? (
lastThreeReviews?.length > 0 ? (
lastThreeReviews?.map((review, index) => (
<UserReviewsCard
showRemoveIcon={props.isAdmin}
review={review}
key={index}
isProfileReviews={props?.isProfileReviews}
userId={userId}
isAdmin={props.isAdmin}
hasGivenReview={isGiven}
givingReview={props.givingReview}
rightReviews={props.rightReviews}
/>
))
) : (
<NoReviews fullHeight={props.fullHeight} />
)
) : filteredReviews?.length > 0 ? (
filteredReviews?.map((review, index) => (
<UserReviewsCard
showRemoveIcon={props.isAdmin}

+ 88
- 71
src/enums/sortEnum.js 查看文件

@@ -1,75 +1,92 @@
export const sortEnum = {
INITIAL: {
value: 0,
mainText: "Sortiraj po",
queryString: ""
},
POPULAR: {
value: 1,
mainText: "Najpopularnije",
queryString: "popular"
},
NEW: {
value: 2,
mainText: "Najnovije",
queryString: "newest"
},
OLD: {
value: 3,
mainText: "Najstarije",
queryString: "oldest"
}
}
INITIAL: {
value: 0,
mainText: "Sortiraj po",
queryString: "",
},
POPULAR: {
value: 1,
mainText: "Najpopularnije",
queryString: "popular",
},
NEW: {
value: 2,
mainText: "Najnovije",
queryString: "newest",
},
OLD: {
value: 3,
mainText: "Najstarije",
queryString: "oldest",
},
};
export const sortAdminEnum = {
INITIAL: {
value: 0,
mainText: "Sortiraj po"
},
GIVEN: {
value: 1,
mainText: "Date"
},
RECIEVED: {
value: 2,
mainText: "Dobijene"
}
}
INITIAL: {
value: 0,
mainText: "Sortiraj po",
},
GIVEN: {
value: 1,
mainText: "Date",
},
RECIEVED: {
value: 2,
mainText: "Dobijene",
},
};
export const sortCategoriesEnum = {
INITIAL: {
value: 0,
mainText: "Sortiraj po",
queryString: ""
},
POPULAR: {
value: 1,
mainText: "Najpopularnije",
queryString: "popular"
},
NEW: {
value: 2,
mainText: "Najskorije dodate",
queryString: "newest"
},
OLD: {
value: 3,
mainText: "Najstarije dodate",
queryString: "oldest"
}
}
INITIAL: {
value: 0,
mainText: "Sortiraj po",
queryString: "",
},
POPULAR: {
value: 1,
mainText: "Najpopularnije",
queryString: "popular",
},
NEW: {
value: 2,
mainText: "Najskorije dodate",
queryString: "newest",
},
OLD: {
value: 3,
mainText: "Najstarije dodate",
queryString: "oldest",
},
};
export const sortUsersEnum = {
INITIAL: {
value: 0,
mainText: "Sortiraj po",
queryString: ""
},
NEW: {
value: 1,
mainText: "Najskorije registrovan",
queryString: "newest"
},
OLD: {
value: 2,
mainText: "Najstarije registrovan",
queryString: "oldest"
}
}
INITIAL: {
value: 0,
mainText: "Sortiraj po",
queryString: "",
},
NEW: {
value: 1,
mainText: "Najskorije registrovan",
queryString: "newest",
},
OLD: {
value: 2,
mainText: "Najstarije registrovan",
queryString: "oldest",
},
};
export const sortPaymentsEnum = {
INITIAL: {
value: 0,
mainText: "Sortiraj po",
queryString: "",
},
NEW: {
value: 1,
mainText: "Najskorije",
queryString: "newest",
},
OLD: {
value: 2,
mainText: "Najstarije",
queryString: "oldest",
},
};

+ 3
- 3
src/hooks/useOffers/useOffers.js 查看文件

@@ -140,17 +140,17 @@ const useOffers = () => {
const applyFilters = () => {
setFiltersCleared(true);
};
const clearFiltersAndApply = () => {
clear();
setFiltersCleared(true);
};
const applySorting = () => {
paging.changePage(1);
setFiltersCleared(true);
};
const applySearch = () => {
paging.changePage(1);
setFiltersCleared(true);

+ 29
- 0
src/i18n/resources/rs.js 查看文件

@@ -563,6 +563,35 @@ export default {
next: "Sledeća",
},
},
payment: {
headerTitle: "Uplate",
placeholder: "Pretražite uplate...",
typeOfPayment: "Tip: ",
date: "Datum: ",
addPayment: "Dodaj uplatu",
reassuranceDelete:
"Da li ste sigurni da želite da obrišete odabranu uplatu?",
cancel: "Otkaži",
delete: "Obriši",
modal: {
newTitle: "Nova Uplata",
editTitle: "Izmena Uplate",
name: "UPLATIOC",
company: "IME KOMPANIJE",
type: "TIP",
date: "DATUM",
offer: "PROIZVOD",
add: "Dodaj",
change: "Izmeni",
},
errors: {
nameRequired: "Ime uplatioca je obavezno!",
companyRequired: "Ime kompanije je obavezno!",
offerRequired: "Proizvod je obavezan!",
typeRequired: "Tip je obavezan!",
dateRequired: "Datum je obavezan!",
},
},
deleteUser: {
reassuranceDelete:
"Da li ste sigurni da želite da obrišete profil kompanje?",

+ 7
- 0
src/initialValues/paymentInitialValues.js 查看文件

@@ -0,0 +1,7 @@
export default (payment) => ({
payerName: payment?.payerName || "",
companyName: payment?.companyName || "",
type: payment?.type || "",
date: payment?.date || "",
offerName: payment?.offerName || "",
});

+ 115
- 9
src/pages/AdminHomePage/AdminPaymentPage/AdminPaymentPage.js 查看文件

@@ -1,15 +1,121 @@
import React from 'react'
import PropTypes from 'prop-types'
import { AdminPaymentPageContainer } from './AdminPaymentPage.styled'
import React, { useEffect, useMemo } from "react";
import PropTypes from "prop-types";
import {
AdminPaymentPageContainer,
AdminPaymentsHeader,
AdminPaymentsSearchField,
// FilterButtonIcon,
// FilterButtonContainer,
NewPaymentButton,
PaymentsList,
// FilterButton,
} from "./AdminPaymentPage.styled";
import { useTranslation } from "react-i18next";
import useSorting from "../../../hooks/useOffers/useSorting";
import { sortPaymentsEnum } from "../../../enums/sortEnum";
import CategoryCard from "../../../components/Cards/CategoryCard/CategoryCard";
import selectedTheme from "../../../themes";
import { useDispatch, useSelector } from "react-redux";
import { setManualSearchString } from "../../../store/actions/filters/filtersActions";
import { toggleCreatePaymentModal } from "../../../store/actions/modal/modalActions";
import { selectPayments } from "../../../store/selectors/paymentSelector";
import { selectManualSearchString } from "../../../store/selectors/filtersSelectors";
import { fetchPayments } from "../../../store/actions/payment/paymentActions";
import { adminSortMethod } from "../../../util/helpers/adminSortHelper";
import { useHistory } from "react-router-dom";
import { selectAllProfiles } from "../../../store/selectors/profileSelectors";
import { fetchAllProfiles } from "../../../store/actions/profile/profileActions";

const AdminPaymentPage = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const sorting = useSorting(() => {}, sortPaymentsEnum);
const payments = useSelector(selectPayments);
const users = useSelector(selectAllProfiles);
const manualSearchString = useSelector(selectManualSearchString);
const history = useHistory();
const user = history?.location?.search?.split("=")[1];
const userName = user?.replaceAll("+", " ");
const userId = users?.filter?.((user) => user.companyName === userName);
useEffect(() => {
dispatch(fetchPayments());
dispatch(fetchAllProfiles());
return () => {
dispatch(setManualSearchString(""));
sorting.clear();
};
}, []);
const paymentsToShow = useMemo(() => {
if (payments && history?.location?.search === "") {
return adminSortMethod(payments, manualSearchString, sorting);
}
if (history.location.search) {
const filteredPayments = payments?.filter(
(payment) => payment?.user?._id === userId[0]?._id
);
return filteredPayments;
}
}, [
payments,
manualSearchString,
sorting.selectedSortOptionLocally,
history.location.search,
]);

const handleSearch = (value) => {
dispatch(setManualSearchString(value));
};
const showAddPaymentModal = () => {
dispatch(toggleCreatePaymentModal());
};

return (
<AdminPaymentPageContainer>Admin payment</AdminPaymentPageContainer>
)
}
<AdminPaymentPageContainer>
<AdminPaymentsSearchField
placeholder={t("admin.payment.placeholder")}
isAdmin
handleSearch={handleSearch}
/>
<NewPaymentButton
variant="contained"
buttoncolor={selectedTheme.colors.iconYellowColor}
textcolor={selectedTheme.colors.messageText}
onClick={showAddPaymentModal}
>
{t("admin.payment.addPayment")}
</NewPaymentButton>
{/* <FilterButtonContainer>
<FilterButton>
<FilterButtonIcon />
</FilterButton>
</FilterButtonContainer> */}
<AdminPaymentsHeader
myOffers
payments
hideGrid
isAdmin
sorting={sorting}
hideBackButton
/>
<PaymentsList>
{paymentsToShow?.map((payment) => (
<CategoryCard
key={payment._id}
category={payment}
type="payment"
secondLabel={t("admin.payment.date")}
dontNavigate
hideCheckButton
payments
/>
))}
</PaymentsList>
</AdminPaymentPageContainer>
);
};

AdminPaymentPage.propTypes = {
children: PropTypes.node,
}
children: PropTypes.node,
};

export default AdminPaymentPage
export default AdminPaymentPage;

+ 83
- 2
src/pages/AdminHomePage/AdminPaymentPage/AdminPaymentPage.styled.js 查看文件

@@ -1,6 +1,87 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import SearchField from "../../../components/TextFields/SearchField/SearchField";
import Header from "../../../components/MarketPlace/Header/Header";
import { PrimaryButton } from "../../../components/Buttons/PrimaryButton/PrimaryButton";
import { ReactComponent as Filter } from "../../../assets/images/svg/filter.svg";

export const AdminPaymentPageContainer = styled(Box)`
`
padding: 60px;
padding-top: 38px;
min-height: 100vh;
padding-bottom: 100px;
position: relative;
@media (max-width: 600px) {
padding: 18px;
min-height: calc(100vh - 72px);
padding-bottom: 100px;
top: 65px;
}
`;

export const AdminPaymentsSearchField = styled(SearchField)`
top: -15px;

@media (max-width: 900px) {
margin-top: 72px;
}

@media (max-width: 600px) {
display: none;
}
`;

export const AdminPaymentsHeader = styled(Header)`
/* top: 40px; */
top: 0;
@media (min-width: 600px) and (max-width: 900px) {
& > div:nth-child(2) {
display: none;
}
}
@media (max-width: 600px) {
top: 0;
margin-top: 0px;
& > div {
margin-top: 0;
}
& > div > div > div {
top: 25px;
left: 0;
}
}
`;

export const PaymentsList = styled(Box)``;

export const NewPaymentButton = styled(PrimaryButton)`
position: relative;
margin-left: auto;
height: 48px;
width: 224px;
& button {
font-weight: 700;
}
@media (max-width: 600px) {
/* bottom: 18px;
right: 16px; */
}
`;

export const FilterButtonContainer = styled(Box)`
display: flex;
justify-content: end;
margin-top: 20px;
`;

export const FilterButton = styled(Box)`
background-color: #e4e4e4;
border-radius: 100%;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
`;

export const FilterButtonIcon = styled(Filter)``;

+ 6
- 0
src/request/apiEndpoints.js 查看文件

@@ -219,4 +219,10 @@ export default {
getUserReviewsAsAdmin: "admin/reviews/{userId}",
},
},
payment: {
getUsersPayments: "admin/payments",
postUsersPayments: "admin/payments",
putUsersPayments: "admin/payments/{id}",
deleteUsersPayments: "admin/payments/{id}",
},
};

+ 28
- 0
src/request/paymentsRequest.js 查看文件

@@ -0,0 +1,28 @@
import {
deleteRequest,
getRequest,
postRequest,
putRequest,
replaceInUrl,
} from ".";
import apiEndpoints from "./apiEndpoints";

export const attemptFetchPayments = () =>
getRequest(apiEndpoints.payment.getUsersPayments);
export const attemptAddNewPayment = (payload) =>
postRequest(apiEndpoints.payment.postUsersPayments, payload);

export const attemptEditPayment = (payload) =>
putRequest(
replaceInUrl(apiEndpoints.payment.putUsersPayments, {
id: payload.id,
}),
payload.body
);

export const attemptDeletePayment = (payload) =>
deleteRequest(
replaceInUrl(apiEndpoints.payment.deleteUsersPayments, {
id: payload.id,
})
);

+ 1
- 0
src/store/actions/filters/filtersActionConstants.js 查看文件

@@ -6,6 +6,7 @@ export const CLEAR_FILTERS = createClearType(FILTERS_SCOPE);
export const SET_CATEGORY = createSetType("FILTERS_SET_CATEGORY");
export const SET_SUBCATEGORY = createSetType("FILTERS_SET_SUBCATEGORY");
export const SET_LOCATIONS = createSetType("FILTERS_SET_LOCATIONS");
export const SET_PAYMENTS = createSetType("FILTERS_SET_PAYMENTS");
export const SET_COMPANY = createSetType("FILTERS_SET_COMPANY");
export const SET_SORT_OPTION = createSetType("FILTERS_SET_SORT_OPTION");
export const SET_IS_APPLIED = createSetType("FILTERS_SET_IS_APPLIED");

+ 5
- 0
src/store/actions/filters/filtersActions.js 查看文件

@@ -7,6 +7,7 @@ import {
SET_IS_APPLIED,
SET_LOCATIONS,
SET_MANUAL_SEARCH_STRING,
SET_PAYMENTS,
SET_QUERY_STRING,
SET_SEARCH_STRING,
SET_SORT_OPTION,
@@ -36,6 +37,10 @@ export const setFilteredLocations = (payload) => ({
type: SET_LOCATIONS,
payload,
});
export const setFilteredPayments = (payload) => ({
type: SET_PAYMENTS,
payload,
});
export const setFilteredCompany = (payload) => ({
type: SET_COMPANY,
payload,

+ 3
- 0
src/store/actions/modal/modalActionConstants.js 查看文件

@@ -9,5 +9,8 @@ export const TOGGLE_DELETE_CATEGORY = createSetType("TOGGLE_DELETE_CATEGORY");
export const TOGGLE_CREATE_REVIEW = createSetType("TOGGLE_CREATE_REVIEW");
export const TOGGLE_DELETE_REVIEW = createSetType("TOGGLE_DELETE_REVIEW");
export const TOGGLE_EDIT_PROFILE = createSetType("TOGGLE_EDIT_PROFILE");
export const TOGGLE_CREATE_PAYMENT = createSetType("TOGGLE_CREATE_PAYMENT");
export const TOGGLE_EDIT_PAYMENT = createSetType("TOGGLE_EDIT_PAYMENT");
export const TOGGLE_DELETE_PAYMENT = createSetType("TOGGLE_DELETE_PAYMENT");
export const SET_MODAL_TYPE = createSetType("SET_MODAL_TYPE");
export const CLOSE_MODAL = createClearType("CLOSE_MODAL");

+ 15
- 0
src/store/actions/modal/modalActions.js 查看文件

@@ -2,12 +2,15 @@ import {
CLOSE_MODAL,
TOGGLE_CREATE_CATEGORY,
TOGGLE_CREATE_OFFER,
TOGGLE_CREATE_PAYMENT,
TOGGLE_CREATE_REVIEW,
TOGGLE_DELETE_CATEGORY,
TOGGLE_DELETE_OFFER,
TOGGLE_DELETE_PAYMENT,
TOGGLE_DELETE_REVIEW,
TOGGLE_EDIT_CATEOGRY,
TOGGLE_EDIT_OFFER,
TOGGLE_EDIT_PAYMENT,
TOGGLE_EDIT_PROFILE,
} from "./modalActionConstants";

@@ -47,6 +50,18 @@ export const toggleEditProfileModal = (payload) => ({
type: TOGGLE_EDIT_PROFILE,
payload,
});
export const toggleCreatePaymentModal = (payload) => ({
type: TOGGLE_CREATE_PAYMENT,
payload,
});
export const toggleEditPaymentModal = (payload) => ({
type: TOGGLE_EDIT_PAYMENT,
payload,
});
export const toggleDeletePaymentModal = (payload) => ({
type: TOGGLE_DELETE_PAYMENT,
payload,
});
export const closeModal = () => ({
type: CLOSE_MODAL,
});

+ 28
- 0
src/store/actions/payment/paymentActionConstants.js 查看文件

@@ -0,0 +1,28 @@
import {
createErrorType,
createFetchType,
createSetType,
createSuccessType,
} from "../actionHelpers";

const PAYMENTS_SCOPE = "PAYMENTS_SCOPE";
export const PAYMENTS_FETCH = createFetchType(PAYMENTS_SCOPE);
export const PAYMENTS_FETCH_SUCCESS = createSuccessType(PAYMENTS_SCOPE);
export const PAYMENTS_FETCH_ERROR = createErrorType(PAYMENTS_SCOPE);

export const PAYMENTS_ADD_SCOPE = "PAYMENTS_ADD_SCOPE";
export const PAYMENTS_ADD = createFetchType(PAYMENTS_ADD_SCOPE);
export const PAYMENTS_ADD_SUCCESS = createSuccessType(PAYMENTS_ADD_SCOPE);
export const PAYMENTS_ADD_ERROR = createErrorType(PAYMENTS_ADD_SCOPE);

export const PAYMENTS_EDIT_SCOPE = "PAYMENTS_EDIT_SCOPE";
export const PAYMENTS_EDIT = createFetchType(PAYMENTS_EDIT_SCOPE);
export const PAYMENTS_EDIT_SUCCESS = createSuccessType(PAYMENTS_EDIT_SCOPE);
export const PAYMENTS_EDIT_ERROR = createErrorType(PAYMENTS_EDIT_SCOPE);

export const PAYMENTS_DELETE_SCOPE = "PAYMENTS_DELETE_SCOPE";
export const PAYMENTS_DELETE = createFetchType(PAYMENTS_DELETE_SCOPE);
export const PAYMENTS_DELETE_SUCCESS = createSuccessType(PAYMENTS_DELETE_SCOPE);
export const PAYMENTS_DELETE_ERROR = createErrorType(PAYMENTS_DELETE_SCOPE);

export const PAYMENTS_SET = createSetType("PAYMENTS_SET");

+ 60
- 0
src/store/actions/payment/paymentActions.js 查看文件

@@ -0,0 +1,60 @@
import {
PAYMENTS_ADD,
PAYMENTS_ADD_ERROR,
PAYMENTS_ADD_SUCCESS,
PAYMENTS_DELETE,
PAYMENTS_DELETE_ERROR,
PAYMENTS_DELETE_SUCCESS,
PAYMENTS_EDIT,
PAYMENTS_EDIT_ERROR,
PAYMENTS_EDIT_SUCCESS,
PAYMENTS_FETCH,
PAYMENTS_FETCH_ERROR,
PAYMENTS_FETCH_SUCCESS,
PAYMENTS_SET,
} from "./paymentActionConstants";

export const fetchPayments = () => ({
type: PAYMENTS_FETCH,
});

export const setPayments = (payload) => ({
type: PAYMENTS_SET,
payload,
});
export const fetchPaymentsSuccess = () => ({
type: PAYMENTS_FETCH_SUCCESS,
});
export const fetchPaymentsError = () => ({
type: PAYMENTS_FETCH_ERROR,
});
export const addPayment = (payload) => ({
type: PAYMENTS_ADD,
payload,
});
export const addPaymentSuccess = () => ({
type: PAYMENTS_ADD_SUCCESS,
});
export const addPaymentError = () => ({
type: PAYMENTS_ADD_ERROR,
});
export const editPayment = (payload) => ({
type: PAYMENTS_EDIT,
payload,
});
export const editPaymentSuccess = () => ({
type: PAYMENTS_EDIT_SUCCESS,
});
export const editPaymentError = () => ({
type: PAYMENTS_EDIT_ERROR,
});
export const deletePayment = (payload) => ({
type: PAYMENTS_DELETE,
payload,
});
export const deletePaymentSuccess = () => ({
type: PAYMENTS_DELETE_SUCCESS,
});
export const deletePaymentError = () => ({
type: PAYMENTS_DELETE_ERROR,
});

+ 14
- 0
src/store/reducers/filters/filtersReducer.js 查看文件

@@ -7,6 +7,7 @@ import {
SET_IS_APPLIED,
SET_LOCATIONS,
SET_MANUAL_SEARCH_STRING,
SET_PAYMENTS,
SET_QUERY_STRING,
SET_SEARCH_STRING,
SET_SORT_OPTION,
@@ -19,6 +20,7 @@ const initialState = {
category: null,
subcategory: null,
locations: [],
payments: [],
company: [],
sortOption: null,
isApplied: false,
@@ -27,6 +29,7 @@ const initialState = {
categoryString: "",
subcategoryString: "",
locationsString: "",
paymentsString: "",
companyString: "",
text: "",
},
@@ -42,6 +45,7 @@ export default createReducer(
[SET_CATEGORY]: setFilteredCategory,
[SET_SUBCATEGORY]: setFilteredSubcategory,
[SET_LOCATIONS]: setFilteredLocations,
[SET_PAYMENTS]: setFilteredPayments,
[SET_COMPANY]: setFilteredCompany,
[SET_SORT_OPTION]: setFilteredSortOption,
[SET_IS_APPLIED]: setIsAppliedStatus,
@@ -103,6 +107,16 @@ function setFilteredLocations(state, { payload }) {
},
};
}

function setFilteredPayments(state, { payload }) {
return {
...state,
filters: {
...state.filters,
payments: payload,
},
};
}
function setFilteredCompany(state, { payload }) {
return {
...state,

+ 9
- 2
src/store/reducers/index.js 查看文件

@@ -16,6 +16,7 @@ import exchangeReducer from "./exchange/exchangeReducer";
import reviewReducer from "./review/reviewReducer";
import appReducer from "./app/appReducer";
import modalReducer from "./modal/modalReducer";
import paymentReducer from "./payment/paymentReducer";

const loginPersistConfig = {
key: "login",
@@ -40,6 +41,11 @@ const locationsPersistConfig = {
storage: storage,
transform: [createFilter("locations", ["_id", "city"])],
};
const paymentsPersistConfig = {
key: "payment",
storage: storage,
transform: [createFilter("payment", ["_id", "payerName"])],
};
const profilePersistConfig = {
key: "profile",
storage: storage,
@@ -48,8 +54,8 @@ const profilePersistConfig = {
const chatPersistConfig = {
key: "chat",
storage: storage,
transform: [createFilter("chat", ["latestChats"])]
}
transform: [createFilter("chat", ["latestChats"])],
};

export default combineReducers({
login: persistReducer(loginPersistConfig, loginReducer),
@@ -59,6 +65,7 @@ export default combineReducers({
offers: offersReducer,
categories: persistReducer(categoriesPersistConfig, categoriesReducer),
locations: persistReducer(locationsPersistConfig, locationsReducer),
payment: persistReducer(paymentsPersistConfig, paymentReducer),
profile: persistReducer(profilePersistConfig, profileReducer),
chat: persistReducer(chatPersistConfig, chatReducer),
queryString: queryStringReducer,

+ 22
- 2
src/store/reducers/modal/modalReducer.js 查看文件

@@ -1,5 +1,5 @@
import {
CLOSE_MODAL,
CLOSE_MODAL,
SET_MODAL_TYPE,
TOGGLE_CREATE_CATEGORY,
TOGGLE_CREATE_OFFER,
@@ -10,6 +10,8 @@ import {
TOGGLE_EDIT_CATEOGRY,
TOGGLE_EDIT_OFFER,
TOGGLE_EDIT_PROFILE,
TOGGLE_CREATE_PAYMENT,
TOGGLE_EDIT_PAYMENT,
} from "../../actions/modal/modalActionConstants";
import createReducer from "../../utils/createReducer";

@@ -23,6 +25,8 @@ const initialState = {
createReviewModal: false,
editProfile: false,
toggleDeleteReview: false,
createPaymentModal: false,
editPaymentModal: false,
props: {},
};

@@ -37,6 +41,8 @@ export default createReducer(
[TOGGLE_CREATE_REVIEW]: toggleCreateReview,
[TOGGLE_DELETE_REVIEW]: toggleDeleteReview,
[TOGGLE_EDIT_PROFILE]: toggleEditProfile,
[TOGGLE_CREATE_PAYMENT]: toggleCreatePayment,
[TOGGLE_EDIT_PAYMENT]: toggleEditPayment,
[SET_MODAL_TYPE]: setModalType,
[CLOSE_MODAL]: closeModal,
},
@@ -105,6 +111,20 @@ function toggleEditProfile(state, { payload }) {
props: payload,
};
}
function toggleCreatePayment(state, { payload }) {
return {
...state,
createPaymentModal: !state.createPaymentModal,
props: payload,
};
}
function toggleEditPayment(state, { payload }) {
return {
...state,
editPaymentModal: !state.editPaymentModal,
props: payload,
};
}
function setModalType(state, { payload }) {
return {
...state,
@@ -112,5 +132,5 @@ function setModalType(state, { payload }) {
};
}
function closeModal() {
return initialState;
return initialState;
}

+ 20
- 0
src/store/reducers/payment/paymentReducer.js 查看文件

@@ -0,0 +1,20 @@
import { PAYMENTS_SET } from "../../actions/payment/paymentActionConstants";
import createReducer from "../../utils/createReducer";

const initialState = {
payments: [],
};

export default createReducer(
{
[PAYMENTS_SET]: setPayments,
},
initialState
);

function setPayments(state, action) {
return {
...state,
payments: action.payload,
};
}

+ 17
- 15
src/store/saga/index.js 查看文件

@@ -1,17 +1,18 @@
import { all } from 'redux-saga/effects';
import adminSaga from './adminSaga';
import categoriesSaga from './categoriesSaga';
import chatSaga from './chatSaga';
import counterSaga from './counterSaga';
import exchangeSaga from './exchangeSaga';
import forgotPasswordSaga from './forgotPasswordSaga';
import locationsSaga from './locationsSaga';
import loginSaga from './loginSaga';
import offersSaga from './offersSaga';
import profileSaga from './profileSaga';
import queryStringSaga from './queryStringSaga';
import registerSaga from './registerSaga';
import reviewSaga from './reviewSaga';
import { all } from "redux-saga/effects";
import adminSaga from "./adminSaga";
import categoriesSaga from "./categoriesSaga";
import chatSaga from "./chatSaga";
import counterSaga from "./counterSaga";
import exchangeSaga from "./exchangeSaga";
import forgotPasswordSaga from "./forgotPasswordSaga";
import locationsSaga from "./locationsSaga";
import loginSaga from "./loginSaga";
import offersSaga from "./offersSaga";
import profileSaga from "./profileSaga";
import queryStringSaga from "./queryStringSaga";
import registerSaga from "./registerSaga";
import reviewSaga from "./reviewSaga";
import paymentSaga from "./paymentSaga";

export default function* rootSaga() {
yield all([
@@ -27,6 +28,7 @@ export default function* rootSaga() {
exchangeSaga(),
reviewSaga(),
counterSaga(),
adminSaga()
adminSaga(),
paymentSaga(),
]);
}

+ 82
- 0
src/store/saga/paymentSaga.js 查看文件

@@ -0,0 +1,82 @@
import { all, call, put, takeLatest } from "@redux-saga/core/effects";
import {
PAYMENTS_ADD,
PAYMENTS_DELETE,
PAYMENTS_EDIT,
PAYMENTS_FETCH,
} from "../actions/payment/paymentActionConstants";
import {
fetchPaymentsError,
fetchPaymentsSuccess,
setPayments,
} from "../actions/payment/paymentActions";
import {
attemptAddNewPayment,
attemptDeletePayment,
attemptEditPayment,
attemptFetchPayments,
} from "../../request/paymentsRequest";
import { fetchPayments } from "../actions/payment/paymentActions";

function* fetchAllPayments() {
try {
const data = yield call(attemptFetchPayments);
if (!data?.data) throw new Error();
yield put(setPayments(data.data));
yield put(fetchPaymentsSuccess());
} catch (e) {
yield put(fetchPaymentsError());
}
}

function* createPayment(payload) {
try {
yield call(attemptAddNewPayment, payload.payload);
yield put(fetchPayments());
if (payload.payload.handleApiResponseSuccess) {
yield call(payload.payload.handleApiResponseSuccess);
}
} catch (e) {
console.dir(e);
}
}

function* editPayment(payload) {
try {
yield call(attemptEditPayment, {
id: payload.payload.id,
body: {
type: payload.payload.type,
payerName: payload.payload.payerName,
userId: payload.payload.userId,
offerId: payload.payload.offerId,
date: payload.payload.date,
},
});
if (payload.payload.handleApiResponseSuccess) {
yield call(payload.payload.handleApiResponseSuccess);
}
} catch (e) {
console.dir(e);
}
}

function* deletePayment(payload) {
try {
yield call(attemptDeletePayment, { id: payload.payload.id });
if (payload.payload.handleApiDeletePaymentResponseSuccess) {
yield call(payload.payload.handleApiDeletePaymentResponseSuccess);
}
} catch (e) {
console.dir(e);
}
}

export default function* paymentSaga() {
yield all([
takeLatest(PAYMENTS_FETCH, fetchAllPayments),
takeLatest(PAYMENTS_ADD, createPayment),
takeLatest(PAYMENTS_EDIT, editPayment),
takeLatest(PAYMENTS_DELETE, deletePayment),
]);
}

+ 1
- 0
src/store/saga/profileSaga.js 查看文件

@@ -97,6 +97,7 @@ function* fetchAllProfilesAsAdmin({ payload }) {
}

const data = yield call(attemptFetchAllProfilesAsAdmin, queryString);
console.log(data);
if (data) yield put(setAllProfiles(data.data));
yield put(fetchAllProfilesAsAdminSuccess());
} catch (e) {

+ 4
- 0
src/store/selectors/filtersSelectors.js 查看文件

@@ -18,6 +18,10 @@ export const selectSelectedLocations = createSelector(
filtersSelector,
(state) => state.filters.locations
);
export const selectSelectedPayments = createSelector(
filtersSelector,
(state) => state.filters.payments
);
export const selectSelectedCompany = createSelector(
filtersSelector,
(state) => state.filters.company

+ 8
- 0
src/store/selectors/paymentSelector.js 查看文件

@@ -0,0 +1,8 @@
import { createSelector } from "reselect";

const paymentSelector = (state) => state.payment;

export const selectPayments = createSelector(
paymentSelector,
(state) => state.payments
);

+ 29
- 1
src/util/helpers/adminSortHelper.js 查看文件

@@ -1,4 +1,5 @@
import { sortCategoriesEnum } from "../../enums/sortEnum";
import { sortPaymentsEnum } from "../../enums/sortEnum";

export const adminSortMethod = (arrayOfItems, manualSearchString, sorting) => {
let arrayOfItemsToReturn = [...arrayOfItems];
@@ -6,7 +7,10 @@ export const adminSortMethod = (arrayOfItems, manualSearchString, sorting) => {
arrayOfItemsToReturn = arrayOfItems.filter(
(item) =>
item?.city?.toLowerCase()?.includes(manualSearchString.toLowerCase()) ||
item?.name?.toLowerCase()?.includes(manualSearchString.toLowerCase())
item?.name?.toLowerCase()?.includes(manualSearchString.toLowerCase()) ||
item?.payerName
?.toLowerCase()
?.includes(manualSearchString.toLowerCase())
);
if (sorting?.selectedSortOptionLocally !== sortCategoriesEnum.INITIAL) {
if (sorting?.selectedSortOptionLocally === sortCategoriesEnum.POPULAR) {
@@ -35,5 +39,29 @@ export const adminSortMethod = (arrayOfItems, manualSearchString, sorting) => {
});
}
}
// if ("payerName" in arrayOfItems[0]) {
if (sorting?.selectedSortOptionLocally === sortPaymentsEnum.OLD) {
arrayOfItemsToReturn.sort((a, b) => {
const firstCreated = new Date(
a?._created || new Date(1970, 1).toISOString()
);
const secondCreated = new Date(
b?._created || new Date(1970, 1).toISOString()
);
return firstCreated - secondCreated;
});
}
if (sorting?.selectedSortOptionLocally === sortPaymentsEnum.NEW) {
arrayOfItemsToReturn.sort((a, b) => {
const firstCreated = new Date(
a?._created || new Date(1970, 1).toISOString()
);
const secondCreated = new Date(
b?._created || new Date(1970, 1).toISOString()
);
return secondCreated - firstCreated;
});
}
// }
return arrayOfItemsToReturn;
};

+ 16
- 0
src/validations/createPaymentValidation.js 查看文件

@@ -0,0 +1,16 @@
import * as Yup from "yup";
import i18n from "../i18n";
export default () =>
Yup.object().shape({
payerName: Yup.string().required(
i18n.t("admin.payment.errors.nameRequired")
),
companyName: Yup.string().required(
i18n.t("admin.payment.errors.companyRequired")
),
offerName: Yup.string().required(
i18n.t("admin.payment.errors.offerRequired")
),
type: Yup.string().required(i18n.t("admin.payment.errors.typeRequired")),
date: Yup.string().required(i18n.t("admin.payment.errors.dateRequired")),
});

Loading…
取消
儲存