276 lines
8.1 KiB
Go
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
|
|
} |