Added edit structure for categories

This commit is contained in:
Barunes Padhy 2024-05-26 09:50:35 +03:00
parent 4bb5b6aa8e
commit 4649023dc5
6 changed files with 188 additions and 17 deletions

View File

@ -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()

View File

@ -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'),

View File

@ -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>

View File

@ -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 (

View 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;

View File

@ -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 };