Added edit structure for categories
This commit is contained in:
parent
4bb5b6aa8e
commit
4649023dc5
@ -62,7 +62,6 @@ class ThemeDataListAPIView(generics.ListAPIView):
|
|||||||
class CategoryCreateAPIView(generics.CreateAPIView):
|
class CategoryCreateAPIView(generics.CreateAPIView):
|
||||||
queryset = Category.objects.all()
|
queryset = Category.objects.all()
|
||||||
serializer_class = CategorySerializer
|
serializer_class = CategorySerializer
|
||||||
lookup_field = 'category_id'
|
|
||||||
|
|
||||||
class CategoryUpdateAPIView(generics.RetrieveUpdateAPIView):
|
class CategoryUpdateAPIView(generics.RetrieveUpdateAPIView):
|
||||||
queryset = Category.objects.all()
|
queryset = Category.objects.all()
|
||||||
|
|||||||
@ -41,8 +41,8 @@ urlpatterns = [
|
|||||||
path('data/shared/theme-config/', ThemeDataListAPIView.as_view(), name='theme-data-list-view'),
|
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/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/', CategoryListAPIView.as_view(), name='category-list-view'),
|
||||||
|
path('data/category/create/', CategoryCreateAPIView.as_view(), name='category-create-view'),
|
||||||
path('data/category/<slug:category_id>/', BlogsByCategoryAPIView.as_view(), name='blogs-by-category-view'),
|
path('data/category/<slug:category_id>/', BlogsByCategoryAPIView.as_view(), name='blogs-by-category-view'),
|
||||||
path('data/category/create/<slug:category_id>/', CategoryCreateAPIView.as_view(), name='category-create-view'),
|
|
||||||
path('data/category/update/<slug:category_id>/', CategoryUpdateAPIView.as_view(), name='category-update-view'),
|
path('data/category/update/<slug:category_id>/', CategoryUpdateAPIView.as_view(), name='category-update-view'),
|
||||||
path('data/category/delete/<slug:category_id>/', CategoryDeleteAPIView.as_view(), name='category-delete-view'),
|
path('data/category/delete/<slug:category_id>/', CategoryDeleteAPIView.as_view(), name='category-delete-view'),
|
||||||
path('data/blog/<slug:blog_id>/', BlogRetrieveAPIView.as_view(), name='blog-retrieve-view'),
|
path('data/blog/<slug:blog_id>/', BlogRetrieveAPIView.as_view(), name='blog-retrieve-view'),
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
//import services
|
//import services
|
||||||
import DataService from '../../services/data-service';
|
import EditableDataService from '../../services/editable-data-service';
|
||||||
|
|
||||||
//import views
|
//import views
|
||||||
import CardListViewer from './shared/card-list-viewer';
|
import CardListViewer from './shared/card-list-viewer';
|
||||||
@ -26,14 +26,80 @@ function Blogs(props) {
|
|||||||
const ThemeConfig = props.ThemeConfig;
|
const ThemeConfig = props.ThemeConfig;
|
||||||
|
|
||||||
const [categoryMetadata, setCategoryMetadata] = useState([]);
|
const [categoryMetadata, setCategoryMetadata] = useState([]);
|
||||||
|
const [idsToUpdate, setIdsToUpdate] = useState({});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
DataService.getData('category/category-metadata').then(response =>
|
setCategoryData()
|
||||||
setCategoryMetadata(response.data)
|
|
||||||
);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<Container fluid className={`p-0 mb-2 ${ThemeConfig[GlobalTheme].background}`}>
|
<Container fluid className={`p-0 mb-2 ${ThemeConfig[GlobalTheme].background}`}>
|
||||||
<Row className="justify-content-center align-items-center">
|
<Row className="justify-content-center align-items-center">
|
||||||
@ -42,7 +108,8 @@ function Blogs(props) {
|
|||||||
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{width: "100%", border: "none"}}>
|
<Card className={`my-2 ${ThemeConfig[GlobalTheme].background}`} style={{width: "100%", border: "none"}}>
|
||||||
<CardBody>
|
<CardBody>
|
||||||
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
|
<CardTitle style={{ display: "grid" }} className={`${ThemeConfig[GlobalTheme].textColor} justify-content-center`} tag="h1">
|
||||||
{"Categories"}<Button className='mt-2' color={ThemeConfig[GlobalTheme].buttonColor} outline>Add New</Button>
|
{"Categories"}
|
||||||
|
<Button className='mt-2' color={ThemeConfig[GlobalTheme].buttonColor} outline onClick={() => addNewCategory()}>Add New</Button>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
@ -54,17 +121,21 @@ function Blogs(props) {
|
|||||||
categoryMetadata.map((item, index) => (
|
categoryMetadata.map((item, index) => (
|
||||||
<CardListViewer
|
<CardListViewer
|
||||||
key={item.id}
|
key={item.id}
|
||||||
|
id = {item.id}
|
||||||
totalItems={categoryMetadata.length}
|
totalItems={categoryMetadata.length}
|
||||||
|
addToIdsToUpdate={addToIdsToUpdate}
|
||||||
cardType={"longCard"}
|
cardType={"longCard"}
|
||||||
|
deleteResource={deleteResource}
|
||||||
resourceType={"categories"}
|
resourceType={"categories"}
|
||||||
textColor={ThemeConfig[GlobalTheme].textColor}
|
textColor={ThemeConfig[GlobalTheme].textColor}
|
||||||
bgColor={ThemeConfig[GlobalTheme].background}
|
bgColor={ThemeConfig[GlobalTheme].background}
|
||||||
borderColor={ThemeConfig[GlobalTheme].borderColor}
|
borderColor={ThemeConfig[GlobalTheme].borderColor}
|
||||||
|
buttonColor={ThemeConfig[GlobalTheme].buttonColor}
|
||||||
itemObject={item}
|
itemObject={item}
|
||||||
/>
|
/>
|
||||||
)) : <Spinner />}
|
)) : <Spinner />}
|
||||||
<ButtonGroup className='mt-4'>
|
<ButtonGroup className='mt-4'>
|
||||||
<Button color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
<Button onClick={() => updateInfo()} color={ThemeConfig[GlobalTheme].buttonColor} outline>Save Data</Button>
|
||||||
<Button color={ThemeConfig[GlobalTheme].buttonColor} outline>Publish Data</Button>
|
<Button color={ThemeConfig[GlobalTheme].buttonColor} outline>Publish Data</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import MediaService from '../../../services/media-service'
|
import MediaService from '../../../services/media-service'
|
||||||
import {
|
import {
|
||||||
Spinner,
|
Spinner,
|
||||||
@ -7,17 +7,73 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
CardText,
|
CardText,
|
||||||
CardBody,
|
CardBody,
|
||||||
Input, InputGroup, InputGroupText
|
Input, InputGroup, InputGroupText, FormFeedback, Button
|
||||||
} from 'reactstrap';
|
} from 'reactstrap';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import ModalComponent from './modal-component';
|
||||||
|
|
||||||
function CardListViewer(props) {
|
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
|
const itemObject = props.itemObject
|
||||||
|
|
||||||
if (props.totalItems > 0 && itemObject && Object.keys(itemObject).length !== 0){
|
if (props.totalItems > 0 && itemObject && Object.keys(itemObject).length !== 0){
|
||||||
if (props.resourceType === 'categories')
|
if (props.resourceType === 'categories')
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<ModalComponent modalText={modalText} modalTitle={modalTitle} modal={modal} toggle={toggle} confirmAction={deleteResource}/>
|
||||||
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
|
<Card color={props.borderColor} outline className={`my-2 ${props.bgColor}`} style={{"width": props.cardType === "smallCard" ? "18rem": "100%"}}>
|
||||||
{itemObject.coverImage !== "" ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""}
|
{itemObject.coverImage !== "" ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""}
|
||||||
<CardBody>
|
<CardBody>
|
||||||
@ -26,7 +82,10 @@ function CardListViewer(props) {
|
|||||||
<InputGroupText>
|
<InputGroupText>
|
||||||
Name
|
Name
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<Input defaultValue={itemObject.name} />
|
<Input defaultValue={itemObject.name} invalid={nameFieldInvalid} innerRef={nameField} onChange={() => handleInputUpdate(nameField.current.value, 'nameField')}/>
|
||||||
|
{nameFieldInvalid ? <FormFeedback tooltip className="mt-1">
|
||||||
|
This field cannot be empty
|
||||||
|
</FormFeedback>:''}
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardText className={`${props.textColor}`}>
|
<CardText className={`${props.textColor}`}>
|
||||||
@ -34,7 +93,10 @@ function CardListViewer(props) {
|
|||||||
<InputGroupText>
|
<InputGroupText>
|
||||||
Description
|
Description
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<Input defaultValue={itemObject.description} />
|
<Input defaultValue={itemObject.description} invalid={descriptionFieldInvalid} innerRef={descriptionField} onChange={() => handleInputUpdate(descriptionField.current.value, 'descriptionField')}/>
|
||||||
|
{descriptionFieldInvalid ? <FormFeedback tooltip className="mt-1">
|
||||||
|
This field cannot be empty
|
||||||
|
</FormFeedback>:''}
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</CardText>
|
</CardText>
|
||||||
<CardText>
|
<CardText>
|
||||||
@ -43,7 +105,10 @@ function CardListViewer(props) {
|
|||||||
<InputGroupText>
|
<InputGroupText>
|
||||||
Tagline
|
Tagline
|
||||||
</InputGroupText>
|
</InputGroupText>
|
||||||
<Input defaultValue={itemObject.tagLine} />
|
<Input defaultValue={itemObject.tagLine} invalid={taglineFieldInvalid} innerRef={taglineField} onChange={() => handleInputUpdate(taglineField.current.value, 'taglineField')} />
|
||||||
|
{taglineFieldInvalid ? <FormFeedback tooltip className="mt-1">
|
||||||
|
This field cannot be empty
|
||||||
|
</FormFeedback>:''}
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</small>
|
</small>
|
||||||
</CardText>
|
</CardText>
|
||||||
@ -51,9 +116,11 @@ function CardListViewer(props) {
|
|||||||
<Link className={`${props.textColor}`} to={`/${props.resourceType}/${itemObject.id}`}>
|
<Link className={`${props.textColor}`} to={`/${props.resourceType}/${itemObject.id}`}>
|
||||||
Open this resource
|
Open this resource
|
||||||
</Link>
|
</Link>
|
||||||
|
<Button color={props.buttonColor} onClick={() => showModal()} outline className="m-1">Delete</Button>
|
||||||
</CardText>
|
</CardText>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
return (
|
return (
|
||||||
|
|||||||
26
frontend/src/components/editable/shared/modal-component.jsx
Normal file
26
frontend/src/components/editable/shared/modal-component.jsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
|
||||||
|
|
||||||
|
function ModalComponent(props) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Modal isOpen={props.modal} toggle={props.toggle}>
|
||||||
|
<ModalHeader toggle={props.toggle}>{props.modalTitle}</ModalHeader>
|
||||||
|
<ModalBody>
|
||||||
|
{props.modalText}
|
||||||
|
</ModalBody>
|
||||||
|
<ModalFooter>
|
||||||
|
<Button color="primary" onClick={props.confirmAction}>
|
||||||
|
Yes
|
||||||
|
</Button>{' '}
|
||||||
|
<Button color="secondary" onClick={props.toggle}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</ModalFooter>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ModalComponent;
|
||||||
@ -18,4 +18,12 @@ const updateData = (endPoint, data) => {
|
|||||||
return axios.patch(endPoint, data);
|
return axios.patch(endPoint, data);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default { getData, updateData };
|
const createData = (endPoint, data) => {
|
||||||
|
return axios.post(endPoint, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteData = (endPoint) => {
|
||||||
|
return axios.delete(endPoint);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default { getData, updateData, createData, deleteData };
|
||||||
Loading…
Reference in New Issue
Block a user