diff --git a/backend/cmd/app/main.go b/backend/cmd/app/main.go index 17f86a3..d63e1ae 100644 --- a/backend/cmd/app/main.go +++ b/backend/cmd/app/main.go @@ -90,7 +90,8 @@ func main() { //mux.HandleFunc("GET /logout",) // super admin endpoints - mux.HandleFunc("GET /admin/platform-overview", adminHanlder.PlatformOverviewView) + mux.HandleFunc("GET /admin/dashboard", adminHanlder.AdminDashboardView) + mux.HandleFunc("POST /v1/admin/dashboard-data", adminHanlder.AdminDashboardDataHandler) mux.HandleFunc("GET /admin/merchants", adminHanlder.MerchantManagementView) mux.HandleFunc("GET /admin/terminals", adminHanlder.TerminalRegistryView) mux.HandleFunc("GET /admin/settings", adminHanlder.SystemSettingsView) @@ -102,6 +103,7 @@ func main() { mux.HandleFunc("POST /v1/admin/addcardauth", adminHanlder.AddCardHandler) mux.HandleFunc("POST /v1/admin/deactivatecardauth", adminHanlder.DeactivateCardHanlder) mux.HandleFunc("POST /v1/admin/deletecardauth", adminHanlder.DeleteCardHandler) + mux.HandleFunc("GET /admin/delete-cards", adminHanlder.DeleteCardView) // Wrap mux with custom handler for root redirect diff --git a/backend/internal/admin/addCard.go b/backend/internal/admin/add_card.go similarity index 93% rename from backend/internal/admin/addCard.go rename to backend/internal/admin/add_card.go index b7c6229..5205634 100644 --- a/backend/internal/admin/addCard.go +++ b/backend/internal/admin/add_card.go @@ -12,13 +12,11 @@ import ( "strings" "time" jsonwrite "unicard-go/backend/internal/pkg/handler" - "unicard-go/backend/internal/pkg/structs" + structure "unicard-go/backend/internal/pkg/structs" "github.com/go-playground/validator/v10" ) -// CardRequest struct mapped directly to your frontend JSON payload - // AddCardsView renders the addCards.html template after checking the admin session. func (h *Handler) AddCardsView(w http.ResponseWriter, r *http.Request) { fmt.Println("AddCardsView running...") @@ -29,7 +27,8 @@ func (h *Handler) AddCardsView(w http.ResponseWriter, r *http.Request) { func (h *Handler) AddCardHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("AddCardHandler running...") - var req structs.CardData + // Define request struct + var req structure.CardData // Decode JSON if err := json.NewDecoder(r.Body).Decode(&req); err != nil { @@ -50,8 +49,8 @@ func (h *Handler) AddCardHandler(w http.ResponseWriter, r *http.Request) { var validationErrs validator.ValidationErrors if errors.As(err, &validationErrs) { errorMap := map[string]string{ - "CardUID": "Card UID is required.", - "InitialAmount": "Initial amount is required and cannot be negative.", + "CardUID": "Card UID is required.", + "Balance": "Initial amount is required and cannot be negative.", } if msg, ok := errorMap[validationErrs[0].Field()]; ok { errorMessage = msg diff --git a/backend/internal/admin/admin_dashboard.go b/backend/internal/admin/admin_dashboard.go new file mode 100644 index 0000000..12a7afd --- /dev/null +++ b/backend/internal/admin/admin_dashboard.go @@ -0,0 +1,156 @@ +package admin + +import ( + "encoding/json" + "log" + "net/http" + jsonwrite "unicard-go/backend/internal/pkg/handler" +) + +// AdminDashboardData struct represents the data to be displayed on the admin dashboard +type AdminDashboardData struct { + GrossRevenue float64 `json:"grossRevenue"` + NetRevenue float64 `json:"netRevenue"` + TotalUsers int `json:"totalUsers"` + TotalCards int `json:"totalCards"` + ActiveMerchants int `json:"activeMerchants"` + ActiveTerminals int `json:"activeTerminals"` +} + +// AdminDashboardView renders the platform_overview.html template after checking the admin session. +func (h *Handler) AdminDashboardView(w http.ResponseWriter, r *http.Request) { + log.Println("AdminDashboardView running...") + h.Tpl.ExecuteTemplate(w, "admin_dashboard.html", nil) +} + +// AdminDashboardDataHandler handles the request for admin dashboard data and returns JSON response. +// AdminDashboardDataHandler handles the request for admin dashboard data and returns JSON response. +func (h *Handler) AdminDashboardDataHandler(w http.ResponseWriter, r *http.Request) { + log.Println("AdminDashboardDataHandler running...") + + var req AdminDashboardData + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + log.Println("Error decoding JSON:", err) + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ + Success: false, + Message: "Invalid request format", + }) + return + } + + // Compute UniCard's Absolute Gross Revenue + // (Transaction Service Fees) + query := `SELECT COALESCE(SUM(service_fee), 0.00) FROM transactions WHERE transaction_type = 'payment'` + row := h.DB.QueryRow(query) + + var grossRevenue float64 + if err := row.Scan(&grossRevenue); err != nil { + log.Println("Error scanning gross revenue:", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Internal server error", + }) + return + } + log.Println("Gross revenue row:", grossRevenue) + + // Compute UniCard's Net Revenue + // (Service fees from successful payments MINUS service fees lost to refunds/reversals) + query = `SELECT COALESCE(SUM( + CASE + WHEN transaction_type = 'payment' THEN service_fee + WHEN transaction_type IN ('refund', 'reversal') THEN -service_fee + ELSE 0 + END + ), 0.00) FROM transactions` + + row = h.DB.QueryRow(query) + + var netRevenue float64 + if err := row.Scan(&netRevenue); err != nil { + log.Println("Error scanning net revenue:", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Internal server error while calculating Net Revenue", + }) + return + } + log.Println("Net revenue row:", netRevenue) + + // Display the number of users + // Counting only 'active' customers (excluding suspended or inactive accounts) + row = h.DB.QueryRow("SELECT COUNT(*) FROM users WHERE role = 'customer'") // status = 'active' + + var totalUsers int + if err := row.Scan(&totalUsers); err != nil { + log.Println("Error scanning total users:", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Internal server error", + }) + return + } + log.Println("Total users row:", totalUsers) + + // Display the number of cards + // Counting only 'active' cards to show actual circulatory supply + row = h.DB.QueryRow("SELECT COUNT(*) FROM cards") // WHERE status = 'active' + + var totalCards int + if err := row.Scan(&totalCards); err != nil { + log.Println("Error scanning total cards:", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Internal server error", + }) + return + } + log.Println("Total cards row:", totalCards) + + // Display the number of merchants + // Counting only 'active' merchants (excluding 'pending_approval' or 'suspended') + row = h.DB.QueryRow("SELECT COUNT(*) FROM merchants WHERE status = 'active'") + + var totalMerchants int + if err := row.Scan(&totalMerchants); err != nil { + log.Println("Error scanning total merchants:", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Internal server error", + }) + return + } + log.Println("Total merchants row:", totalMerchants) + + // Display the number of terminals + // Counting 'active' ESP32 nodes (excluding 'offline' or 'suspended') + row = h.DB.QueryRow("SELECT COUNT(*) FROM terminals WHERE status = 'active'") + + var totalTerminals int + if err := row.Scan(&totalTerminals); err != nil { + log.Println("Error scanning total terminals:", err) + jsonwrite.WriteJSON(w, http.StatusInternalServerError, jsonwrite.APIResponse{ + Success: false, + Message: "Internal server error", + }) + return + } + log.Println("Total terminals row:", totalTerminals) + + // Return the data as JSON + response := AdminDashboardData{ + GrossRevenue: grossRevenue, + NetRevenue: netRevenue, + TotalUsers: totalUsers, + TotalCards: totalCards, + ActiveMerchants: totalMerchants, + ActiveTerminals: totalTerminals, + } + jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ + Success: true, + Message: "Admin dashboard data retrieved successfully", + Data: response, + }) + log.Println("AdminDashboardDataHandler finished") +} diff --git a/backend/internal/admin/deactivate_card.go b/backend/internal/admin/deactivate_card.go index c3e7422..70a309b 100644 --- a/backend/internal/admin/deactivate_card.go +++ b/backend/internal/admin/deactivate_card.go @@ -8,13 +8,13 @@ import ( "net/http" "strings" jsonwrite "unicard-go/backend/internal/pkg/handler" - "unicard-go/backend/internal/pkg/structs" + structure "unicard-go/backend/internal/pkg/structs" "github.com/go-playground/validator/v10" ) // Type alias for CardData to resolve the redeclaration error -type CardData = structs.CardData +type CardData = structure.CardData func (h *Handler) DeactivateView(w http.ResponseWriter, r *http.Request) { fmt.Println("DeactivateView running...") diff --git a/backend/internal/admin/deleteCard.go b/backend/internal/admin/delete_card.go similarity index 66% rename from backend/internal/admin/deleteCard.go rename to backend/internal/admin/delete_card.go index a20d886..b866e9a 100644 --- a/backend/internal/admin/deleteCard.go +++ b/backend/internal/admin/delete_card.go @@ -2,48 +2,54 @@ package admin import ( "encoding/json" + "errors" "fmt" "net/http" "strings" jsonwrite "unicard-go/backend/internal/pkg/handler" + structure "unicard-go/backend/internal/pkg/structs" + + "github.com/go-playground/validator/v10" ) func (h *Handler) DeleteCardView(w http.ResponseWriter, r *http.Request) { fmt.Println("DeleteCardView running...") - h.Tpl.ExecuteTemplate(w, "deleteCard.html", nil) + h.Tpl.ExecuteTemplate(w, "delete_card.html", nil) } // DeleteCardHandler handles deleting a card by card_number and returns JSON. func (h *Handler) DeleteCardHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("DeleteCardHandler running...") - if r.Method != http.MethodPost { - jsonwrite.WriteJSON(w, http.StatusMethodNotAllowed, jsonwrite.APIResponse{ + var req structure.CardData + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ Success: false, - Message: "Method not allowed", + Message: "Invalid request format", }) return } - var req struct { - CardNumber string `json:"cardNumber"` - } + cardNumber := strings.TrimSpace(req.CardNumber) - // Try reading JSON body - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - // Fallback to post form values - if err := r.ParseForm(); err == nil { - req.CardNumber = r.PostFormValue("cardNumber") + validate := validator.New() + if err := validate.Struct(req); err != nil { + errorMessage := "Invalid input provided." + var validationErrs validator.ValidationErrors + if errors.As(err, &validationErrs) { + errorMap := map[string]string{ + "CardNumber": "Card number is required.", + } + if msg, ok := errorMap[validationErrs[0].Field()]; ok { + errorMessage = msg + } } - } - cardNumber := strings.TrimSpace(req.CardNumber) - - if cardNumber == "" { - jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ + jsonwrite.WriteJSON(w, http.StatusBadRequest, jsonwrite.APIResponse{ Success: false, - Message: "Card number is required for deletion.", + Message: errorMessage, }) return } @@ -54,7 +60,7 @@ func (h *Handler) DeleteCardHandler(w http.ResponseWriter, r *http.Request) { fmt.Println("Error deleting card from DB:", err) jsonwrite.WriteJSON(w, http.StatusOK, jsonwrite.APIResponse{ Success: false, - Message: "Failed to delete card due to database error.", + Message: "Failed to delete card.", }) return } diff --git a/backend/internal/pkg/handler/jsonWrite.go b/backend/internal/pkg/handler/jsonWrite.go index 3eb07ef..1498534 100644 --- a/backend/internal/pkg/handler/jsonWrite.go +++ b/backend/internal/pkg/handler/jsonWrite.go @@ -10,6 +10,7 @@ type APIResponse struct { Success bool `json:"success"` Message string `json:"message"` Field string `json:"field,omitempty"` + Data any `json:"data,omitempty"` // Optional field for additional data } // Login specific response — returns user data after login diff --git a/backend/internal/pkg/structs/card_struct.go b/backend/internal/pkg/structs/struct.go similarity index 67% rename from backend/internal/pkg/structs/card_struct.go rename to backend/internal/pkg/structs/struct.go index 26c0a9b..879f17d 100644 --- a/backend/internal/pkg/structs/card_struct.go +++ b/backend/internal/pkg/structs/struct.go @@ -1,9 +1,10 @@ -package structs +package structure +// CardData struct represents the data required to create a new card type CardData struct { - CardUID string `json:"card_uid" db:"card_uid" validate:"required"` + CardUID string `json:"card_uid" db:"card_uid" validate:"required"` CardNumber string `json:"cardNumber" db:"card_number" validate:"required"` CardHolder string `json:"cardHolder" db:"user_id" validate:"required"` CardType string `json:"cardType" db:"card_type" validate:"required"` Balance float64 `json:"initial_amount" db:"balance" validate:"required,min=0"` -} +} \ No newline at end of file diff --git a/frontend/assets/js/admin/admin-dasboard.js b/frontend/assets/js/admin-dasboard.js similarity index 100% rename from frontend/assets/js/admin/admin-dasboard.js rename to frontend/assets/js/admin-dasboard.js diff --git a/frontend/templates/admin/admin_dashboard.html b/frontend/templates/admin/admin_dashboard.html index 4c77912..40e7804 100644 --- a/frontend/templates/admin/admin_dashboard.html +++ b/frontend/templates/admin/admin_dashboard.html @@ -1,10 +1,9 @@ - - UniCard Admin - Dashboard + Super Admin - Dashboard @@ -22,9 +21,7 @@ }; - -
@@ -45,59 +42,50 @@

Core System

-

- Legacy Operations

+

Legacy Operations

-
-

Logout Confirmation

-
-

- Are you sure you want to log out of the UniCard Admin Control Panel? -

+
+

Active Regions

+
+ 4 +
+
+
Metro Manila
+
Cebu
+
Others
-
-
-
- - Logout - - +
- + - - - + + - - \ No newline at end of file + diff --git a/frontend/templates/admin/platform_overview.html b/frontend/templates/admin/platform_overview.html deleted file mode 100644 index 6d55abd..0000000 --- a/frontend/templates/admin/platform_overview.html +++ /dev/null @@ -1,211 +0,0 @@ - - - - - - Super Admin - Platform Overview - - - - - - - - -
- - - - -
- -
-

Platform Overview

-
- Welcome, Super Admin -
- SA -
-
-
- - -
-
- -
-

Global Command Center

-

Real-time metrics for UniCard system-wide performance and revenue.

-
- - -
- -
-
-
-

Gross Transaction Vol.

-

₱42.8M

-

12.5%

-
-
- -
-
-
- - -
-
-
-

Total Active Users

-

128.4K

-

3.2%

-
-
- -
-
-
- - -
-
-
-

Net Gateway Fees

-

₱1.24M

-

8.1%

-
-
- -
-
-
-
- - -
-
-

Recent System Events

-
    -
  • -
    -
    -

    Main Gateway API Updated

    -

    v2.4.1 deployed successfully without downtime.

    -
    -
  • -
  • -
    -
    -

    New Merchant Onboarded

    -

    Alfamart Phils. activated 12 new terminals.

    -
    -
  • -
-
-
-

Active Regions

-
- 4 -
-
-
Metro Manila
-
Cebu
-
Others
-
-
-
-
-
-
-
- -