diff --git a/backend/internal/domain/models.go b/backend/internal/domain/models.go
index b4b515e..93b6c4b 100644
--- a/backend/internal/domain/models.go
+++ b/backend/internal/domain/models.go
@@ -251,6 +251,34 @@ type Shipment struct {
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
+// ReviewFilter captures review listing constraints.
+type ReviewFilter struct {
+ Limit int
+ Offset int
+}
+
+// ReviewPage wraps paginated review results.
+type ReviewPage struct {
+ Reviews []Review `json:"reviews"`
+ Total int64 `json:"total"`
+ Page int `json:"page"`
+ PageSize int `json:"page_size"`
+}
+
+// ShipmentFilter captures shipment listing constraints.
+type ShipmentFilter struct {
+ Limit int
+ Offset int
+}
+
+// ShipmentPage wraps paginated shipment results.
+type ShipmentPage struct {
+ Shipments []Shipment `json:"shipments"`
+ Total int64 `json:"total"`
+ Page int `json:"page"`
+ PageSize int `json:"page_size"`
+}
+
// OrderStatus enumerates supported transitions.
type OrderStatus string
diff --git a/backend/internal/http/handler/admin_handler.go b/backend/internal/http/handler/admin_handler.go
new file mode 100644
index 0000000..95a49da
--- /dev/null
+++ b/backend/internal/http/handler/admin_handler.go
@@ -0,0 +1,53 @@
+package handler
+
+import (
+ "net/http"
+
+ "github.com/saveinmed/backend-go/internal/domain"
+)
+
+// ListAllReviews godoc
+// @Summary Lista todas as avaliações (Admin)
+// @Tags Admin
+// @Security BearerAuth
+// @Produce json
+// @Param page query int false "Página"
+// @Param page_size query int false "Tamanho da página"
+// @Success 200 {object} domain.ReviewPage
+// @Failure 401 {object} map[string]string
+// @Failure 500 {object} map[string]string
+// @Router /api/v1/admin/reviews [get]
+func (h *Handler) ListAllReviews(w http.ResponseWriter, r *http.Request) {
+ page, pageSize := parsePagination(r)
+
+ result, err := h.svc.ListReviews(r.Context(), domain.ReviewFilter{}, page, pageSize)
+ if err != nil {
+ writeError(w, http.StatusInternalServerError, err)
+ return
+ }
+
+ writeJSON(w, http.StatusOK, result)
+}
+
+// ListAllShipments godoc
+// @Summary Lista todos os envios (Admin)
+// @Tags Admin
+// @Security BearerAuth
+// @Produce json
+// @Param page query int false "Página"
+// @Param page_size query int false "Tamanho da página"
+// @Success 200 {object} domain.ShipmentPage
+// @Failure 401 {object} map[string]string
+// @Failure 500 {object} map[string]string
+// @Router /api/v1/admin/shipments [get]
+func (h *Handler) ListAllShipments(w http.ResponseWriter, r *http.Request) {
+ page, pageSize := parsePagination(r)
+
+ result, err := h.svc.ListShipments(r.Context(), domain.ShipmentFilter{}, page, pageSize)
+ if err != nil {
+ writeError(w, http.StatusInternalServerError, err)
+ return
+ }
+
+ writeJSON(w, http.StatusOK, result)
+}
diff --git a/backend/internal/repository/postgres/postgres.go b/backend/internal/repository/postgres/postgres.go
index 3200ce2..a3874c0 100644
--- a/backend/internal/repository/postgres/postgres.go
+++ b/backend/internal/repository/postgres/postgres.go
@@ -1090,3 +1090,53 @@ SET active = EXCLUDED.active,
}
return nil
}
+
+func (r *Repository) ListReviews(ctx context.Context, filter domain.ReviewFilter) ([]domain.Review, int64, error) {
+ baseQuery := `FROM reviews`
+ var args []any
+
+ // Add where clauses if needed in future
+
+ var total int64
+ if err := r.db.GetContext(ctx, &total, "SELECT count(*) "+baseQuery, args...); err != nil {
+ return nil, 0, err
+ }
+
+ if filter.Limit <= 0 {
+ filter.Limit = 20
+ }
+ args = append(args, filter.Limit, filter.Offset)
+
+ listQuery := fmt.Sprintf("SELECT id, order_id, buyer_id, seller_id, rating, comment, created_at %s ORDER BY created_at DESC LIMIT $%d OFFSET $%d", baseQuery, len(args)-1, len(args))
+
+ var reviews []domain.Review
+ if err := r.db.SelectContext(ctx, &reviews, listQuery, args...); err != nil {
+ return nil, 0, err
+ }
+ return reviews, total, nil
+}
+
+func (r *Repository) ListShipments(ctx context.Context, filter domain.ShipmentFilter) ([]domain.Shipment, int64, error) {
+ baseQuery := `FROM shipments`
+ var args []any
+
+ // Add where clauses if needed in future
+
+ var total int64
+ if err := r.db.GetContext(ctx, &total, "SELECT count(*) "+baseQuery, args...); err != nil {
+ return nil, 0, err
+ }
+
+ if filter.Limit <= 0 {
+ filter.Limit = 20
+ }
+ args = append(args, filter.Limit, filter.Offset)
+
+ listQuery := fmt.Sprintf("SELECT id, order_id, carrier, tracking_code, external_tracking, status, created_at, updated_at %s ORDER BY created_at DESC LIMIT $%d OFFSET $%d", baseQuery, len(args)-1, len(args))
+
+ var shipments []domain.Shipment
+ if err := r.db.SelectContext(ctx, &shipments, listQuery, args...); err != nil {
+ return nil, 0, err
+ }
+ return shipments, total, nil
+}
diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go
index 82ad078..d2b31be 100644
--- a/backend/internal/server/server.go
+++ b/backend/internal/server/server.go
@@ -98,6 +98,9 @@ func New(cfg config.Config) (*Server, error) {
mux.Handle("GET /api/v1/dashboard/seller", chain(http.HandlerFunc(h.GetSellerDashboard), middleware.Logger, middleware.Gzip, auth))
mux.Handle("GET /api/v1/dashboard/admin", chain(http.HandlerFunc(h.GetAdminDashboard), middleware.Logger, middleware.Gzip, adminOnly))
+ mux.Handle("GET /api/v1/admin/reviews", chain(http.HandlerFunc(h.ListAllReviews), middleware.Logger, middleware.Gzip, adminOnly))
+ mux.Handle("GET /api/v1/admin/shipments", chain(http.HandlerFunc(h.ListAllShipments), middleware.Logger, middleware.Gzip, adminOnly))
+
mux.Handle("POST /api/v1/auth/register", chain(http.HandlerFunc(h.Register), middleware.Logger, middleware.Gzip))
mux.Handle("POST /api/v1/auth/register/customer", chain(http.HandlerFunc(h.RegisterCustomer), middleware.Logger, middleware.Gzip))
mux.Handle("POST /api/v1/auth/register/tenant", chain(http.HandlerFunc(h.RegisterTenant), middleware.Logger, middleware.Gzip))
diff --git a/backend/internal/usecase/usecase.go b/backend/internal/usecase/usecase.go
index 1943b43..e211862 100644
--- a/backend/internal/usecase/usecase.go
+++ b/backend/internal/usecase/usecase.go
@@ -59,7 +59,10 @@ type Repository interface {
SellerDashboard(ctx context.Context, sellerID uuid.UUID) (*domain.SellerDashboard, error)
AdminDashboard(ctx context.Context, since time.Time) (*domain.AdminDashboard, error)
GetShippingMethodsByVendor(ctx context.Context, vendorID uuid.UUID) ([]domain.ShippingMethod, error)
+
UpsertShippingMethods(ctx context.Context, methods []domain.ShippingMethod) error
+ ListReviews(ctx context.Context, filter domain.ReviewFilter) ([]domain.Review, int64, error)
+ ListShipments(ctx context.Context, filter domain.ShipmentFilter) ([]domain.Shipment, int64, error)
}
// PaymentGateway abstracts Mercado Pago integration.
@@ -807,3 +810,35 @@ func (s *Service) VerifyCompany(ctx context.Context, id uuid.UUID) (*domain.Comp
}
return company, nil
}
+
+func (s *Service) ListReviews(ctx context.Context, filter domain.ReviewFilter, page, pageSize int) (*domain.ReviewPage, error) {
+ if pageSize <= 0 {
+ pageSize = 20
+ }
+ if page <= 0 {
+ page = 1
+ }
+ filter.Limit = pageSize
+ filter.Offset = (page - 1) * pageSize
+ reviews, total, err := s.repo.ListReviews(ctx, filter)
+ if err != nil {
+ return nil, err
+ }
+ return &domain.ReviewPage{Reviews: reviews, Total: total, Page: page, PageSize: pageSize}, nil
+}
+
+func (s *Service) ListShipments(ctx context.Context, filter domain.ShipmentFilter, page, pageSize int) (*domain.ShipmentPage, error) {
+ if pageSize <= 0 {
+ pageSize = 20
+ }
+ if page <= 0 {
+ page = 1
+ }
+ filter.Limit = pageSize
+ filter.Offset = (page - 1) * pageSize
+ shipments, total, err := s.repo.ListShipments(ctx, filter)
+ if err != nil {
+ return nil, err
+ }
+ return &domain.ShipmentPage{Shipments: shipments, Total: total, Page: page, PageSize: pageSize}, nil
+}
diff --git a/marketplace/src/App.tsx b/marketplace/src/App.tsx
index be2a8de..e853046 100644
--- a/marketplace/src/App.tsx
+++ b/marketplace/src/App.tsx
@@ -2,7 +2,6 @@ import { Navigate, Route, Routes } from 'react-router-dom'
import { LoginPage } from './pages/Login'
import { CartPage } from './pages/Cart'
import { CheckoutPage } from './pages/Checkout'
-import { ProfilePage } from './pages/Profile'
import { OrdersPage as UserOrdersPage } from './pages/Orders'
import { InventoryPage } from './pages/Inventory'
import { CompanyPage } from './pages/Company'
@@ -16,7 +15,10 @@ import {
UsersPage,
CompaniesPage,
ProductsPage,
- OrdersPage
+ OrdersPage,
+ ReviewsPage,
+ LogisticsPage,
+ ProfilePage
} from './pages/admin'
function App() {
@@ -38,6 +40,9 @@ function App() {
} />
} />
} />
+ } />
+ } />
+ } />
{/* Legacy admin route - redirect to dashboard */}
diff --git a/marketplace/src/components/Header.tsx b/marketplace/src/components/Header.tsx
index 7ac2bb6..c39ad13 100644
--- a/marketplace/src/components/Header.tsx
+++ b/marketplace/src/components/Header.tsx
@@ -7,6 +7,8 @@ const navItems = [
{ path: '/dashboard/companies', label: 'Empresas' },
{ path: '/dashboard/products', label: 'Produtos' },
{ path: '/dashboard/orders', label: 'Pedidos' },
+ { path: '/dashboard/reviews', label: 'Avaliações' },
+ { path: '/dashboard/logistics', label: 'Logística' },
]
export function Header() {
@@ -35,8 +37,8 @@ export function Header() {
key={item.path}
to={item.path}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-all ${isActive
- ? 'bg-white/20 text-white'
- : 'text-white/80 hover:bg-white/10 hover:text-white'
+ ? 'bg-white/20 text-white'
+ : 'text-white/80 hover:bg-white/10 hover:text-white'
}`}
>
{item.label}
@@ -48,7 +50,9 @@ export function Header() {
{/* User info */}
-
{user?.name}
+
+ {user?.name}
+
{user?.role}