Skip to content

Commit 6cd5085

Browse files
committed
Added Product Management
1 parent ee8feed commit 6cd5085

File tree

10 files changed

+308
-3
lines changed

10 files changed

+308
-3
lines changed
Binary file not shown.

Backend/EcommerceInventory/ProductServices/controller/CategoryController.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99

1010
class CategorySerializer(serializers.ModelSerializer):
1111
children = serializers.SerializerMethodField()
12+
domain_user_id=serializers.SerializerMethodField()
13+
added_by_user_id=serializers.SerializerMethodField()
14+
parent_id=serializers.SerializerMethodField()
15+
1216
class Meta:
1317
model = Categories
1418
fields = '__all__'
@@ -17,6 +21,14 @@ def get_children(self, obj):
1721
children = Categories.objects.filter(parent_id=obj.id)
1822
return CategorySerializer(children, many=True).data
1923

24+
def get_domain_user_id(self,obj):
25+
return "#"+str(obj.domain_user_id.id)+" "+obj.domain_user_id.username
26+
27+
def get_added_by_user_id(self,obj):
28+
return "#"+str(obj.added_by_user_id.id)+" "+obj.added_by_user_id.username
29+
30+
def get_parent_id(self,obj):
31+
return "#"+str(obj.parent_id.id)+" "+obj.parent_id.name if obj.parent_id else None
2032
class CategoryListView(generics.ListAPIView):
2133
serializer_class = CategorySerializer
2234
authentication_classes = [JWTAuthentication]
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from EcommerceInventory.Helpers import CustomPageNumberPagination, renderResponse
2+
from ProductServices.models import Products
3+
from rest_framework import generics
4+
from rest_framework import serializers
5+
from rest_framework.permissions import IsAuthenticated
6+
from rest_framework_simplejwt.authentication import JWTAuthentication
7+
from django.db.models import Q
8+
from django.db import models
9+
10+
class ProductSerializer(serializers.ModelSerializer):
11+
category_id=serializers.SerializerMethodField()
12+
domain_user_id=serializers.SerializerMethodField()
13+
added_by_user_id=serializers.SerializerMethodField()
14+
class Meta:
15+
model = Products
16+
fields = '__all__'
17+
18+
def get_category_id(self,obj):
19+
return "#"+str(obj.category_id.id)+" "+obj.category_id.name
20+
21+
def get_domain_user_id(self,obj):
22+
return "#"+str(obj.domain_user_id.id)+" "+obj.domain_user_id.username
23+
24+
def get_added_by_user_id(self,obj):
25+
return "#"+str(obj.added_by_user_id.id)+" "+obj.added_by_user_id.username
26+
27+
28+
class ProductListView(generics.ListAPIView):
29+
serializer_class = ProductSerializer
30+
authentication_classes = [JWTAuthentication]
31+
permission_classes = [IsAuthenticated]
32+
pagination_class = CustomPageNumberPagination
33+
34+
def get_queryset(self):
35+
queryset=Products.objects.filter(domain_user_id=self.request.user.domain_user_id.id)
36+
search_query=self.request.query_params.get('search',None)
37+
38+
if search_query:
39+
search_conditions=Q()
40+
41+
for field in Products._meta.get_fields():
42+
if isinstance(field,(models.CharField,models.TextField)):
43+
search_conditions|=Q(**{f"{field.name}__icontains":search_query})
44+
queryset=queryset.filter(search_conditions)
45+
46+
ordering=self.request.query_params.get('ordering',None)
47+
48+
if ordering:
49+
queryset=queryset.order_by(ordering)
50+
51+
return queryset
52+
53+
def list(self,request,*args,**kwargs):
54+
queryset=self.filter_queryset(self.get_queryset())
55+
56+
page=self.paginate_queryset(queryset)
57+
58+
if page is not None:
59+
serializer=self.get_serializer(page,many=True)
60+
data=serializer.data
61+
total_pages=self.paginator.page.paginator.num_pages
62+
current_page=self.paginator.page.number
63+
page_size=self.paginator.page.paginator.per_page
64+
total_items=self.paginator.page.paginator.count
65+
else:
66+
serializer=self.get_serializer(queryset,many=True)
67+
data=serializer.data
68+
total_pages=1
69+
current_page=1
70+
page_size=len(data)
71+
total_items=len(data)
72+
73+
return renderResponse(data={'data':data,'totalPages':total_pages,'currentPage':current_page,'pageSize':page_size,'totalItems':total_items},message='Products Retrieved Successfully',status=200)
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from .controller.CategoryController import CategoryListView
2+
from .controller.ProductController import ProductListView
23
from django.urls import path
34

45
urlpatterns = [
5-
path('categories/',CategoryListView.as_view(),name='category_list')
6+
path('categories/',CategoryListView.as_view(),name='category_list'),
7+
path('',ProductListView.as_view(),name='product_list')
68
]

Frontend/ecommerce_inventory/src/App.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import DynamicForm from './pages/DynamicForm';
1414
import 'react-toastify/dist/ReactToastify.css';
1515
import './style/style.css';
1616
import ManageCategories from './pages/category/ManageCategories';
17+
import ManageProducts from './pages/products/ManageProducts';
1718

1819
function App() {
1920
const {status,error,items}=useSelector(state=>state.sidebardata);
@@ -35,7 +36,8 @@ function App() {
3536
{path:"/",element:<ProtectedRoute element={<Home/>}/>},
3637
{path:"/home",element:<ProtectedRoute element={<Home/>}/>},
3738
{path:"/form/:formName/:id?",element:<ProtectedRoute element={<DynamicForm/>}/>},
38-
{path:"/manage/category",element:<ProtectedRoute element={<ManageCategories/>}/>}
39+
{path:"/manage/category",element:<ProtectedRoute element={<ManageCategories/>}/>},
40+
{path:"/manage/product",element:<ProtectedRoute element={<ManageProducts/>}/>}
3941
]},
4042
]
4143
)
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
import { useState,useEffect } from "react";
2+
import useApi from "../../hooks/APIHandler";
3+
import { useNavigate } from "react-router-dom";
4+
import { Box, Breadcrumbs, Button, Dialog, DialogContent, Divider, IconButton, LinearProgress, Table, TextField, Typography } from "@mui/material";
5+
import { DataGrid, GridToolbar } from "@mui/x-data-grid";
6+
import { isValidUrl } from "../../utils/Helper";
7+
import Add from '@mui/icons-material/Add';
8+
import Delete from '@mui/icons-material/Delete';
9+
import Edit from '@mui/icons-material/Edit';
10+
import RenderImage from "../../components/RenderImage";
11+
import { Circle, Dashboard } from "@mui/icons-material";
12+
import { set } from "react-hook-form";
13+
import React from "react";
14+
import ViewCompactIcon from '@mui/icons-material/ViewCompact';
15+
16+
const ManageProducts = () => {
17+
const [data,setData]=useState([]);
18+
const [columns,setColumns]=useState([]);
19+
const [paginationModel,setPaginationModel]=useState({
20+
page:0,
21+
pageSize:5
22+
})
23+
const [totalItems,setTotalItems]=useState(0);
24+
const [searchQuery,setSearchQuery]=useState("");
25+
const [debounceSearch,setDebounceSearch]=useState("");
26+
const [ordering,setOrdering]=useState([{field:'id',sort:'desc'}]);
27+
const {error,loading,callApi}=useApi();
28+
const [jsonData,setJsonData]=useState([]);
29+
const [open,setOpen]=useState(false);
30+
const [htmldata,setHtmldata]=useState('');
31+
const [openHtml,setOpenHtml]=useState(false);
32+
const [modalTitle,setModalTitle]=useState('');
33+
const navigate=useNavigate();
34+
35+
useEffect(()=>{
36+
const timer=setTimeout(()=>{
37+
setDebounceSearch(searchQuery);
38+
},1000)
39+
40+
return ()=>{
41+
clearTimeout(timer);
42+
}
43+
},[searchQuery])
44+
45+
const handleClose=()=>{
46+
setOpen(false);
47+
}
48+
49+
const handleClose2=()=>{
50+
setOpenHtml(false);
51+
}
52+
53+
const getProducts=async()=>{
54+
let order='-id';
55+
if(ordering.length>0){
56+
order=ordering[0].sort==='asc'?ordering[0].field:'-'+ordering[0].field
57+
}
58+
const result=await callApi({url:'products/',method:'GET',params:{
59+
page:paginationModel.page+1,
60+
pageSize:paginationModel.pageSize,
61+
search:debounceSearch,
62+
ordering:order
63+
}})
64+
if(result){
65+
setData(result.data.data.data);
66+
setTotalItems(result.data.data.totalItems);
67+
generateColumns(result.data.data.data);
68+
}
69+
}
70+
71+
const onDeleteClick=(params)=>{
72+
console.log(params);
73+
}
74+
const onEditClick=(params)=>{
75+
console.log(params);
76+
navigate(`/form/product/${params.row.id}`)
77+
}
78+
const onAddClick=(params)=>{
79+
console.log(params);
80+
navigate('/form/product')
81+
}
82+
83+
const showHTMLData=(row)=>{
84+
setHtmldata(row);
85+
setOpenHtml(true);
86+
}
87+
const showJSONData=(item,title)=>{
88+
setModalTitle(title)
89+
setJsonData(item)
90+
setOpen(true);
91+
}
92+
93+
const generateColumns=(data)=>{
94+
if(data.length>0){
95+
let columns=[{field:'action',headerName:'Action',width:180,sortable:false,renderCell:(params)=>{
96+
return <>
97+
<IconButton onClick={()=>onAddClick(params)}>
98+
<Add color="light" />
99+
</IconButton>
100+
<IconButton onClick={()=>onEditClick(params)}>
101+
<Edit color="primary" />
102+
</IconButton>
103+
<IconButton onClick={()=>onDeleteClick(params)}>
104+
<Delete color="secondary" />
105+
</IconButton>
106+
</>
107+
}}];
108+
for(const key in data[0]){
109+
if(key==='image'){
110+
columns.push({field:key,headerName:key.charAt(0).toUpperCase()+key.slice(1).replaceAll("_"," "),width:150,sortable:false,renderCell:(params)=>{
111+
return <RenderImage data={params.row.image} name={params.row.name}/>
112+
}})
113+
}
114+
else{
115+
columns.push({field:key,headerName:key.charAt(0).toUpperCase()+key.slice(1).replaceAll("_"," "),width:200})
116+
}
117+
}
118+
columns.push({field:'questions',headerName:'Questions',width:150,sortable:false,renderCell:(params)=>{
119+
return <Button startIcon={<ViewCompactIcon/>} variant="contained">View</Button>
120+
}})
121+
columns.push({field:'reviews',headerName:'Reviews',width:150,sortable:false,renderCell:(params)=>{
122+
return <Button startIcon={<ViewCompactIcon/>} variant="contained">View</Button>
123+
}})
124+
columns=columns.map((column)=>{
125+
if(column.field==='specifications' || column.field==='highlights' || column.field==='seo_keywords' || column.field==='addition_details'){
126+
return {field:column.field,headerName:column.field.charAt(0).toUpperCase()+column.field.slice(1).replaceAll("_"," "),width:150,sortable:false,renderCell:(params)=>{
127+
return <Button onClick={()=>showJSONData(params.row[column.field],column.field.charAt(0).toUpperCase()+column.field.slice(1).replaceAll("_"," "))} startIcon={<ViewCompactIcon/>} variant="contained">View</Button>
128+
}}
129+
}
130+
if(column.field==='html_description'){
131+
return {field:'html_description',headerName:'HTML Description',width:150,sortable:false,renderCell:(params)=>{
132+
return <Button onClick={()=>showHTMLData(params.row.html_description)} startIcon={<ViewCompactIcon/>} variant="contained">View</Button>
133+
}}
134+
}
135+
136+
return column;
137+
})
138+
setColumns(columns);
139+
}
140+
}
141+
142+
const handleSorting=(newModel)=>{
143+
setOrdering(newModel);
144+
}
145+
146+
useEffect(()=>{
147+
getProducts();
148+
},[paginationModel,debounceSearch,ordering])
149+
150+
return (
151+
<Box component={"div"} sx={{width:'100%'}}>
152+
<Breadcrumbs>
153+
<Typography variant="body2" onClick={()=>navigate('/')}>Home</Typography>
154+
<Typography variant="body2" onClick={()=>navigate('/manage/product')}>Manage Products</Typography>
155+
</Breadcrumbs>
156+
<TextField label="Search" variant="outlined" fullWidth onChange={(e)=>setSearchQuery(e.target.value)} margin="normal"/>
157+
<DataGrid
158+
rows={data}
159+
columns={columns}
160+
rowHeight={75}
161+
sortingOrder={['asc','desc']}
162+
sortModel={ordering}
163+
onSortModelChange={handleSorting}
164+
paginationMode="server"
165+
initialState={{
166+
...data.initialState,
167+
pagination:{paginationModel:paginationModel}
168+
}}
169+
pageSizeOptions={[5,10,20]}
170+
pagination
171+
rowCount={totalItems}
172+
loading={loading}
173+
rowSelection={false}
174+
onPaginationModelChange={(pagedetails)=>{
175+
setPaginationModel({
176+
page:pagedetails.page,
177+
pageSize:pagedetails.pageSize
178+
179+
})
180+
}}
181+
slots={
182+
{
183+
184+
loadingOverlay:LinearProgress,
185+
toolbar:GridToolbar,
186+
187+
}
188+
}
189+
190+
/>
191+
<Dialog open={open} fullWidth={true} onClose={handleClose} aria-labelledby="form-dialog-title">
192+
<DialogContent>
193+
<Typography variant="h5" mb={2}>{modalTitle} Details </Typography>
194+
<Divider />
195+
{
196+
jsonData.map((item,index)=>(
197+
<React.Fragment key={index}>
198+
<Typography mt={2}><Circle sx={{fontSize:'12px',marginRight:'10px'}} /> {item.key} - {item.value}</Typography>
199+
<Divider/>
200+
</React.Fragment>
201+
))
202+
}
203+
</DialogContent>
204+
</Dialog>
205+
<Dialog open={openHtml} fullWidth={true} onClose={handleClose2} aria-labelledby="form-dialog-title">
206+
<DialogContent>
207+
<Typography variant="h5" mb={2}>HTML Description </Typography>
208+
<Divider />
209+
<div dangerouslySetInnerHTML={{__html:htmldata}}/>
210+
</DialogContent>
211+
</Dialog>
212+
</Box>
213+
)
214+
}
215+
216+
export default ManageProducts;

Frontend/ecommerce_inventory/src/redux/reducer/sidebardata.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ const sidebarSlice=createSlice({
6363
item.expanded=false;
6464
item.submenus.forEach(submenu=>{
6565
submenu.active=false;
66-
if(submenu.module_url && window.location.pathname.indexOf(item.module_url)!==-1){
66+
if(submenu.module_url && window.location.pathname.indexOf(submenu.module_url)!==-1){
6767
submenu.active=true;
6868
item.active=true;
6969
item.expanded=true;

0 commit comments

Comments
 (0)