Basdat/internal/services/dashboard_service.go
2025-12-20 00:01:08 +07:00

276 lines
8.1 KiB
Go

// internal/services/dashboard_service.go
package services
import (
"context"
"time"
"gorm.io/gorm"
"lost-and-found/internal/repositories"
)
// ✅ KRITERIA BASDAT: Menggunakan VIEWS dari enhancement.sql
type DashboardService struct {
db *gorm.DB
itemRepo *repositories.ItemRepository // ✅ Tambahkan ini
}
func NewDashboardService(db *gorm.DB) *DashboardService {
return &DashboardService{
db: db,
itemRepo: repositories.NewItemRepository(db), // ✅ Inisialisasi
}
}
// DashboardStats menggunakan VIEW vw_dashboard_stats
type DashboardStats struct {
TotalUnclaimed int64 `json:"total_unclaimed"`
TotalVerified int64 `json:"total_verified"`
TotalLostReports int64 `json:"total_lost_reports"`
PendingClaims int64 `json:"pending_claims"`
UnnotifiedMatches int64 `json:"unnotified_matches"`
}
// GetDashboardStats - ✅ MENGGUNAKAN VIEW
func (s *DashboardService) GetDashboardStats() (*DashboardStats, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var stats DashboardStats
// ✅ QUERY VIEW vw_dashboard_stats (dari enhancement.sql)
err := s.db.WithContext(ctx).
Table("vw_dashboard_stats").
Select("*").
Scan(&stats).Error
if err != nil {
return nil, err
}
return &stats, nil
}
// ItemDetail menggunakan VIEW vw_items_detail
type ItemDetail struct {
ID uint `json:"id"`
Name string `json:"name"`
CategoryName string `json:"category_name"`
CategorySlug string `json:"category_slug"`
PhotoURL string `json:"photo_url"`
Location string `json:"location"`
DateFound time.Time `json:"date_found"`
Status string `json:"status"`
ReporterName string `json:"reporter_name"`
ReporterContact string `json:"reporter_contact"`
ExpiresAt *time.Time `json:"expires_at"`
ReporterUserName string `json:"reporter_user_name"`
ReporterEmail string `json:"reporter_email"`
DaysUntilExpire int `json:"days_until_expire"`
CreatedAt time.Time `json:"created_at"`
}
// GetItemsWithDetails - ✅ MENGGUNAKAN VIEW
func (s *DashboardService) GetItemsWithDetails(page, limit int, status string) ([]ItemDetail, int64, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var items []ItemDetail
var total int64
query := s.db.WithContext(ctx).Table("vw_items_detail")
if status != "" {
query = query.Where("status = ?", status)
}
// Count
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
// Paginate
offset := (page - 1) * limit
err := query.Offset(offset).Limit(limit).Scan(&items).Error
if err != nil {
return nil, 0, err
}
return items, total, nil
}
// ClaimDetail menggunakan VIEW vw_claims_detail
type ClaimDetail struct {
ID uint `json:"id"`
Status string `json:"status"`
ItemName string `json:"item_name"`
CategoryName string `json:"category_name"`
ClaimantName string `json:"claimant_name"`
ClaimantEmail string `json:"claimant_email"`
ClaimantPhone string `json:"claimant_phone"`
ClaimDescription string `json:"claim_description"`
Contact string `json:"contact"`
SimilarityScore *float64 `json:"similarity_score"`
VerifiedAt *time.Time `json:"verified_at"`
VerifiedByName string `json:"verified_by_name"`
Notes string `json:"notes"`
CreatedAt time.Time `json:"created_at"`
}
// GetClaimsWithDetails - ✅ MENGGUNAKAN VIEW
func (s *DashboardService) GetClaimsWithDetails(status string) ([]ClaimDetail, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var claims []ClaimDetail
query := s.db.WithContext(ctx).Table("vw_claims_detail")
if status != "" {
query = query.Where("status = ?", status)
}
err := query.Order("created_at DESC").Scan(&claims).Error
if err != nil {
return nil, err
}
return claims, nil
}
// MatchDetail menggunakan VIEW vw_match_results_detail
type MatchDetail struct {
ID uint `json:"id"`
LostItemName string `json:"lost_item_name"`
LostByUserID uint `json:"lost_by_user_id"`
LostByUserName string `json:"lost_by_user_name"`
LostByEmail string `json:"lost_by_email"`
FoundItemName string `json:"found_item_name"`
FoundByName string `json:"found_by_name"`
SimilarityScore float64 `json:"similarity_score"`
IsNotified bool `json:"is_notified"`
MatchedAt time.Time `json:"matched_at"`
FoundItemID uint `json:"found_item_id"`
LostItemID uint `json:"lost_item_id"`
}
// GetMatchesWithDetails - ✅ MENGGUNAKAN VIEW
func (s *DashboardService) GetMatchesWithDetails(minScore float64) ([]MatchDetail, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var matches []MatchDetail
query := s.db.WithContext(ctx).Table("vw_match_results_detail")
if minScore > 0 {
query = query.Where("similarity_score >= ?", minScore)
}
err := query.Limit(100).Scan(&matches).Error
if err != nil {
return nil, err
}
return matches, nil
}
// CategoryStats menggunakan VIEW vw_category_stats
type CategoryStats struct {
ID uint `json:"id"`
Name string `json:"name"`
Slug string `json:"slug"`
TotalItems int `json:"total_items"`
UnclaimedItems int `json:"unclaimed_items"`
VerifiedItems int `json:"verified_items"`
TotalLostReports int `json:"total_lost_reports"`
}
// GetCategoryStats - ✅ MENGGUNAKAN VIEW
func (s *DashboardService) GetCategoryStats() ([]CategoryStats, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var stats []CategoryStats
err := s.db.WithContext(ctx).
Table("vw_category_stats").
Order("total_items DESC").
Scan(&stats).Error
if err != nil {
return nil, err
}
return stats, nil
}
// UserActivity menggunakan VIEW vw_user_activity
type UserActivity struct {
ID uint `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
RoleName string `json:"role_name"`
ItemsReported int `json:"items_reported"`
LostItemsReported int `json:"lost_items_reported"`
ClaimsMade int `json:"claims_made"`
ClaimsApproved int `json:"claims_approved"`
MemberSince time.Time `json:"member_since"`
}
// GetUserActivity - ✅ MENGGUNAKAN VIEW
func (s *DashboardService) GetUserActivity(limit int) ([]UserActivity, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var activities []UserActivity
err := s.db.WithContext(ctx).
Table("vw_user_activity").
Order("items_reported DESC").
Limit(limit).
Scan(&activities).Error
if err != nil {
return nil, err
}
return activities, nil
}
func (s *DashboardService) GetStatsFromSP() (map[string]int64, error) {
return s.itemRepo.GetDashboardStatsSP()
}
// RecentActivity menggunakan VIEW vw_recent_activities
type RecentActivity struct {
ID uint `json:"id"`
Action string `json:"action"`
EntityType string `json:"entity_type"`
EntityID *uint `json:"entity_id"`
Details string `json:"details"`
UserName string `json:"user_name"`
UserEmail string `json:"user_email"`
UserRole string `json:"user_role"`
IPAddress string `json:"ip_address"`
CreatedAt time.Time `json:"created_at"`
}
// GetRecentActivities - ✅ MENGGUNAKAN VIEW (limited to 100)
func (s *DashboardService) GetRecentActivities() ([]RecentActivity, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var activities []RecentActivity
err := s.db.WithContext(ctx).
Table("vw_recent_activities").
Scan(&activities).Error
if err != nil {
return nil, err
}
return activities, nil
}