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):
queryset = Category.objects.all()
serializer_class = CategorySerializer
lookup_field = 'category_id'
class CategoryUpdateAPIView(generics.RetrieveUpdateAPIView):
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/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/<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/delete/<slug:category_id>/', CategoryDeleteAPIView.as_view(), name='category-delete-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 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 (
<Container fluid className={`p-0 mb-2 ${ThemeConfig[GlobalTheme].background}`}>
<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"}}>
<CardBody>
<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>
</CardBody>
</Card>
@ -54,17 +121,21 @@ function Blogs(props) {
categoryMetadata.map((item, index) => (
<CardListViewer
key={item.id}
id = {item.id}
totalItems={categoryMetadata.length}
addToIdsToUpdate={addToIdsToUpdate}
cardType={"longCard"}
deleteResource={deleteResource}
resourceType={"categories"}
textColor={ThemeConfig[GlobalTheme].textColor}
bgColor={ThemeConfig[GlobalTheme].background}
borderColor={ThemeConfig[GlobalTheme].borderColor}
buttonColor={ThemeConfig[GlobalTheme].buttonColor}
itemObject={item}
/>
)) : <Spinner />}
<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>
</ButtonGroup>
</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 {
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 (
<>
<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%"}}>
{itemObject.coverImage !== "" ? <CardImg src={MediaService.getMedia(itemObject.coverImage)} style={{ "height": "180px", "objectFit": "cover" }} top width="100%" /> : ""}
<CardBody>
@ -26,7 +82,10 @@ function CardListViewer(props) {
<InputGroupText>
Name
</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>
</CardTitle>
<CardText className={`${props.textColor}`}>
@ -34,7 +93,10 @@ function CardListViewer(props) {
<InputGroupText>
Description
</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>
</CardText>
<CardText>
@ -43,7 +105,10 @@ function CardListViewer(props) {
<InputGroupText>
Tagline
</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>
</small>
</CardText>
@ -51,9 +116,11 @@ function CardListViewer(props) {
<Link className={`${props.textColor}`} to={`/${props.resourceType}/${itemObject.id}`}>
Open this resource
</Link>
<Button color={props.buttonColor} onClick={() => showModal()} outline className="m-1">Delete</Button>
</CardText>
</CardBody>
</Card>
</>
)
else
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);
};
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 };