diff --git a/backend/apimanager/views.py b/backend/apimanager/views.py index 275955e..015b131 100644 --- a/backend/apimanager/views.py +++ b/backend/apimanager/views.py @@ -62,7 +62,6 @@ class ThemeDataListAPIView(generics.ListAPIView): class CategoryCreateAPIView(generics.CreateAPIView): queryset = Category.objects.all() serializer_class = CategorySerializer - lookup_field = 'category_id' class CategoryUpdateAPIView(generics.RetrieveUpdateAPIView): queryset = Category.objects.all() diff --git a/backend/backend/urls.py b/backend/backend/urls.py index b39bd42..624d28a 100644 --- a/backend/backend/urls.py +++ b/backend/backend/urls.py @@ -41,8 +41,8 @@ urlpatterns = [ path('data/shared/theme-config/', ThemeDataListAPIView.as_view(), name='theme-data-list-view'), path('data/shared/update/theme-config/', ThemeDataUpdateAPIView.as_view(), name='theme-data-update-view'), path('data/category/', CategoryListAPIView.as_view(), name='category-list-view'), + path('data/category/create/', CategoryCreateAPIView.as_view(), name='category-create-view'), path('data/category//', BlogsByCategoryAPIView.as_view(), name='blogs-by-category-view'), - path('data/category/create//', CategoryCreateAPIView.as_view(), name='category-create-view'), path('data/category/update//', CategoryUpdateAPIView.as_view(), name='category-update-view'), path('data/category/delete//', CategoryDeleteAPIView.as_view(), name='category-delete-view'), path('data/blog//', BlogRetrieveAPIView.as_view(), name='blog-retrieve-view'), diff --git a/frontend/src/components/editable/category-list.jsx b/frontend/src/components/editable/category-list.jsx index 9596e6f..7b153c4 100755 --- a/frontend/src/components/editable/category-list.jsx +++ b/frontend/src/components/editable/category-list.jsx @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; //import services -import DataService from '../../services/data-service'; +import EditableDataService from '../../services/editable-data-service'; //import views import CardListViewer from './shared/card-list-viewer'; @@ -26,14 +26,80 @@ function Blogs(props) { const ThemeConfig = props.ThemeConfig; const [categoryMetadata, setCategoryMetadata] = useState([]); + const [idsToUpdate, setIdsToUpdate] = useState({}); useEffect(() => { - DataService.getData('category/category-metadata').then(response => - setCategoryMetadata(response.data) - ); + setCategoryData() }, []); - if (GlobalTheme && ThemeConfig) { + const setCategoryData = () => { + EditableDataService.getData('/data/category/').then(response => { + let responseData = response.data + let localCategoryMetadata = [] + for (let eachResponse of responseData){ + localCategoryMetadata.push({ + "id": eachResponse["category_id"], + "name": eachResponse["name"], + "featuredBlog": eachResponse["featured_id"], + "description": eachResponse["description"], + "tagLine": eachResponse["tagline"], + "coverImage": eachResponse["cover_image"] + }) + } + setCategoryMetadata(localCategoryMetadata) + } + ); + } + + const addToIdsToUpdate = (resourceObject) => { + let localIdsToUpdate = {...idsToUpdate} + localIdsToUpdate[resourceObject.id]={ + "name": resourceObject.name, + "featured_blog": resourceObject.featuredBlog, + "description": resourceObject.description, + "tagline": resourceObject.tagLine, + "cover_image": resourceObject.coverImage + } + setIdsToUpdate(localIdsToUpdate) + } + + const deleteResource = (id) => { + EditableDataService.deleteData(`/data/category/delete/${id}/`).then(response => { + props.notificationToggler('Category delete successfully') + setCategoryData() + }).catch(error => { + props.notificationToggler('Failed to delete category', 'danger'); + }); + } + + const addNewCategory = () => { + EditableDataService.createData('/data/category/create/', { + "name": "Enter a blog name", + "featured_blog": "", + "description": "Enter description", + "tagline": "Enter category tagline", + "cover_image": "" + }).then(response => { + props.notificationToggler('Category created successfully') + setCategoryData() + } + ).catch(error => { + props.notificationToggler('Failed to add category', 'danger'); + }); + } + + const updateInfo = () => { + for (let id of Object.keys(idsToUpdate)) { + EditableDataService.updateData(`/data/category/update/${id}/`, idsToUpdate[id]).then(response=>{ + props.notificationToggler('Category data updated successfully') + }).catch(error => { + props.notificationToggler('Failed to update category data', 'danger'); + }); + } + setCategoryData() + } + + if (GlobalTheme && ThemeConfig && categoryMetadata.length > 0) { return ( @@ -42,7 +108,8 @@ function Blogs(props) { - {"Categories"} + {"Categories"} + @@ -54,17 +121,21 @@ function Blogs(props) { categoryMetadata.map((item, index) => ( )) : } - + diff --git a/frontend/src/components/editable/shared/card-list-viewer.jsx b/frontend/src/components/editable/shared/card-list-viewer.jsx index 145b22d..ee918d6 100755 --- a/frontend/src/components/editable/shared/card-list-viewer.jsx +++ b/frontend/src/components/editable/shared/card-list-viewer.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import MediaService from '../../../services/media-service' import { Spinner, @@ -7,17 +7,73 @@ import { CardTitle, CardText, CardBody, - Input, InputGroup, InputGroupText + Input, InputGroup, InputGroupText, FormFeedback, Button } from 'reactstrap'; import { Link } from 'react-router-dom'; +import ModalComponent from './modal-component'; function CardListViewer(props) { + + const [nameFieldInvalid, setNameFieldInvalid] = useState(false) + const [descriptionFieldInvalid, setDescriptionFieldInvalid] = useState(false) + const [taglineFieldInvalid, setTaglineFieldInvalid] = useState(false) + const [modal, setModal] = useState(false); + const [modalText, setModalText] = useState(false); + const [modalTitle, setModalTitle] = useState(false); + + const toggle = () => setModal(!modal); + + const nameField = useRef(null) + const descriptionField = useRef(null) + const taglineField = useRef(null) + + const handleInputUpdate = (elementValue, fieldType) => { + if (fieldType === 'nameField'){ + if (elementValue === '') + setNameFieldInvalid(true) + else + setNameFieldInvalid(false) + } + if (fieldType === 'descriptionField'){ + if (elementValue === '') + setDescriptionFieldInvalid(true) + else + setDescriptionFieldInvalid(false) + } + if (fieldType === 'taglineField'){ + if (elementValue === '') + setTaglineFieldInvalid(true) + else + setTaglineFieldInvalid(false) + } + props.addToIdsToUpdate({ + "id": props.id, + "name": nameField.current.value, + "featuredBlog": "", + "description": descriptionField.current.value, + "tagLine": taglineField.current.value, + "coverImage": "" + }) + } + + const showModal = () => { + setModalTitle('Confirm') + setModalText('Are you sure that you wish to delete this category?') + toggle() + } + + const deleteResource = () => { + props.deleteResource(props.id) + toggle() + } const itemObject = props.itemObject if (props.totalItems > 0 && itemObject && Object.keys(itemObject).length !== 0){ if (props.resourceType === 'categories') return ( + <> + {itemObject.coverImage !== "" ? : ""} @@ -26,7 +82,10 @@ function CardListViewer(props) { Name - + handleInputUpdate(nameField.current.value, 'nameField')}/> + {nameFieldInvalid ? + This field cannot be empty + :''} @@ -34,7 +93,10 @@ function CardListViewer(props) { Description - + handleInputUpdate(descriptionField.current.value, 'descriptionField')}/> + {descriptionFieldInvalid ? + This field cannot be empty + :''} @@ -43,7 +105,10 @@ function CardListViewer(props) { Tagline - + handleInputUpdate(taglineField.current.value, 'taglineField')} /> + {taglineFieldInvalid ? + This field cannot be empty + :''} @@ -51,9 +116,11 @@ function CardListViewer(props) { Open this resource + + ) else return ( diff --git a/frontend/src/components/editable/shared/modal-component.jsx b/frontend/src/components/editable/shared/modal-component.jsx new file mode 100644 index 0000000..dd818f5 --- /dev/null +++ b/frontend/src/components/editable/shared/modal-component.jsx @@ -0,0 +1,26 @@ +import React, { useState } from 'react'; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; + +function ModalComponent(props) { + + return ( +
+ + {props.modalTitle} + + {props.modalText} + + + {' '} + + + +
+ ); +} + +export default ModalComponent; \ No newline at end of file diff --git a/frontend/src/services/editable-data-service.jsx b/frontend/src/services/editable-data-service.jsx index 96f258e..2076aff 100644 --- a/frontend/src/services/editable-data-service.jsx +++ b/frontend/src/services/editable-data-service.jsx @@ -18,4 +18,12 @@ const updateData = (endPoint, data) => { return axios.patch(endPoint, data); }; -export default { getData, updateData }; \ No newline at end of file +const createData = (endPoint, data) => { + return axios.post(endPoint, data); +}; + +const deleteData = (endPoint) => { + return axios.delete(endPoint); +}; + +export default { getData, updateData, createData, deleteData }; \ No newline at end of file