diff --git a/backend/handlers/analytics_handler.go b/backend/handlers/analytics_handler.go new file mode 100644 index 0000000..316f5fa --- /dev/null +++ b/backend/handlers/analytics_handler.go @@ -0,0 +1,34 @@ +package handlers + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "fishfish/database" +) + +func GetTopItems(c *gin.Context) { + type TopItem struct { + Name string `json:"name"` + Revenue float64 `json:"revenue"` + Price float64 `json:"price"` + SaleQuantity float64 `json:"sale_quantity"` + Unit string `json:"unit"` + } + + results := []TopItem{} + + err := database.DB.Table("order_items"). + Select("items.name, SUM(order_items.price * order_items.quantity) AS revenue, order_items.price, SUM(order_items.quantity) as sale_quantity, items.unit"). + Joins("JOIN items ON items.id = order_items.item_id"). + Group("items.name, order_items.price, items.unit"). + Order("revenue DESC"). + Limit(5). + Scan(&results).Error + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + } + + c.JSON(http.StatusOK, results) +} diff --git a/backend/main.go b/backend/main.go index 5b30fe4..3f1b9a2 100644 --- a/backend/main.go +++ b/backend/main.go @@ -66,6 +66,7 @@ func main() { admin.POST("/register", handlers.RegisterAdmin) admin.GET("/orders", handlers.GetAllOrders) admin.PATCH("/orders/:id", handlers.UpdateOrderStatus) + admin.GET("/items/top", handlers.GetTopItems) admin.PATCH("/items/:id/avatar", handlers.UploadItemAvatar) admin.POST("/stock/in", handlers.StockIn) admin.POST("/stock/out", handlers.StockOut) diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index fbf987d..3dddee5 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -70,3 +70,4 @@ export const stockOut = (data) => axios.post(`${API_URL}/admin/stock/out`, data); export const getStockMovements = () => axios.get(`${API_URL}/admin/stock/movements`); +export const getTopItems = () => axios.get(`${API_URL}/admin/items/top`); diff --git a/frontend/src/components/AdminItems.jsx b/frontend/src/components/AdminItems.jsx index 01447fb..36d902f 100644 --- a/frontend/src/components/AdminItems.jsx +++ b/frontend/src/components/AdminItems.jsx @@ -1,9 +1,9 @@ import { useState, useEffect } from "react"; import { - getAllItems, deleteItem, addItem, + getAllItems, updateItem, uploadItemAvatar, } from "../api/index"; @@ -170,7 +170,9 @@ export default function AdminItems() { const loadItems = () => { getAllItems() - .then((res) => setItems(res.data)) + .then((res) => { + setItems(res.data); + }) .catch(() => { }) .finally(() => setLoading(false)); }; diff --git a/frontend/src/components/AdminTopItems.jsx b/frontend/src/components/AdminTopItems.jsx new file mode 100644 index 0000000..a49982d --- /dev/null +++ b/frontend/src/components/AdminTopItems.jsx @@ -0,0 +1,55 @@ +import { useState, useEffect } from "react"; + +import { getTopItems } from "../api/index"; + +export default function AdminItems() { + const [items, setItems] = useState([]); + const [loading, setLoading] = useState(true); + + const loadItems = () => { + getTopItems() + .then((res) => { + setItems(res.data); + }) + .catch(() => { }) + .finally(() => setLoading(false)); + }; + + useEffect(() => { + loadItems(); + }, []); + + if (loading) return
Загрузка...
; + + return ( +| Название | +Общая стоимость продаж | +Цена | +Проданный объем | +
|---|---|---|---|
| {item.name} | +{item.revenue} | +{item.price} ₽ | ++ {item.sale_quantity} {item.unit} + | +