student před 1 měsícem
rodič
revize
295eaff1da

+ 2 - 0
SicboSub/Common/Constant/CommonConstant.cs

@@ -114,6 +114,8 @@ public static class ApiUrlConstant
     public const String UserInfoUrl = "/apis/user/info";
     public const String PurchaseHistoryUrl = "/apis/game/purchase_history";
 
+    public const String RankingCointHistoryUrl = "/apis/game/ranking_coin_history";
+
     // Exchange OTP URLs
     public const String ExchangeRequestOtpUrl = "/apis/exchange/request-otp";
     public const String ExchangeVerifyOtpUrl = "/apis/exchange/verify-otp";

+ 115 - 0
SicboSub/SicboSub.Api/Business/GameBusinessImpl.cs

@@ -355,5 +355,120 @@ namespace SicboSub.Api.Business
                 new { }
             );
         }
+
+        public async Task<IActionResult> RankingCoinHistory(HttpRequest httpRequest, RankingHistoryReq request)
+        {
+            var url = httpRequest.Path;
+            var json = JsonConvert.SerializeObject(request);
+            log.Debug("URL: " + url + " => Request: " + json);
+
+            try
+            {
+                string lang = CommonLogic.GetLanguage(httpRequest, request.lang);
+                int pageNumber = request.pageNumber < 0 ? 0 : request.pageNumber;
+                int pageSize = request.pageSize <= 0 ? 20 : request.pageSize;
+
+                string token = httpRequest.Headers["Authorization"].ToString();
+                if (string.IsNullOrEmpty(token))
+                {
+                    return DotnetLib.Http.HttpResponse.BuildResponse(
+                        log, url, json,
+                        CommonErrorCode.UnauthorizedAccess,
+                        ConfigManager.Instance.GetConfigWebValue("UNAUTHORIZED", lang),
+                        new { }
+                    );
+                }
+                token = token.Replace("Bearer ", "");
+
+                // Standalone standing query from RankingHistory
+                var standingQuery = dbContext.BettingHistories.AsNoTracking();
+
+                if (!string.IsNullOrEmpty(request.fromDate))
+                {
+                    if (DateTime.TryParseExact(request.fromDate, "dd/MM/yyyy HH:mm:ss", null, System.Globalization.DateTimeStyles.None, out DateTime from))
+                    {
+                        standingQuery = standingQuery.Where(x => x.BettingTime >= from);
+                    }
+                }
+
+                if (!string.IsNullOrEmpty(request.toDate))
+                {
+                    if (DateTime.TryParseExact(request.toDate, "dd/MM/yyyy HH:mm:ss", null, System.Globalization.DateTimeStyles.None, out DateTime to))
+                    {
+                        standingQuery = standingQuery.Where(x => x.BettingTime <= to);
+                    }
+                }
+
+                // Group by Msisdn to calculate real-time totals
+                var groupedQuery = standingQuery
+                    .GroupBy(x => x.Msisdn)
+                    .Select(g => new {
+                        Msisdn = g.Key,
+                        TotalBetValue = g.Sum(x => x.BettingValue)
+                    });
+
+                // Sorting the entire aggregated set to accurately position users
+                var rankedList = await groupedQuery
+                    .OrderByDescending(x => x.TotalBetValue)
+                    .ToListAsync();
+
+                var itemsStanding = rankedList
+                    .Select((x, index) => new RankingHistoryItem
+                    {
+                        id = index + 1,
+                        msisdn = x.Msisdn ?? "",
+                        rankType = request.rankType ?? "",
+                        rankDate = DateTime.Now,
+                        rankPosition = index + 1,
+                        rewardValue = 0,
+                        rewardUnit = "",
+                        rewardDate = DateTime.MinValue,
+                        totalBetValue = x.TotalBetValue
+                    })
+                    .ToList();
+
+                // Apply msisdn filter if requested for current user standings display
+                if (!string.IsNullOrEmpty(request.msisdn))
+                {
+                    itemsStanding = itemsStanding.Where(x => x.msisdn == request.msisdn).ToList();
+                }
+
+                int totalCountStanding = itemsStanding.Count;
+                int totalPagesStanding = (int)Math.Ceiling((double)totalCountStanding / pageSize);
+
+                var paginationItems = itemsStanding
+                    .Skip(pageNumber * pageSize)
+                    .Take(pageSize)
+                    .ToList();
+
+                return DotnetLib.Http.HttpResponse.BuildResponse(
+                    log, url, json,
+                    CommonErrorCode.Success,
+                    ConfigManager.Instance.GetConfigWebValue("LOAD_SUCCESS", lang),
+                    new
+                    {
+                        history = paginationItems,
+                        pagination = new
+                        {
+                            pageNumber,
+                            pageSize,
+                            totalCount = totalCountStanding,
+                            totalPages = totalPagesStanding
+                        }
+                    }
+                );
+            }
+            catch (Exception ex)
+            {
+                log.Error("Exception: ", ex);
+            }
+
+            return DotnetLib.Http.HttpResponse.BuildResponse(
+                log, url, json,
+                CommonErrorCode.SystemError,
+                ConfigManager.Instance.GetConfigWebValue("SYSTEM_FAILURE"),
+                new { }
+            );
+        }
     }
 }

+ 5 - 0
SicboSub/SicboSub.Api/Business/IGameBusiness.cs

@@ -19,5 +19,10 @@ namespace SicboSub.Api.Business
         /// Get purchase history (reg/buymore logs)
         /// </summary>
         Task<IActionResult> PurchaseHistory(HttpRequest httpRequest, PurchaseHistoryReq request);
+
+        /// <summary>
+        /// Get ranking coin standing (daily/monthly standings without reward log)
+        /// </summary>
+        Task<IActionResult> RankingCoinHistory(HttpRequest httpRequest, RankingHistoryReq request);
     }
 }

+ 11 - 0
SicboSub/SicboSub.Api/Controllers/GameController.cs

@@ -44,5 +44,16 @@ namespace SicboSub.Api.Controllers
         {
             return await gameBusiness.PurchaseHistory(HttpContext.Request, request);
         }
+
+        /// <summary>
+        /// Get ranking coin standing (daily/monthly standings without reward log)
+        /// POST /apis/game/ranking_coin_history
+        /// </summary>
+        [HttpPost]
+        [Route(ApiUrlConstant.RankingCointHistoryUrl)]
+        public async Task<IActionResult> RankingCoinHistory([FromBody] RankingHistoryReq request)
+        {
+             return await gameBusiness.RankingCoinHistory(HttpContext.Request, request);
+        }
     }
 }

+ 6 - 0
SicboSub/SicboSub.Api/DTO/GameDto.cs

@@ -52,6 +52,12 @@ namespace SicboSub.Api.DTO
         public string? toDate { get; set; }
         public int pageNumber { get; set; } = 0;
         public int pageSize { get; set; } = 20;
+
+        /// <summary>
+        /// Optional flag to load directly from RankingHistory without RewardLogs
+        /// </summary>
+        public bool? onlyRanking { get; set; }
+
     }
 
     /// <summary>

+ 6 - 6
SicboSub/SicboSub.Api/appsettings.json

@@ -1,11 +1,11 @@
 {
-  //"Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1539))(CONNECT_DATA=(SERVICE_NAME=ORA12C)));User Id=sicbo;Password=qwK99QfuhEmcFE;Connection Timeout=120;",
-  //"wsUser": "sicboSub",
-  //"wsPass": "123456a@",
-  "Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=pdbvas1)));User Id=SICBO;Password=S1cbo$2025;Connection Timeout=120;",
+  "Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1539))(CONNECT_DATA=(SERVICE_NAME=ORA12C)));User Id=sicbo;Password=qwK99QfuhEmcFE;Connection Timeout=120;",
+  "wsUser": "sicboSub",
+  "wsPass": "123456a@",
+  //"Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=pdbvas1)));User Id=SICBO;Password=S1cbo$2025;Connection Timeout=120;",
   "wsUrl": "http://127.0.0.1:8199/SicboSubWs?wsdl",
-  "wsUser": "ws_SicboSub",
-  "wsPass": "SicboSub@123",
+  //"wsUser": "ws_SicboSub",
+  //"wsPass": "SicboSub@123",
   "PackageCodeDaily": "SICBO_DAILY",
   "Kestrel": {
     "EndPoints": {

+ 199 - 6
SicboSub/SicboSub.Web/Controllers/HomeController.cs

@@ -499,7 +499,7 @@ namespace SicboSub.Web.Controllers
 
                 if (!string.IsNullOrEmpty(specificDate))
                 {
-                    if (rankType == "MONTHLY")
+                    if (rankType == "MONTHLY" || rankType == "MONTHLYCOIN")
                     {
                         // Expected input: yyyy-MM
                         if (DateTime.TryParseExact(specificDate, "yyyy-MM", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime parsedMonth))
@@ -521,7 +521,23 @@ namespace SicboSub.Web.Controllers
                              }
                         }
                     }
-                    else 
+                    else if (rankType == "DAILYCOIN")
+                    {
+                        if (DateTime.TryParseExact(specificDate, "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime parsedDate))
+                        {
+                            fromDate = parsedDate.ToString("dd/MM/yyyy 00:00:00");
+                            toDate = parsedDate.ToString("dd/MM/yyyy 23:59:59");
+                        }
+                        else
+                        {
+                            // Fallback try basic parsing
+                            if (DateTime.TryParse(specificDate, out DateTime parsed))
+                            {
+                                fromDate = parsed.ToString("dd/MM/yyyy 00:00:00");
+                                toDate = parsed.ToString("dd/MM/yyyy 23:59:59");
+                            }
+                        }
+                    } else
                     {
                         // DAILY - Exact Date
                         // Convert yyyy-MM-dd to dd/MM/yyyy HH:mm:ss
@@ -541,12 +557,12 @@ namespace SicboSub.Web.Controllers
                 }
                 else
                 {
-                    if (rankType == "DAILY")
+                    if (rankType == "DAILY" || rankType == "DAILYCOIN")
                     {
                         fromDate = DateTime.Now.ToString("dd/MM/yyyy 00:00:00");
                         toDate = DateTime.Now.ToString("dd/MM/yyyy 23:59:59");
                     }
-                    else if (rankType == "MONTHLY")
+                    else if (rankType == "MONTHLY" || rankType == "MONTHLYCOIN")
                     {
                         var firstDay = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
                         var lastDay = firstDay.AddMonths(1).AddDays(-1);
@@ -559,11 +575,11 @@ namespace SicboSub.Web.Controllers
                 {
                     msisdn = msisdn, // Pass specific msisdn or null/empty
                     lang = GetLanguage(),
-                    rankType = rankType,
+                    rankType = (rankType == "DAILY" || rankType == "DAILYCOIN") ? "DAILY" : "MONTHLY",
                     fromDate = fromDate,
                     toDate = toDate,
                     pageNumber = 0,
-                    pageSize = rankType == "DAILY" ? 50 : 5
+                    pageSize = (rankType == "DAILY" || rankType == "DAILYCOIN" || rankType == "MONTHLYCOIN") ? 50 : 5
                 };
                 
                 var url = GetParameter("Url") + ApiUrlConstant.RankingHistoryUrl;
@@ -583,6 +599,14 @@ namespace SicboSub.Web.Controllers
                      RankingHistoryRes response = new RankingHistoryRes(resData.data);
                      if (response.errorCode == CommonErrorCode.Success && response.data != null)
                      {
+                         string currentMsisdn = GetMsisdn();
+                         foreach (var item in response.data)
+                         {
+                             if (item.msisdn != currentMsisdn)
+                             {
+                                 item.msisdn = MaskMsisdn(item.msisdn);
+                             }
+                         }
                          return response.data;
                      }
                 }
@@ -594,6 +618,150 @@ namespace SicboSub.Web.Controllers
             return new List<RankingHistoryItem>();
         }
 
+        [HttpPost]
+        public async Task<IActionResult> GetRankingCoinData([FromBody] RankingHistoryReq request)
+        {
+            if (!IsAuthenticated()) return Json(new { errorCode = CommonErrorCode.UnauthorizedAccess });
+
+            try
+            {
+                string rankType = request?.rankType ?? "MONTHLYCOIN";
+
+                var specificDate = rankType == "DAILYCOIN" ? DateTime.Now.ToString("yyyy-MM-dd") : DateTime.Now.ToString("yyyy-MM");
+                
+                var globalRankings = await LoadRankingCoinHistoryInternal(rankType, null, specificDate);
+                var userRanking = await LoadRankingCoinHistoryInternal(rankType, GetMsisdn(), specificDate);
+                var currentUserItem = userRanking?.FirstOrDefault(x => x.msisdn == GetMsisdn());
+
+                return Json(new { 
+                    errorCode = CommonErrorCode.Success, 
+                    data = new { global = globalRankings, user = currentUserItem } 
+                });
+            }
+            catch (Exception ex)
+            {
+                log.Error("GetRankingCoinData: Error", ex);
+                return Json(new { errorCode = CommonErrorCode.SystemError });
+            }
+        }
+
+
+        private async Task<List<RankingHistoryItem>> LoadRankingCoinHistoryInternal(string rankType, string msisdn = null, string specificDate = null)
+        {
+            try
+            {
+                var token = GetToken();
+                // Calculate date range based on rankType
+                string fromDate = "";
+                string toDate = DateTime.Now.ToString("dd/MM/yyyy");
+
+                if (!string.IsNullOrEmpty(specificDate))
+                {
+                    if (rankType == "MONTHLYCOIN")
+                    {
+                        // Expected input: yyyy-MM
+                        if (DateTime.TryParseExact(specificDate, "yyyy-MM", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime parsedMonth))
+                        {
+                            var firstDay = new DateTime(parsedMonth.Year, parsedMonth.Month, 1);
+                            var lastDay = firstDay.AddMonths(1).AddDays(-1);
+
+                            fromDate = firstDay.ToString("dd/MM/yyyy 00:00:00");
+                            toDate = lastDay.ToString("dd/MM/yyyy 23:59:59");
+                        }
+                        else
+                        {
+                            // Fallback try basic parsing
+                            if (DateTime.TryParse(specificDate, out DateTime parsed))
+                            {
+                                var firstDay = new DateTime(parsed.Year, parsed.Month, 1);
+                                var lastDay = firstDay.AddMonths(1).AddDays(-1);
+                                fromDate = firstDay.ToString("dd/MM/yyyy 00:00:00");
+                                toDate = lastDay.ToString("dd/MM/yyyy 23:59:59");
+                            }
+                        }
+                    }
+                    else if (rankType == "DAILYCOIN")
+                    {
+                        if (DateTime.TryParseExact(specificDate, "yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out DateTime parsedDate))
+                        {
+                            fromDate = parsedDate.ToString("dd/MM/yyyy 00:00:00");
+                            toDate = parsedDate.ToString("dd/MM/yyyy 23:59:59");
+                        }
+                        else
+                        {
+                            // Fallback try basic parsing
+                            if (DateTime.TryParse(specificDate, out DateTime parsed))
+                            {
+                                fromDate = parsed.ToString("dd/MM/yyyy 00:00:00");
+                                toDate = parsed.ToString("dd/MM/yyyy 23:59:59");
+                            }
+                        }
+                    }
+                    
+                }
+                else
+                {
+                    if (rankType == "DAILYCOIN")
+                    {
+                        fromDate = DateTime.Now.ToString("dd/MM/yyyy 00:00:00");
+                        toDate = DateTime.Now.ToString("dd/MM/yyyy 23:59:59");
+                    }
+                    else if (rankType == "MONTHLYCOIN")
+                    {
+                        var firstDay = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1);
+                        var lastDay = firstDay.AddMonths(1).AddDays(-1);
+                        fromDate = firstDay.ToString("dd/MM/yyyy 00:00:00");
+                        toDate = lastDay.ToString("dd/MM/yyyy 23:59:59");
+                    }
+                }
+
+                var request = new RankingHistoryReq
+                {
+                    msisdn = msisdn, // Pass specific msisdn or null/empty
+                    lang = GetLanguage(),
+                    rankType = (rankType == "DAILY" || rankType == "DAILYCOIN") ? "DAILY" : "MONTHLY",
+                    fromDate = fromDate,
+                    toDate = toDate,
+                    pageNumber = 0,
+                    pageSize = (rankType == "DAILY" || rankType == "DAILYCOIN" || rankType == "MONTHLYCOIN") ? 50 : 5
+                };
+
+                var url = GetParameter("Url") + ApiUrlConstant.RankingCointHistoryUrl;
+
+                var resData = await DotnetLib.Rest.RestHandler.SendPostWithAuthen(
+                    log,
+                    url,
+                    request,
+                    token,
+                    token,
+                    "",
+                    GetLanguage()
+                );
+
+                if (resData != null)
+                {
+                    RankingHistoryRes response = new RankingHistoryRes(resData.data);
+                    if (response.errorCode == CommonErrorCode.Success && response.data != null)
+                    {
+                        string currentMsisdn = GetMsisdn();
+                        foreach (var item in response.data)
+                        {
+                            if (item.msisdn != currentMsisdn)
+                            {
+                                item.msisdn = MaskMsisdn(item.msisdn);
+                            }
+                        }
+                        return response.data;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error($"LoadRankingInternal ({rankType}): Error", ex);
+            }
+            return new List<RankingHistoryItem>();
+        }
+
         public async Task<IActionResult> DailyRanking(string date = null)
         {
             if (!IsAuthenticated()) return RedirectToLogin(_configuration);
@@ -929,5 +1097,30 @@ namespace SicboSub.Web.Controllers
             var packages = await LoadPackagesInternal();
             return Json(new { errorCode = CommonErrorCode.Success, data = packages });
         }
+
+        private string MaskMsisdn(string msisdn)
+        {
+            if (string.IsNullOrEmpty(msisdn)) return msisdn;
+            
+            int len = msisdn.Length;
+            if (len <= 5) return msisdn;
+            
+            if (len == 10)
+            {
+                return msisdn.Substring(0, 3) + "xxxxx" + msisdn.Substring(8);
+            }
+            else if (len == 11)
+            {
+                return msisdn.Substring(0, 3) + "xxxxx" + msisdn.Substring(8);
+            }
+            else
+            {
+                if (len > 5) {
+                    int maskStart = (len - 5) / 2;
+                    return msisdn.Substring(0, maskStart) + "xxxxx" + msisdn.Substring(maskStart + 5);
+                }
+                return msisdn;
+            }
+        }
     }
 }

+ 161 - 1
SicboSub/SicboSub.Web/Views/Home/Index.cshtml

@@ -29,7 +29,7 @@
             <div class="medal-parent">
                 <img class="hourglass-icon" src="~/img/021-medal-2.svg" alt="">
                 <div class="ranking-coin">@Language.Rankingcoin</div>
-                <a class="view-ranking-coin">@Language.ViewRankingCoin</a>
+                <a class="view-ranking-coin" style="cursor: pointer;" onclick="handleRankingCoinClick()">@Language.ViewRankingCoin</a>
             </div>
             <div class="frame-group">
                 <div class="frame-container">
@@ -97,6 +97,53 @@
     </div>
     <!-- <img class="home-barpro-icon" alt=""> -->
     }
+    <style>
+        .ranking-content::-webkit-scrollbar {
+            width: 4px;
+        }
+        .ranking-content::-webkit-scrollbar-track {
+            background: transparent;
+        }
+        .ranking-content::-webkit-scrollbar-thumb {
+            background: #8b5a2b; /* Brown colour */
+            border-radius: 4px;
+        }
+        .ranking-content {
+            scrollbar-width: thin;
+            scrollbar-color: #8b5a2b transparent;
+        }
+    </style>
+
+    <div id="rankingCoinPopup" class="popup-overlay" style="display: none;">
+        <div class="popup-container" style="max-width: 420px; width: 90%;">
+            <div class="popup-header">
+                <span class="popup-title">@Language.Rankingcoin</span>
+            </div>
+            
+            <!-- Toggle Tab Buttons in Normal Flow -->
+            <div style="display: flex; gap: 10px; margin: 10px 10px 5px 10px; background: rgba(255, 255, 255, 0.04); padding: 5px; border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.1);">
+                <button id="rankDailyBtn" style="flex: 1; padding: 7px; border-radius: 6px; border: 1px solid rgba(255,255,255,0.2); background: transparent; color: white; cursor: pointer; font-size: 14px; transition: all 0.2s;" onclick="switchRankingType('DAILYCOIN')">Daily</button>
+                <button id="rankMonthlyBtn" style="flex: 1; padding: 7px; border-radius: 6px; border: 1px solid #ffd700; background: rgba(255,215,0,0.2); color: white; font-weight: bold; cursor: pointer; font-size: 14px; transition: all 0.2s;" onclick="switchRankingType('MONTHLYCOIN')">Monthly</button>
+            </div>
+
+            <button class="popup-close-btn" onclick="closeRankingCoinPopup()">
+                <svg width="24" height="24" viewBox="0 0 24 24" fill="white">
+                    <path d="M18.3 5.71a1 1 0 0 0-1.41 0L12 10.59 7.11 5.7a1 1 0 0 0-1.41 1.41L10.59 12l-4.89 4.89a1 1 0 1 0 1.41 1.41L12 13.41l4.89 4.89a1 1 0 0 0 1.41-1.41L13.41 12l4.89-4.89a1 1 0 0 0 0-1.4z"/>
+                </svg>
+            </button>
+            
+            <div class="ranking-content" style="position: relative; max-height: 210px; overflow-y: auto; color: white; padding: 10px; padding-top: 45px;">
+                <div id="myRankingInfo" style="position: absolute; top: 10px; right: 10px; z-index: 10; padding: 4px 12px; border: 1px solid #ffd700; border-radius: 4px; background: rgba(0, 0, 0, 0.82); font-size: 13px; font-weight: bold; box-shadow: 0 2px 8px rgba(0,0,0,0.4);">
+                    <!-- User rank injected here -->
+                </div>
+                
+                <div id="globalRankingList" style="display: flex; flex-direction: column; gap: 8px;">
+                    <!-- Global list injected here -->
+                </div>
+            </div>
+        </div>
+    </div>
+
     <!-- Select Package Popup -->
     <div id="selectPackagePopup" class="popup-overlay" style="display: none;">
         <div class="popup-container">
@@ -855,6 +902,119 @@
             }
         });
 
+        document.getElementById('rankingCoinPopup')?.addEventListener('click', function(e) {
+            if (e.target === this) {
+                closeRankingCoinPopup();
+            }
+        });
+
+        let currentRankType = 'MONTHLYCOIN'; // Default
+
+        function handleRankingCoinClick() {
+            Site.showLoading();
+            API.post('/Home/GetRankingCoinData', { rankType: currentRankType }, function(response) {
+                Site.hideLoading();
+                if (response.errorCode === 0 || response.errorCode === '0') {
+                    document.getElementById('rankingCoinPopup').style.display = 'flex';
+                    renderRankingCoinData(response.data);
+                } else {
+                    Site.showError('Failed to load ranking data');
+                }
+            });
+        }
+
+        function closeRankingCoinPopup() {
+            document.getElementById('rankingCoinPopup').style.display = 'none';
+        }
+
+        function switchRankingType(type) {
+            currentRankType = type;
+            const dailyBtn = document.getElementById('rankDailyBtn');
+            const monthlyBtn = document.getElementById('rankMonthlyBtn');
+
+            if (type === 'DAILYCOIN') {
+                dailyBtn.style.border = '1px solid #ffd700';
+                dailyBtn.style.background = 'rgba(255,215,0,0.2)';
+                dailyBtn.style.fontWeight = 'bold';
+
+                monthlyBtn.style.border = '1px solid rgba(255,255,255,0.3)';
+                monthlyBtn.style.background = 'transparent';
+                monthlyBtn.style.fontWeight = 'normal';
+            } else {
+                monthlyBtn.style.border = '1px solid #ffd700';
+                monthlyBtn.style.background = 'rgba(255,215,0,0.2)';
+                monthlyBtn.style.fontWeight = 'bold';
+
+                dailyBtn.style.border = '1px solid rgba(255,255,255,0.3)';
+                dailyBtn.style.background = 'transparent';
+                dailyBtn.style.fontWeight = 'normal';
+            }
+
+            Site.showLoading();
+            API.post('/Home/GetRankingCoinData', { rankType: type }, function(response) {
+                Site.hideLoading();
+                if (response.errorCode === 0 || response.errorCode === '0') {
+                    renderRankingCoinData(response.data);
+                } else {
+                    Site.showError('Failed to load ranking data');
+                }
+            });
+        }
+
+        function renderRankingCoinData(data) {
+            const myList = document.getElementById('myRankingInfo');
+            const globalList = document.getElementById('globalRankingList');
+            myList.innerHTML = '';
+            globalList.innerHTML = '';
+
+            if (data.user) {
+                const rankPos = data.user.rankPosition;
+                let rankBadge = `<span>Rank: <strong>${rankPos}</strong></span>`;
+                if (rankPos === 1) rankBadge = `<span style="color: #ffd700;">🥇 Rank 1</span>`;
+                else if (rankPos === 2) rankBadge = `<span style="color: #c0c0c0;">🥈 Rank 2</span>`;
+                else if (rankPos === 3) rankBadge = `<span style="color: #cd7f32;">🥉 Rank 3</span>`;
+
+                myList.innerHTML = `<div style="display: flex; gap: 8px; align-items: center;">
+                                        ${rankBadge}
+                                        <span style="opacity: 0.8; border-left: 1px solid rgba(255,255,255,0.3); padding-left: 8px; font-size: 13px;">${data.user.msisdn}</span>
+                                        <span style="color:#ffd700; font-weight: bold;">${Site.formatNumber(data.user.totalBetValue)}</span>
+                                    </div>`;
+            }
+
+            if (data.global && data.global.length > 0) {
+                data.global.forEach(item => {
+                    const rankPos = item.rankPosition;
+                    let rankDisplay = `<span style="width: 24px; text-align: center; font-weight: bold; font-family: sans-serif; opacity: 0.9;">${rankPos}</span>`;
+                    let bgColor = 'rgba(255, 255, 255, 0.02)';
+                    let borderColor = 'rgba(255, 255, 255, 0.04)';
+                    
+                    if (rankPos === 1) {
+                        rankDisplay = `<span style="font-size: 20px;">🥇</span>`;
+                        bgColor = 'rgba(255, 215, 0, 0.08)';
+                        borderColor = 'rgba(255, 215, 0, 0.15)';
+                    } else if (rankPos === 2) {
+                        rankDisplay = `<span style="font-size: 20px;">🥈</span>`;
+                        bgColor = 'rgba(192, 192, 192, 0.06)';
+                        borderColor = 'rgba(192, 192, 192, 0.12)';
+                    } else if (rankPos === 3) {
+                        rankDisplay = `<span style="font-size: 20px;">🥉</span>`;
+                        bgColor = 'rgba(205, 127, 50, 0.06)';
+                        borderColor = 'rgba(205, 127, 50, 0.12)';
+                    }
+
+                    globalList.innerHTML += `<div class="rank-row" style="display: flex; justify-content: space-between; align-items: center; padding: 12px 15px; border-radius: 8px; background: ${bgColor}; border: 1px solid ${borderColor}; margin-bottom: 2px;">
+                        <div style="display: flex; align-items: center; gap: 12px;">
+                            ${rankDisplay}
+                            <span style="font-family: monospace; font-size: 14px; color: #f0f0f0;">${item.msisdn}</span>
+                        </div>
+                        <span style="color:#ffd700; font-weight: bold; font-size: 14px;">${Site.formatNumber(item.totalBetValue)} <span style="font-size: 11px; opacity: 0.5; font-weight: normal;">COIN</span></span>
+                    </div>`;
+                });
+            } else {
+                globalList.innerHTML = `<div style="text-align: center; padding: 25px; opacity: 0.5; font-style: italic;">No rankings found</div>`;
+            }
+        }
+
         function handlePlay() {
             // TODO: Implement play logic
             // Site.showSuccess('Starting game...'); Use new popup example

+ 1 - 1
SicboSub/SicboSub.Web/wwwroot/css/sicbo-ranking.css

@@ -132,7 +132,7 @@
 
 .calendar-popup {
     width: 80%;
-    max-width: 360px;
+    max-width: 300px;
     background: #F0F0F0;
     border-radius: 24px;
     padding: 24px;