Ver código fonte

Finished feature 657

feature/657
djordjemitrovic00 3 anos atrás
pai
commit
370b93e8a6

+ 36
- 3
src/components/Cards/CategoryCard/CategoryCard.js Ver arquivo

@@ -14,15 +14,32 @@ import CategoryRemoveButton from "./CategoryRemoveButton/CategoryRemoveButton";
import { useTranslation } from "react-i18next";
import { useState } from "react";
import DeleteCategory from "../../Modals/DeleteCategory/DeleteCategory";
import EditCategory from "../../Modals/EditCategory/EditCategory";
import history from "../../../store/utils/history";
import { replaceInRoute } from "../../../util/helpers/routeHelpers";
import { ADMIN_SUBCATEGORIES_PAGE } from "../../../constants/pages";

const CategoryCard = (props) => {
const { t } = useTranslation();
const [openedDeleteModal, setOpenedDeleteModal] = useState(false);
const [openedEditModal, setOpenedEditModal] = useState(false);
const navigateToCategory = () => {
if (!props.subcategory) {
history.push(
replaceInRoute(ADMIN_SUBCATEGORIES_PAGE, {
categoryId: props.category._id,
})
);
}
};
return (
<>
<CategoryCardContainer className={props.className}>
<CategoryCardLeftContainer>
<CategoryCardName categoryName={props?.category?.name} />
<CategoryCardName
categoryName={props?.category?.name}
onClick={navigateToCategory}
/>
<CategoryCardDetailsContainer>
<CategoryDetail
label={t("admin.categories.noOfOffers")}
@@ -38,14 +55,28 @@ const CategoryCard = (props) => {
</CategoryCardLeftContainer>
<CategoryCardRightContainer>
<CategoryRemoveButton onClick={() => setOpenedDeleteModal(true)} />
<CategoryEditButton />
{!props.hideCheckButton && <CategoryCheckButton />}
<CategoryEditButton onClick={() => setOpenedEditModal(true)} />
{!props.hideCheckButton && (
<CategoryCheckButton onClick={navigateToCategory} />
)}
</CategoryCardRightContainer>
</CategoryCardContainer>
{openedDeleteModal && (
<DeleteCategory
setOpenedDeleteModal={setOpenedDeleteModal}
subcategory={props.subcategory}
category={props.category}
type={props.type}
/>
)}
{openedEditModal && (
<EditCategory
hideImagePicker
setOpenedEditModal={setOpenedEditModal}
category={props.category}
subcategory={props.subcategory}
type={props.type}
method="edit"
/>
)}
</>
@@ -59,6 +90,8 @@ CategoryCard.propTypes = {
secondLabel: PropTypes.string,
hideSecondLabel: PropTypes.bool,
className: PropTypes.string,
subcategory: PropTypes.bool,
type: PropTypes.string,
};

export default CategoryCard;

+ 2
- 1
src/components/Cards/CategoryCard/CategoryCardName/CategoryCardName.js Ver arquivo

@@ -7,7 +7,7 @@ import {

const CategoryCardName = (props) => {
return (
<CategoryCardNameContainer>
<CategoryCardNameContainer onClick={props.onClick}>
<CategoryCardNameText>{props.categoryName}</CategoryCardNameText>
</CategoryCardNameContainer>
);
@@ -16,6 +16,7 @@ const CategoryCardName = (props) => {
CategoryCardName.propTypes = {
children: PropTypes.node,
categoryName: PropTypes.string,
onClick: PropTypes.func,
};

export default CategoryCardName;

+ 3
- 1
src/components/Cards/CategoryCard/CategoryCheckButton/CategoryCheckButton.js Ver arquivo

@@ -4,10 +4,11 @@ import selectedTheme from "../../../../themes";
import { CheckButton } from "./CategoryCheckButton.styled";
import { useTranslation } from "react-i18next";

const CategoryCheckButton = () => {
const CategoryCheckButton = (props) => {
const { t } = useTranslation();
return (
<CheckButton
onClick={props.onClick}
variant={"outlined"}
buttoncolor={selectedTheme.colors.primaryPurple}
textcolor={selectedTheme.colors.primaryPurple}
@@ -20,6 +21,7 @@ const CategoryCheckButton = () => {

CategoryCheckButton.propTypes = {
category: PropTypes.any,
onClick: PropTypes.func,
};

export default CategoryCheckButton;

+ 3
- 2
src/components/Cards/CategoryCard/CategoryEditButton/CategoryEditButton.js Ver arquivo

@@ -5,9 +5,9 @@ import {
EditIcon,
} from "./CategoryEditButton.styled";

const CategoryEditButton = () => {
const CategoryEditButton = (props) => {
return (
<CategoryEditButtonContainer>
<CategoryEditButtonContainer onClick={props.onClick}>
<EditIcon />
</CategoryEditButtonContainer>
);
@@ -15,6 +15,7 @@ const CategoryEditButton = () => {

CategoryEditButton.propTypes = {
category: PropTypes.any,
onClick: PropTypes.func,
};

export default CategoryEditButton;

+ 7
- 3
src/components/Modals/DeleteCategory/DeleteCategory.js Ver arquivo

@@ -11,6 +11,7 @@ import {
import LabeledCard from "../../Cards/LabeledCard/LabeledCard";
import { useTranslation } from "react-i18next";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { useMemo } from "react";

const DeleteCategory = (props) => {
const { t } = useTranslation();
@@ -18,6 +19,10 @@ const DeleteCategory = (props) => {
const handleCancel = () => {
props.setOpenedDeleteModal(false);
};
const reassuranceText = useMemo(() => {
if (props.subcategory) return t("admin.subcategories.reassuranceDelete");
return t("admin.categories.reassuranceDelete");
}, [props]);
return (
<>
<BackdropComponent
@@ -29,9 +34,7 @@ const DeleteCategory = (props) => {
<LabeledCard icon={<DeleteIcon />}>
<CategoryName>{props.category.name}</CategoryName>
</LabeledCard>
<ReassuranceText>
{t("admin.categories.reassuranceDelete")}
</ReassuranceText>
<ReassuranceText>{reassuranceText}</ReassuranceText>
<ButtonsContainer>
<PrimaryButton
onClick={handleCancel}
@@ -53,6 +56,7 @@ const DeleteCategory = (props) => {
DeleteCategory.propTypes = {
setOpenedDeleteModal: PropTypes.func,
category: PropTypes.object,
subcategory: PropTypes.bool,
};

export default DeleteCategory;

+ 118
- 0
src/components/Modals/EditCategory/EditCategory.js Ver arquivo

@@ -0,0 +1,118 @@
import React from "react";
import PropTypes from "prop-types";
import BackdropComponent from "../../MUI/BackdropComponent";
import {
ButtonsContainer,
CategoryTitleField,
EditCategoryContainer,
EditCategoryImagePicker,
EditCategoryTitle,
FieldLabel,
SaveButton,
SupportedFormats,
XIcon,
} from "./EditCategory.styled";
import { useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { useFormik } from "formik";
import { useMemo } from "react";

const EditCategory = (props) => {
const { t } = useTranslation();
const [image, setImage] = useState("");
console.log(props);
const title = useMemo(() => {
return t(`admin.${props.type}.${props.method}.title`);
}, [props.type, props.method]);
const placeholder = useMemo(() => {
return t(`admin.${props.type}.${props.method}.placeholder`);
}, [props.type, props.method]);
const fieldLabel = useMemo(() => {
return t(`admin.${props.type}.${props.method}.fieldTitle`);
}, [props.type, props.method]);
const firstButtonText = useMemo(() => {
return t(`admin.${props.type}.${props.method}.next`);
}, [props.type, props.method]);
const secondButtonText = useMemo(() => {
return t(`admin.${props.type}.${props.method}.save`);
}, [props.type, props.method]);
const setImageHandler = (image) => {
setImage(image);
formik.setFieldValue("image", image);
};
const handleSubmit = (values) => {
console.log(values);
console.log(image);
};
const formik = useFormik({
initialValues: {
image: "",
title: props?.category?.name || "",
},
onSubmit: handleSubmit,
});
return (
<>
<BackdropComponent
isLoading
handleClose={() => props.setOpenedEditModal(false)}
position="fixed"
/>
<EditCategoryContainer hideImagePicker={props.hideImagePicker}>
<XIcon onClick={() => props.setOpenedEditModal(false)} />
<EditCategoryTitle>{title}</EditCategoryTitle>
{!props.hideImagePicker && (
<>
<EditCategoryImagePicker setImage={setImageHandler} singleImage />
<SupportedFormats>
<Trans i18nKey="offer.supportedImagesFormats" />
</SupportedFormats>
</>
)}
<FieldLabel leftText={fieldLabel} />
<CategoryTitleField
name="title"
placeholder={placeholder}
italicPlaceholder
margin="normal"
value={formik.values.title}
onChange={formik.handleChange}
error={formik.touched.title && formik.errors.title}
helperText={formik.touched.title && formik.errors.title}
autoFocus
fullWidth
/>
<ButtonsContainer>
{props.method === "add" && (
<SaveButton
variant="contained"
width="180px"
height="48px"
onClick={formik.handleSubmit}
>
{firstButtonText}
</SaveButton>
)}
<SaveButton
variant={props.method === "add" ? "outlined" : "contained"}
width={props.method === "add" ? "180px" : "376px"}
height="48px"
onClick={formik.handleSubmit}
>
{secondButtonText}
</SaveButton>
</ButtonsContainer>
</EditCategoryContainer>
</>
);
};

EditCategory.propTypes = {
category: PropTypes.any,
setOpenedEditModal: PropTypes.func,
hideImagePicker: PropTypes.bool,
type: PropTypes.string,
method: PropTypes.string,
};

export default EditCategory;

+ 112
- 0
src/components/Modals/EditCategory/EditCategory.styled.js Ver arquivo

@@ -0,0 +1,112 @@
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 ImagePicker from "../../ImagePicker/ImagePicker";
import { TextField } from "../../TextFields/TextField/TextField";
import { ReactComponent as X } from "../../../assets/images/svg/plus.svg";

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

background: white;
z-index: 150;
& > * {
margin-left: auto;
margin-right: auto;
}
`;
export const EditCategoryTitle = 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};
`;
export const EditCategoryImagePicker = styled(ImagePicker)`
background: none;
margin-top: 36px;
background: ${selectedTheme.colors.primaryIconBackgroundColor};
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='100' ry='100' stroke='%235A3984FF' stroke-width='2' stroke-dasharray='7%2c 12' stroke-dashoffset='44' stroke-linecap='square'/%3e%3c/svg%3e");
border-radius: 100px;
overflow: hidden;
position: relative;
left: calc(50% - 72px);
margin-bottom: 18px;
`;
export const SupportedFormats = styled(Typography)`
font-size: 13px;
width: 100%;
text-align: center;
font-family: ${selectedTheme.fonts.textFont};
`;
export const CategoryTitleField = styled(TextField)`
width: 375px;
margin-bottom: 36px;

@media (max-width: 600px) {
margin-bottom: 0;
& div div input {
font-size: 12px !important;
}
& div {
height: 40px;
}
}
`;
export const FieldLabel = styled(Label)`
position: relative;
bottom: -14px;
width: 376px;
margin-top: 22px;
& 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 SaveButton = styled(PrimaryButton)`
max-width: 376px;
height: 48px;
& button {
letter-spacing: 1.5px;
}
`;
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;
}
`;
export const ButtonsContainer = styled(Box)`
display: flex;
justify-content: center;
flex-direction: row;
gap: 18px;
margin: 36px auto;
`;

+ 29
- 0
src/i18n/resources/rs.js Ver arquivo

@@ -439,12 +439,41 @@ export default {
"Da li ste sigurni da želite da obrišete odabranu kategoriju?",
cancel: "Otkaži",
delete: "Obriši",
addCategory: "Dodaj kategoriju",
edit: {
title: "Izmena Kategorije",
fieldTitle: "Naslov",
placeholder: "Naziv kategorije...",
save: "Izmeni"
},
add: {
title: "Nova Kategorija",
fieldTitle: "Naslov",
placeholder: "Naziv kategorije...",
save: "Dodaj",
next: "Sledeća"
}
},
subcategories: {
noOfOffers: "Broj objava: ",
headerTitle: "Kategorija",
subcategoriesHeaderTitle: "Podkategorije",
placeholder: "Pretražite podkategorije...",
addSubcategory: "Dodaj podkategoriju",
reassuranceDelete: "Da li ste sigurni da želite da obrišete odabranu podkategoriju?",
edit: {
title: "Izmena Podkategorije",
fieldTitle: "Naslov",
placeholder: "Naziv podkategorije...",
save: "Izmeni"
},
add: {
title: "Nova Podkategorija",
fieldTitle: "Naslov",
placeholder: "Naziv podkategorije...",
save: "Dodaj",
next: "Sledeća"
}
},
},
};

+ 23
- 0
src/pages/AdminCategoriesPage/AdminCategoriesPage.js Ver arquivo

@@ -10,16 +10,21 @@ import {
AdminCategoriesHeader,
AdminCategoriesPageContainer,
AdminCategoriesSearchField,
NewCategoryButton,
} from "./AdminCategoriesPage.styled";
import { selectManualSearchString } from "../../store/selectors/filtersSelectors";
import { useMemo } from "react";
import { setManualSearchString } from "../../store/actions/filters/filtersActions";
import selectedTheme from "../../themes";
import { useState } from "react";
import EditCategory from "../../components/Modals/EditCategory/EditCategory";

const AdminCategoriesPage = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const categories = useSelector(selectCategories);
const manualSearchString = useSelector(selectManualSearchString);
const [openedAddModal, setOpenedAddModal] = useState(false);
useEffect(() => {
dispatch(fetchCategories());
}, []);
@@ -40,6 +45,7 @@ const AdminCategoriesPage = () => {
return [];
}, [categories, manualSearchString]);
return (
<>
<AdminCategoriesPageContainer>
<AdminCategoriesSearchField
isAdmin
@@ -57,10 +63,27 @@ const AdminCategoriesPage = () => {
<CategoryCard
key={category._id}
category={category}
type="categories"
secondLabel={t("admin.categories.noOfSubcategories")}
/>
))}
<NewCategoryButton
variant="contained"
buttoncolor={selectedTheme.colors.iconYellowColor}
textcolor={selectedTheme.colors.messageText}
onClick={() => setOpenedAddModal(true)}
>
{t("admin.categories.addCategory")}
</NewCategoryButton>
</AdminCategoriesPageContainer>
{openedAddModal && (
<EditCategory
setOpenedEditModal={setOpenedAddModal}
type={"categories"}
method="add"
/>
)}
</>
);
};


+ 20
- 1
src/pages/AdminCategoriesPage/AdminCategoriesPage.styled.js Ver arquivo

@@ -1,14 +1,19 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton";
import Header from "../../components/MarketPlace/Header/Header";
import SearchField from "../../components/TextFields/SearchField/SearchField";

export const AdminCategoriesPageContainer = styled(Box)`
padding: 60px;
min-height: 100vh;
position: relative;
padding-bottom: 100px;
@media (max-width: 600px) {
padding: 18px;
position: relative;
min-height: calc(100vh - 72px);
top: 65px;
padding-bottom: 54px;
}
`;
export const AdminCategoriesHeader = styled(Header)`
@@ -30,3 +35,17 @@ export const AdminCategoriesSearchField = styled(SearchField)`
display: none;
}
`;
export const NewCategoryButton = styled(PrimaryButton)`
position: absolute;
bottom: 18px;
right: 37px;
height: 48px;
width: 224px;
& button {
font-weight: 700;
}
@media (max-width: 600px) {
bottom: 18px;
right: 16px;
}
`

+ 26
- 0
src/pages/AdminSubcategoriesPage/AdminSubcategoriesPage.js Ver arquivo

@@ -9,6 +9,7 @@ import {
AdminSubcategoriesHeader,
AdminSubcategoriesPageContainer,
AdminSubcategoriesSearchField,
NewSubcategoryButton,
SponsoredCategoryCard,
} from "./AdminSubcategoriesPage.styled";
import { selectManualSearchString } from "../../store/selectors/filtersSelectors";
@@ -16,6 +17,9 @@ import { useMemo } from "react";
import { setManualSearchString } from "../../store/actions/filters/filtersActions";
import { useRouteMatch } from "react-router-dom";
import CategoryCard from "../../components/Cards/CategoryCard/CategoryCard";
import selectedTheme from "../../themes";
import { useState } from "react";
import EditCategory from "../../components/Modals/EditCategory/EditCategory";

const AdminSubcategoriesPage = () => {
const { t } = useTranslation();
@@ -23,6 +27,8 @@ const AdminSubcategoriesPage = () => {
const categories = useSelector(selectCategories);
const routeMatch = useRouteMatch();
const manualSearchString = useSelector(selectManualSearchString);
const [openedAddModal, setOpenedAddModal] = useState(false);

useEffect(() => {
dispatch(fetchCategories());
}, []);
@@ -66,8 +72,10 @@ const AdminSubcategoriesPage = () => {
hideBackButton
/>
<SponsoredCategoryCard
subcategory
category={category}
hideSecondLabel
type="categories"
hideCheckButton
/>
<AdminSubcategoriesHeader
@@ -80,13 +88,31 @@ const AdminSubcategoriesPage = () => {
/>
{subcategories.map((category) => (
<CategoryCard
subcategory
key={category._id}
category={category}
type="subcategories"
hideSecondLabel
hideCheckButton
/>
))}
<NewSubcategoryButton
variant="contained"
buttoncolor={selectedTheme.colors.iconYellowColor}
textcolor={selectedTheme.colors.messageText}
onClick={() => setOpenedAddModal(true)}
>
{t("admin.subcategories.addSubcategory")}
</NewSubcategoryButton>
</AdminSubcategoriesPageContainer>
{openedAddModal && (
<EditCategory
hideImagePicker
setOpenedEditModal={setOpenedAddModal}
type="subcategories"
method="add"
/>
)}
</>
);
};

+ 22
- 1
src/pages/AdminSubcategoriesPage/AdminSubcategoriesPage.styled.js Ver arquivo

@@ -1,5 +1,6 @@
import { Box } from "@mui/material";
import styled from "styled-components";
import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton";
import CategoryCard from "../../components/Cards/CategoryCard/CategoryCard";
import Header from "../../components/MarketPlace/Header/Header";
import SearchField from "../../components/TextFields/SearchField/SearchField";
@@ -7,9 +8,15 @@ import selectedTheme from "../../themes";

export const AdminSubcategoriesPageContainer = styled(Box)`
padding: 60px;
display: flex;
position: relative;
padding-bottom: 100px;
min-height: 100vh;
flex-direction: column;
@media (max-width: 600px) {
padding: 18px;
position: relative;
min-height: (100vh - 72px);
padding-bottom: 54px;
top: 65px;
}
`;
@@ -37,3 +44,17 @@ export const SponsoredCategoryCard = styled(CategoryCard)`
background: ${selectedTheme.colors.backgroundSponsoredColor};
border: 1px solid ${selectedTheme.colors.borderSponsoredColor};
`;
export const NewSubcategoryButton = styled(PrimaryButton)`
position: absolute;
bottom: 18px;
right: 37px;
height: 48px;
width: 224px;
& button {
font-weight: 700;
}
@media (max-width: 600px) {
bottom: 18px;
right: 16px;
}
`

Carregando…
Cancelar
Salvar