Sfoglia il codice sorgente

Finished feature 657

feature/657
djordjemitrovic00 3 anni fa
parent
commit
370b93e8a6

+ 36
- 3
src/components/Cards/CategoryCard/CategoryCard.js Vedi File

import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useState } from "react"; import { useState } from "react";
import DeleteCategory from "../../Modals/DeleteCategory/DeleteCategory"; 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 CategoryCard = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [openedDeleteModal, setOpenedDeleteModal] = useState(false); 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 ( return (
<> <>
<CategoryCardContainer className={props.className}> <CategoryCardContainer className={props.className}>
<CategoryCardLeftContainer> <CategoryCardLeftContainer>
<CategoryCardName categoryName={props?.category?.name} />
<CategoryCardName
categoryName={props?.category?.name}
onClick={navigateToCategory}
/>
<CategoryCardDetailsContainer> <CategoryCardDetailsContainer>
<CategoryDetail <CategoryDetail
label={t("admin.categories.noOfOffers")} label={t("admin.categories.noOfOffers")}
</CategoryCardLeftContainer> </CategoryCardLeftContainer>
<CategoryCardRightContainer> <CategoryCardRightContainer>
<CategoryRemoveButton onClick={() => setOpenedDeleteModal(true)} /> <CategoryRemoveButton onClick={() => setOpenedDeleteModal(true)} />
<CategoryEditButton />
{!props.hideCheckButton && <CategoryCheckButton />}
<CategoryEditButton onClick={() => setOpenedEditModal(true)} />
{!props.hideCheckButton && (
<CategoryCheckButton onClick={navigateToCategory} />
)}
</CategoryCardRightContainer> </CategoryCardRightContainer>
</CategoryCardContainer> </CategoryCardContainer>
{openedDeleteModal && ( {openedDeleteModal && (
<DeleteCategory <DeleteCategory
setOpenedDeleteModal={setOpenedDeleteModal} setOpenedDeleteModal={setOpenedDeleteModal}
subcategory={props.subcategory}
category={props.category} category={props.category}
type={props.type}
/>
)}
{openedEditModal && (
<EditCategory
hideImagePicker
setOpenedEditModal={setOpenedEditModal}
category={props.category}
subcategory={props.subcategory}
type={props.type}
method="edit"
/> />
)} )}
</> </>
secondLabel: PropTypes.string, secondLabel: PropTypes.string,
hideSecondLabel: PropTypes.bool, hideSecondLabel: PropTypes.bool,
className: PropTypes.string, className: PropTypes.string,
subcategory: PropTypes.bool,
type: PropTypes.string,
}; };


export default CategoryCard; export default CategoryCard;

+ 2
- 1
src/components/Cards/CategoryCard/CategoryCardName/CategoryCardName.js Vedi File



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


export default CategoryCardName; export default CategoryCardName;

+ 3
- 1
src/components/Cards/CategoryCard/CategoryCheckButton/CategoryCheckButton.js Vedi File

import { CheckButton } from "./CategoryCheckButton.styled"; import { CheckButton } from "./CategoryCheckButton.styled";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";


const CategoryCheckButton = () => {
const CategoryCheckButton = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<CheckButton <CheckButton
onClick={props.onClick}
variant={"outlined"} variant={"outlined"}
buttoncolor={selectedTheme.colors.primaryPurple} buttoncolor={selectedTheme.colors.primaryPurple}
textcolor={selectedTheme.colors.primaryPurple} textcolor={selectedTheme.colors.primaryPurple}


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


export default CategoryCheckButton; export default CategoryCheckButton;

+ 3
- 2
src/components/Cards/CategoryCard/CategoryEditButton/CategoryEditButton.js Vedi File

EditIcon, EditIcon,
} from "./CategoryEditButton.styled"; } from "./CategoryEditButton.styled";


const CategoryEditButton = () => {
const CategoryEditButton = (props) => {
return ( return (
<CategoryEditButtonContainer>
<CategoryEditButtonContainer onClick={props.onClick}>
<EditIcon /> <EditIcon />
</CategoryEditButtonContainer> </CategoryEditButtonContainer>
); );


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


export default CategoryEditButton; export default CategoryEditButton;

+ 7
- 3
src/components/Modals/DeleteCategory/DeleteCategory.js Vedi File

import LabeledCard from "../../Cards/LabeledCard/LabeledCard"; import LabeledCard from "../../Cards/LabeledCard/LabeledCard";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton"; import { PrimaryButton } from "../../Buttons/PrimaryButton/PrimaryButton";
import { useMemo } from "react";


const DeleteCategory = (props) => { const DeleteCategory = (props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const handleCancel = () => { const handleCancel = () => {
props.setOpenedDeleteModal(false); props.setOpenedDeleteModal(false);
}; };
const reassuranceText = useMemo(() => {
if (props.subcategory) return t("admin.subcategories.reassuranceDelete");
return t("admin.categories.reassuranceDelete");
}, [props]);
return ( return (
<> <>
<BackdropComponent <BackdropComponent
<LabeledCard icon={<DeleteIcon />}> <LabeledCard icon={<DeleteIcon />}>
<CategoryName>{props.category.name}</CategoryName> <CategoryName>{props.category.name}</CategoryName>
</LabeledCard> </LabeledCard>
<ReassuranceText>
{t("admin.categories.reassuranceDelete")}
</ReassuranceText>
<ReassuranceText>{reassuranceText}</ReassuranceText>
<ButtonsContainer> <ButtonsContainer>
<PrimaryButton <PrimaryButton
onClick={handleCancel} onClick={handleCancel}
DeleteCategory.propTypes = { DeleteCategory.propTypes = {
setOpenedDeleteModal: PropTypes.func, setOpenedDeleteModal: PropTypes.func,
category: PropTypes.object, category: PropTypes.object,
subcategory: PropTypes.bool,
}; };


export default DeleteCategory; export default DeleteCategory;

+ 118
- 0
src/components/Modals/EditCategory/EditCategory.js Vedi File

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 Vedi File

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 Vedi File

"Da li ste sigurni da želite da obrišete odabranu kategoriju?", "Da li ste sigurni da želite da obrišete odabranu kategoriju?",
cancel: "Otkaži", cancel: "Otkaži",
delete: "Obriš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: { subcategories: {
noOfOffers: "Broj objava: ", noOfOffers: "Broj objava: ",
headerTitle: "Kategorija", headerTitle: "Kategorija",
subcategoriesHeaderTitle: "Podkategorije", subcategoriesHeaderTitle: "Podkategorije",
placeholder: "Pretražite 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 Vedi File

AdminCategoriesHeader, AdminCategoriesHeader,
AdminCategoriesPageContainer, AdminCategoriesPageContainer,
AdminCategoriesSearchField, AdminCategoriesSearchField,
NewCategoryButton,
} from "./AdminCategoriesPage.styled"; } from "./AdminCategoriesPage.styled";
import { selectManualSearchString } from "../../store/selectors/filtersSelectors"; import { selectManualSearchString } from "../../store/selectors/filtersSelectors";
import { useMemo } from "react"; import { useMemo } from "react";
import { setManualSearchString } from "../../store/actions/filters/filtersActions"; 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 AdminCategoriesPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useDispatch(); const dispatch = useDispatch();
const categories = useSelector(selectCategories); const categories = useSelector(selectCategories);
const manualSearchString = useSelector(selectManualSearchString); const manualSearchString = useSelector(selectManualSearchString);
const [openedAddModal, setOpenedAddModal] = useState(false);
useEffect(() => { useEffect(() => {
dispatch(fetchCategories()); dispatch(fetchCategories());
}, []); }, []);
return []; return [];
}, [categories, manualSearchString]); }, [categories, manualSearchString]);
return ( return (
<>
<AdminCategoriesPageContainer> <AdminCategoriesPageContainer>
<AdminCategoriesSearchField <AdminCategoriesSearchField
isAdmin isAdmin
<CategoryCard <CategoryCard
key={category._id} key={category._id}
category={category} category={category}
type="categories"
secondLabel={t("admin.categories.noOfSubcategories")} 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> </AdminCategoriesPageContainer>
{openedAddModal && (
<EditCategory
setOpenedEditModal={setOpenedAddModal}
type={"categories"}
method="add"
/>
)}
</>
); );
}; };



+ 20
- 1
src/pages/AdminCategoriesPage/AdminCategoriesPage.styled.js Vedi File

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


export const AdminCategoriesPageContainer = styled(Box)` export const AdminCategoriesPageContainer = styled(Box)`
padding: 60px; padding: 60px;
min-height: 100vh;
position: relative;
padding-bottom: 100px;
@media (max-width: 600px) { @media (max-width: 600px) {
padding: 18px; padding: 18px;
position: relative;
min-height: calc(100vh - 72px);
top: 65px; top: 65px;
padding-bottom: 54px;
} }
`; `;
export const AdminCategoriesHeader = styled(Header)` export const AdminCategoriesHeader = styled(Header)`
display: none; 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 Vedi File

AdminSubcategoriesHeader, AdminSubcategoriesHeader,
AdminSubcategoriesPageContainer, AdminSubcategoriesPageContainer,
AdminSubcategoriesSearchField, AdminSubcategoriesSearchField,
NewSubcategoryButton,
SponsoredCategoryCard, SponsoredCategoryCard,
} from "./AdminSubcategoriesPage.styled"; } from "./AdminSubcategoriesPage.styled";
import { selectManualSearchString } from "../../store/selectors/filtersSelectors"; import { selectManualSearchString } from "../../store/selectors/filtersSelectors";
import { setManualSearchString } from "../../store/actions/filters/filtersActions"; import { setManualSearchString } from "../../store/actions/filters/filtersActions";
import { useRouteMatch } from "react-router-dom"; import { useRouteMatch } from "react-router-dom";
import CategoryCard from "../../components/Cards/CategoryCard/CategoryCard"; 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 AdminSubcategoriesPage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const categories = useSelector(selectCategories); const categories = useSelector(selectCategories);
const routeMatch = useRouteMatch(); const routeMatch = useRouteMatch();
const manualSearchString = useSelector(selectManualSearchString); const manualSearchString = useSelector(selectManualSearchString);
const [openedAddModal, setOpenedAddModal] = useState(false);

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

+ 22
- 1
src/pages/AdminSubcategoriesPage/AdminSubcategoriesPage.styled.js Vedi File

import { Box } from "@mui/material"; import { Box } from "@mui/material";
import styled from "styled-components"; import styled from "styled-components";
import { PrimaryButton } from "../../components/Buttons/PrimaryButton/PrimaryButton";
import CategoryCard from "../../components/Cards/CategoryCard/CategoryCard"; import CategoryCard from "../../components/Cards/CategoryCard/CategoryCard";
import Header from "../../components/MarketPlace/Header/Header"; import Header from "../../components/MarketPlace/Header/Header";
import SearchField from "../../components/TextFields/SearchField/SearchField"; import SearchField from "../../components/TextFields/SearchField/SearchField";


export const AdminSubcategoriesPageContainer = styled(Box)` export const AdminSubcategoriesPageContainer = styled(Box)`
padding: 60px; padding: 60px;
display: flex;
position: relative;
padding-bottom: 100px;
min-height: 100vh;
flex-direction: column;
@media (max-width: 600px) { @media (max-width: 600px) {
padding: 18px; padding: 18px;
position: relative;
min-height: (100vh - 72px);
padding-bottom: 54px;
top: 65px; top: 65px;
} }
`; `;
background: ${selectedTheme.colors.backgroundSponsoredColor}; background: ${selectedTheme.colors.backgroundSponsoredColor};
border: 1px solid ${selectedTheme.colors.borderSponsoredColor}; 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;
}
`

Loading…
Annulla
Salva