Ducnt 3 nedēļas atpakaļ
vecāks
revīzija
c4e52aa8c4
46 mainītis faili ar 9130 papildinājumiem un 297 dzēšanām
  1. BIN
      10102025_CMS_BalancePlus_Mytel (4).xlsx
  2. 10 16
      ApiWeb/ApiProcessToken/Controllers/balance.cs
  3. 1571 0
      ApiWeb/ApiProcessToken/Controllers/systemApi.cs
  4. 4 0
      ApiWeb/ApiProcessToken/Models/banlance/apiServiceObj.cs
  5. 383 8
      ApiWeb/ApiProcessToken/Models/banlance/balanceDataAccess.cs
  6. 230 0
      ApiWeb/ApiProcessToken/Models/reportCountDailyObj.cs
  7. 378 0
      ApiWeb/ApiProcessToken/Models/systemDataAccess.cs
  8. 596 0
      ApiWeb/ApiProcessToken/Models/systemUserDataAccess.cs
  9. 108 0
      ApiWeb/ApiProcessToken/Models/systemUserObj.cs
  10. 847 2
      SuperAdmin/SuperAdmin/Controllers/AdminController.cs
  11. 1 1
      SuperAdmin/SuperAdmin/Controllers/HomeController.cs
  12. 1 0
      SuperAdmin/SuperAdmin/Models/Http/ApiService.cs
  13. 1 0
      SuperAdmin/SuperAdmin/Models/Http/ApiServiceUpdate.cs
  14. 2 0
      SuperAdmin/SuperAdmin/Models/Http/Campaign.cs
  15. 1 0
      SuperAdmin/SuperAdmin/Models/Http/CampaignList.cs
  16. 194 0
      SuperAdmin/SuperAdmin/Models/Http/ReportCountDaily.cs
  17. 1 0
      SuperAdmin/SuperAdmin/Models/Http/Service.cs
  18. 190 0
      SuperAdmin/SuperAdmin/Models/Http/UserManagement.cs
  19. 1 1
      SuperAdmin/SuperAdmin/Properties/PublishProfiles/FolderProfile.pubxml
  20. 14 0
      SuperAdmin/SuperAdmin/Source/CommonUtils.cs
  21. 9 0
      SuperAdmin/SuperAdmin/Views/Admin/ApiWebserviceManagement.cshtml
  22. 82 46
      SuperAdmin/SuperAdmin/Views/Admin/CampaignManagement.cshtml
  23. 14 26
      SuperAdmin/SuperAdmin/Views/Admin/CampaignScheduler.cshtml
  24. 1655 0
      SuperAdmin/SuperAdmin/Views/Admin/DashboardReport.cshtml
  25. 473 0
      SuperAdmin/SuperAdmin/Views/Admin/FunctionManagement.cshtml
  26. 508 0
      SuperAdmin/SuperAdmin/Views/Admin/ReportCountDaily.cshtml
  27. 144 0
      SuperAdmin/SuperAdmin/Views/Admin/ReportUssdDetail.cshtml
  28. 16 5
      SuperAdmin/SuperAdmin/Views/Admin/ServiceManagement.cshtml
  29. 334 0
      SuperAdmin/SuperAdmin/Views/Admin/UserManagement.cshtml
  30. 2 6
      SuperAdmin/SuperAdmin/Views/Home/Login.cshtml
  31. 4 2
      SuperAdmin/SuperAdmin/Views/Partial/_ApiWebserviceManagement.cshtml
  32. 15 31
      SuperAdmin/SuperAdmin/Views/Partial/_Campaign.cshtml
  33. 269 0
      SuperAdmin/SuperAdmin/Views/Partial/_CampaignSchedulerCalendar.cshtml
  34. 13 2
      SuperAdmin/SuperAdmin/Views/Partial/_CampaignSchedulerTable.cshtml
  35. 8 8
      SuperAdmin/SuperAdmin/Views/Partial/_CampaignService.cshtml
  36. 51 0
      SuperAdmin/SuperAdmin/Views/Partial/_Functions.cshtml
  37. 71 9
      SuperAdmin/SuperAdmin/Views/Partial/_Menu.cshtml
  38. 92 0
      SuperAdmin/SuperAdmin/Views/Partial/_ReportCountDaily.cshtml
  39. 47 0
      SuperAdmin/SuperAdmin/Views/Partial/_ReportErrorDaily.cshtml
  40. 58 0
      SuperAdmin/SuperAdmin/Views/Partial/_ReportUssdDetail.cshtml
  41. 64 0
      SuperAdmin/SuperAdmin/Views/Partial/_Users.cshtml
  42. 15 1
      SuperAdmin/SuperAdmin/appsettings.json
  43. 49 117
      SuperAdmin/SuperAdmin/wwwroot/css/login.css
  44. 19 15
      SuperAdmin/SuperAdmin/wwwroot/css/menu-custom.css
  45. 585 1
      SuperAdmin/SuperAdmin/wwwroot/css/site.css
  46. BIN
      SuperAdmin/SuperAdmin/wwwroot/images/pexels-tirachard-kumtanom-112571-733852.jpg

BIN
10102025_CMS_BalancePlus_Mytel (4).xlsx


+ 10 - 16
ApiWeb/ApiProcessToken/Controllers/balance.cs

@@ -3567,6 +3567,7 @@ namespace ApiProcess.Controllers
                 string msgConfirm = Convert.ToString(userObj["msgConfirm"]);
                 string serviceGroupId = Convert.ToString(userObj["serviceGroupId"]);
                 string apiServiceId = Convert.ToString(userObj["apiServiceId"]);
+                string isMyservice = Convert.ToString(userObj["isMyservice"]);
 
                 if (string.IsNullOrEmpty(msgRegisterSuccess)) msgRegisterSuccess = "-1";
                 if (string.IsNullOrEmpty(msgRegisterFlase)) msgRegisterFlase = "-1";
@@ -3651,7 +3652,7 @@ namespace ApiProcess.Controllers
 
 
                 DataSet ds_regist = balanceDataAccess.svInsert(code, name, description, shortCode, command, contentEn, contentFr,
-                    contentLc,note, companyId, users, msgRegisterSuccess, msgRegisterFlase, msgConfirm, serviceGroupId, apiServiceId);
+                    contentLc,note, companyId, users, msgRegisterSuccess, msgRegisterFlase, msgConfirm, serviceGroupId, apiServiceId,isMyservice);
                 logger.Info("Call database svInsert success:");
                 if (ds_regist != null & ds_regist.Tables[0].Rows.Count > 0)
                 {
@@ -3723,6 +3724,8 @@ namespace ApiProcess.Controllers
 
                 string serviceGroupId = Convert.ToString(userObj["serviceGroupId"]);
                 string apiServiceId = Convert.ToString(userObj["apiServiceId"]);
+                string isMyservice = Convert.ToString(userObj["isMyservice"]);
+
 
                 string type = Convert.ToString(userObj["type"]);
 
@@ -3815,7 +3818,7 @@ namespace ApiProcess.Controllers
 
 
                 DataSet ds_regist = balanceDataAccess.svUpdate(id, code, name, description, shortCode, command, contentEn, contentFr,
-                    contentLc, note, companyId, users, type, msgRegisterSuccess, msgRegisterFlase, msgConfirm, serviceGroupId, apiServiceId);
+                    contentLc, note, companyId, users, type, msgRegisterSuccess, msgRegisterFlase, msgConfirm, serviceGroupId, apiServiceId, isMyservice);
                 logger.Info("Call database svUpdate success:");
                 if (ds_regist != null & ds_regist.Tables[0].Rows.Count > 0)
                 {
@@ -8874,8 +8877,6 @@ namespace ApiProcess.Controllers
 
                 }    
 
-          
-
                 logger.Info("Call database balGetList success:");
                 response.responseCode = "0";
                 response.responseMessage = "Success";
@@ -8884,16 +8885,6 @@ namespace ApiProcess.Controllers
                 response.list = _listCalendar;
 
 
-
-
-
-
-
-
-
-
-
-
             }
             catch (Exception ex)
             {
@@ -9330,6 +9321,7 @@ namespace ApiProcess.Controllers
                         _obj.wsdl = ds_regist.Tables[0].Rows[j]["WSDL"].ToString();
                         _obj.msg_template = ds_regist.Tables[0].Rows[j]["MSG_TEMPLATE"].ToString();
                         _obj.error_tag = ds_regist.Tables[0].Rows[j]["ERROR_TAG"].ToString();
+                        _obj.success_code = ds_regist.Tables[0].Rows[j]["SUCCESS_CODE"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["SUCCESS_CODE"].ToString() : "";
 
                         _obj.isActive = ds_regist.Tables[0].Rows[j]["IS_ACTIVE"].ToString();
 
@@ -9372,6 +9364,7 @@ namespace ApiProcess.Controllers
                 string wsdl = Convert.ToString(userObj["wsdl"]);
                 string msg_template = Convert.ToString(userObj["msg_template"]);
                 string error_tag = Convert.ToString(userObj["error_tag"]);
+                string success_code = Convert.ToString(userObj["success_code"]);
                 string isActive = Convert.ToString(userObj["isActive"]);
 
                 string users = Convert.ToString(userObj["users"]);
@@ -9384,6 +9377,7 @@ namespace ApiProcess.Controllers
                 if (string.IsNullOrEmpty(wsdl)) wsdl = "";
                 if (string.IsNullOrEmpty(msg_template)) msg_template = "";
                 if (string.IsNullOrEmpty(error_tag)) error_tag = "";
+                if (string.IsNullOrEmpty(success_code)) success_code = "";
                 if (string.IsNullOrEmpty(isActive)) isActive = "1";
 
 
@@ -9462,12 +9456,12 @@ namespace ApiProcess.Controllers
                 DataSet ds_regist;
                 if (id != null && id != "" && id != "-1")
                 {
-                    ds_regist = balanceDataAccess.apiServiceUpdate(id, ws_name, ws_code, wsdl, msg_template, error_tag, isActive, users);
+                    ds_regist = balanceDataAccess.apiServiceUpdate(id, ws_name, ws_code, wsdl, msg_template, error_tag, success_code, isActive, users);
                     logger.Info("Call database apiServiceUpdate success:");
                 }
                 else
                 {
-                    ds_regist = balanceDataAccess.apiServiceInsert(ws_name, ws_code, wsdl, msg_template, error_tag, isActive, users);
+                    ds_regist = balanceDataAccess.apiServiceInsert(ws_name, ws_code, wsdl, msg_template, error_tag, success_code, isActive, users);
                     logger.Info("Call database apiServiceInsert success:");
                 }
                 if (ds_regist != null & ds_regist.Tables[0].Rows.Count > 0)

+ 1571 - 0
ApiWeb/ApiProcessToken/Controllers/systemApi.cs

@@ -5,11 +5,17 @@ using System.Net;
 using System.Net.Http;
 
 using ResfullApi.Models;
+using ResfullApi.Models.balance;
 using Newtonsoft.Json;
 using System.Data;
 using System.IO;
 using System.Xml;
 using Microsoft.AspNetCore.Mvc;
+using Newtonsoft.Json.Linq;
+using Microsoft.Extensions.Caching.Memory;
+using CommonObj.common;
+using CommonObj.model;
+using StackExchange.Redis;
 
 namespace ApiProcess.Controllers
 {
@@ -18,6 +24,11 @@ namespace ApiProcess.Controllers
     public class systemApi : ControllerBase
     {
         static readonly log4net.ILog logger = log4net.LogManager.GetLogger(typeof(systemApi));
+        private IMemoryCache memoryCache;
+        public systemApi(IMemoryCache memoryCache)
+        {
+            this.memoryCache = memoryCache;
+        }
         // {"msisdn":"50940227941","serviceId":"23","money":"2000"}
         [HttpPost]
         public IActionResult systemCharge([FromBody] dynamic sendData)
@@ -195,8 +206,1568 @@ namespace ApiProcess.Controllers
             return rp;
         }
 
+        // USER WEB CMS MANAGEMENT APIs
+        [HttpPost]
+        public IActionResult sysUserWebCmsGetList([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysUserWebCmsGetList :" + sendData.ToString());
+            userWebCmsObjList response = new userWebCmsObjList();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string users = Convert.ToString(userObj["users"]);
+                string id = Convert.ToString(userObj["id"]);
+                string username = Convert.ToString(userObj["username"]);
+                string role = Convert.ToString(userObj["role"]);
+                string isLock = Convert.ToString(userObj["isLock"]);
+                string order = Convert.ToString(userObj["order"]);
+                string rowsOnPage = Convert.ToString(userObj["rowsOnPage"]);
+                string seqPage = Convert.ToString(userObj["seqPage"]);
+                
+                if (string.IsNullOrEmpty(users)) users = "-1";
+                if (string.IsNullOrEmpty(id)) id = "-1";
+                if (string.IsNullOrEmpty(username)) username = "-1";
+                if (string.IsNullOrEmpty(role)) role = "-1";
+                if (string.IsNullOrEmpty(isLock)) isLock = "-1";
+                if (string.IsNullOrEmpty(order)) order = "asc";
+                if (string.IsNullOrEmpty(rowsOnPage)) rowsOnPage = "1000000000000";
+                if (string.IsNullOrEmpty(seqPage)) seqPage = "1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                //---------------------Lay ra va luu mot redis tu cache-----------------------------
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("confirmTicket response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+                //----------------------Ket thuc lay redis tu cache -------------------------------------
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("confirmTicket response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds_regist = systemUserDataAccess.SYS_USER_WEB_CMS_GET_LIST(users, id, username, role, isLock, order, rowsOnPage, seqPage);
+                logger.Info("Call database SYS_USER_WEB_CMS_GET_LIST success:");
+                
+                response.responseCode = "0";
+                response.responseMessage = "Success";
+                
+                if (ds_regist != null && ds_regist.Tables.Count > 0 && ds_regist.Tables[0].Rows.Count > 0)
+                {
+                    response.rowsOnPage = ds_regist.Tables[0].Rows[0]["ROW_ON_PAGE"].ToString();
+                    response.seqPage = ds_regist.Tables[0].Rows[0]["SEQ_PAGE"].ToString();
+                    response.totalPage = ds_regist.Tables[0].Rows[0]["TOTAL_PAGE"].ToString();
+
+                    response.list = new userWebCmsObj[ds_regist.Tables[0].Rows.Count];
+                    for (int j = 0; j < ds_regist.Tables[0].Rows.Count; j++)
+                    {
+                        userWebCmsObj _obj = new userWebCmsObj();
+
+                        _obj.id = ds_regist.Tables[0].Rows[j]["ID"].ToString();
+                        _obj.username = ds_regist.Tables[0].Rows[j]["USERNAME"].ToString();
+                        _obj.password = ds_regist.Tables[0].Rows[j]["PASSWORD"].ToString();
+                        _obj.role = ds_regist.Tables[0].Rows[j]["ROLE"].ToString();
+                        _obj.countryCode = ds_regist.Tables[0].Rows[j]["COUNTRY_CODE"].ToString();
+                        _obj.isLock = ds_regist.Tables[0].Rows[j]["IS_LOCK"].ToString();
+                        _obj.totalFalse = ds_regist.Tables[0].Rows[j]["TOTAL_FALSE"].ToString();
+                        _obj.timeLock = ds_regist.Tables[0].Rows[j]["TIME_LOCK"].ToString();
+                        _obj.note = ds_regist.Tables[0].Rows[j]["NOTE"].ToString();
+
+                        response.list[j] = _obj;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("Response to web: " + response.ToString());
+            return Ok(response);
+        }
+
+        [HttpPost]
+        public IActionResult sysUserWebCmsInsert([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysUserWebCmsInsert :" + sendData.ToString());
+            Response response = new Response();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string username = Convert.ToString(userObj["username"]);
+                string password = Convert.ToString(userObj["password"]);
+                string role = Convert.ToString(userObj["role"]);
+                string countryCode = Convert.ToString(userObj["countryCode"]);
+                string note = Convert.ToString(userObj["note"]);
+                string users = Convert.ToString(userObj["users"]);
+
+                if (string.IsNullOrEmpty(username)) username = "-1";
+                if (string.IsNullOrEmpty(password)) password = "-1";
+                if (string.IsNullOrEmpty(role)) role = "-1";
+                if (string.IsNullOrEmpty(countryCode)) countryCode = "-1";
+                if (string.IsNullOrEmpty(note)) note = "-1";
+                if (string.IsNullOrEmpty(users)) users = "-1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                //---------------------Lay ra va luu mot redis tu cache-----------------------------
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("confirmTicket response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+                //----------------------Ket thuc lay redis tu cache -------------------------------------
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("confirmTicket response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds_regist = systemUserDataAccess.SYS_USER_WEB_CMS_INSERT(username, password, role, countryCode, note, users);
+                logger.Info("Call database SYS_USER_WEB_CMS_INSERT success:");
+                
+                if (ds_regist != null && ds_regist.Tables[0].Rows.Count > 0)
+                {
+                    response.responseCode = ds_regist.Tables[0].Rows[0]["status"].ToString();
+                    response.responseMessage = ds_regist.Tables[0].Rows[0]["msg"].ToString();
+                }
+                else
+                {
+                    response.responseCode = "-1";
+                    response.responseMessage = "Err unknow";
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("Response to web: " + response.ToString());
+            return Ok(response);
+        }
+
+        [HttpPost]
+        public IActionResult sysUserWebCmsUpdate([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysUserWebCmsUpdate :" + sendData.ToString());
+            Response response = new Response();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string id = Convert.ToString(userObj["id"]);
+                string username = Convert.ToString(userObj["username"]);
+                string password = Convert.ToString(userObj["password"]);
+                string role = Convert.ToString(userObj["role"]);
+                string countryCode = Convert.ToString(userObj["countryCode"]);
+                string isLock = Convert.ToString(userObj["isLock"]);
+                string timeLock = Convert.ToString(userObj["timeLock"]);
+                string totalFalse = Convert.ToString(userObj["totalFalse"]);
+                string note = Convert.ToString(userObj["note"]);
+                string users = Convert.ToString(userObj["users"]);
+
+                if (string.IsNullOrEmpty(id)) id = "-1";
+                if (string.IsNullOrEmpty(username)) username = "-1";
+                if (string.IsNullOrEmpty(password)) password = "-1";
+                if (string.IsNullOrEmpty(role)) role = "-1";
+                if (string.IsNullOrEmpty(countryCode)) countryCode = "-1";
+                if (string.IsNullOrEmpty(isLock)) isLock = "-1";
+                if (string.IsNullOrEmpty(timeLock)) timeLock = "-1";
+                if (string.IsNullOrEmpty(totalFalse)) totalFalse = "-1";
+                if (string.IsNullOrEmpty(note)) note = "-1";
+                if (string.IsNullOrEmpty(users)) users = "-1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                //---------------------Lay ra va luu mot redis tu cache-----------------------------
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("confirmTicket response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+                //----------------------Ket thuc lay redis tu cache -------------------------------------
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("confirmTicket response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds_regist = systemUserDataAccess.SYS_USER_WEB_CMS_UPDATE(id, username, password, role, countryCode, isLock, timeLock, totalFalse, note, users);
+                logger.Info("Call database SYS_USER_WEB_CMS_UPDATE success:");
+                
+                if (ds_regist != null && ds_regist.Tables[0].Rows.Count > 0)
+                {
+                    response.responseCode = ds_regist.Tables[0].Rows[0]["status"].ToString();
+                    response.responseMessage = ds_regist.Tables[0].Rows[0]["msg"].ToString();
+                }
+                else
+                {
+                    response.responseCode = "-1";
+                    response.responseMessage = "Err unknow";
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("Response to web: " + response.ToString());
+            return Ok(response);
+        }
+
+        // FUNCTION WEB CMS MANAGEMENT APIs
+        [HttpPost]
+        public IActionResult sysFunctionWebCmsGetList([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysFunctionWebCmsGetList :" + sendData.ToString());
+            functionWebCmsObjList response = new functionWebCmsObjList();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string users = Convert.ToString(userObj["users"]);
+                string id = Convert.ToString(userObj["id"]);
+                string role = Convert.ToString(userObj["role"]);
+                string name = Convert.ToString(userObj["name"]);
+                string link = Convert.ToString(userObj["link"]);
+                string order = Convert.ToString(userObj["order"]);
+                string rowsOnPage = Convert.ToString(userObj["rowsOnPage"]);
+                string seqPage = Convert.ToString(userObj["seqPage"]);
+                
+                if (string.IsNullOrEmpty(users)) users = "-1";
+                if (string.IsNullOrEmpty(id)) id = "-1";
+                if (string.IsNullOrEmpty(role)) role = "-1";
+                if (string.IsNullOrEmpty(name)) name = "-1";
+                if (string.IsNullOrEmpty(link)) link = "-1";
+                if (string.IsNullOrEmpty(order)) order = "asc";
+                if (string.IsNullOrEmpty(rowsOnPage)) rowsOnPage = "1000000000000";
+                if (string.IsNullOrEmpty(seqPage)) seqPage = "1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                //---------------------Lay ra va luu mot redis tu cache-----------------------------
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("confirmTicket response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+                //----------------------Ket thuc lay redis tu cache -------------------------------------
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("confirmTicket response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds_regist = systemUserDataAccess.SYS_FUNCTION_WEB_CMS_GET_LIST(users, id, role, name, link, order, rowsOnPage, seqPage);
+                logger.Info("Call database SYS_FUNCTION_WEB_CMS_GET_LIST success:");
+                
+                response.responseCode = "0";
+                response.responseMessage = "Success";
+                
+                if (ds_regist != null && ds_regist.Tables.Count > 0 && ds_regist.Tables[0].Rows.Count > 0)
+                {
+                    response.rowsOnPage = ds_regist.Tables[0].Rows[0]["ROW_ON_PAGE"].ToString();
+                    response.seqPage = ds_regist.Tables[0].Rows[0]["SEQ_PAGE"].ToString();
+                    response.totalPage = ds_regist.Tables[0].Rows[0]["TOTAL_PAGE"].ToString();
+
+                    response.list = new functionWebCmsObj[ds_regist.Tables[0].Rows.Count];
+                    for (int j = 0; j < ds_regist.Tables[0].Rows.Count; j++)
+                    {
+                        functionWebCmsObj _obj = new functionWebCmsObj();
+
+                        _obj.id = ds_regist.Tables[0].Rows[j]["ID"].ToString();
+                        _obj.role = ds_regist.Tables[0].Rows[j]["ROLE"].ToString();
+                        _obj.name = ds_regist.Tables[0].Rows[j]["NAME"].ToString();
+                        _obj.link = ds_regist.Tables[0].Rows[j]["LINK"].ToString();
+                        _obj.note = ds_regist.Tables[0].Rows[j]["NOTE"].ToString();
+
+                        response.list[j] = _obj;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("Response to web: " + response.ToString());
+            return Ok(response);
+        }
+
+        [HttpPost]
+        public IActionResult sysFunctionWebCmsInsert([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysFunctionWebCmsInsert :" + sendData.ToString());
+            Response response = new Response();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
 
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string role = Convert.ToString(userObj["role"]);
+                string name = Convert.ToString(userObj["name"]);
+                string link = Convert.ToString(userObj["link"]);
+                string note = Convert.ToString(userObj["note"]);
+                string users = Convert.ToString(userObj["users"]);
+
+                if (string.IsNullOrEmpty(role)) role = "-1";
+                if (string.IsNullOrEmpty(name)) name = "-1";
+                if (string.IsNullOrEmpty(link)) link = "-1";
+                if (string.IsNullOrEmpty(note)) note = "-1";
+                if (string.IsNullOrEmpty(users)) users = "-1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                //---------------------Lay ra va luu mot redis tu cache-----------------------------
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("confirmTicket response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+                //----------------------Ket thuc lay redis tu cache -------------------------------------
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("confirmTicket response : " + response.ToString());
+                    return Ok(response);
+                }
 
+                DataSet ds_regist = systemUserDataAccess.SYS_FUNCTION_WEB_CMS_INSERT(role, name, link, note, users);
+                logger.Info("Call database SYS_FUNCTION_WEB_CMS_INSERT success:");
+                
+                if (ds_regist != null && ds_regist.Tables[0].Rows.Count > 0)
+                {
+                    response.responseCode = ds_regist.Tables[0].Rows[0]["status"].ToString();
+                    response.responseMessage = ds_regist.Tables[0].Rows[0]["msg"].ToString();
+                }
+                else
+                {
+                    response.responseCode = "-1";
+                    response.responseMessage = "Err unknow";
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("Response to web: " + response.ToString());
+            return Ok(response);
+        }
 
+        [HttpPost]
+        public IActionResult sysFunctionWebCmsUpdate([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysFunctionWebCmsUpdate :" + sendData.ToString());
+            Response response = new Response();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string id = Convert.ToString(userObj["id"]);
+                string role = Convert.ToString(userObj["role"]);
+                string name = Convert.ToString(userObj["name"]);
+                string link = Convert.ToString(userObj["link"]);
+                string note = Convert.ToString(userObj["note"]);
+                string users = Convert.ToString(userObj["users"]);
+
+                if (string.IsNullOrEmpty(id)) id = "-1";
+                if (string.IsNullOrEmpty(role)) role = "-1";
+                if (string.IsNullOrEmpty(name)) name = "-1";
+                if (string.IsNullOrEmpty(link)) link = "-1";
+                if (string.IsNullOrEmpty(note)) note = "-1";
+                if (string.IsNullOrEmpty(users)) users = "-1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                //---------------------Lay ra va luu mot redis tu cache-----------------------------
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("confirmTicket response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+                //----------------------Ket thuc lay redis tu cache -------------------------------------
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("confirmTicket response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds_regist = systemUserDataAccess.SYS_FUNCTION_WEB_CMS_UPDATE(id, role, name, link, note, users);
+                logger.Info("Call database SYS_FUNCTION_WEB_CMS_UPDATE success:");
+                
+                if (ds_regist != null && ds_regist.Tables[0].Rows.Count > 0)
+                {
+                    response.responseCode = ds_regist.Tables[0].Rows[0]["status"].ToString();
+                    response.responseMessage = ds_regist.Tables[0].Rows[0]["msg"].ToString();
+                }
+                else
+                {
+                    response.responseCode = "-1";
+                    response.responseMessage = "Err unknow";
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("Response to web: " + response.ToString());
+            return Ok(response);
+        }
+
+        // REPORT COUNT DAILY MANAGEMENT APIs
+        [HttpPost]
+        public IActionResult sysReportCountDailyGetList([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysReportCountDailyGetList :" + sendData.ToString());
+            reportCountDailyObjList response = new reportCountDailyObjList();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string users = Convert.ToString(userObj["users"]);
+                string id = Convert.ToString(userObj["id"]);
+                string reportDate = Convert.ToString(userObj["reportDate"]);
+                string campaignId = Convert.ToString(userObj["campaignId"]);
+                string serviceId = Convert.ToString(userObj["serviceId"]);
+                string fromDate = Convert.ToString(userObj["fromDate"]);
+                string toDate = Convert.ToString(userObj["toDate"]);
+                string order = Convert.ToString(userObj["order"]);
+                string rowsOnPage = Convert.ToString(userObj["rowsOnPage"]);
+                string seqPage = Convert.ToString(userObj["seqPage"]);
+                
+                if (string.IsNullOrEmpty(users)) users = "-1";
+                if (string.IsNullOrEmpty(id)) id = "-1";
+                if (string.IsNullOrEmpty(reportDate)) reportDate = "-1";
+                if (string.IsNullOrEmpty(campaignId)) campaignId = "-1";
+                if (string.IsNullOrEmpty(serviceId)) serviceId = "-1";
+                if (string.IsNullOrEmpty(fromDate)) fromDate = "-1";
+                if (string.IsNullOrEmpty(toDate)) toDate = "-1";
+                if (string.IsNullOrEmpty(order)) order = "desc";
+                if (string.IsNullOrEmpty(rowsOnPage)) rowsOnPage = "1000000000000";
+                if (string.IsNullOrEmpty(seqPage)) seqPage = "1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                //---------------------Lay ra va luu mot redis tu cache-----------------------------
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("confirmTicket response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+                //----------------------Ket thuc lay redis tu cache -------------------------------------
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("confirmTicket response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds_regist = systemDataAccess.SYS_REPORT_COUNT_DAILY_GET_LIST(users, id, reportDate, campaignId, serviceId, fromDate, toDate, order, rowsOnPage, seqPage);
+                logger.Info("Call database SYS_REPORT_COUNT_DAILY_GET_LIST success:");
+                
+                response.responseCode = "0";
+                response.responseMessage = "Success";
+                
+                if (ds_regist != null && ds_regist.Tables.Count > 0 && ds_regist.Tables[0].Rows.Count > 0)
+                {
+                    response.rowsOnPage = ds_regist.Tables[0].Rows[0]["ROW_ON_PAGE"].ToString();
+                    response.seqPage = ds_regist.Tables[0].Rows[0]["SEQ_PAGE"].ToString();
+                    response.totalPage = ds_regist.Tables[0].Rows[0]["TOTAL_PAGE"].ToString();
+
+                    response.list = new reportCountDailyObj[ds_regist.Tables[0].Rows.Count];
+                    for (int j = 0; j < ds_regist.Tables[0].Rows.Count; j++)
+                    {
+                        reportCountDailyObj _obj = new reportCountDailyObj();
+
+                        _obj.id = ds_regist.Tables[0].Rows[j]["ID"].ToString();
+                        _obj.reportDate = ds_regist.Tables[0].Rows[j]["REPORT_DATE"] != DBNull.Value ? Convert.ToDateTime(ds_regist.Tables[0].Rows[j]["REPORT_DATE"]).ToString("dd/MM/yyyy") : "";
+                        _obj.campaignId = ds_regist.Tables[0].Rows[j]["CAMPAIGN_ID"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["CAMPAIGN_ID"].ToString() : "";
+                        _obj.campaignName = ds_regist.Tables[0].Rows[j]["CAMPAIGN_NAME"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["CAMPAIGN_NAME"].ToString() : "";
+                        _obj.priority = ds_regist.Tables[0].Rows[j]["PRIORITY"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["PRIORITY"].ToString() : "";
+                        _obj.isDefault = ds_regist.Tables[0].Rows[j]["IS_DEFAULT"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["IS_DEFAULT"].ToString() : "";
+                        _obj.isMyService = ds_regist.Tables[0].Rows[j]["IS_MYSERVICE"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["IS_MYSERVICE"].ToString() : "";
+                        _obj.addType = ds_regist.Tables[0].Rows[j]["ADD_TYPE"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["ADD_TYPE"].ToString() : "";
+                        _obj.serviceId = ds_regist.Tables[0].Rows[j]["SERVICE_ID"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["SERVICE_ID"].ToString() : "";
+                        _obj.serviceName = ds_regist.Tables[0].Rows[j]["SERVICE_NAME"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["SERVICE_NAME"].ToString() : "";
+                        _obj.countSend1 = ds_regist.Tables[0].Rows[j]["COUNT_SEND_1"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["COUNT_SEND_1"].ToString() : "0";
+                        _obj.countSuccess1 = ds_regist.Tables[0].Rows[j]["COUNT_SUCCESS_1"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["COUNT_SUCCESS_1"].ToString() : "0";
+                        _obj.countFail1 = ds_regist.Tables[0].Rows[j]["COUNT_FAIL_1"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["COUNT_FAIL_1"].ToString() : "0";
+                        _obj.countPress1 = ds_regist.Tables[0].Rows[j]["COUNT_PRESS_1"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["COUNT_PRESS_1"].ToString() : "0";
+                        _obj.countSend2 = ds_regist.Tables[0].Rows[j]["COUNT_SEND_2"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["COUNT_SEND_2"].ToString() : "0";
+                        _obj.countPress2 = ds_regist.Tables[0].Rows[j]["COUNT_PRESS_2"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["COUNT_PRESS_2"].ToString() : "0";
+                        _obj.countRegSuccess = ds_regist.Tables[0].Rows[j]["COUNT_REG_SUCCESS"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["COUNT_REG_SUCCESS"].ToString() : "0";
+                        _obj.countRegFail = ds_regist.Tables[0].Rows[j]["COUNT_REG_FAIL"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["COUNT_REG_FAIL"].ToString() : "0";
+                        _obj.insertTime = ds_regist.Tables[0].Rows[j]["INSERT_TIME"] != DBNull.Value ? Convert.ToDateTime(ds_regist.Tables[0].Rows[j]["INSERT_TIME"]).ToString("dd/MM/yyyy HH:mm:ss") : "";
+
+                        response.list[j] = _obj;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("Response to web: " + response.ToString());
+            return Ok(response);
+        }
+
+        // REPORT ERROR DAILY MANAGEMENT APIs
+        [HttpPost]
+        public IActionResult sysReportErrorDailyGetList([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysReportErrorDailyGetList :" + sendData.ToString());
+            reportErrorDailyObjList response = new reportErrorDailyObjList();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string users = Convert.ToString(userObj["users"]);
+                string reportDate = Convert.ToString(userObj["reportDate"]);
+                string campaignId = Convert.ToString(userObj["campaignId"]);
+                string serviceId = Convert.ToString(userObj["serviceId"]);
+                
+                if (string.IsNullOrEmpty(users)) users = "-1";
+                if (string.IsNullOrEmpty(reportDate)) reportDate = "-1";
+                if (string.IsNullOrEmpty(campaignId)) campaignId = "-1";
+                if (string.IsNullOrEmpty(serviceId)) serviceId = "-1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                //---------------------Lay ra va luu mot redis tu cache-----------------------------
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("confirmTicket response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+                //----------------------Ket thuc lay redis tu cache -------------------------------------
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("confirmTicket response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds_regist = systemDataAccess.SYS_REPORT_ERROR_DAILY_GET_LIST(users, reportDate, campaignId, serviceId);
+                logger.Info("Call database SYS_REPORT_ERROR_DAILY_GET_LIST success:");
+                
+                response.responseCode = "0";
+                response.responseMessage = "Success";
+                
+                if (ds_regist != null && ds_regist.Tables.Count > 0 && ds_regist.Tables[0].Rows.Count > 0)
+                {
+                    response.list = new reportErrorDailyObj[ds_regist.Tables[0].Rows.Count];
+                    for (int j = 0; j < ds_regist.Tables[0].Rows.Count; j++)
+                    {
+                        reportErrorDailyObj _obj = new reportErrorDailyObj();
+
+                        _obj.id = ds_regist.Tables[0].Rows[j]["ID"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["ID"].ToString() : "";
+                        _obj.reportDate = ds_regist.Tables[0].Rows[j]["REPORT_DATE"] != DBNull.Value ? Convert.ToDateTime(ds_regist.Tables[0].Rows[j]["REPORT_DATE"]).ToString("dd/MM/yyyy") : "";
+                        _obj.campaignId = ds_regist.Tables[0].Rows[j]["CAMPAIGN_ID"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["CAMPAIGN_ID"].ToString() : "";
+                        _obj.serviceId = ds_regist.Tables[0].Rows[j]["SERVICE_ID"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["SERVICE_ID"].ToString() : "";
+                        _obj.errorCode = ds_regist.Tables[0].Rows[j]["ERROR_CODE"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["ERROR_CODE"].ToString() : "";
+                        _obj.countNum = ds_regist.Tables[0].Rows[j]["COUNT_NUM"] != DBNull.Value ? ds_regist.Tables[0].Rows[j]["COUNT_NUM"].ToString() : "0";
+                        _obj.insertTime = ds_regist.Tables[0].Rows[j]["INSERT_TIME"] != DBNull.Value ? Convert.ToDateTime(ds_regist.Tables[0].Rows[j]["INSERT_TIME"]).ToString("dd/MM/yyyy HH:mm:ss") : "";
+
+                        response.list[j] = _obj;
+                    }
+                }
+                else
+                {
+                    response.list = new reportErrorDailyObj[0];
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("Response to web: " + response.ToString());
+            return Ok(response);
+        }
+
+        // HOURLY IMPRESSIONS API
+        [HttpPost]
+        public IActionResult sysHourlyImpressionsGetList([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysHourlyImpressionsGetList :" + sendData.ToString());
+            hourlyImpressionsObjList response = new hourlyImpressionsObjList();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string campaignId = Convert.ToString(userObj["campaignId"]);
+                string serviceId = Convert.ToString(userObj["serviceId"]);
+                string hours = Convert.ToString(userObj["hours"]);
+                string quickJump = Convert.ToString(userObj["quickJump"]);
+                
+                if (string.IsNullOrEmpty(campaignId)) campaignId = "-1";
+                if (string.IsNullOrEmpty(serviceId)) serviceId = "-1";
+                if (string.IsNullOrEmpty(hours)) hours = "72h";
+                if (string.IsNullOrEmpty(quickJump)) quickJump = "-1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                //---------------------Lay ra va luu mot redis tu cache-----------------------------
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("sysHourlyImpressionsGetList response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+                //----------------------Ket thuc lay redis tu cache -------------------------------------
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("sysHourlyImpressionsGetList response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds = balanceDataAccess.GET_HOURLY_IMPRESSIONS(campaignId, serviceId, hours, quickJump);
+                logger.Info("Call database GET_HOURLY_IMPRESSIONS success:");
+                
+                response.responseCode = "0";
+                response.responseMessage = "Success";
+                
+                if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
+                {
+                    response.list = new hourlyImpressionsObj[ds.Tables[0].Rows.Count];
+                    for (int j = 0; j < ds.Tables[0].Rows.Count; j++)
+                    {
+                        hourlyImpressionsObj _obj = new hourlyImpressionsObj();
+
+                        _obj.hourLabel = ds.Tables[0].Rows[j]["HOUR_LABEL"] != DBNull.Value ? ds.Tables[0].Rows[j]["HOUR_LABEL"].ToString() : "";
+                        _obj.hourValue = ds.Tables[0].Rows[j]["HOUR_VALUE"] != DBNull.Value ? ds.Tables[0].Rows[j]["HOUR_VALUE"].ToString() : "";
+                        _obj.campaignId = ds.Tables[0].Rows[j]["CAMPAIGN_ID"] != DBNull.Value ? ds.Tables[0].Rows[j]["CAMPAIGN_ID"].ToString() : "";
+                        _obj.campaignName = ds.Tables[0].Rows[j]["CAMPAIGN_NAME"] != DBNull.Value ? ds.Tables[0].Rows[j]["CAMPAIGN_NAME"].ToString() : "";
+                        _obj.serviceId = ""; // No longer using service
+                        _obj.serviceName = ""; // No longer using service
+                        _obj.countImpressions = ds.Tables[0].Rows[j]["COUNT_IMPRESSED"] != DBNull.Value ? ds.Tables[0].Rows[j]["COUNT_IMPRESSED"].ToString() : "0";
+
+                        response.list[j] = _obj;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("Response to web: " + response.ToString());
+            return Ok(response);
+        }
+
+        // DAILY IMPRESSIONS API
+        [HttpPost]
+        public IActionResult sysDailyImpressionsGetList([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysDailyImpressionsGetList :" + sendData.ToString());
+            hourlyImpressionsObjList response = new hourlyImpressionsObjList();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string campaignId = Convert.ToString(userObj["campaignId"]);
+                string fromDate = Convert.ToString(userObj["fromDate"]);
+                string toDate = Convert.ToString(userObj["toDate"]);
+                
+                if (string.IsNullOrEmpty(campaignId)) campaignId = "-1";
+                if (string.IsNullOrEmpty(fromDate)) fromDate = "-1";
+                if (string.IsNullOrEmpty(toDate)) toDate = "-1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                //---------------------Lay ra va luu mot redis tu cache-----------------------------
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("sysDailyImpressionsGetList response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+                //----------------------Ket thuc lay redis tu cache -------------------------------------
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("sysDailyImpressionsGetList response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds = balanceDataAccess.GET_DAILY_IMPRESSIONS(campaignId, fromDate, toDate);
+                logger.Info("Call database GET_DAILY_IMPRESSIONS success:");
+                
+                response.responseCode = "0";
+                response.responseMessage = "Success";
+                
+                if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
+                {
+                    response.list = new hourlyImpressionsObj[ds.Tables[0].Rows.Count];
+                    for (int j = 0; j < ds.Tables[0].Rows.Count; j++)
+                    {
+                        hourlyImpressionsObj _obj = new hourlyImpressionsObj();
+
+                        _obj.hourLabel = ds.Tables[0].Rows[j]["DAY_LABEL"] != DBNull.Value ? ds.Tables[0].Rows[j]["DAY_LABEL"].ToString() : "";
+                        _obj.hourValue = ds.Tables[0].Rows[j]["DAY_VALUE"] != DBNull.Value ? Convert.ToDateTime(ds.Tables[0].Rows[j]["DAY_VALUE"]).ToString("yyyy-MM-dd") : "";
+                        _obj.campaignId = ds.Tables[0].Rows[j]["CAMPAIGN_ID"] != DBNull.Value ? ds.Tables[0].Rows[j]["CAMPAIGN_ID"].ToString() : "";
+                        _obj.campaignName = ds.Tables[0].Rows[j]["CAMPAIGN_NAME"] != DBNull.Value ? ds.Tables[0].Rows[j]["CAMPAIGN_NAME"].ToString() : "";
+                        _obj.serviceId = ""; // No longer using service
+                        _obj.serviceName = ""; // No longer using service
+                        _obj.countImpressions = ds.Tables[0].Rows[j]["COUNT_IMPRESSED"] != DBNull.Value ? ds.Tables[0].Rows[j]["COUNT_IMPRESSED"].ToString() : "0";
+
+                        response.list[j] = _obj;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("Response to web: " + response.ToString());
+            return Ok(response);
+        }
+
+        [HttpPost]
+        public IActionResult sysDailyUniqueImpressionsGetList([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysDailyUniqueImpressionsGetList :" + sendData.ToString());
+            hourlyImpressionsObjList response = new hourlyImpressionsObjList();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string campaignId = Convert.ToString(userObj["campaignId"]);
+                string serviceId = Convert.ToString(userObj["serviceId"]);
+                string fromDate = Convert.ToString(userObj["fromDate"]);
+                string toDate = Convert.ToString(userObj["toDate"]);
+
+                if (string.IsNullOrEmpty(campaignId)) campaignId = "-1";
+                if (string.IsNullOrEmpty(serviceId)) serviceId = "-1";
+                if (string.IsNullOrEmpty(fromDate)) fromDate = "-1";
+                if (string.IsNullOrEmpty(toDate)) toDate = "-1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("sysDailyUniqueImpressionsGetList response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("sysDailyUniqueImpressionsGetList response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds = balanceDataAccess.GET_DAILY_UNIQUE_IMPRESSIONS(campaignId, serviceId, fromDate, toDate);
+                logger.Info("Call database GET_DAILY_UNIQUE_IMPRESSIONS success:");
+
+                response.responseCode = "0";
+                response.responseMessage = "Success";
+
+                if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
+                {
+                    response.list = new hourlyImpressionsObj[ds.Tables[0].Rows.Count];
+                    for (int j = 0; j < ds.Tables[0].Rows.Count; j++)
+                    {
+                        hourlyImpressionsObj _obj = new hourlyImpressionsObj();
+
+                        _obj.hourLabel = ds.Tables[0].Rows[j]["DAY_LABEL"] != DBNull.Value ? ds.Tables[0].Rows[j]["DAY_LABEL"].ToString() : "";
+                        _obj.hourValue = ds.Tables[0].Rows[j]["DAY_VALUE"] != DBNull.Value ? Convert.ToDateTime(ds.Tables[0].Rows[j]["DAY_VALUE"]).ToString("yyyy-MM-dd") : "";
+                        _obj.campaignId = ds.Tables[0].Rows[j]["CAMPAIGN_ID"] != DBNull.Value ? ds.Tables[0].Rows[j]["CAMPAIGN_ID"].ToString() : "";
+                        _obj.campaignName = ds.Tables[0].Rows[j]["CAMPAIGN_NAME"] != DBNull.Value ? ds.Tables[0].Rows[j]["CAMPAIGN_NAME"].ToString() : "";
+                        _obj.serviceId = "";
+                        _obj.serviceName = "";
+                        _obj.countImpressions = ds.Tables[0].Rows[j]["COUNT_IMPRESSED"] != DBNull.Value ? ds.Tables[0].Rows[j]["COUNT_IMPRESSED"].ToString() : "0";
+
+                        response.list[j] = _obj;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("sysDailyUniqueImpressionsGetList response : " + response.ToString());
+            return Ok(response);
+        }
+
+        [HttpPost]
+        public IActionResult sysDailyEngagedGetList([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysDailyEngagedGetList :" + sendData.ToString());
+            hourlyImpressionsObjList response = new hourlyImpressionsObjList();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string campaignId = Convert.ToString(userObj["campaignId"]);
+                string serviceId = Convert.ToString(userObj["serviceId"]);
+                string fromDate = Convert.ToString(userObj["fromDate"]);
+                string toDate = Convert.ToString(userObj["toDate"]);
+
+                if (string.IsNullOrEmpty(campaignId)) campaignId = "-1";
+                if (string.IsNullOrEmpty(serviceId)) serviceId = "-1";
+                if (string.IsNullOrEmpty(fromDate)) fromDate = "-1";
+                if (string.IsNullOrEmpty(toDate)) toDate = "-1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("sysDailyEngagedGetList response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("sysDailyEngagedGetList response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds = balanceDataAccess.GET_DAILY_ENGAGED_USERS(campaignId, serviceId, fromDate, toDate);
+                logger.Info("Call database GET_DAILY_ENGAGED_USERS success:");
+
+                response.responseCode = "0";
+                response.responseMessage = "Success";
+
+                if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0)
+                {
+                    response.list = new hourlyImpressionsObj[ds.Tables[0].Rows.Count];
+                    for (int j = 0; j < ds.Tables[0].Rows.Count; j++)
+                    {
+                        hourlyImpressionsObj _obj = new hourlyImpressionsObj();
+
+                        _obj.hourLabel = ds.Tables[0].Rows[j]["DAY_LABEL"] != DBNull.Value ? ds.Tables[0].Rows[j]["DAY_LABEL"].ToString() : "";
+                        _obj.hourValue = ds.Tables[0].Rows[j]["DAY_VALUE"] != DBNull.Value ? Convert.ToDateTime(ds.Tables[0].Rows[j]["DAY_VALUE"]).ToString("yyyy-MM-dd") : "";
+                        _obj.campaignId = ds.Tables[0].Rows[j]["CAMPAIGN_ID"] != DBNull.Value ? ds.Tables[0].Rows[j]["CAMPAIGN_ID"].ToString() : "";
+                        _obj.campaignName = ds.Tables[0].Rows[j]["CAMPAIGN_NAME"] != DBNull.Value ? ds.Tables[0].Rows[j]["CAMPAIGN_NAME"].ToString() : "";
+                        _obj.serviceId = "";
+                        _obj.serviceName = "";
+                        _obj.countImpressions = ds.Tables[0].Rows[j]["COUNT_ENGAGED"] != DBNull.Value ? ds.Tables[0].Rows[j]["COUNT_ENGAGED"].ToString() : "0";
+
+                        response.list[j] = _obj;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("sysDailyEngagedGetList response : " + response.ToString());
+            return Ok(response);
+        }
+
+        // PUSH USSD DETAIL REPORT
+        [HttpPost]
+        public IActionResult sysPushUssdDetailGetList([FromBody] dynamic sendData)
+        {
+            logger.Info("New request income sysPushUssdDetailGetList :" + sendData.ToString());
+            pushUssdDetailObjList response = new pushUssdDetailObjList();
+            response.responseCode = "-1";
+            response.responseMessage = "Err unknow";
+
+            try
+            {
+                var userObj = JObject.Parse(sendData.ToString());
+                string users = Convert.ToString(userObj["users"]);
+                string campaignId = Convert.ToString(userObj["campaignId"]);
+                string serviceId = Convert.ToString(userObj["serviceId"]);
+                string msisdn = Convert.ToString(userObj["msisdn"]);
+                string sendStatus = Convert.ToString(userObj["sendStatus"]);
+                string isSuccess = Convert.ToString(userObj["isSuccess"]);
+                string fromDate = Convert.ToString(userObj["fromDate"]);
+                string toDate = Convert.ToString(userObj["toDate"]);
+                string order = Convert.ToString(userObj["order"]);
+                string rowsOnPage = Convert.ToString(userObj["rowsOnPage"]);
+                string seqPage = Convert.ToString(userObj["seqPage"]);
+
+                if (string.IsNullOrEmpty(users)) users = "-1";
+                if (string.IsNullOrEmpty(campaignId)) campaignId = "-1";
+                if (string.IsNullOrEmpty(serviceId)) serviceId = "-1";
+                if (string.IsNullOrEmpty(msisdn)) msisdn = "-1";
+                if (string.IsNullOrEmpty(sendStatus)) sendStatus = "-1";
+                if (string.IsNullOrEmpty(isSuccess)) isSuccess = "-1";
+                if (string.IsNullOrEmpty(fromDate)) fromDate = "-1";
+                if (string.IsNullOrEmpty(toDate)) toDate = "-1";
+                if (string.IsNullOrEmpty(order)) order = "desc";
+                if (string.IsNullOrEmpty(rowsOnPage)) rowsOnPage = "100";
+                if (string.IsNullOrEmpty(seqPage)) seqPage = "1";
+
+                string token = Convert.ToString(userObj["token"]);
+                string channel = Convert.ToString(userObj["channel"]);
+                string language = Convert.ToString(userObj["language"]);
+                if (string.IsNullOrEmpty(language)) language = "-1";
+
+                string RedisIp = Common.GetValuesAppSetting("webConfig", "RedisIp");
+                string RedisPort = Common.GetValuesAppSetting("webConfig", "RedisPort");
+                string RedisPass = Common.GetValuesAppSetting("webConfig", "RedisPass");
+                var clientIp = HttpContext.Connection.RemoteIpAddress.ToString();
+
+                redisConnection _redis;
+                memoryCache.TryGetValue("redis", out _redis);
+                if (_redis == null)
+                {
+                    var cacheExpiryOptions = new MemoryCacheEntryOptions
+                    {
+                        AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                        Priority = CacheItemPriority.High,
+                        SlidingExpiration = TimeSpan.FromMinutes(2),
+                        Size = 1024,
+                    };
+                    _redis = new redisConnection(RedisIp, RedisPort, RedisPass);
+                    _redis.connet();
+                    memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                }
+                if (!_redis.isConnet())
+                {
+                    if (!_redis.connet())
+                    {
+                        logger.Info("Connect to redis false");
+                        response.responseCode = "-2";
+                        response.responseMessage = "System Update";
+                        logger.Info("sysPushUssdDetailGetList response : " + response.ToString());
+                        return Ok(response);
+                    }
+                    else
+                    {
+                        var cacheExpiryOptions = new MemoryCacheEntryOptions
+                        {
+                            AbsoluteExpiration = DateTime.Now.AddMonths(12),
+                            Priority = CacheItemPriority.High,
+                            SlidingExpiration = TimeSpan.FromMinutes(2),
+                            Size = 1024,
+                        };
+                        memoryCache.Set("redis", _redis, cacheExpiryOptions);
+                    }
+                }
+
+                tokenObj _tokenObj = null;
+                if (!CommonFunction.checkToken(clientIp, token, channel, _redis, logger, out _tokenObj))
+                {
+                    logger.Info("Authen token false");
+                    response.responseCode = "35";
+                    response.responseMessage = CommonObj.common.CommonFunction.getErrCodeObjFromRedis(_redis, channel, response.responseCode, language);
+                    logger.Info("sysPushUssdDetailGetList response : " + response.ToString());
+                    return Ok(response);
+                }
+
+                DataSet ds = systemDataAccess.SYS_PUSH_USSD_DETAIL_GET_LIST(users, campaignId, serviceId, msisdn, sendStatus, isSuccess, fromDate, toDate, order, rowsOnPage, seqPage);
+                logger.Info("Call database SYS_PUSH_USSD_DETAIL_GET_LIST success:");
+
+                response.responseCode = "0";
+                response.responseMessage = "Success";
+
+                if (ds != null && ds.Tables.Count > 0)
+                {
+                    var dataTable = ds.Tables[0];
+                    response.list = new pushUssdDetailObj[dataTable.Rows.Count];
+                    for (int j = 0; j < dataTable.Rows.Count; j++)
+                    {
+                        var _obj = new pushUssdDetailObj();
+                        _obj.id = dataTable.Rows[j]["ID"].ToString();
+                        _obj.requestId = dataTable.Rows[j]["REQUEST_ID"].ToString();
+                        _obj.campaignId = dataTable.Rows[j]["CAMPAIGN_ID"] != DBNull.Value ? dataTable.Rows[j]["CAMPAIGN_ID"].ToString() : "";
+                        _obj.campaignName = dataTable.Rows[j].Table.Columns.Contains("CAMPAIGN_NAME") && dataTable.Rows[j]["CAMPAIGN_NAME"] != DBNull.Value ? dataTable.Rows[j]["CAMPAIGN_NAME"].ToString() : "";
+                        _obj.serviceId = dataTable.Rows[j]["SERVICE_ID"] != DBNull.Value ? dataTable.Rows[j]["SERVICE_ID"].ToString() : "";
+                        _obj.msisdn = dataTable.Rows[j]["MSISDN"].ToString();
+                        _obj.sendTime = dataTable.Rows[j]["SEND_TIME"] != DBNull.Value ? Convert.ToDateTime(dataTable.Rows[j]["SEND_TIME"]).ToString("dd/MM/yyyy HH:mm:ss") : "";
+                        _obj.sendStatus = dataTable.Rows[j]["SEND_STATUS"] != DBNull.Value ? dataTable.Rows[j]["SEND_STATUS"].ToString() : "";
+                        _obj.totalStep = dataTable.Rows[j]["TOTAL_STEP"] != DBNull.Value ? dataTable.Rows[j]["TOTAL_STEP"].ToString() : "";
+                        _obj.isStep1 = dataTable.Rows[j]["IS_STEP_1"] != DBNull.Value ? dataTable.Rows[j]["IS_STEP_1"].ToString() : "";
+                        _obj.step1Time = dataTable.Rows[j]["STEP_1_TIME"] != DBNull.Value ? Convert.ToDateTime(dataTable.Rows[j]["STEP_1_TIME"]).ToString("dd/MM/yyyy HH:mm:ss") : "";
+                        _obj.isStep2 = dataTable.Rows[j]["IS_STEP_2"] != DBNull.Value ? dataTable.Rows[j]["IS_STEP_2"].ToString() : "";
+                        _obj.step2Time = dataTable.Rows[j]["STEP_2_TIME"] != DBNull.Value ? Convert.ToDateTime(dataTable.Rows[j]["STEP_2_TIME"]).ToString("dd/MM/yyyy HH:mm:ss") : "";
+                        _obj.errorCode = dataTable.Rows[j]["ERROR_CODE"] != DBNull.Value ? dataTable.Rows[j]["ERROR_CODE"].ToString() : "";
+                        _obj.isSuccess = dataTable.Rows[j]["IS_SUCCESS"] != DBNull.Value ? dataTable.Rows[j]["IS_SUCCESS"].ToString() : "";
+                        _obj.insertTime = dataTable.Rows[j]["INSERT_TIME"] != DBNull.Value ? Convert.ToDateTime(dataTable.Rows[j]["INSERT_TIME"]).ToString("dd/MM/yyyy HH:mm:ss") : "";
+                        _obj.lastUpdate = dataTable.Rows[j]["LAST_UPDATE"] != DBNull.Value ? Convert.ToDateTime(dataTable.Rows[j]["LAST_UPDATE"]).ToString("dd/MM/yyyy HH:mm:ss") : "";
+                        response.list[j] = _obj;
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                logger.Info("exception: " + ex.ToString());
+                response.responseCode = "-2";
+                response.responseMessage = "System upgrade";
+            }
+            logger.Info("sysPushUssdDetailGetList response : " + response.ToString());
+            return Ok(response);
+        }
     }
 }

+ 4 - 0
ApiWeb/ApiProcessToken/Models/banlance/apiServiceObj.cs

@@ -33,6 +33,10 @@ namespace ApiProcess.Models.balance
 
         public string error_tag { get; set; }
 
+        [JsonProperty("success_code")]
+
+        public string success_code { get; set; }
+
 
         [JsonProperty("isActive")]
         public string isActive { get; set; }

+ 383 - 8
ApiWeb/ApiProcessToken/Models/banlance/balanceDataAccess.cs

@@ -1140,7 +1140,8 @@ namespace ResfullApi.Models.balance
 
         public static DataSet svInsert(string V_CODE, string V_NAME, string V_DESCRIPTION,
             string V_SHORT_CODE, string V_COMMAND_REGISTER, string V_CONTENT_EN, string V_CONTENT_FR, string V_CONTENT_LC, string V_NOTE,
-            string V_COMPANY_ID, string V_USERS, string msgRegisterSuccess, string msgRegisterFlase,string msgConfirm,string serviceGroupId,string apiServiceId)
+            string V_COMPANY_ID, string V_USERS, string msgRegisterSuccess, string msgRegisterFlase,string msgConfirm,string serviceGroupId,string apiServiceId, string isMyservice
+            )
         {
 
             string str;
@@ -1165,11 +1166,13 @@ namespace ResfullApi.Models.balance
                                
                                 new OracleParameter("V_USERS", OracleDbType.NVarchar2),
 
-                                 new OracleParameter("V_USSD_MSG_REGISTER_SUC", OracleDbType.NVarchar2),
+                                new OracleParameter("V_USSD_MSG_REGISTER_SUC", OracleDbType.NVarchar2),
                                 new OracleParameter("V_USSD_MSG_REGISTER_FAIL", OracleDbType.NVarchar2),
                                 new OracleParameter("V_USSD_CONFIRM", OracleDbType.NVarchar2),
                                 new OracleParameter("V_SERVICE_GROUP_ID", OracleDbType.NVarchar2),
                                 new OracleParameter("V_API_SERVICE_ID", OracleDbType.NVarchar2),
+                                new OracleParameter("v_isMyservice", OracleDbType.NVarchar2),
+
                                 new OracleParameter("P_RESULT",OracleDbType.RefCursor,ParameterDirection.Output),
             };
             parms[0].Value = V_CODE;
@@ -1190,6 +1193,7 @@ namespace ResfullApi.Models.balance
             parms[13].Value = msgConfirm;
             parms[14].Value = serviceGroupId;
             parms[15].Value = apiServiceId;
+            parms[16].Value = isMyservice;
             return DataAccess.getDataFromProcedure(str, "", parms);
         }
 
@@ -1197,7 +1201,7 @@ namespace ResfullApi.Models.balance
 
         public static DataSet svUpdate(string V_ID,string V_CODE, string V_NAME, string V_DESCRIPTION,
             string V_SHORT_CODE, string V_COMMAND_REGISTER, string V_CONTENT_EN, string V_CONTENT_FR, string V_CONTENT_LC, string V_NOTE,
-            string V_COMPANY_ID, string V_USERS, string V_TYPE, string msgRegisterSuccess, string msgRegisterFlase,string msgConfirm, string serviceGroupId, string apiServiceId)
+            string V_COMPANY_ID, string V_USERS, string V_TYPE, string msgRegisterSuccess, string msgRegisterFlase,string msgConfirm, string serviceGroupId, string apiServiceId, string isMyservice)
         {
 
             string str;
@@ -1229,6 +1233,8 @@ namespace ResfullApi.Models.balance
 
                                 new OracleParameter("V_SERVICE_GROUP_ID", OracleDbType.NVarchar2),
                                 new OracleParameter("V_API_SERVICE_ID", OracleDbType.NVarchar2),
+                                new OracleParameter("v_isMyservice", OracleDbType.NVarchar2),
+
                                 new OracleParameter("P_RESULT",OracleDbType.RefCursor,ParameterDirection.Output),
             };
             parms[0].Value = V_ID;
@@ -1252,6 +1258,9 @@ namespace ResfullApi.Models.balance
             parms[15].Value = msgConfirm;
             parms[16].Value = serviceGroupId;
             parms[17].Value = apiServiceId;
+
+            parms[18].Value = isMyservice;
+
             return DataAccess.getDataFromProcedure(str, "", parms);
         }
 
@@ -2723,7 +2732,7 @@ namespace ResfullApi.Models.balance
                 
                 // Build base query for counting total records
                 string countSql = "SELECT COUNT(*) FROM WEBSERVICE WHERE 1=1";
-                string dataSql = "SELECT WS_ID, WS_NAME, WS_CODE, WSDL, MSG_TEMPLATE, ERROR_TAG, STATUS FROM WEBSERVICE WHERE 1=1";
+                string dataSql = "SELECT WS_ID, WS_NAME, WS_CODE, WSDL, MSG_TEMPLATE, ERROR_TAG, SUCCESS_CODE, STATUS FROM WEBSERVICE WHERE 1=1";
                 
                 // Add filters
                 if (v_id != null && v_id != "-1")
@@ -2825,7 +2834,7 @@ namespace ResfullApi.Models.balance
 
 
 
-		public static DataSet apiServiceInsert(string ws_name, string ws_code, string wsdl, string msg_template, string error_tag, string isActive, string users)
+		public static DataSet apiServiceInsert(string ws_name, string ws_code, string wsdl, string msg_template, string error_tag, string success_code, string isActive, string users)
 		{
 			DataSet ds = new DataSet();
 			DataTable tb = new DataTable();
@@ -2835,8 +2844,8 @@ namespace ResfullApi.Models.balance
 			try
 			{
 				dbConnection.Open();
-				string sql = @"INSERT INTO WEBSERVICE(WS_ID, WS_NAME, WS_CODE, WSDL, MSG_TEMPLATE, ERROR_TAG, STATUS)
-							 VALUES(WEBSERVICE_SEQ.NEXTVAL, :ws_name, :ws_code, :wsdl, :msg_template, :error_tag, :status)";
+				string sql = @"INSERT INTO WEBSERVICE(WS_ID, WS_NAME, WS_CODE, WSDL, MSG_TEMPLATE, ERROR_TAG, SUCCESS_CODE, STATUS)
+							 VALUES(WEBSERVICE_SEQ.NEXTVAL, :ws_name, :ws_code, :wsdl, :msg_template, :error_tag, :success_code, :status)";
 				using (OracleCommand cmd = new OracleCommand(sql, dbConnection))
 				{
 					cmd.CommandType = CommandType.Text;
@@ -2845,6 +2854,7 @@ namespace ResfullApi.Models.balance
 					cmd.Parameters.Add(":wsdl", OracleDbType.NVarchar2).Value = wsdl ?? "";
 					cmd.Parameters.Add(":msg_template", OracleDbType.NVarchar2).Value = msg_template ?? "";
 					cmd.Parameters.Add(":error_tag", OracleDbType.NVarchar2).Value = error_tag ?? "";
+					cmd.Parameters.Add(":success_code", OracleDbType.NVarchar2).Value = success_code ?? "";
 					cmd.Parameters.Add(":status", OracleDbType.Int32).Value = (isActive == "0" ? 0 : 1);
 					int affected = cmd.ExecuteNonQuery();
 					// get generated id in this session
@@ -2879,7 +2889,7 @@ namespace ResfullApi.Models.balance
 			return ds;
 		}
 
-		public static DataSet apiServiceUpdate(string id, string ws_name, string ws_code, string wsdl, string msg_template, string error_tag, string isActive, string users)
+		public static DataSet apiServiceUpdate(string id, string ws_name, string ws_code, string wsdl, string msg_template, string error_tag, string success_code, string isActive, string users)
 		{
 			DataSet ds = new DataSet();
 			DataTable tb = new DataTable();
@@ -2895,6 +2905,7 @@ namespace ResfullApi.Models.balance
 								WSDL = :wsdl,
 								MSG_TEMPLATE = :msg_template,
 								ERROR_TAG = :error_tag,
+								SUCCESS_CODE = :success_code,
 								STATUS = :status
 							WHERE WS_ID = :id";
 				using (OracleCommand cmd = new OracleCommand(sql, dbConnection))
@@ -2905,6 +2916,7 @@ namespace ResfullApi.Models.balance
 					cmd.Parameters.Add(":wsdl", OracleDbType.NVarchar2).Value = wsdl ?? "";
 					cmd.Parameters.Add(":msg_template", OracleDbType.NVarchar2).Value = msg_template ?? "";
 					cmd.Parameters.Add(":error_tag", OracleDbType.NVarchar2).Value = error_tag ?? "";
+					cmd.Parameters.Add(":success_code", OracleDbType.NVarchar2).Value = success_code ?? "";
 					cmd.Parameters.Add(":status", OracleDbType.Int32).Value = (isActive == "0" ? 0 : 1);
 					cmd.Parameters.Add(":id", OracleDbType.NVarchar2).Value = id ?? "";
 					int affected = cmd.ExecuteNonQuery();
@@ -2929,6 +2941,369 @@ namespace ResfullApi.Models.balance
 			return ds;
 		}
 
+		public static DataSet GET_HOURLY_IMPRESSIONS(string v_campaignId, string v_serviceId, string v_hours, string v_quickJump)
+		{
+			DataSet ds = new DataSet();
+			OracleConnection dbConnection = DataAccess.getPoolingConnection();
+			try
+			{
+				dbConnection.Open();
+				
+				// Calculate date range based on hours parameter (default 72 hours)
+				int hours = 72;
+				if (!string.IsNullOrEmpty(v_hours) && v_hours != "-1" && v_hours != "All")
+				{
+					hours = int.TryParse(v_hours.Replace("h", ""), out int h) ? h : 72;
+				}
+				
+				DateTime endDate = DateTime.Now;
+				DateTime startDate = endDate.AddHours(-hours);
+				
+				// If quickJump is provided, use it as the center point
+				if (!string.IsNullOrEmpty(v_quickJump) && v_quickJump != "-1")
+				{
+					if (DateTime.TryParseExact(v_quickJump, "yyyy-MM-dd HH", null, System.Globalization.DateTimeStyles.None, out DateTime jumpDate))
+					{
+						startDate = jumpDate.AddHours(-hours / 2);
+						endDate = jumpDate.AddHours(hours / 2);
+					}
+				}
+				
+				// Build WHERE clause
+				string whereClause = "WHERE TRUNC(REPORT_DATE, 'HH') >= TRUNC(:startDate, 'HH') AND TRUNC(REPORT_DATE, 'HH') <= TRUNC(:endDate, 'HH')";
+				
+				if (!string.IsNullOrEmpty(v_campaignId) && v_campaignId != "-1" && v_campaignId != "All")
+				{
+					whereClause += " AND CAMPAIGN_ID = :campaignId";
+				}
+				
+				if (!string.IsNullOrEmpty(v_serviceId) && v_serviceId != "-1" && v_serviceId != "All")
+				{
+					whereClause += " AND SERVICE_ID = :serviceId";
+				}
+				
+				// Build SQL with subquery first, then LEFT JOIN
+				// Always group by CAMPAIGN_ID to show each campaign separately, even when "All" is selected
+				string sql = @"SELECT 
+					base.HOUR_LABEL,
+					base.HOUR_VALUE,
+					base.CAMPAIGN_ID,
+					c.NAME AS CAMPAIGN_NAME,
+					base.COUNT_IMPRESSED
+				FROM (
+					SELECT 
+						TO_CHAR(TRUNC(REPORT_DATE, 'HH'), 'YYYY-MM-DD HH24') AS HOUR_LABEL,
+						TO_CHAR(TRUNC(REPORT_DATE, 'HH'), 'YYYY-MM-DD HH24:MI:SS') AS HOUR_VALUE,
+						CAMPAIGN_ID,
+						SUM(NVL(COUNT_SEND_1, 0)) AS COUNT_IMPRESSED
+					FROM REPORT_COUNT_HOURLY
+					" + whereClause + @"
+					GROUP BY 
+						TRUNC(REPORT_DATE, 'HH'),
+						CAMPAIGN_ID
+				) base
+				LEFT JOIN B_CAMPAIGN c ON base.CAMPAIGN_ID = c.ID
+				ORDER BY base.HOUR_VALUE, base.CAMPAIGN_ID";
+				
+				using (OracleCommand cmd = new OracleCommand(sql, dbConnection))
+				{
+					cmd.CommandType = CommandType.Text;
+					cmd.Parameters.Add(":startDate", OracleDbType.Date).Value = startDate;
+					cmd.Parameters.Add(":endDate", OracleDbType.Date).Value = endDate;
+					
+					if (!string.IsNullOrEmpty(v_campaignId) && v_campaignId != "-1" && v_campaignId != "All")
+					{
+						cmd.Parameters.Add(":campaignId", OracleDbType.NVarchar2).Value = v_campaignId;
+					}
+					
+					if (!string.IsNullOrEmpty(v_serviceId) && v_serviceId != "-1" && v_serviceId != "All")
+					{
+						cmd.Parameters.Add(":serviceId", OracleDbType.NVarchar2).Value = v_serviceId;
+					}
+					
+					OracleDataAdapter adapter = new OracleDataAdapter(cmd);
+					adapter.Fill(ds);
+				}
+			}
+			catch (Exception ex)
+			{
+				throw ex;
+			}
+			finally
+			{
+				dbConnection.Close();
+			}
+			
+			return ds;
+		}
+
+		public static DataSet GET_DAILY_IMPRESSIONS(string v_campaignId, string v_fromDate, string v_toDate)
+		{
+			DataSet ds = new DataSet();
+			OracleConnection dbConnection = DataAccess.getPoolingConnection();
+			try
+			{
+				dbConnection.Open();
+				
+				// Build WHERE clause
+				string whereClause = "WHERE 1=1";
+				
+				if (!string.IsNullOrEmpty(v_fromDate) && v_fromDate != "-1")
+				{
+					whereClause += " AND TRUNC(REPORT_DATE) >= TRUNC(TO_DATE(:fromDate, 'DD/MM/YYYY'))";
+				}
+				
+				if (!string.IsNullOrEmpty(v_toDate) && v_toDate != "-1")
+				{
+					whereClause += " AND TRUNC(REPORT_DATE) <= TRUNC(TO_DATE(:toDate, 'DD/MM/YYYY'))";
+				}
+				
+				if (!string.IsNullOrEmpty(v_campaignId) && v_campaignId != "-1" && v_campaignId != "All")
+				{
+					whereClause += " AND CAMPAIGN_ID = :campaignId";
+				}
+				
+				// Build SQL with subquery first, then LEFT JOIN
+				// Group by date and campaign to show each campaign separately
+				// Using REPORT_COUNT_DAILY table with REPORT_DATE and COUNT_SEND_1
+				string sql = @"SELECT 
+					base.DAY_LABEL,
+					base.DAY_VALUE,
+					base.CAMPAIGN_ID,
+					c.NAME AS CAMPAIGN_NAME,
+					base.COUNT_IMPRESSED
+				FROM (
+					SELECT 
+						TO_CHAR(TRUNC(REPORT_DATE), 'YYYY-MM-DD') AS DAY_LABEL,
+						TRUNC(REPORT_DATE) AS DAY_VALUE,
+						CAMPAIGN_ID,
+						SUM(COUNT_SEND_1) AS COUNT_IMPRESSED
+					FROM REPORT_COUNT_DAILY
+					" + whereClause + @"
+					GROUP BY 
+						TRUNC(REPORT_DATE),
+						CAMPAIGN_ID
+				) base
+				LEFT JOIN B_CAMPAIGN c ON base.CAMPAIGN_ID = c.ID
+				ORDER BY base.DAY_VALUE, base.CAMPAIGN_ID";
+				
+				using (OracleCommand cmd = new OracleCommand(sql, dbConnection))
+				{
+					cmd.CommandType = CommandType.Text;
+					
+					if (!string.IsNullOrEmpty(v_fromDate) && v_fromDate != "-1")
+					{
+						cmd.Parameters.Add(":fromDate", OracleDbType.NVarchar2).Value = v_fromDate;
+					}
+					
+					if (!string.IsNullOrEmpty(v_toDate) && v_toDate != "-1")
+					{
+						cmd.Parameters.Add(":toDate", OracleDbType.NVarchar2).Value = v_toDate;
+					}
+					
+					if (!string.IsNullOrEmpty(v_campaignId) && v_campaignId != "-1" && v_campaignId != "All")
+					{
+						cmd.Parameters.Add(":campaignId", OracleDbType.NVarchar2).Value = v_campaignId;
+					}
+					
+					OracleDataAdapter adapter = new OracleDataAdapter(cmd);
+					adapter.Fill(ds);
+				}
+			}
+			catch (Exception ex)
+			{
+				throw ex;
+			}
+			finally
+			{
+				dbConnection.Close();
+			}
+			
+			return ds;
+		}
+
+		public static DataSet GET_DAILY_UNIQUE_IMPRESSIONS(string v_campaignId, string v_serviceId, string v_fromDate, string v_toDate)
+		{
+			DataSet ds = new DataSet();
+			OracleConnection dbConnection = DataAccess.getPoolingConnection();
+			try
+			{
+				dbConnection.Open();
+
+				string whereClause = "WHERE 1=1";
+
+				if (!string.IsNullOrEmpty(v_fromDate) && v_fromDate != "-1")
+				{
+					whereClause += " AND TRUNC(REPORT_DATE) >= TRUNC(TO_DATE(:fromDate, 'DD/MM/YYYY'))";
+				}
+
+				if (!string.IsNullOrEmpty(v_toDate) && v_toDate != "-1")
+				{
+					whereClause += " AND TRUNC(REPORT_DATE) <= TRUNC(TO_DATE(:toDate, 'DD/MM/YYYY'))";
+				}
+
+				if (!string.IsNullOrEmpty(v_campaignId) && v_campaignId != "-1" && v_campaignId != "All")
+				{
+					whereClause += " AND NVL(CAMPAIGN_ID, -1) = :campaignId";
+				}
+
+				if (!string.IsNullOrEmpty(v_serviceId) && v_serviceId != "-1" && v_serviceId != "All")
+				{
+					whereClause += " AND NVL(SERVICE_ID, -1) = :serviceId";
+				}
+
+				string sql = @"SELECT 
+					base.DAY_LABEL,
+					base.DAY_VALUE,
+					base.CAMPAIGN_ID,
+					c.NAME AS CAMPAIGN_NAME,
+					base.COUNT_IMPRESSED
+				FROM (
+					SELECT 
+						TO_CHAR(TRUNC(REPORT_DATE), 'YYYY-MM-DD') AS DAY_LABEL,
+						TRUNC(REPORT_DATE) AS DAY_VALUE,
+						NVL(CAMPAIGN_ID, -1) AS CAMPAIGN_ID,
+						SUM(NVL(COUNT_IMPRESSED, 0)) AS COUNT_IMPRESSED
+					FROM REPORT_USER_DAILY
+					" + whereClause + @"
+					GROUP BY 
+						TRUNC(REPORT_DATE),
+						NVL(CAMPAIGN_ID, -1)
+				) base
+				LEFT JOIN B_CAMPAIGN c ON base.CAMPAIGN_ID = c.ID
+				ORDER BY base.DAY_VALUE, base.CAMPAIGN_ID";
+
+				using (OracleCommand cmd = new OracleCommand(sql, dbConnection))
+				{
+					cmd.CommandType = CommandType.Text;
+
+					if (!string.IsNullOrEmpty(v_fromDate) && v_fromDate != "-1")
+					{
+						cmd.Parameters.Add(":fromDate", OracleDbType.NVarchar2).Value = v_fromDate;
+					}
+
+					if (!string.IsNullOrEmpty(v_toDate) && v_toDate != "-1")
+					{
+						cmd.Parameters.Add(":toDate", OracleDbType.NVarchar2).Value = v_toDate;
+					}
+
+					if (!string.IsNullOrEmpty(v_campaignId) && v_campaignId != "-1" && v_campaignId != "All")
+					{
+						cmd.Parameters.Add(":campaignId", OracleDbType.NVarchar2).Value = v_campaignId;
+					}
+
+					if (!string.IsNullOrEmpty(v_serviceId) && v_serviceId != "-1" && v_serviceId != "All")
+					{
+						cmd.Parameters.Add(":serviceId", OracleDbType.NVarchar2).Value = v_serviceId;
+					}
+
+					OracleDataAdapter adapter = new OracleDataAdapter(cmd);
+					adapter.Fill(ds);
+				}
+			}
+			catch (Exception ex)
+			{
+				throw ex;
+			}
+			finally
+			{
+				dbConnection.Close();
+			}
+
+			return ds;
+		}
+
+		public static DataSet GET_DAILY_ENGAGED_USERS(string v_campaignId, string v_serviceId, string v_fromDate, string v_toDate)
+		{
+			DataSet ds = new DataSet();
+			OracleConnection dbConnection = DataAccess.getPoolingConnection();
+			try
+			{
+				dbConnection.Open();
+
+				string whereClause = "WHERE 1=1";
+
+				if (!string.IsNullOrEmpty(v_fromDate) && v_fromDate != "-1")
+				{
+					whereClause += " AND TRUNC(REPORT_DATE) >= TRUNC(TO_DATE(:fromDate, 'DD/MM/YYYY'))";
+				}
+
+				if (!string.IsNullOrEmpty(v_toDate) && v_toDate != "-1")
+				{
+					whereClause += " AND TRUNC(REPORT_DATE) <= TRUNC(TO_DATE(:toDate, 'DD/MM/YYYY'))";
+				}
+
+				if (!string.IsNullOrEmpty(v_campaignId) && v_campaignId != "-1" && v_campaignId != "All")
+				{
+					whereClause += " AND NVL(CAMPAIGN_ID, -1) = :campaignId";
+				}
+
+				if (!string.IsNullOrEmpty(v_serviceId) && v_serviceId != "-1" && v_serviceId != "All")
+				{
+					whereClause += " AND NVL(SERVICE_ID, -1) = :serviceId";
+				}
+
+				string sql = @"SELECT 
+					base.DAY_LABEL,
+					base.DAY_VALUE,
+					base.CAMPAIGN_ID,
+					c.NAME AS CAMPAIGN_NAME,
+					base.COUNT_ENGAGED
+				FROM (
+					SELECT 
+						TO_CHAR(TRUNC(REPORT_DATE), 'YYYY-MM-DD') AS DAY_LABEL,
+						TRUNC(REPORT_DATE) AS DAY_VALUE,
+						NVL(CAMPAIGN_ID, -1) AS CAMPAIGN_ID,
+						SUM(NVL(COUNT_ENGAGED, 0)) AS COUNT_ENGAGED
+					FROM REPORT_USER_DAILY
+					" + whereClause + @"
+					GROUP BY 
+						TRUNC(REPORT_DATE),
+						NVL(CAMPAIGN_ID, -1)
+				) base
+				LEFT JOIN B_CAMPAIGN c ON base.CAMPAIGN_ID = c.ID
+				ORDER BY base.DAY_VALUE, base.CAMPAIGN_ID";
+
+				using (OracleCommand cmd = new OracleCommand(sql, dbConnection))
+				{
+					cmd.CommandType = CommandType.Text;
+
+					if (!string.IsNullOrEmpty(v_fromDate) && v_fromDate != "-1")
+					{
+						cmd.Parameters.Add(":fromDate", OracleDbType.NVarchar2).Value = v_fromDate;
+					}
+
+					if (!string.IsNullOrEmpty(v_toDate) && v_toDate != "-1")
+					{
+						cmd.Parameters.Add(":toDate", OracleDbType.NVarchar2).Value = v_toDate;
+					}
+
+					if (!string.IsNullOrEmpty(v_campaignId) && v_campaignId != "-1" && v_campaignId != "All")
+					{
+						cmd.Parameters.Add(":campaignId", OracleDbType.NVarchar2).Value = v_campaignId;
+					}
+
+					if (!string.IsNullOrEmpty(v_serviceId) && v_serviceId != "-1" && v_serviceId != "All")
+					{
+						cmd.Parameters.Add(":serviceId", OracleDbType.NVarchar2).Value = v_serviceId;
+					}
+
+					OracleDataAdapter adapter = new OracleDataAdapter(cmd);
+					adapter.Fill(ds);
+				}
+			}
+			catch (Exception ex)
+			{
+				throw ex;
+			}
+			finally
+			{
+				dbConnection.Close();
+			}
+
+			return ds;
+		}
+
 
 
     }

+ 230 - 0
ApiWeb/ApiProcessToken/Models/reportCountDailyObj.cs

@@ -0,0 +1,230 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using CommonObj.model;
+using Newtonsoft.Json;
+
+namespace ResfullApi.Models
+{
+    public class reportCountDailyObj
+    {
+        [JsonProperty("id")]
+        public string id { get; set; }
+
+        [JsonProperty("reportDate")]
+        public string reportDate { get; set; }
+
+        [JsonProperty("campaignId")]
+        public string campaignId { get; set; }
+
+        [JsonProperty("campaignName")]
+        public string campaignName { get; set; }
+
+        [JsonProperty("priority")]
+        public string priority { get; set; }
+
+        [JsonProperty("isDefault")]
+        public string isDefault { get; set; }
+
+        [JsonProperty("isMyService")]
+        public string isMyService { get; set; }
+
+        [JsonProperty("addType")]
+        public string addType { get; set; }
+
+        [JsonProperty("serviceId")]
+        public string serviceId { get; set; }
+
+        [JsonProperty("serviceName")]
+        public string serviceName { get; set; }
+
+        [JsonProperty("countSend1")]
+        public string countSend1 { get; set; }
+
+        [JsonProperty("countSuccess1")]
+        public string countSuccess1 { get; set; }
+
+        [JsonProperty("countFail1")]
+        public string countFail1 { get; set; }
+
+        [JsonProperty("countPress1")]
+        public string countPress1 { get; set; }
+
+        [JsonProperty("countSend2")]
+        public string countSend2 { get; set; }
+
+        [JsonProperty("countPress2")]
+        public string countPress2 { get; set; }
+
+        [JsonProperty("countRegSuccess")]
+        public string countRegSuccess { get; set; }
+
+        [JsonProperty("countRegFail")]
+        public string countRegFail { get; set; }
+
+        [JsonProperty("insertTime")]
+        public string insertTime { get; set; }
+
+        public override string ToString()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+
+    public class reportCountDailyObjList : Response
+    {
+        [JsonProperty("rowsOnPage")]
+        public string rowsOnPage { get; set; }
+
+        [JsonProperty("seqPage")]
+        public string seqPage { get; set; }
+
+        [JsonProperty("totalPage")]
+        public string totalPage { get; set; }
+
+        [JsonProperty("list")]
+        public reportCountDailyObj[] list { get; set; }
+
+        public override string ToString()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+
+    public class reportErrorDailyObj
+    {
+        [JsonProperty("id")]
+        public string id { get; set; }
+
+        [JsonProperty("reportDate")]
+        public string reportDate { get; set; }
+
+        [JsonProperty("campaignId")]
+        public string campaignId { get; set; }
+
+        [JsonProperty("serviceId")]
+        public string serviceId { get; set; }
+
+        [JsonProperty("errorCode")]
+        public string errorCode { get; set; }
+
+        [JsonProperty("countNum")]
+        public string countNum { get; set; }
+
+        [JsonProperty("insertTime")]
+        public string insertTime { get; set; }
+
+        public override string ToString()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+
+    public class reportErrorDailyObjList : Response
+    {
+        [JsonProperty("list")]
+        public reportErrorDailyObj[] list { get; set; }
+
+        public override string ToString()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+
+    public class hourlyImpressionsObj
+    {
+        [JsonProperty("hourLabel")]
+        public string hourLabel { get; set; }
+
+        [JsonProperty("hourValue")]
+        public string hourValue { get; set; }
+
+        [JsonProperty("campaignId")]
+        public string campaignId { get; set; }
+
+        [JsonProperty("campaignName")]
+        public string campaignName { get; set; }
+
+        [JsonProperty("serviceId")]
+        public string serviceId { get; set; }
+
+        [JsonProperty("serviceName")]
+        public string serviceName { get; set; }
+
+        [JsonProperty("countImpressions")]
+        public string countImpressions { get; set; }
+
+        public override string ToString()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+
+    public class hourlyImpressionsObjList : Response
+    {
+        [JsonProperty("list")]
+        public hourlyImpressionsObj[] list { get; set; }
+
+        public override string ToString()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+
+    public class pushUssdDetailObj
+    {
+        [JsonProperty("id")]
+        public string id { get; set; }
+        [JsonProperty("requestId")]
+        public string requestId { get; set; }
+        [JsonProperty("campaignId")]
+        public string campaignId { get; set; }
+        [JsonProperty("campaignName")]
+        public string campaignName { get; set; }
+        [JsonProperty("serviceId")]
+        public string serviceId { get; set; }
+        [JsonProperty("msisdn")]
+        public string msisdn { get; set; }
+        [JsonProperty("sendTime")]
+        public string sendTime { get; set; }
+        [JsonProperty("sendStatus")]
+        public string sendStatus { get; set; }
+        [JsonProperty("totalStep")]
+        public string totalStep { get; set; }
+        [JsonProperty("isStep1")]
+        public string isStep1 { get; set; }
+        [JsonProperty("step1Time")]
+        public string step1Time { get; set; }
+        [JsonProperty("isStep2")]
+        public string isStep2 { get; set; }
+        [JsonProperty("step2Time")]
+        public string step2Time { get; set; }
+        [JsonProperty("errorCode")]
+        public string errorCode { get; set; }
+        [JsonProperty("isSuccess")]
+        public string isSuccess { get; set; }
+        [JsonProperty("insertTime")]
+        public string insertTime { get; set; }
+        [JsonProperty("lastUpdate")]
+        public string lastUpdate { get; set; }
+    }
+
+    public class pushUssdDetailObjList : Response
+    {
+        [JsonProperty("rowsOnPage")]
+        public string rowsOnPage { get; set; }
+        [JsonProperty("seqPage")]
+        public string seqPage { get; set; }
+        [JsonProperty("totalPage")]
+        public string totalPage { get; set; }
+        [JsonProperty("list")]
+        public pushUssdDetailObj[] list { get; set; }
+
+        public override string ToString()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+}
+

+ 378 - 0
ApiWeb/ApiProcessToken/Models/systemDataAccess.cs

@@ -31,6 +31,107 @@ namespace ResfullApi.Models
             return DataAccess.getDataFromProcedure(str, "", parms);
         }
 
+        public static DataSet SYS_PUSH_USSD_DETAIL_GET_LIST(
+            string v_users, string v_campaignId, string v_serviceId, string v_msisdn, string v_sendStatus, string v_isSuccess,
+            string v_fromDate, string v_toDate, string v_order, string v_rowsOnPage, string v_seqPage)
+        {
+            DataSet ds = new DataSet();
+            OracleConnection dbConnection = DataAccess.getPoolingConnection();
+            try
+            {
+                dbConnection.Open();
+
+                int rowsOnPage = int.TryParse(v_rowsOnPage, out int r) ? r : 50;
+                int seqPage = int.TryParse(v_seqPage, out int s) ? s : 1;
+
+                string baseSql = @"SELECT PUD.ID, PUD.REQUEST_ID, PUD.CAMPAIGN_ID, CAMP.NAME AS CAMPAIGN_NAME, PUD.SERVICE_ID, PUD.MSISDN,
+                                          PUD.SEND_TIME, PUD.SEND_STATUS, PUD.TOTAL_STEP, PUD.IS_STEP_1, PUD.STEP_1_TIME,
+                                          PUD.IS_STEP_2, PUD.STEP_2_TIME, PUD.ERROR_CODE, PUD.IS_SUCCESS, PUD.INSERT_TIME, PUD.LAST_UPDATE
+                                   FROM PUSH_USSD_DETAIL PUD
+                                        LEFT JOIN CAMPAIGN CAMP ON PUD.CAMPAIGN_ID = CAMP.ID
+                                   WHERE 1=1";
+
+                string where = "";
+                if (!string.IsNullOrEmpty(v_campaignId) && v_campaignId != "-1")
+                {
+                    where += " AND PUD.CAMPAIGN_ID = :v_campaignId";
+                }
+                if (!string.IsNullOrEmpty(v_serviceId) && v_serviceId != "-1")
+                {
+                    where += " AND PUD.SERVICE_ID = :v_serviceId";
+                }
+                if (!string.IsNullOrEmpty(v_msisdn) && v_msisdn != "-1")
+                {
+                    where += " AND PUD.MSISDN = :v_msisdn";
+                }
+                if (!string.IsNullOrEmpty(v_sendStatus) && v_sendStatus != "-1")
+                {
+                    where += " AND PUD.SEND_STATUS = :v_sendStatus";
+                }
+                if (!string.IsNullOrEmpty(v_isSuccess) && v_isSuccess != "-1")
+                {
+                    where += " AND PUD.IS_SUCCESS = :v_isSuccess";
+                }
+                if (!string.IsNullOrEmpty(v_fromDate) && v_fromDate != "-1")
+                {
+                    where += " AND TRUNC(PUD.SEND_TIME) >= TRUNC(TO_DATE(:v_fromDate, 'DD/MM/YYYY'))";
+                }
+                if (!string.IsNullOrEmpty(v_toDate) && v_toDate != "-1")
+                {
+                    where += " AND TRUNC(PUD.SEND_TIME) <= TRUNC(TO_DATE(:v_toDate, 'DD/MM/YYYY'))";
+                }
+
+                string orderBy = " ORDER BY PUD.SEND_TIME " + (v_order == "asc" ? "ASC" : "DESC") + ", PUD.ID " + (v_order == "asc" ? "ASC" : "DESC");
+
+                string countSql = "SELECT COUNT(*) FROM (" + baseSql + where + ")";
+                OracleCommand countCmd = new OracleCommand(countSql, dbConnection);
+                if (!string.IsNullOrEmpty(v_campaignId) && v_campaignId != "-1") countCmd.Parameters.Add(":v_campaignId", OracleDbType.NVarchar2).Value = v_campaignId;
+                if (!string.IsNullOrEmpty(v_serviceId) && v_serviceId != "-1") countCmd.Parameters.Add(":v_serviceId", OracleDbType.NVarchar2).Value = v_serviceId;
+                if (!string.IsNullOrEmpty(v_msisdn) && v_msisdn != "-1") countCmd.Parameters.Add(":v_msisdn", OracleDbType.NVarchar2).Value = v_msisdn;
+                if (!string.IsNullOrEmpty(v_sendStatus) && v_sendStatus != "-1") countCmd.Parameters.Add(":v_sendStatus", OracleDbType.NVarchar2).Value = v_sendStatus;
+                if (!string.IsNullOrEmpty(v_isSuccess) && v_isSuccess != "-1") countCmd.Parameters.Add(":v_isSuccess", OracleDbType.NVarchar2).Value = v_isSuccess;
+                if (!string.IsNullOrEmpty(v_fromDate) && v_fromDate != "-1") countCmd.Parameters.Add(":v_fromDate", OracleDbType.NVarchar2).Value = v_fromDate;
+                if (!string.IsNullOrEmpty(v_toDate) && v_toDate != "-1") countCmd.Parameters.Add(":v_toDate", OracleDbType.NVarchar2).Value = v_toDate;
+                int totalRows = Convert.ToInt32(countCmd.ExecuteScalar());
+
+                int minRow = (seqPage - 1) * rowsOnPage;
+                string dataSql = string.Format(@"SELECT * FROM (
+                        SELECT A.*, ROWNUM rnum FROM ({0}{1}{2}) A WHERE ROWNUM <= {3}
+                    ) WHERE rnum > {4}", baseSql, where, orderBy, minRow + rowsOnPage, minRow);
+                OracleCommand dataCmd = new OracleCommand(dataSql, dbConnection);
+                if (!string.IsNullOrEmpty(v_campaignId) && v_campaignId != "-1") dataCmd.Parameters.Add(":v_campaignId", OracleDbType.NVarchar2).Value = v_campaignId;
+                if (!string.IsNullOrEmpty(v_serviceId) && v_serviceId != "-1") dataCmd.Parameters.Add(":v_serviceId", OracleDbType.NVarchar2).Value = v_serviceId;
+                if (!string.IsNullOrEmpty(v_msisdn) && v_msisdn != "-1") dataCmd.Parameters.Add(":v_msisdn", OracleDbType.NVarchar2).Value = v_msisdn;
+                if (!string.IsNullOrEmpty(v_sendStatus) && v_sendStatus != "-1") dataCmd.Parameters.Add(":v_sendStatus", OracleDbType.NVarchar2).Value = v_sendStatus;
+                if (!string.IsNullOrEmpty(v_isSuccess) && v_isSuccess != "-1") dataCmd.Parameters.Add(":v_isSuccess", OracleDbType.NVarchar2).Value = v_isSuccess;
+                if (!string.IsNullOrEmpty(v_fromDate) && v_fromDate != "-1") dataCmd.Parameters.Add(":v_fromDate", OracleDbType.NVarchar2).Value = v_fromDate;
+                if (!string.IsNullOrEmpty(v_toDate) && v_toDate != "-1") dataCmd.Parameters.Add(":v_toDate", OracleDbType.NVarchar2).Value = v_toDate;
+
+                OracleDataAdapter da = new OracleDataAdapter(dataCmd);
+                da.Fill(ds);
+
+                // Append page info
+                DataTable meta = new DataTable("META");
+                meta.Columns.Add("TOTAL_PAGE");
+                meta.Columns.Add("ROW_ON_PAGE");
+                meta.Columns.Add("SEQ_PAGE");
+                var rowMeta = meta.NewRow();
+                rowMeta["TOTAL_PAGE"] = Math.Ceiling((double)totalRows / rowsOnPage).ToString();
+                rowMeta["ROW_ON_PAGE"] = rowsOnPage.ToString();
+                rowMeta["SEQ_PAGE"] = seqPage.ToString();
+                meta.Rows.Add(rowMeta);
+                ds.Tables.Add(meta);
+            }
+            catch (Exception ex)
+            {
+                throw ex;
+            }
+            finally
+            {
+                dbConnection.Close();
+            }
+            return ds;
+        }
         public static DataSet SYS_UPDATE_LOG_CHARGE(string msisdn, string sv_id, string money, string cmd_code, string cmd_msg, string err_code, string err_msg, string isRenew)
         {
 
@@ -63,6 +164,283 @@ namespace ResfullApi.Models
             return DataAccess.getDataFromProcedure(str, "", parms);
         }
 
+        public static DataSet SYS_REPORT_COUNT_DAILY_GET_LIST(string v_users, string v_id, string v_reportDate, string v_campaignId, string v_serviceId, string v_fromDate, string v_toDate, string v_order, string v_rowsOnPage, string v_seqPage)
+        {
+            DataSet ds = new DataSet();
+            OracleConnection dbConnection = DataAccess.getPoolingConnection();
+            try
+            {
+                dbConnection.Open();
+                
+                // Parse pagination parameters
+                int rowsOnPage = int.TryParse(v_rowsOnPage, out int r) ? r : 10;
+                int seqPage = int.TryParse(v_seqPage, out int s) ? s : 1;
+                
+                // Build base query for counting total records
+                string countSql = @"SELECT COUNT(*) FROM REPORT_COUNT_DAILY rcd 
+                                   LEFT JOIN B_CAMPAIGN c ON rcd.CAMPAIGN_ID = c.ID
+                                   LEFT JOIN B_CAMPAIGN_ADD ca ON c.ID = ca.CAMPAIGN_ID AND rcd.SERVICE_ID = ca.SERVICE_ADD_ID AND ca.IS_DELETE = 0
+                                   LEFT JOIN B_SERVICE s ON (ca.SERVICE_ADD_ID = s.ID OR (ca.SERVICE_ADD_ID IS NULL AND rcd.SERVICE_ID = s.ID))
+                                   WHERE 1=1";
+                string dataSql = @"SELECT rcd.ID, rcd.REPORT_DATE, rcd.CAMPAIGN_ID, c.NAME AS CAMPAIGN_NAME,
+                                   c.PRIORITY, c.IS_DEFAULT, c.IS_MYSERVICE, c.ADD_TYPE,
+                                   rcd.SERVICE_ID, s.NAME AS SERVICE_NAME,
+                                   rcd.COUNT_SEND_1, rcd.COUNT_SUCCESS_1, rcd.COUNT_FAIL_1, rcd.COUNT_PRESS_1,
+                                   rcd.COUNT_SEND_2, rcd.COUNT_PRESS_2,
+                                   rcd.COUNT_REG_SUCCESS, rcd.COUNT_REG_FAIL, rcd.INSERT_TIME
+                                   FROM REPORT_COUNT_DAILY rcd
+                                   LEFT JOIN B_CAMPAIGN c ON rcd.CAMPAIGN_ID = c.ID
+                                   LEFT JOIN B_CAMPAIGN_ADD ca ON c.ID = ca.CAMPAIGN_ID AND rcd.SERVICE_ID = ca.SERVICE_ADD_ID AND ca.IS_DELETE = 0
+                                   LEFT JOIN B_SERVICE s ON (ca.SERVICE_ADD_ID = s.ID OR (ca.SERVICE_ADD_ID IS NULL AND rcd.SERVICE_ID = s.ID))
+                                   WHERE 1=1";
+                
+                // Add filters
+                if (v_id != null && v_id != "-1")
+                {
+                    countSql += " AND rcd.ID = :v_id";
+                    dataSql += " AND rcd.ID = :v_id";
+                }
+                
+                if (v_reportDate != null && v_reportDate != "-1")
+                {
+                    countSql += " AND TRUNC(rcd.REPORT_DATE) = TRUNC(TO_DATE(:v_reportDate, 'DD/MM/YYYY'))";
+                    dataSql += " AND TRUNC(rcd.REPORT_DATE) = TRUNC(TO_DATE(:v_reportDate, 'DD/MM/YYYY'))";
+                }
+                
+                if (v_campaignId != null && v_campaignId != "-1")
+                {
+                    countSql += " AND rcd.CAMPAIGN_ID = :v_campaignId";
+                    dataSql += " AND rcd.CAMPAIGN_ID = :v_campaignId";
+                }
+                
+                if (v_serviceId != null && v_serviceId != "-1")
+                {
+                    countSql += " AND rcd.SERVICE_ID = :v_serviceId";
+                    dataSql += " AND rcd.SERVICE_ID = :v_serviceId";
+                }
+                
+                if (v_fromDate != null && v_fromDate != "-1")
+                {
+                    countSql += " AND TRUNC(rcd.REPORT_DATE) >= TRUNC(TO_DATE(:v_fromDate, 'DD/MM/YYYY'))";
+                    dataSql += " AND TRUNC(rcd.REPORT_DATE) >= TRUNC(TO_DATE(:v_fromDate, 'DD/MM/YYYY'))";
+                }
+                
+                if (v_toDate != null && v_toDate != "-1")
+                {
+                    countSql += " AND TRUNC(rcd.REPORT_DATE) <= TRUNC(TO_DATE(:v_toDate, 'DD/MM/YYYY'))";
+                    dataSql += " AND TRUNC(rcd.REPORT_DATE) <= TRUNC(TO_DATE(:v_toDate, 'DD/MM/YYYY'))";
+                }
+                
+                // Add ordering
+                dataSql += " ORDER BY rcd.REPORT_DATE " + (v_order == "desc" ? "DESC" : "ASC") + ", rcd.ID " + (v_order == "desc" ? "DESC" : "ASC");
+                
+                // Calculate pagination
+                OracleCommand countCmd = new OracleCommand(countSql, dbConnection);
+                countCmd.CommandType = CommandType.Text;
+                
+                if (v_id != null && v_id != "-1")
+                {
+                    countCmd.Parameters.Add(":v_id", OracleDbType.NVarchar2).Value = v_id;
+                }
+                
+                if (v_reportDate != null && v_reportDate != "-1")
+                {
+                    countCmd.Parameters.Add(":v_reportDate", OracleDbType.NVarchar2).Value = v_reportDate;
+                }
+                
+                if (v_campaignId != null && v_campaignId != "-1")
+                {
+                    countCmd.Parameters.Add(":v_campaignId", OracleDbType.NVarchar2).Value = v_campaignId;
+                }
+                
+                if (v_serviceId != null && v_serviceId != "-1")
+                {
+                    countCmd.Parameters.Add(":v_serviceId", OracleDbType.NVarchar2).Value = v_serviceId;
+                }
+                
+                if (v_fromDate != null && v_fromDate != "-1")
+                {
+                    countCmd.Parameters.Add(":v_fromDate", OracleDbType.NVarchar2).Value = v_fromDate;
+                }
+                
+                if (v_toDate != null && v_toDate != "-1")
+                {
+                    countCmd.Parameters.Add(":v_toDate", OracleDbType.NVarchar2).Value = v_toDate;
+                }
+                
+                int totalRows = Convert.ToInt32(countCmd.ExecuteScalar());
+                int totalPage = (int)Math.Ceiling((double)totalRows / rowsOnPage);
+                
+                // Get paginated data
+                int minRow = (seqPage - 1) * rowsOnPage;
+                dataSql = string.Format(@"SELECT * FROM (
+                    SELECT A.*, ROWNUM rnum FROM ({0}) A WHERE ROWNUM <= {1}
+                ) WHERE rnum > {2}", dataSql, minRow + rowsOnPage, minRow);
+                
+                OracleCommand dataCmd = new OracleCommand(dataSql, dbConnection);
+                dataCmd.CommandType = CommandType.Text;
+                
+                if (v_id != null && v_id != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_id", OracleDbType.NVarchar2).Value = v_id;
+                }
+                
+                if (v_reportDate != null && v_reportDate != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_reportDate", OracleDbType.NVarchar2).Value = v_reportDate;
+                }
+                
+                if (v_campaignId != null && v_campaignId != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_campaignId", OracleDbType.NVarchar2).Value = v_campaignId;
+                }
+                
+                if (v_serviceId != null && v_serviceId != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_serviceId", OracleDbType.NVarchar2).Value = v_serviceId;
+                }
+                
+                if (v_fromDate != null && v_fromDate != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_fromDate", OracleDbType.NVarchar2).Value = v_fromDate;
+                }
+                
+                if (v_toDate != null && v_toDate != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_toDate", OracleDbType.NVarchar2).Value = v_toDate;
+                }
+                
+                OracleDataAdapter dataAdapter = new OracleDataAdapter(dataCmd);
+                dataAdapter.Fill(ds);
+                
+                // Add pagination metadata to each row
+                if (ds.Tables.Count > 0)
+                {
+                    if (ds.Tables[0].Columns.Contains("ROW_ON_PAGE"))
+                    {
+                        ds.Tables[0].Columns.Remove("ROW_ON_PAGE");
+                    }
+                    if (ds.Tables[0].Columns.Contains("SEQ_PAGE"))
+                    {
+                        ds.Tables[0].Columns.Remove("SEQ_PAGE");
+                    }
+                    if (ds.Tables[0].Columns.Contains("TOTAL_PAGE"))
+                    {
+                        ds.Tables[0].Columns.Remove("TOTAL_PAGE");
+                    }
+                    
+                    ds.Tables[0].Columns.Add("ROW_ON_PAGE", typeof(string));
+                    ds.Tables[0].Columns.Add("SEQ_PAGE", typeof(string));
+                    ds.Tables[0].Columns.Add("TOTAL_PAGE", typeof(string));
+                    ds.Tables[0].Columns.Add("STATUS", typeof(string));
+                    ds.Tables[0].Columns.Add("MSG", typeof(string));
+                    
+                    foreach (DataRow row in ds.Tables[0].Rows)
+                    {
+                        row["ROW_ON_PAGE"] = rowsOnPage.ToString();
+                        row["SEQ_PAGE"] = seqPage.ToString();
+                        row["TOTAL_PAGE"] = totalPage.ToString();
+                        row["STATUS"] = "0";
+                        row["MSG"] = "Success";
+                    }
+                }
+            }
+            catch (OracleException ex)
+            {
+                throw ex;
+            }
+            catch (Exception ex)
+            {
+                throw ex;
+            }
+            finally
+            {
+                dbConnection.Close();
+            }
+            
+            return ds;
+        }
+
+        public static DataSet SYS_REPORT_ERROR_DAILY_GET_LIST(string v_users, string v_reportDate, string v_campaignId, string v_serviceId)
+        {
+            DataSet ds = new DataSet();
+            OracleConnection dbConnection = DataAccess.getPoolingConnection();
+            try
+            {
+                dbConnection.Open();
+                
+                string dataSql = @"SELECT ID, REPORT_DATE, CAMPAIGN_ID, SERVICE_ID, ERROR_CODE, COUNT_NUM, INSERT_TIME
+                                   FROM REPORT_ERROR_DAILY
+                                   WHERE 1=1";
+                
+                // Add filters based on foreign keys
+                if (v_reportDate != null && v_reportDate != "-1" && !string.IsNullOrEmpty(v_reportDate))
+                {
+                    dataSql += " AND TRUNC(REPORT_DATE) = TRUNC(TO_DATE(:v_reportDate, 'DD/MM/YYYY'))";
+                }
+                
+                if (v_campaignId != null && v_campaignId != "-1" && !string.IsNullOrEmpty(v_campaignId))
+                {
+                    dataSql += " AND CAMPAIGN_ID = :v_campaignId";
+                }
+                
+                if (v_serviceId != null && v_serviceId != "-1" && !string.IsNullOrEmpty(v_serviceId))
+                {
+                    dataSql += " AND SERVICE_ID = :v_serviceId";
+                }
+                
+                // Order by error code
+                dataSql += " ORDER BY ERROR_CODE, ID";
+                
+                OracleCommand dataCmd = new OracleCommand(dataSql, dbConnection);
+                dataCmd.CommandType = CommandType.Text;
+                
+                if (v_reportDate != null && v_reportDate != "-1" && !string.IsNullOrEmpty(v_reportDate))
+                {
+                    dataCmd.Parameters.Add(":v_reportDate", OracleDbType.NVarchar2).Value = v_reportDate;
+                }
+                
+                if (v_campaignId != null && v_campaignId != "-1" && !string.IsNullOrEmpty(v_campaignId))
+                {
+                    dataCmd.Parameters.Add(":v_campaignId", OracleDbType.NVarchar2).Value = v_campaignId;
+                }
+                
+                if (v_serviceId != null && v_serviceId != "-1" && !string.IsNullOrEmpty(v_serviceId))
+                {
+                    dataCmd.Parameters.Add(":v_serviceId", OracleDbType.NVarchar2).Value = v_serviceId;
+                }
+                
+                OracleDataAdapter dataAdapter = new OracleDataAdapter(dataCmd);
+                dataAdapter.Fill(ds);
+                
+                // Add status columns
+                if (ds.Tables.Count > 0)
+                {
+                    ds.Tables[0].Columns.Add("STATUS", typeof(string));
+                    ds.Tables[0].Columns.Add("MSG", typeof(string));
+                    
+                    foreach (DataRow row in ds.Tables[0].Rows)
+                    {
+                        row["STATUS"] = "0";
+                        row["MSG"] = "Success";
+                    }
+                }
+            }
+            catch (OracleException ex)
+            {
+                throw ex;
+            }
+            catch (Exception ex)
+            {
+                throw ex;
+            }
+            finally
+            {
+                dbConnection.Close();
+            }
+            
+            return ds;
+        }
 
 
 

+ 596 - 0
ApiWeb/ApiProcessToken/Models/systemUserDataAccess.cs

@@ -0,0 +1,596 @@
+using Oracle.ManagedDataAccess.Client;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Web;
+
+namespace ResfullApi.Models
+{
+    public class systemUserDataAccess
+    {
+        public systemUserDataAccess()
+        {
+
+        }
+
+        public static DataSet SYS_USER_WEB_CMS_GET_LIST(string v_users, string v_id, string v_username, string v_role, string v_isLock, string v_order, string v_rowsOnPage, string v_seqPage)
+        {
+            DataSet ds = new DataSet();
+            OracleConnection dbConnection = DataAccess.getPoolingConnection();
+            try
+            {
+                dbConnection.Open();
+                
+                // Parse pagination parameters
+                int rowsOnPage = int.TryParse(v_rowsOnPage, out int r) ? r : 10;
+                int seqPage = int.TryParse(v_seqPage, out int s) ? s : 1;
+                
+                // Build base query for counting total records
+                string countSql = "SELECT COUNT(*) FROM USER_WEB_CMS WHERE 1=1";
+                string dataSql = @"SELECT ID, USERNAME, PASSWORD, ROLE, COUNTRY_CODE, IS_LOCK, TOTAL_FALSE, TIME_LOCK, NOTE 
+                                   FROM USER_WEB_CMS WHERE 1=1";
+                
+                // Add filters
+                if (v_id != null && v_id != "-1")
+                {
+                    countSql += " AND ID = :v_id";
+                    dataSql += " AND ID = :v_id";
+                }
+                
+                if (v_username != null && v_username != "-1")
+                {
+                    countSql += " AND UPPER(USERNAME) LIKE UPPER(:v_username)";
+                    dataSql += " AND UPPER(USERNAME) LIKE UPPER(:v_username)";
+                }
+                
+                if (v_role != null && v_role != "-1")
+                {
+                    countSql += " AND UPPER(ROLE) LIKE UPPER(:v_role)";
+                    dataSql += " AND UPPER(ROLE) LIKE UPPER(:v_role)";
+                }
+                
+                if (v_isLock != null && v_isLock != "-1")
+                {
+                    countSql += " AND IS_LOCK = :v_isLock";
+                    dataSql += " AND IS_LOCK = :v_isLock";
+                }
+                
+                // Add ordering
+                dataSql += " ORDER BY ID " + (v_order == "desc" ? "DESC" : "ASC");
+                
+                // Calculate pagination
+                OracleCommand countCmd = new OracleCommand(countSql, dbConnection);
+                countCmd.CommandType = CommandType.Text;
+                
+                if (v_id != null && v_id != "-1")
+                {
+                    countCmd.Parameters.Add(":v_id", OracleDbType.NVarchar2).Value = v_id;
+                }
+                
+                if (v_username != null && v_username != "-1")
+                {
+                    countCmd.Parameters.Add(":v_username", OracleDbType.NVarchar2).Value = "%" + v_username + "%";
+                }
+                
+                if (v_role != null && v_role != "-1")
+                {
+                    countCmd.Parameters.Add(":v_role", OracleDbType.NVarchar2).Value = "%" + v_role + "%";
+                }
+                
+                if (v_isLock != null && v_isLock != "-1")
+                {
+                    countCmd.Parameters.Add(":v_isLock", OracleDbType.Int32).Value = int.Parse(v_isLock);
+                }
+                
+                int totalRows = Convert.ToInt32(countCmd.ExecuteScalar());
+                int totalPage = (int)Math.Ceiling((double)totalRows / rowsOnPage);
+                
+                // Get paginated data
+                int minRow = (seqPage - 1) * rowsOnPage;
+                dataSql = string.Format(@"SELECT * FROM (
+                    SELECT A.*, ROWNUM rnum FROM ({0}) A WHERE ROWNUM <= {1}
+                ) WHERE rnum > {2}", dataSql, minRow + rowsOnPage, minRow);
+                
+                OracleCommand dataCmd = new OracleCommand(dataSql, dbConnection);
+                dataCmd.CommandType = CommandType.Text;
+                
+                if (v_id != null && v_id != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_id", OracleDbType.NVarchar2).Value = v_id;
+                }
+                
+                if (v_username != null && v_username != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_username", OracleDbType.NVarchar2).Value = "%" + v_username + "%";
+                }
+                
+                if (v_role != null && v_role != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_role", OracleDbType.NVarchar2).Value = "%" + v_role + "%";
+                }
+                
+                if (v_isLock != null && v_isLock != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_isLock", OracleDbType.Int32).Value = int.Parse(v_isLock);
+                }
+                
+                OracleDataAdapter dataAdapter = new OracleDataAdapter(dataCmd);
+                dataAdapter.Fill(ds);
+                
+                // Add pagination metadata to each row
+                if (ds.Tables.Count > 0)
+                {
+                    if (ds.Tables[0].Columns.Contains("ROW_ON_PAGE"))
+                    {
+                        ds.Tables[0].Columns.Remove("ROW_ON_PAGE");
+                    }
+                    if (ds.Tables[0].Columns.Contains("SEQ_PAGE"))
+                    {
+                        ds.Tables[0].Columns.Remove("SEQ_PAGE");
+                    }
+                    if (ds.Tables[0].Columns.Contains("TOTAL_PAGE"))
+                    {
+                        ds.Tables[0].Columns.Remove("TOTAL_PAGE");
+                    }
+                    
+                    ds.Tables[0].Columns.Add("ROW_ON_PAGE", typeof(string));
+                    ds.Tables[0].Columns.Add("SEQ_PAGE", typeof(string));
+                    ds.Tables[0].Columns.Add("TOTAL_PAGE", typeof(string));
+                    ds.Tables[0].Columns.Add("STATUS", typeof(string));
+                    ds.Tables[0].Columns.Add("MSG", typeof(string));
+                    
+                    foreach (DataRow row in ds.Tables[0].Rows)
+                    {
+                        row["ROW_ON_PAGE"] = rowsOnPage.ToString();
+                        row["SEQ_PAGE"] = seqPage.ToString();
+                        row["TOTAL_PAGE"] = totalPage.ToString();
+                        row["STATUS"] = "0";
+                        row["MSG"] = "Success";
+                    }
+                }
+            }
+            catch (OracleException ex)
+            {
+                throw ex;
+            }
+            catch (Exception ex)
+            {
+                throw ex;
+            }
+            finally
+            {
+                dbConnection.Close();
+            }
+            
+            return ds;
+        }
+
+        public static DataSet SYS_USER_WEB_CMS_INSERT(string V_USERNAME, string V_PASSWORD, string V_ROLE, string V_COUNTRY_CODE, string V_NOTE, string V_USERS)
+        {
+            DataSet ds = new DataSet();
+            DataTable tb = new DataTable();
+            tb.Columns.Add("status", typeof(string));
+            tb.Columns.Add("msg", typeof(string));
+            OracleConnection dbConnection = DataAccess.getPoolingConnection();
+            try
+            {
+                dbConnection.Open();
+                string sql = @"INSERT INTO USER_WEB_CMS(ID, USERNAME, PASSWORD, ROLE, COUNTRY_CODE, NOTE)
+                               VALUES(USER_WEB_CMS_SEQ.NEXTVAL, :username, :password, :role, :countryCode, :note)";
+                using (OracleCommand cmd = new OracleCommand(sql, dbConnection))
+                {
+                    cmd.CommandType = CommandType.Text;
+                    cmd.Parameters.Add(":username", OracleDbType.NVarchar2).Value = V_USERNAME ?? "";
+                    cmd.Parameters.Add(":password", OracleDbType.NVarchar2).Value = V_PASSWORD ?? "";
+                    cmd.Parameters.Add(":role", OracleDbType.NVarchar2).Value = V_ROLE ?? "";
+                    cmd.Parameters.Add(":countryCode", OracleDbType.NVarchar2).Value = V_COUNTRY_CODE ?? "";
+                    cmd.Parameters.Add(":note", OracleDbType.NVarchar2).Value = V_NOTE ?? "";
+                    int affected = cmd.ExecuteNonQuery();
+                    
+                    // get generated id in this session
+                    string newId = "";
+                    try
+                    {
+                        using (OracleCommand idCmd = new OracleCommand("SELECT USER_WEB_CMS_SEQ.CURRVAL FROM DUAL", dbConnection))
+                        {
+                            object val = idCmd.ExecuteScalar();
+                            newId = val == null ? "" : Convert.ToString(val);
+                        }
+                    }
+                    catch { }
+                    
+                    var row = tb.NewRow();
+                    row["status"] = affected > 0 ? "0" : "-1";
+                    row["msg"] = affected > 0 ? ("Success" + (newId != "" ? ("|" + newId) : "")) : "Insert failed";
+                    tb.Rows.Add(row);
+                }
+            }
+            catch (Exception ex)
+            {
+                var row = tb.NewRow();
+                row["status"] = "-1";
+                row["msg"] = ex.Message;
+                tb.Rows.Add(row);
+            }
+            finally
+            {
+                dbConnection.Close();
+            }
+            ds.Tables.Add(tb);
+            return ds;
+        }
+
+        public static DataSet SYS_USER_WEB_CMS_UPDATE(string V_ID, string V_USERNAME, string V_PASSWORD, string V_ROLE, string V_COUNTRY_CODE, string V_IS_LOCK, string V_TIME_LOCK, string V_TOTAL_FALSE, string V_NOTE, string V_USERS)
+        {
+            DataSet ds = new DataSet();
+            DataTable tb = new DataTable();
+            tb.Columns.Add("status", typeof(string));
+            tb.Columns.Add("msg", typeof(string));
+            OracleConnection dbConnection = DataAccess.getPoolingConnection();
+            try
+            {
+                dbConnection.Open();
+                
+                // Build dynamic update SQL based on provided fields
+                List<string> updates = new List<string>();
+                if (!string.IsNullOrEmpty(V_USERNAME) && V_USERNAME != "-1")
+                {
+                    updates.Add("USERNAME = :username");
+                }
+                if (!string.IsNullOrEmpty(V_PASSWORD) && V_PASSWORD != "-1")
+                {
+                    updates.Add("PASSWORD = :password");
+                }
+                if (!string.IsNullOrEmpty(V_ROLE) && V_ROLE != "-1")
+                {
+                    updates.Add("ROLE = :role");
+                }
+                if (!string.IsNullOrEmpty(V_COUNTRY_CODE) && V_COUNTRY_CODE != "-1")
+                {
+                    updates.Add("COUNTRY_CODE = :countryCode");
+                }
+                if (!string.IsNullOrEmpty(V_NOTE) && V_NOTE != "-1")
+                {
+                    updates.Add("NOTE = :note");
+                }
+                if (!string.IsNullOrEmpty(V_IS_LOCK) && V_IS_LOCK != "-1")
+                {
+                    updates.Add("IS_LOCK = :isLock");
+                }
+                if (!string.IsNullOrEmpty(V_TIME_LOCK) && V_TIME_LOCK != "-1")
+                {
+                    updates.Add("TIME_LOCK = :timeLock");
+                }
+                if (!string.IsNullOrEmpty(V_TOTAL_FALSE) && V_TOTAL_FALSE != "-1")
+                {
+                    updates.Add("TOTAL_FALSE = :totalFalse");
+                }
+                
+                if (updates.Count == 0)
+                {
+                    var row = tb.NewRow();
+                    row["status"] = "-1";
+                    row["msg"] = "No fields to update";
+                    tb.Rows.Add(row);
+                    ds.Tables.Add(tb);
+                    return ds;
+                }
+                
+                string sql = @"UPDATE USER_WEB_CMS SET " + string.Join(", ", updates) + " WHERE ID = :id";
+                
+                using (OracleCommand cmd = new OracleCommand(sql, dbConnection))
+                {
+                    cmd.CommandType = CommandType.Text;
+                    
+                    if (!string.IsNullOrEmpty(V_USERNAME) && V_USERNAME != "-1")
+                    {
+                        cmd.Parameters.Add(":username", OracleDbType.NVarchar2).Value = V_USERNAME;
+                    }
+                    if (!string.IsNullOrEmpty(V_PASSWORD) && V_PASSWORD != "-1")
+                    {
+                        cmd.Parameters.Add(":password", OracleDbType.NVarchar2).Value = V_PASSWORD;
+                    }
+                    if (!string.IsNullOrEmpty(V_ROLE) && V_ROLE != "-1")
+                    {
+                        cmd.Parameters.Add(":role", OracleDbType.NVarchar2).Value = V_ROLE;
+                    }
+                    if (!string.IsNullOrEmpty(V_COUNTRY_CODE) && V_COUNTRY_CODE != "-1")
+                    {
+                        cmd.Parameters.Add(":countryCode", OracleDbType.NVarchar2).Value = V_COUNTRY_CODE;
+                    }
+                    if (!string.IsNullOrEmpty(V_NOTE) && V_NOTE != "-1")
+                    {
+                        cmd.Parameters.Add(":note", OracleDbType.NVarchar2).Value = V_NOTE;
+                    }
+                    if (!string.IsNullOrEmpty(V_IS_LOCK) && V_IS_LOCK != "-1")
+                    {
+                        cmd.Parameters.Add(":isLock", OracleDbType.Int32).Value = int.Parse(V_IS_LOCK);
+                    }
+                    if (!string.IsNullOrEmpty(V_TIME_LOCK) && V_TIME_LOCK != "-1")
+                    {
+                        cmd.Parameters.Add(":timeLock", OracleDbType.Date).Value = DateTime.Parse(V_TIME_LOCK);
+                    }
+                    if (!string.IsNullOrEmpty(V_TOTAL_FALSE) && V_TOTAL_FALSE != "-1")
+                    {
+                        cmd.Parameters.Add(":totalFalse", OracleDbType.Int32).Value = int.Parse(V_TOTAL_FALSE);
+                    }
+                    
+                    cmd.Parameters.Add(":id", OracleDbType.NVarchar2).Value = V_ID ?? "";
+                    int affected = cmd.ExecuteNonQuery();
+                    
+                    var row = tb.NewRow();
+                    row["status"] = affected > 0 ? "0" : "-1";
+                    row["msg"] = affected > 0 ? "Success" : "Update failed";
+                    tb.Rows.Add(row);
+                }
+            }
+            catch (Exception ex)
+            {
+                var row = tb.NewRow();
+                row["status"] = "-1";
+                row["msg"] = ex.Message;
+                tb.Rows.Add(row);
+            }
+            finally
+            {
+                dbConnection.Close();
+            }
+            ds.Tables.Add(tb);
+            return ds;
+        }
+
+        public static DataSet SYS_FUNCTION_WEB_CMS_GET_LIST(string v_users, string v_id, string v_role, string v_name, string v_link, string v_order, string v_rowsOnPage, string v_seqPage)
+        {
+            DataSet ds = new DataSet();
+            OracleConnection dbConnection = DataAccess.getPoolingConnection();
+            try
+            {
+                dbConnection.Open();
+                
+                // Parse pagination parameters
+                int rowsOnPage = int.TryParse(v_rowsOnPage, out int r) ? r : 10;
+                int seqPage = int.TryParse(v_seqPage, out int s) ? s : 1;
+                
+                // Build base query for counting total records
+                string countSql = "SELECT COUNT(*) FROM USER_WEB_CMS_FUNCTION WHERE 1=1";
+                string dataSql = @"SELECT ID, ROLE, NAME, LINK, NOTE 
+                                   FROM USER_WEB_CMS_FUNCTION WHERE 1=1";
+                
+                // Add filters
+                if (v_id != null && v_id != "-1")
+                {
+                    countSql += " AND ID = :v_id";
+                    dataSql += " AND ID = :v_id";
+                }
+                
+                if (v_role != null && v_role != "-1")
+                {
+                    countSql += " AND UPPER(ROLE) LIKE UPPER(:v_role)";
+                    dataSql += " AND UPPER(ROLE) LIKE UPPER(:v_role)";
+                }
+                
+                if (v_name != null && v_name != "-1")
+                {
+                    countSql += " AND UPPER(NAME) LIKE UPPER(:v_name)";
+                    dataSql += " AND UPPER(NAME) LIKE UPPER(:v_name)";
+                }
+                
+                if (v_link != null && v_link != "-1")
+                {
+                    countSql += " AND UPPER(LINK) LIKE UPPER(:v_link)";
+                    dataSql += " AND UPPER(LINK) LIKE UPPER(:v_link)";
+                }
+                
+                // Add ordering
+                dataSql += " ORDER BY ID " + (v_order == "desc" ? "DESC" : "ASC");
+                
+                // Calculate pagination
+                OracleCommand countCmd = new OracleCommand(countSql, dbConnection);
+                countCmd.CommandType = CommandType.Text;
+                
+                if (v_id != null && v_id != "-1")
+                {
+                    countCmd.Parameters.Add(":v_id", OracleDbType.NVarchar2).Value = v_id;
+                }
+                
+                if (v_role != null && v_role != "-1")
+                {
+                    countCmd.Parameters.Add(":v_role", OracleDbType.NVarchar2).Value = "%" + v_role + "%";
+                }
+                
+                if (v_name != null && v_name != "-1")
+                {
+                    countCmd.Parameters.Add(":v_name", OracleDbType.NVarchar2).Value = "%" + v_name + "%";
+                }
+                
+                if (v_link != null && v_link != "-1")
+                {
+                    countCmd.Parameters.Add(":v_link", OracleDbType.NVarchar2).Value = "%" + v_link + "%";
+                }
+                
+                int totalRows = Convert.ToInt32(countCmd.ExecuteScalar());
+                int totalPage = (int)Math.Ceiling((double)totalRows / rowsOnPage);
+                
+                // Get paginated data
+                int minRow = (seqPage - 1) * rowsOnPage;
+                dataSql = string.Format(@"SELECT * FROM (
+                    SELECT A.*, ROWNUM rnum FROM ({0}) A WHERE ROWNUM <= {1}
+                ) WHERE rnum > {2}", dataSql, minRow + rowsOnPage, minRow);
+                
+                OracleCommand dataCmd = new OracleCommand(dataSql, dbConnection);
+                dataCmd.CommandType = CommandType.Text;
+                
+                if (v_id != null && v_id != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_id", OracleDbType.NVarchar2).Value = v_id;
+                }
+                
+                if (v_role != null && v_role != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_role", OracleDbType.NVarchar2).Value = "%" + v_role + "%";
+                }
+                
+                if (v_name != null && v_name != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_name", OracleDbType.NVarchar2).Value = "%" + v_name + "%";
+                }
+                
+                if (v_link != null && v_link != "-1")
+                {
+                    dataCmd.Parameters.Add(":v_link", OracleDbType.NVarchar2).Value = "%" + v_link + "%";
+                }
+                
+                OracleDataAdapter dataAdapter = new OracleDataAdapter(dataCmd);
+                dataAdapter.Fill(ds);
+                
+                // Add pagination metadata to each row
+                if (ds.Tables.Count > 0)
+                {
+                    if (ds.Tables[0].Columns.Contains("ROW_ON_PAGE"))
+                    {
+                        ds.Tables[0].Columns.Remove("ROW_ON_PAGE");
+                    }
+                    if (ds.Tables[0].Columns.Contains("SEQ_PAGE"))
+                    {
+                        ds.Tables[0].Columns.Remove("SEQ_PAGE");
+                    }
+                    if (ds.Tables[0].Columns.Contains("TOTAL_PAGE"))
+                    {
+                        ds.Tables[0].Columns.Remove("TOTAL_PAGE");
+                    }
+                    
+                    ds.Tables[0].Columns.Add("ROW_ON_PAGE", typeof(string));
+                    ds.Tables[0].Columns.Add("SEQ_PAGE", typeof(string));
+                    ds.Tables[0].Columns.Add("TOTAL_PAGE", typeof(string));
+                    ds.Tables[0].Columns.Add("STATUS", typeof(string));
+                    ds.Tables[0].Columns.Add("MSG", typeof(string));
+                    
+                    foreach (DataRow row in ds.Tables[0].Rows)
+                    {
+                        row["ROW_ON_PAGE"] = rowsOnPage.ToString();
+                        row["SEQ_PAGE"] = seqPage.ToString();
+                        row["TOTAL_PAGE"] = totalPage.ToString();
+                        row["STATUS"] = "0";
+                        row["MSG"] = "Success";
+                    }
+                }
+            }
+            catch (OracleException ex)
+            {
+                throw ex;
+            }
+            catch (Exception ex)
+            {
+                throw ex;
+            }
+            finally
+            {
+                dbConnection.Close();
+            }
+            
+            return ds;
+        }
+
+        public static DataSet SYS_FUNCTION_WEB_CMS_INSERT(string V_ROLE, string V_NAME, string V_LINK, string V_NOTE, string V_USERS)
+        {
+            DataSet ds = new DataSet();
+            DataTable tb = new DataTable();
+            tb.Columns.Add("status", typeof(string));
+            tb.Columns.Add("msg", typeof(string));
+            OracleConnection dbConnection = DataAccess.getPoolingConnection();
+            try
+            {
+                dbConnection.Open();
+                string sql = @"INSERT INTO USER_WEB_CMS_FUNCTION(ID, ROLE, NAME, LINK, NOTE)
+                               VALUES(USER_WEB_CMS_FUNCTION_SEQ.NEXTVAL, :role, :name, :link, :note)";
+                using (OracleCommand cmd = new OracleCommand(sql, dbConnection))
+                {
+                    cmd.CommandType = CommandType.Text;
+                    cmd.Parameters.Add(":role", OracleDbType.NVarchar2).Value = V_ROLE ?? "";
+                    cmd.Parameters.Add(":name", OracleDbType.NVarchar2).Value = V_NAME ?? "";
+                    cmd.Parameters.Add(":link", OracleDbType.NVarchar2).Value = V_LINK ?? "";
+                    cmd.Parameters.Add(":note", OracleDbType.NVarchar2).Value = V_NOTE ?? "";
+                    int affected = cmd.ExecuteNonQuery();
+                    
+                    // get generated id in this session
+                    string newId = "";
+                    try
+                    {
+                        using (OracleCommand idCmd = new OracleCommand("SELECT USER_WEB_CMS_FUNCTION_SEQ.CURRVAL FROM DUAL", dbConnection))
+                        {
+                            object val = idCmd.ExecuteScalar();
+                            newId = val == null ? "" : Convert.ToString(val);
+                        }
+                    }
+                    catch { }
+                    
+                    var row = tb.NewRow();
+                    row["status"] = affected > 0 ? "0" : "-1";
+                    row["msg"] = affected > 0 ? ("Success" + (newId != "" ? ("|" + newId) : "")) : "Insert failed";
+                    tb.Rows.Add(row);
+                }
+            }
+            catch (Exception ex)
+            {
+                var row = tb.NewRow();
+                row["status"] = "-1";
+                row["msg"] = ex.Message;
+                tb.Rows.Add(row);
+            }
+            finally
+            {
+                dbConnection.Close();
+            }
+            ds.Tables.Add(tb);
+            return ds;
+        }
+
+        public static DataSet SYS_FUNCTION_WEB_CMS_UPDATE(string V_ID, string V_ROLE, string V_NAME, string V_LINK, string V_NOTE, string V_USERS)
+        {
+            DataSet ds = new DataSet();
+            DataTable tb = new DataTable();
+            tb.Columns.Add("status", typeof(string));
+            tb.Columns.Add("msg", typeof(string));
+            OracleConnection dbConnection = DataAccess.getPoolingConnection();
+            try
+            {
+                dbConnection.Open();
+                string sql = @"UPDATE USER_WEB_CMS_FUNCTION
+                               SET ROLE = :role,
+                                   NAME = :name,
+                                   LINK = :link,
+                                   NOTE = :note
+                               WHERE ID = :id";
+                using (OracleCommand cmd = new OracleCommand(sql, dbConnection))
+                {
+                    cmd.CommandType = CommandType.Text;
+                    cmd.Parameters.Add(":role", OracleDbType.NVarchar2).Value = V_ROLE ?? "";
+                    cmd.Parameters.Add(":name", OracleDbType.NVarchar2).Value = V_NAME ?? "";
+                    cmd.Parameters.Add(":link", OracleDbType.NVarchar2).Value = V_LINK ?? "";
+                    cmd.Parameters.Add(":note", OracleDbType.NVarchar2).Value = V_NOTE ?? "";
+                    cmd.Parameters.Add(":id", OracleDbType.NVarchar2).Value = V_ID ?? "";
+                    int affected = cmd.ExecuteNonQuery();
+                    
+                    var row = tb.NewRow();
+                    row["status"] = affected > 0 ? "0" : "-1";
+                    row["msg"] = affected > 0 ? "Success" : "Update failed";
+                    tb.Rows.Add(row);
+                }
+            }
+            catch (Exception ex)
+            {
+                var row = tb.NewRow();
+                row["status"] = "-1";
+                row["msg"] = ex.Message;
+                tb.Rows.Add(row);
+            }
+            finally
+            {
+                dbConnection.Close();
+            }
+            ds.Tables.Add(tb);
+            return ds;
+        }
+    }
+}

+ 108 - 0
ApiWeb/ApiProcessToken/Models/systemUserObj.cs

@@ -0,0 +1,108 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using CommonObj.model;
+using Newtonsoft.Json;
+
+namespace ResfullApi.Models
+{
+    public class userWebCmsObj
+    {
+        [JsonProperty("id")]
+        public string id { get; set; }
+
+        [JsonProperty("username")]
+        public string username { get; set; }
+
+        [JsonProperty("password")]
+        public string password { get; set; }
+
+        [JsonProperty("role")]
+        public string role { get; set; }
+
+        [JsonProperty("countryCode")]
+        public string countryCode { get; set; }
+
+        [JsonProperty("isLock")]
+        public string isLock { get; set; }
+
+        [JsonProperty("totalFalse")]
+        public string totalFalse { get; set; }
+
+        [JsonProperty("timeLock")]
+        public string timeLock { get; set; }
+
+        [JsonProperty("note")]
+        public string note { get; set; }
+
+        public override string ToString()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+
+    public class userWebCmsObjList : Response
+    {
+        [JsonProperty("rowsOnPage")]
+        public string rowsOnPage { get; set; }
+
+        [JsonProperty("seqPage")]
+        public string seqPage { get; set; }
+
+        [JsonProperty("totalPage")]
+        public string totalPage { get; set; }
+
+        [JsonProperty("list")]
+        public userWebCmsObj[] list { get; set; }
+
+        public override string ToString()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+
+    public class functionWebCmsObj
+    {
+        [JsonProperty("id")]
+        public string id { get; set; }
+
+        [JsonProperty("role")]
+        public string role { get; set; }
+
+        [JsonProperty("name")]
+        public string name { get; set; }
+
+        [JsonProperty("link")]
+        public string link { get; set; }
+
+        [JsonProperty("note")]
+        public string note { get; set; }
+
+        public override string ToString()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+
+    public class functionWebCmsObjList : Response
+    {
+        [JsonProperty("rowsOnPage")]
+        public string rowsOnPage { get; set; }
+
+        [JsonProperty("seqPage")]
+        public string seqPage { get; set; }
+
+        [JsonProperty("totalPage")]
+        public string totalPage { get; set; }
+
+        [JsonProperty("list")]
+        public functionWebCmsObj[] list { get; set; }
+
+        public override string ToString()
+        {
+            return JsonConvert.SerializeObject(this);
+        }
+    }
+}
+

+ 847 - 2
SuperAdmin/SuperAdmin/Controllers/AdminController.cs

@@ -12,6 +12,7 @@ using SuperAdmin.Models.Http;
 using SuperAdmin.Models.Object;
 using SuperAdmin.Source;
 using SuperCms.Extensions;
+using Newtonsoft.Json;
 using System;
 using System.Collections.Generic;
 using System.IO;
@@ -30,6 +31,115 @@ namespace SuperAdmin.Controllers
         {
         }
 
+        public IActionResult ReportUssdDetail()
+        {
+            if (!CheckAuthToken())
+            {
+                return Redirect(GetParameter(UtilsController.Constant.SUB_DOMAIN) + "/Home/Login");
+            }
+            return View();
+        }
+
+        [HttpPost]
+        public IActionResult ReportUssdDetailSearch(PushUssdDetailReq req)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+
+            req.rowsOnPage = req.rowsOnPage ?? "100";
+            req.seqPage = req.seqPage ?? "1";
+            req.order = req.order ?? "desc";
+
+            String url = GetParameter(CommonUtils.WsType.PushUssdDetailGetList);
+            PushUssdDetailRes res = PushUssdDetailRes.Parse(SendPost(req, url));
+
+            return PartialView("../Partial/_ReportUssdDetail", res.list);
+        }
+
+        [HttpPost]
+        public IActionResult ReportUssdDetailExport(PushUssdDetailReq req)
+        {
+            if (!CheckAuthToken())
+            {
+                return null;
+            }
+
+            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+
+            String url = GetParameter(CommonUtils.WsType.PushUssdDetailGetList);
+            PushUssdDetailRes res = PushUssdDetailRes.Parse(SendPost(req, url));
+            var list = res.list ?? new List<PushUssdDetail>();
+
+            string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+            string fileName = "campaign_detail.xlsx";
+            try
+            {
+                using (var workbook = new XLWorkbook())
+                {
+                    int i = 1;
+                    IXLWorksheet worksheet = workbook.Worksheets.Add("Campaign Detail");
+                    worksheet.Column(i).Width = 15; worksheet.Cell(1, i++).Value = "No.";
+                    worksheet.Column(i).Width = 20; worksheet.Cell(1, i++).Value = "Request ID";
+                    worksheet.Column(i).Width = 12; worksheet.Cell(1, i++).Value = "Campaign ID";
+                    worksheet.Column(i).Width = 12; worksheet.Cell(1, i++).Value = "Service ID";
+                    worksheet.Column(i).Width = 18; worksheet.Cell(1, i++).Value = "Msisdn";
+                    worksheet.Column(i).Width = 22; worksheet.Cell(1, i++).Value = "Send Time";
+                    worksheet.Column(i).Width = 12; worksheet.Cell(1, i++).Value = "Send Status";
+                    worksheet.Column(i).Width = 10; worksheet.Cell(1, i++).Value = "Total Step";
+                    worksheet.Column(i).Width = 10; worksheet.Cell(1, i++).Value = "Is Step 1";
+                    worksheet.Column(i).Width = 22; worksheet.Cell(1, i++).Value = "Step 1 Time";
+                    worksheet.Column(i).Width = 10; worksheet.Cell(1, i++).Value = "Is Step 2";
+                    worksheet.Column(i).Width = 22; worksheet.Cell(1, i++).Value = "Step 2 Time";
+                    worksheet.Column(i).Width = 18; worksheet.Cell(1, i++).Value = "Error Code";
+                    worksheet.Column(i).Width = 12; worksheet.Cell(1, i++).Value = "Is Success";
+                    worksheet.Column(i).Width = 22; worksheet.Cell(1, i++).Value = "Insert Time";
+                    worksheet.Column(i).Width = 22; worksheet.Cell(1, i++).Value = "Last Update";
+                    worksheet.Row(1).Style.Font.Bold = true;
+                    worksheet.Row(1).Style.Fill.BackgroundColor = XLColor.Yellow;
+
+                    if (list.Count > 0)
+                    {
+                        for (int index = 1; index <= list.Count; index++)
+                        {
+                            i = 1;
+                            var obj = list[index - 1];
+                            worksheet.Cell(index + 1, i++).Value = index;
+                            worksheet.Cell(index + 1, i++).Value = obj.requestId;
+                            worksheet.Cell(index + 1, i++).Value = obj.campaignId;
+                            worksheet.Cell(index + 1, i++).Value = obj.serviceId;
+                            worksheet.Cell(index + 1, i++).Value = obj.msisdn;
+                            worksheet.Cell(index + 1, i++).Value = obj.sendTime;
+                            worksheet.Cell(index + 1, i++).Value = obj.sendStatus;
+                            worksheet.Cell(index + 1, i++).Value = obj.totalStep;
+                            worksheet.Cell(index + 1, i++).Value = obj.isStep1;
+                            worksheet.Cell(index + 1, i++).Value = obj.step1Time;
+                            worksheet.Cell(index + 1, i++).Value = obj.isStep2;
+                            worksheet.Cell(index + 1, i++).Value = obj.step2Time;
+                            worksheet.Cell(index + 1, i++).Value = obj.errorCode;
+                            worksheet.Cell(index + 1, i++).Value = obj.isSuccess;
+                            worksheet.Cell(index + 1, i++).Value = obj.insertTime;
+                            worksheet.Cell(index + 1, i++).Value = obj.lastUpdate;
+                        }
+                    }
+                    using (var stream = new MemoryStream())
+                    {
+                        workbook.SaveAs(stream);
+                        var content = stream.ToArray();
+                        return File(content, contentType, fileName);
+                    }
+                }
+            }
+            catch (Exception)
+            {
+                return null;
+            }
+        }
         public IActionResult Index(String fromDate, String toDate)
         {
             try
@@ -823,7 +933,7 @@ namespace SuperAdmin.Controllers
             return View();
         }
 
-        public IActionResult SchedulerSearch(CalendarGetListReq req)
+        public IActionResult SchedulerSearch(CalendarGetListReq req, string viewType = "calendar")
         {
             if (!CheckAuthToken())
             {
@@ -838,7 +948,8 @@ namespace SuperAdmin.Controllers
             String url = GetParameter(CommonUtils.WsType.CalendarGetList);
             CalendarGetListRes res = CalendarGetListRes.Parse(SendPost(req, url));
 
-            return PartialView("../Partial/_CampaignScheduler", res.list);
+            string viewName = viewType == "table" ? "../Partial/_CampaignSchedulerTable" : "../Partial/_CampaignSchedulerCalendar";
+            return PartialView(viewName, res.list);
         }
 
         [HttpPost]
@@ -1827,5 +1938,739 @@ namespace SuperAdmin.Controllers
                 return null;
             }
         }
+
+        // USER MANAGEMENT
+        public IActionResult UserManagement()
+        {
+            if (!CheckAuthToken())
+            {
+                return Redirect(GetParameter(UtilsController.Constant.SUB_DOMAIN) + "/Home/Login");
+            }
+            return View();
+        }
+
+        [HttpPost]
+        public IActionResult UserSearch(UserWebCmsGetListReq req)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+
+            if (req.rowsOnPage == null)
+            {
+                req.rowsOnPage = "1000000";
+                req.seqPage = "1";
+                req.order = "desc";
+            }
+
+            String url = GetParameter(CommonUtils.WsType.UserWebCmsGetList);
+            UserWebCmsGetListRes res = UserWebCmsGetListRes.Parse(SendPost(req, url));
+
+            return PartialView("../Partial/_Users", res.list);
+        }
+
+        [HttpPost]
+        public JsonResult UserLoadInfo(UserWebCmsGetListReq request)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+            string account = HttpContext.Session.GetString("account");
+
+            String url = GetParameter(CommonUtils.WsType.UserWebCmsGetList);
+            var res = UserWebCmsGetListRes.Parse(SendPost(request, url));
+
+            if (res.list != null && res.list.Count > 0)
+            {
+                return Json(new
+                {
+                    error = res.responseCode,
+                    content = res.responseMessage,
+                    data = res.list[0]
+                });
+            }
+            return Json(new
+            {
+                error = "-1",
+                content = "Not found"
+            });
+        }
+
+        [HttpPost]
+        public JsonResult UserAddUpdate(UserWebCmsUpdateReq request)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+            string account = HttpContext.Session.GetString("account");
+
+            string url = "";
+            if (request.id != null && request.id != "")
+            {
+                url = GetParameter(CommonUtils.WsType.UserWebCmsUpdate);
+            }
+            else
+            {
+                url = GetParameter(CommonUtils.WsType.UserWebCmsInsert);
+            }
+            request.users = account;
+            
+            var res = UserWebCmsUpdateRes.Parse(SendPost(request, url));
+
+            return Json(new
+            {
+                error = res.responseCode,
+                content = res.responseMessage
+            });
+        }
+
+        // REPORT COUNT DAILY
+        public IActionResult ReportCountDaily()
+        {
+            if (!CheckAuthToken())
+            {
+                return Redirect(GetParameter(UtilsController.Constant.SUB_DOMAIN) + "/Home/Login");
+            }
+            
+            // Load campaign list if not in session
+            try
+            {
+                var listCampaign = HttpContext.Session.GetComplexData<List<Campaign>>("listCampaign");
+                if (listCampaign == null)
+                {
+                    CampaignGetListReq req = new CampaignGetListReq();
+                    req.rowsOnPage = "1000000";
+                    req.seqPage = "1";
+                    req.order = "desc";
+
+                    String url = GetParameter(CommonUtils.WsType.CamGetList);
+                    CampaignGetListRes res = CampaignGetListRes.Parse(SendPost(req, url));
+                    HttpContext.Session.SetComplexData("listCampaign", res.list);
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("Error load campaign: ", ex);
+            }
+            
+            return View();
+        }
+
+        [HttpPost]
+        public IActionResult ReportCountDailySearch(ReportCountDailyReq req)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+
+            req.rowsOnPage = "1000000";
+            req.seqPage = "1";
+            req.order = "desc";
+
+            String url = GetParameter(CommonUtils.WsType.ReportCountDailyGetList);
+            ReportCountDailyRes res = ReportCountDailyRes.Parse(SendPost(req, url));
+
+            return PartialView("../Partial/_ReportCountDaily", res.list);
+        }
+
+        [HttpPost]
+        public IActionResult ReportCountDailyExport(ReportCountDailyReq req)
+        {
+            if (!CheckAuthToken())
+            {
+                return null;
+            }
+
+            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+
+            req.rowsOnPage = "1000000";
+            req.seqPage = "1";
+            req.order = "desc";
+
+            String url = GetParameter(CommonUtils.WsType.ReportCountDailyGetList);
+            ReportCountDailyRes res = ReportCountDailyRes.Parse(SendPost(req, url));
+            var list = res.list;
+
+            string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+            string fileName = "report_count_daily.xlsx";
+            try
+            {
+                using (var workbook = new XLWorkbook())
+                {
+                    int i = 1;
+                    IXLWorksheet worksheet = workbook.Worksheets.Add("Report Count Daily");
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "No.";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "ID";
+                    worksheet.Column(i).Width = 20;
+                    worksheet.Cell(1, i++).Value = "Report Date";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Campaign ID";
+                    worksheet.Column(i).Width = 30;
+                    worksheet.Cell(1, i++).Value = "Campaign Name";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Priority";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Is Default";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Add Type";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Is My Service";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Service ID";
+                    worksheet.Column(i).Width = 30;
+                    worksheet.Cell(1, i++).Value = "Service Name";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Count Send 1";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Count Success 1";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Count Fail 1";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Count Press 1";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Count Send 2";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Count Press 2";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Count Reg Success";
+                    worksheet.Column(i).Width = 15;
+                    worksheet.Cell(1, i++).Value = "Count Reg Fail";
+                    worksheet.Column(i).Width = 20;
+                    worksheet.Cell(1, i++).Value = "Insert Time";
+                    // make color
+                    worksheet.Row(1).Style.Font.Bold = true;
+                    worksheet.Row(1).Style.Fill.BackgroundColor = XLColor.Yellow;
+                    if (list != null && list.Count > 0)
+                    {
+                        for (int index = 1; index <= list.Count; index++)
+                        {
+                            i = 1;
+                            var obj = list[index - 1];
+                            worksheet.Cell(index + 1, i++).Value = index;
+                            worksheet.Cell(index + 1, i++).Value = obj.id;
+                            worksheet.Cell(index + 1, i++).SetValue(obj.reportDate);
+                            worksheet.Cell(index + 1, i++).Value = obj.campaignId;
+                            worksheet.Cell(index + 1, i++).Value = obj.campaignName;
+                            worksheet.Cell(index + 1, i++).Value = obj.priority;
+                            worksheet.Cell(index + 1, i++).Value = obj.isDefault;
+                            worksheet.Cell(index + 1, i++).Value = obj.addType == "1" ? "Text" : obj.addType == "2" ? "1-Verification" : obj.addType == "3" ? "2-Verification" : obj.addType;
+                            worksheet.Cell(index + 1, i++).Value = obj.isMyService == "0" ? "mytel" : obj.isMyService == "1" ? "viettech" : obj.isMyService;
+                            worksheet.Cell(index + 1, i++).Value = obj.serviceId;
+                            worksheet.Cell(index + 1, i++).Value = obj.serviceName;
+                            worksheet.Cell(index + 1, i++).Value = obj.countSend1;
+                            worksheet.Cell(index + 1, i++).Value = obj.countSuccess1;
+                            worksheet.Cell(index + 1, i++).Value = obj.countFail1;
+                            worksheet.Cell(index + 1, i++).Value = obj.countPress1;
+                            worksheet.Cell(index + 1, i++).Value = obj.countSend2;
+                            worksheet.Cell(index + 1, i++).Value = obj.countPress2;
+                            worksheet.Cell(index + 1, i++).Value = obj.countRegSuccess;
+                            worksheet.Cell(index + 1, i++).Value = obj.countRegFail;
+                            worksheet.Cell(index + 1, i++).SetValue(obj.insertTime);
+                        }
+                    }
+                    using (var stream = new MemoryStream())
+                    {
+                        workbook.SaveAs(stream);
+                        var content = stream.ToArray();
+                        return File(content, contentType, fileName);
+                    }
+                }
+            }
+            catch (Exception ex)
+            {
+                return null;
+            }
+        }
+
+        // FUNCTION MANAGEMENT
+        public IActionResult FunctionManagement()
+        {
+            if (!CheckAuthToken())
+            {
+                return Redirect(GetParameter(UtilsController.Constant.SUB_DOMAIN) + "/Home/Login");
+            }
+            return View();
+        }
+
+        [HttpPost]
+        public IActionResult FunctionSearch(FunctionWebCmsGetListReq req)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+
+            if (req.rowsOnPage == null)
+            {
+                req.rowsOnPage = "1000000";
+                req.seqPage = "1";
+                req.order = "desc";
+            }
+
+            String url = GetParameter(CommonUtils.WsType.FunctionWebCmsGetList);
+            FunctionWebCmsGetListRes res = FunctionWebCmsGetListRes.Parse(SendPost(req, url));
+
+            return PartialView("../Partial/_Functions", res.list);
+        }
+
+        [HttpPost]
+        public JsonResult FunctionLoadInfo(FunctionWebCmsGetListReq request)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+            string account = HttpContext.Session.GetString("account");
+
+            String url = GetParameter(CommonUtils.WsType.FunctionWebCmsGetList);
+            var res = FunctionWebCmsGetListRes.Parse(SendPost(request, url));
+
+            if (res.list != null && res.list.Count > 0)
+            {
+                return Json(new
+                {
+                    error = res.responseCode,
+                    content = res.responseMessage,
+                    data = res.list[0]
+                });
+            }
+            return Json(new
+            {
+                error = "-1",
+                content = "Not found"
+            });
+        }
+
+        [HttpPost]
+        public JsonResult FunctionAddUpdate(FunctionWebCmsUpdateReq request)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+            string account = HttpContext.Session.GetString("account");
+
+            string url = "";
+            if (request.id != null && request.id != "")
+            {
+                url = GetParameter(CommonUtils.WsType.FunctionWebCmsUpdate);
+            }
+            else
+            {
+                url = GetParameter(CommonUtils.WsType.FunctionWebCmsInsert);
+            }
+            request.users = account;
+            
+            var res = FunctionWebCmsUpdateRes.Parse(SendPost(request, url));
+
+            return Json(new
+            {
+                error = res.responseCode,
+                content = res.responseMessage
+            });
+        }
+
+        [HttpPost]
+        public IActionResult ReportErrorDailyDetail(ReportErrorDailyReq req)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+
+            string account = HttpContext.Session.GetString("account");
+            if (string.IsNullOrEmpty(account))
+            {
+                account = "-1";
+            }
+            req.users = account;
+
+            String url = GetParameter(CommonUtils.WsType.ReportErrorDailyGetList);
+            if (string.IsNullOrEmpty(url))
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "API URL not configured"
+                });
+            }
+
+            try
+            {
+                ReportErrorDailyRes res = ReportErrorDailyRes.Parse(SendPost(req, url));
+
+                if (res == null || res.responseCode != "0")
+                {
+                    return Json(new
+                    {
+                        error = res?.responseCode ?? "-1",
+                        content = res?.responseMessage ?? "Error occurred"
+                    });
+                }
+
+                return PartialView("../Partial/_ReportErrorDaily", res.list ?? new List<ReportErrorDaily>());
+            }
+            catch (Exception ex)
+            {
+                log.Error("Error in ReportErrorDailyDetail: ", ex);
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Error occurred: " + ex.Message
+                });
+            }
+        }
+
+        public IActionResult DashboardReport()
+        {
+            if (!CheckAuthToken())
+            {
+                return Redirect(GetParameter(UtilsController.Constant.SUB_DOMAIN) + "/Home/Login");
+            }
+            
+            // Load campaign list if not in session
+            try
+            {
+                var listCampaign = HttpContext.Session.GetComplexData<List<Campaign>>("listCampaign");
+                if (listCampaign == null)
+                {
+                    CampaignGetListReq req = new CampaignGetListReq();
+                    req.rowsOnPage = "1000000";
+                    req.seqPage = "1";
+                    req.order = "desc";
+
+                    String url = GetParameter(CommonUtils.WsType.CamGetList);
+                    CampaignGetListRes res = CampaignGetListRes.Parse(SendPost(req, url));
+                    HttpContext.Session.SetComplexData("listCampaign", res.list);
+                }
+                
+                var listService = HttpContext.Session.GetComplexData<List<Service>>("listService");
+                if (listService == null)
+                {
+                    PreLoadService();
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("Error load campaign/service: ", ex);
+            }
+            
+            return View();
+        }
+
+        [HttpPost]
+        public JsonResult DashboardReportGetData(string campaignId, string serviceId, string hours, string quickJump)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+
+            string account = HttpContext.Session.GetString("account");
+            if (string.IsNullOrEmpty(account))
+            {
+                account = "-1";
+            }
+
+            try
+            {
+                HourlyImpressionsGetListReq req = new HourlyImpressionsGetListReq();
+                req.users = account;
+                req.campaignId = campaignId ?? "-1";
+                req.serviceId = serviceId ?? "-1";
+                req.hours = hours ?? "72h";
+                req.quickJump = quickJump ?? "-1";
+                req.language = "vi";
+
+                String url = GetParameter(CommonUtils.WsType.HourlyImpressionsGetList);
+                if (string.IsNullOrEmpty(url))
+                {
+                    return Json(new
+                    {
+                        error = "-1",
+                        content = "API URL not configured"
+                    });
+                }
+
+                HourlyImpressionsGetListRes res = HourlyImpressionsGetListRes.Parse(SendPost(req, url));
+                
+                if (res == null || res.responseCode != "0")
+                {
+                    return Json(new
+                    {
+                        error = res?.responseCode ?? "-1",
+                        content = res?.responseMessage ?? "Error occurred"
+                    });
+                }
+
+                return Json(new
+                {
+                    error = "0",
+                    data = JsonConvert.SerializeObject(res)
+                });
+            }
+            catch (Exception ex)
+            {
+                log.Error("Error in DashboardReportGetData: ", ex);
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Error occurred: " + ex.Message
+                });
+            }
+        }
+
+        [HttpPost]
+        public JsonResult DashboardReportGetDailyData(string campaignId, string fromDate, string toDate)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+
+            string account = HttpContext.Session.GetString("account");
+            if (string.IsNullOrEmpty(account))
+            {
+                account = "-1";
+            }
+
+            try
+            {
+                HourlyImpressionsGetListReq req = new HourlyImpressionsGetListReq();
+                req.users = account;
+                req.campaignId = campaignId ?? "-1";
+                req.hours = "-1"; // Not used for daily
+                req.quickJump = "-1"; // Not used for daily
+                req.language = "vi";
+
+                String url = GetParameter(CommonUtils.WsType.DailyImpressionsGetList);
+                if (string.IsNullOrEmpty(url))
+                {
+                    return Json(new
+                    {
+                        error = "-1",
+                        content = "API URL not configured"
+                    });
+                }
+
+                // Create custom request object for daily (with fromDate and toDate)
+                DailyImpressionsGetListReq dailyReq = new DailyImpressionsGetListReq();
+                dailyReq.users = account;
+                dailyReq.campaignId = campaignId ?? "-1";
+                dailyReq.fromDate = fromDate ?? "-1";
+                dailyReq.toDate = toDate ?? "-1";
+                dailyReq.language = "vi";
+                dailyReq.channel = "WEB";
+                dailyReq.token = HttpContext.Session.GetString("token") ?? "";
+
+                HourlyImpressionsGetListRes res = HourlyImpressionsGetListRes.Parse(SendPost(dailyReq, url));
+                
+                if (res == null || res.responseCode != "0")
+                {
+                    return Json(new
+                    {
+                        error = res?.responseCode ?? "-1",
+                        content = res?.responseMessage ?? "Error occurred"
+                    });
+                }
+
+                return Json(new
+                {
+                    error = "0",
+                    data = JsonConvert.SerializeObject(res)
+                });
+            }
+            catch (Exception ex)
+            {
+                log.Error("Error in DashboardReportGetDailyData: ", ex);
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Error occurred: " + ex.Message
+                });
+            }
+        }
+
+        [HttpPost]
+        public JsonResult DashboardReportGetDailyUniqueData(string campaignId, string serviceId, string fromDate, string toDate)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+
+            string account = HttpContext.Session.GetString("account");
+            if (string.IsNullOrEmpty(account))
+            {
+                account = "-1";
+            }
+
+            try
+            {
+                DailyImpressionsGetListReq req = new DailyImpressionsGetListReq();
+                req.users = account;
+                req.campaignId = campaignId ?? "-1";
+                req.serviceId = serviceId ?? "-1";
+                req.fromDate = fromDate ?? "-1";
+                req.toDate = toDate ?? "-1";
+                req.language = "vi";
+                req.channel = "WEB";
+                req.token = HttpContext.Session.GetString("token") ?? "";
+
+                String url = GetParameter(CommonUtils.WsType.DailyUniqueImpressionsGetList);
+                if (string.IsNullOrEmpty(url))
+                {
+                    return Json(new
+                    {
+                        error = "-1",
+                        content = "API URL not configured"
+                    });
+                }
+
+                HourlyImpressionsGetListRes res = HourlyImpressionsGetListRes.Parse(SendPost(req, url));
+
+                if (res == null || res.responseCode != "0")
+                {
+                    return Json(new
+                    {
+                        error = res?.responseCode ?? "-1",
+                        content = res?.responseMessage ?? "Error occurred"
+                    });
+                }
+
+                return Json(new
+                {
+                    error = "0",
+                    data = JsonConvert.SerializeObject(res)
+                });
+            }
+            catch (Exception ex)
+            {
+                log.Error("Error in DashboardReportGetDailyUniqueData: ", ex);
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Error occurred: " + ex.Message
+                });
+            }
+        }
+
+        [HttpPost]
+        public JsonResult DashboardReportGetDailyEngagedData(string campaignId, string fromDate, string toDate)
+        {
+            if (!CheckAuthToken())
+            {
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Login first"
+                });
+            }
+
+            string account = HttpContext.Session.GetString("account");
+            if (string.IsNullOrEmpty(account))
+            {
+                account = "-1";
+            }
+
+            try
+            {
+                DailyImpressionsGetListReq req = new DailyImpressionsGetListReq();
+                req.users = account;
+                req.campaignId = campaignId ?? "-1";
+                req.serviceId = "-1";
+                req.fromDate = fromDate ?? "-1";
+                req.toDate = toDate ?? "-1";
+                req.language = "vi";
+                req.channel = "WEB";
+                req.token = HttpContext.Session.GetString("token") ?? "";
+
+                String url = GetParameter(CommonUtils.WsType.DailyEngagedGetList);
+                if (string.IsNullOrEmpty(url))
+                {
+                    return Json(new
+                    {
+                        error = "-1",
+                        content = "API URL not configured"
+                    });
+                }
+
+                HourlyImpressionsGetListRes res = HourlyImpressionsGetListRes.Parse(SendPost(req, url));
+
+                if (res == null || res.responseCode != "0")
+                {
+                    return Json(new
+                    {
+                        error = res?.responseCode ?? "-1",
+                        content = res?.responseMessage ?? "Error occurred"
+                    });
+                }
+
+                return Json(new
+                {
+                    error = "0",
+                    data = JsonConvert.SerializeObject(res)
+                });
+            }
+            catch (Exception ex)
+            {
+                log.Error("Error in DashboardReportGetDailyEngagedData: ", ex);
+                return Json(new
+                {
+                    error = "-1",
+                    content = "Error occurred: " + ex.Message
+                });
+            }
+        }
     }
 }

+ 1 - 1
SuperAdmin/SuperAdmin/Controllers/HomeController.cs

@@ -103,7 +103,7 @@ namespace SuperAdmin.Controllers
             if (res.status == UtilsController.Constant.SUCCESS)
             {
                 CreateAuthToken(account, res);
-                return Redirect(GetParameter(UtilsController.Constant.SUB_DOMAIN) + "/Admin/Index");
+                return Redirect(GetParameter(UtilsController.Constant.SUB_DOMAIN) + "/Admin/DashboardReport");
             }
             else
             {

+ 1 - 0
SuperAdmin/SuperAdmin/Models/Http/ApiService.cs

@@ -42,6 +42,7 @@ namespace Timor_BPSuperAdmin.Models.Http
         public string isActive { get; set; }
         public string msg_template { get; set; }
         public string error_tag { get; set; }
+        public string success_code { get; set; }
 
     }
 }

+ 1 - 0
SuperAdmin/SuperAdmin/Models/Http/ApiServiceUpdate.cs

@@ -17,6 +17,7 @@ namespace SuperAdmin.Models.Http
         public string isActive { get; set; }
         public string msg_template { get; set; }
         public string error_tag { get; set; }
+        public string success_code { get; set; }
         public string serviceGroupId { get; set; }
         public string apiServiceId { get; set; }
         public string language { get; set; }

+ 2 - 0
SuperAdmin/SuperAdmin/Models/Http/Campaign.cs

@@ -41,6 +41,7 @@ namespace SuperAdmin.Models.Http
         public string title { get; set; }
         public string language { get; set; }
         public string isDefault { get; set; }
+        public string isMyservice { get; set; }
 
         public CampaignUpdateReq() { }
         public CampaignUpdateReq(Campaign cmp)
@@ -56,6 +57,7 @@ namespace SuperAdmin.Models.Http
             this.priority = cmp.priority;
             this.numberDisplay = cmp.numberDisplay;
             this.addType = cmp.addType;
+            this.isMyservice = cmp.isMyService;
         }
     }
 

+ 1 - 0
SuperAdmin/SuperAdmin/Models/Http/CampaignList.cs

@@ -53,6 +53,7 @@ namespace SuperAdmin.Models.Http
         public string title { get; set; }
         public string isReload { get; set; }
         public string isDefault { get; set; }
+        public string isMyService { get; set; }
         public string balanceId { get; set; }
         public string expireDateId { get; set; }
         public string status { get; set; }

+ 194 - 0
SuperAdmin/SuperAdmin/Models/Http/ReportCountDaily.cs

@@ -0,0 +1,194 @@
+using Newtonsoft.Json;
+using SuperAdmin.Models.Object;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace SuperAdmin.Models.Http
+{
+    public class ReportCountDailyReq : Posting
+    {
+        public string users { get; set; }
+        public string id { get; set; }
+        public string reportDate { get; set; }
+        public string campaignId { get; set; }
+        public string serviceId { get; set; }
+        public string fromDate { get; set; }
+        public string toDate { get; set; }
+        public string rowsOnPage { get; set; }
+        public string seqPage { get; set; }
+        public string order { get; set; }
+        public string language { get; set; }
+    }
+
+    public class ReportCountDaily
+    {
+        public string id { get; set; }
+        public string reportDate { get; set; }
+        public string campaignId { get; set; }
+        public string campaignName { get; set; }
+        public string priority { get; set; }
+        public string isDefault { get; set; }
+        public string isMyService { get; set; }
+        public string addType { get; set; }
+        public string serviceId { get; set; }
+        public string serviceName { get; set; }
+        public string countSend1 { get; set; }
+        public string countSuccess1 { get; set; }
+        public string countFail1 { get; set; }
+        public string countPress1 { get; set; }
+        public string countSend2 { get; set; }
+        public string countPress2 { get; set; }
+        public string countRegSuccess { get; set; }
+        public string countRegFail { get; set; }
+        public string insertTime { get; set; }
+    }
+
+    public class ReportCountDailyRes
+    {
+        public string rowsOnPage { get; set; }
+        public string seqPage { get; set; }
+        public string totalPage { get; set; }
+        public List<ReportCountDaily> list { get; set; }
+        public string responseCode { get; set; }
+        public string responseMessage { get; set; }
+
+        public ReportCountDailyRes() { }
+
+        public static ReportCountDailyRes Parse(String json)
+        {
+            return JsonConvert.DeserializeObject<ReportCountDailyRes>(json);
+        }
+    }
+
+    public class ReportErrorDailyReq : Posting
+    {
+        public string users { get; set; }
+        public string reportDate { get; set; }
+        public string campaignId { get; set; }
+        public string serviceId { get; set; }
+    }
+
+    public class ReportErrorDaily
+    {
+        public string id { get; set; }
+        public string reportDate { get; set; }
+        public string campaignId { get; set; }
+        public string serviceId { get; set; }
+        public string errorCode { get; set; }
+        public string countNum { get; set; }
+        public string insertTime { get; set; }
+    }
+
+    public class ReportErrorDailyRes
+    {
+        public List<ReportErrorDaily> list { get; set; }
+        public string responseCode { get; set; }
+        public string responseMessage { get; set; }
+
+        public ReportErrorDailyRes() { }
+
+        public static ReportErrorDailyRes Parse(String json)
+        {
+            return JsonConvert.DeserializeObject<ReportErrorDailyRes>(json);
+        }
+    }
+
+    public class HourlyImpressionsGetListReq : Posting
+    {
+        public string users { get; set; }
+        public string campaignId { get; set; }
+        public string serviceId { get; set; }
+        public string hours { get; set; }
+        public string quickJump { get; set; }
+        public string language { get; set; }
+    }
+
+    public class DailyImpressionsGetListReq : Posting
+    {
+        public string users { get; set; }
+        public string campaignId { get; set; }
+        public string serviceId { get; set; }
+        public string fromDate { get; set; }
+        public string toDate { get; set; }
+        public string language { get; set; }
+    }
+
+    public class HourlyImpressions
+    {
+        public string hourLabel { get; set; }
+        public string hourValue { get; set; }
+        public string campaignId { get; set; }
+        public string campaignName { get; set; }
+        public string serviceId { get; set; }
+        public string serviceName { get; set; }
+        public string countImpressions { get; set; }
+    }
+
+    public class HourlyImpressionsGetListRes
+    {
+        public List<HourlyImpressions> list { get; set; }
+        public string responseCode { get; set; }
+        public string responseMessage { get; set; }
+
+        public HourlyImpressionsGetListRes() { }
+
+        public static HourlyImpressionsGetListRes Parse(String json)
+        {
+            return JsonConvert.DeserializeObject<HourlyImpressionsGetListRes>(json);
+        }
+    }
+
+    public class PushUssdDetailReq : Posting
+    {
+        public string users { get; set; }
+        public string campaignId { get; set; }
+        public string serviceId { get; set; }
+        public string msisdn { get; set; }
+        public string sendStatus { get; set; }
+        public string isSuccess { get; set; }
+        public string fromDate { get; set; }
+        public string toDate { get; set; }
+        public string order { get; set; }
+        public string rowsOnPage { get; set; }
+        public string seqPage { get; set; }
+    }
+
+    public class PushUssdDetail
+    {
+        public string id { get; set; }
+        public string requestId { get; set; }
+        public string campaignId { get; set; }
+        public string campaignName { get; set; }
+        public string serviceId { get; set; }
+        public string msisdn { get; set; }
+        public string sendTime { get; set; }
+        public string sendStatus { get; set; }
+        public string totalStep { get; set; }
+        public string isStep1 { get; set; }
+        public string step1Time { get; set; }
+        public string isStep2 { get; set; }
+        public string step2Time { get; set; }
+        public string errorCode { get; set; }
+        public string isSuccess { get; set; }
+        public string insertTime { get; set; }
+        public string lastUpdate { get; set; }
+    }
+
+    public class PushUssdDetailRes
+    {
+        public string responseCode { get; set; }
+        public string responseMessage { get; set; }
+        public string rowsOnPage { get; set; }
+        public string seqPage { get; set; }
+        public string totalPage { get; set; }
+        public List<PushUssdDetail> list { get; set; }
+
+        public static PushUssdDetailRes Parse(String json)
+        {
+            return JsonConvert.DeserializeObject<PushUssdDetailRes>(json);
+        }
+    }
+}
+

+ 1 - 0
SuperAdmin/SuperAdmin/Models/Http/Service.cs

@@ -27,6 +27,7 @@ namespace SuperAdmin.Models.Http
         public string msgConfirm { get; set; }
         public string serviceGroupId { get; set; }
         public string apiServiceId { get; set; }
+        public string isMyservice { get; set; }
         public string language { get; set; }
         public ServiceUpdateReq() { }
     }

+ 190 - 0
SuperAdmin/SuperAdmin/Models/Http/UserManagement.cs

@@ -0,0 +1,190 @@
+using Newtonsoft.Json;
+using SuperAdmin.Models.Object;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+namespace SuperAdmin.Models.Http
+{
+    public class UserWebCmsGetListReq : Posting
+    {
+        public string users { get; set; }
+        public string language { get; set; }
+        public string id { get; set; }
+        public string username { get; set; }
+        public string role { get; set; }
+        public string isLock { get; set; }
+        public string rowsOnPage { get; set; }
+        public string seqPage { get; set; }
+        public string order { get; set; }
+    }
+
+    public class UserWebCmsGetListRes
+    {
+        public string rowsOnPage { get; set; }
+        public string seqPage { get; set; }
+        public string totalPage { get; set; }
+        public string responseCode { get; set; }
+        public string responseMessage { get; set; }
+        public List<UserWebCms> list { get; set; }
+
+        public UserWebCmsGetListRes() { }
+
+        public static UserWebCmsGetListRes Parse(String json)
+        {
+            return JsonConvert.DeserializeObject<UserWebCmsGetListRes>(json);
+        }
+    }
+
+    public class UserWebCms
+    {
+        public string id { get; set; }
+        public string username { get; set; }
+        public string password { get; set; }
+        public string role { get; set; }
+        public string countryCode { get; set; }
+        public string isLock { get; set; }
+        public string totalFalse { get; set; }
+        public string timeLock { get; set; }
+        public string note { get; set; }
+    }
+
+    public class UserWebCmsInsertReq : Posting
+    {
+        public string username { get; set; }
+        public string password { get; set; }
+        public string role { get; set; }
+        public string countryCode { get; set; }
+        public string note { get; set; }
+        public string users { get; set; }
+    }
+
+    public class UserWebCmsUpdateReq : Posting
+    {
+        public string id { get; set; }
+        public string username { get; set; }
+        public string password { get; set; }
+        public string role { get; set; }
+        public string countryCode { get; set; }
+        public string isLock { get; set; }
+        public string timeLock { get; set; }
+        public string totalFalse { get; set; }
+        public string note { get; set; }
+        public string users { get; set; }
+    }
+
+    public class UserWebCmsInsertRes
+    {
+        public string responseCode { get; set; }
+        public string responseMessage { get; set; }
+        public string id { get; set; }
+
+        public UserWebCmsInsertRes() { }
+
+        public static UserWebCmsInsertRes Parse(String json)
+        {
+            return JsonConvert.DeserializeObject<UserWebCmsInsertRes>(json);
+        }
+    }
+
+    public class UserWebCmsUpdateRes
+    {
+        public string responseCode { get; set; }
+        public string responseMessage { get; set; }
+
+        public UserWebCmsUpdateRes() { }
+
+        public static UserWebCmsUpdateRes Parse(String json)
+        {
+            return JsonConvert.DeserializeObject<UserWebCmsUpdateRes>(json);
+        }
+    }
+
+    // Function Management Models
+    public class FunctionWebCmsGetListReq : Posting
+    {
+        public string users { get; set; }
+        public string language { get; set; }
+        public string id { get; set; }
+        public string role { get; set; }
+        public string name { get; set; }
+        public string link { get; set; }
+        public string rowsOnPage { get; set; }
+        public string seqPage { get; set; }
+        public string order { get; set; }
+    }
+
+    public class FunctionWebCmsGetListRes
+    {
+        public string rowsOnPage { get; set; }
+        public string seqPage { get; set; }
+        public string totalPage { get; set; }
+        public string responseCode { get; set; }
+        public string responseMessage { get; set; }
+        public List<FunctionWebCms> list { get; set; }
+
+        public FunctionWebCmsGetListRes() { }
+
+        public static FunctionWebCmsGetListRes Parse(String json)
+        {
+            return JsonConvert.DeserializeObject<FunctionWebCmsGetListRes>(json);
+        }
+    }
+
+    public class FunctionWebCms
+    {
+        public string id { get; set; }
+        public string role { get; set; }
+        public string name { get; set; }
+        public string link { get; set; }
+        public string note { get; set; }
+    }
+
+    public class FunctionWebCmsInsertReq : Posting
+    {
+        public string role { get; set; }
+        public string name { get; set; }
+        public string link { get; set; }
+        public string note { get; set; }
+        public string users { get; set; }
+    }
+
+    public class FunctionWebCmsUpdateReq : Posting
+    {
+        public string id { get; set; }
+        public string role { get; set; }
+        public string name { get; set; }
+        public string link { get; set; }
+        public string note { get; set; }
+        public string users { get; set; }
+    }
+
+    public class FunctionWebCmsInsertRes
+    {
+        public string responseCode { get; set; }
+        public string responseMessage { get; set; }
+        public string id { get; set; }
+
+        public FunctionWebCmsInsertRes() { }
+
+        public static FunctionWebCmsInsertRes Parse(String json)
+        {
+            return JsonConvert.DeserializeObject<FunctionWebCmsInsertRes>(json);
+        }
+    }
+
+    public class FunctionWebCmsUpdateRes
+    {
+        public string responseCode { get; set; }
+        public string responseMessage { get; set; }
+
+        public FunctionWebCmsUpdateRes() { }
+
+        public static FunctionWebCmsUpdateRes Parse(String json)
+        {
+            return JsonConvert.DeserializeObject<FunctionWebCmsUpdateRes>(json);
+        }
+    }
+}
+

+ 1 - 1
SuperAdmin/SuperAdmin/Properties/PublishProfiles/FolderProfile.pubxml

@@ -17,6 +17,6 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
     <ProjectGuid>000a9a28-ee22-4214-887d-108273a43763</ProjectGuid>
     <SelfContained>true</SelfContained>
     <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
-    <PublishTrimmed>true</PublishTrimmed>
+    <PublishTrimmed>false</PublishTrimmed>
   </PropertyGroup>
 </Project>

+ 14 - 0
SuperAdmin/SuperAdmin/Source/CommonUtils.cs

@@ -271,6 +271,13 @@ namespace SuperAdmin.Source
             public const String SvUnderUpdate = "wsSvUnderUpdate";
             // report
             public const String ReportByCampaign = "wsReportByCampaign";
+            public const String ReportCountDailyGetList = "wsReportCountDailyGetList";
+            public const String ReportErrorDailyGetList = "wsReportErrorDailyGetList";
+            public const String HourlyImpressionsGetList = "wsHourlyImpressionsGetList";
+            public const String DailyImpressionsGetList = "wsDailyImpressionsGetList";
+            public const String DailyUniqueImpressionsGetList = "wsDailyUniqueImpressionsGetList";
+            public const String DailyEngagedGetList = "wsDailyEngagedGetList";
+            public const String PushUssdDetailGetList = "wsPushUssdDetailGetList";
             // criteria
             public const String balanceGetList = "balanceGetList";
             public const String balanceInsert = "balanceInsert";
@@ -290,6 +297,13 @@ namespace SuperAdmin.Source
             public const String listSubFileUpdate = "listSubFileUpdate";
             //
             public const String changeStatus = "changeStatus";
+            // User Management
+            public const String UserWebCmsGetList = "wsUserWebCmsGetList";
+            public const String UserWebCmsInsert = "wsUserWebCmsInsert";
+            public const String UserWebCmsUpdate = "wsUserWebCmsUpdate";
+            public const String FunctionWebCmsGetList = "wsFunctionWebCmsGetList";
+            public const String FunctionWebCmsInsert = "wsFunctionWebCmsInsert";
+            public const String FunctionWebCmsUpdate = "wsFunctionWebCmsUpdate";
         } 
         public static String GetStatusExportName(string status)
         {

+ 9 - 0
SuperAdmin/SuperAdmin/Views/Admin/ApiWebserviceManagement.cshtml

@@ -143,6 +143,12 @@
                             <input type="text" class="form-control" id="error_tag" name="error_tag" >
                         </div>
                     </div>
+                    <div class="col-md-6">
+                        <div class="form-group">
+                            <label for="date">success_code</label>
+                            <input type="text" class="form-control" id="success_code" name="success_code" maxlength="100">
+                        </div>
+                    </div>
                     <div class="col-md-6">
                         <div class="form-group">
                             <label for="status">status</label>
@@ -216,6 +222,7 @@
             $("#wsdl").val("");
             $("#msg_template").val("");
             $("#error_tag").val("");
+            $("#success_code").val("");
             $("#btnAddUpdate").html("Add");
             showModal("modal-add-service");
         }
@@ -245,6 +252,7 @@
                         $("#wsdl").val(sv.wsdl);
                         $("#msg_template").val(sv.msg_template);
                         $("#error_tag").val(sv.error_tag);
+                        $("#success_code").val(sv.success_code || "");
                         $("#status").val(sv.isActive);
                     } else {
                         console.log("error");
@@ -305,6 +313,7 @@
                     wsdl: $("#wsdl").val(),
                     msg_template: $("#msg_template").val(),
                     error_tag: $("#error_tag").val(),
+                    success_code: $("#success_code").val(),
                     isActive: $("#status").val()
                 },
                 success: function (result) {

+ 82 - 46
SuperAdmin/SuperAdmin/Views/Admin/CampaignManagement.cshtml

@@ -9,9 +9,6 @@
 @using SuperAdmin.Source;
 
 @model SearchMsisdnRes
-@{
-
-}
 
 <style>
     img {
@@ -292,6 +289,7 @@
 
             search();
             loadSvGroup();
+            toggleServiceTypeFields();
         });
 
 
@@ -334,7 +332,7 @@
                 success: function (result) {
                     //console.log(result);
                     if (result.error != null) {
-                        alert(result.content);
+                        Swal.fire("Error!", result.content, "error");
                     } else {
                         $("#campaign-info").html(result);
                         updateTotalChar();
@@ -348,6 +346,7 @@
                         
                         if (isEdit) {
                             enableElement();
+                            toggleServiceTypeFields();
                             $("#btnAddUpdate").html("Update");
                         } else {
                             // hide button
@@ -371,20 +370,35 @@
             }
         }
 
+        function updateTitleRequirement(serviceType) {
+            var requireTitle = serviceType === "1";
+            $("#title-required-indicator").toggle(requireTitle);
+            $("#title-helper").toggle(requireTitle);
+            $("#title").attr("data-required", requireTitle ? "true" : "false");
+            if (!requireTitle) {
+                $("#title").removeClass("input-invalid");
+                $("#lblWarning").html("");
+            }
+        }
+
+        function setInputReadonly($input, isReadonly, makeEditableClass) {
+            $input.prop('readonly', isReadonly);
+            if (isReadonly) {
+                $input.removeClass('editable-field').addClass('readonly-field');
+            } else {
+                $input.removeClass('readonly-field');
+                if (makeEditableClass) {
+                    $input.addClass('editable-field');
+                }
+            }
+        }
+
         function toggleServiceTypeFields() {
             var serviceType = $("#addType").val();
             console.log("Service Type changed to: " + serviceType);
+
+            updateTitleRequirement(serviceType);
             
-            // Toggle header visibility
-            if (serviceType == "1" || serviceType == "2") {
-                $("#ussd-teaser-2-header").hide();
-                $("#key-2-header").hide();
-            } else if (serviceType == "3") {
-                $("#ussd-teaser-2-header").show();
-                $("#key-2-header").show();
-            }
-            
-            // Get all service rows
             $("[name='campService']").each(function() {
                 var index = $(this).attr('id').split('-')[1];
                 toggleServiceFieldsByType(serviceType, index);
@@ -392,33 +406,43 @@
         }
 
         function toggleServiceFieldsByType(serviceType, index) {
-            var ussdTeaser2Col = $("#campService-" + index + " .col-md-3:nth-child(4)");
-            var key2Col = $("#campService-" + index + " .col-md-1:nth-child(5)");
+            var row = $("#campService-" + index);
+            var ussd1Col = row.find(".service-field--ussd1");
+            var key1Col = row.find(".service-field--key1");
+            var ussd2Col = row.find(".service-field--ussd2");
+            var key2Col = row.find(".service-field--key2");
             var msgConfirmInput = $("#msgConfirm-" + index);
-            
-            if (serviceType == "1" || serviceType == "2") {
-                // Service Type 1 hoặc 2: chỉ hiển thị Title teaser và USSD Teaser 1
-                ussdTeaser2Col.hide();
+            var ussd1Input = $("#ussdDisplay-" + index);
+            var key1Input = $("#keyRegister-" + index);
+
+            if (serviceType === "1") {
+                ussd1Col.hide();
+                key1Col.hide();
+                ussd2Col.hide();
                 key2Col.hide();
-                console.log("Hiding USSD Teaser 2 and Key 2 for service " + index);
-            } else if (serviceType == "3") {
-                // Service Type 3: hiển thị cả 4 ô và cho phép chỉnh sửa USSD Teaser 2
-                ussdTeaser2Col.show();
+
+                setInputReadonly(ussd1Input, true);
+                setInputReadonly(key1Input, true);
+                setInputReadonly(msgConfirmInput, true);
+                msgConfirmInput.val("");
+            } else if (serviceType === "2") {
+                ussd1Col.show();
+                key1Col.show();
+                ussd2Col.hide();
+                key2Col.hide();
+
+                setInputReadonly(ussd1Input, false);
+                setInputReadonly(key1Input, false);
+                setInputReadonly(msgConfirmInput, true);
+            } else if (serviceType === "3") {
+                ussd1Col.show();
+                key1Col.show();
+                ussd2Col.show();
                 key2Col.show();
-                
-                // Store current value before making editable
-                var currentValue = msgConfirmInput.val();
-                
-                // Remove readonly attribute để có thể chỉnh sửa
-                msgConfirmInput.removeAttr('readonly');
-                msgConfirmInput.addClass('editable-field');
-                
-                // Restore the value if it exists (to preserve existing data)
-                if (currentValue && currentValue.trim() !== '') {
-                    msgConfirmInput.val(currentValue);
-                }
-                
-                console.log("Showing all fields for service " + index + " and making USSD Teaser 2 editable. Current value: " + currentValue);
+
+                setInputReadonly(ussd1Input, false);
+                setInputReadonly(key1Input, false);
+                setInputReadonly(msgConfirmInput, false, true);
             }
         }
 
@@ -734,6 +758,7 @@
             $("#name").val("");
             $("#title").val("");
             $("#priority").val(1);
+            $("#my_service").val("0");
             $("#total_characters").val("0");
             $("#div-services").html("");
             $("#list-campBalance").html("");
@@ -807,6 +832,16 @@
                 return;
             }
 
+            var serviceType = $("#addType").val();
+            var titleValue = $("#title").val();
+
+            if (serviceType === "1" && (titleValue == null || titleValue.trim() === "")) {
+                stopSpinner("btnAddUpdate");
+                $("#lblWarning").html("Title teaser is required for Service Type: Text");
+                $("#title").addClass('input-invalid').focus();
+                return;
+            }
+
             // list camp service
             //var len = $("#div-services").children().length;
             var services = [];
@@ -817,30 +852,30 @@
             var isPass = true;
             // services
             $("#div-services").children().each(function () {
-                var title = $("#title").val();
                 var i = parseInt($(this).attr('id').split('-')[1]);
                 let svId = $("#serviceAddId-" + i).val();
                 let ussdDisplay = $("#ussdDisplay-" + i).val();
                 let keyRegister = $("#keyRegister-" + i).val();
                 let msgConfirm = $("#msgConfirm-" + i).val();
                 let note = $("#serviceAddId-" + i + " option:selected").text();
-                
-                if(ussdDisplay == null || ussdDisplay == "") {
+                let requireKey1 = serviceType === "2" || serviceType === "3";
+                let requireUssd1 = serviceType === "2" || serviceType === "3";
+                let requireUssd2 = serviceType === "3";
+
+                if(requireUssd1 && (ussdDisplay == null || ussdDisplay.trim() === "")) {
                     $("#ussdDisplay-" + i).addClass('input-invalid');
                     $("#ussdDisplay-" + i).focus();
                     isPass = false;
                     return;
                 }
-                if(keyRegister == null || keyRegister == "") {
+                if(requireKey1 && (keyRegister == null || keyRegister.trim() === "")) {
                     $("#keyRegister-" + i).addClass('input-invalid');
                     $("#keyRegister-" + i).focus();
                     isPass = false;
                     return;
                 }
                 
-                // Validate msgConfirm based on Service Type
-                var serviceType = $("#addType").val();
-                if (serviceType == "3" && (msgConfirm == null || msgConfirm == "")) {
+                if (requireUssd2 && (msgConfirm == null || msgConfirm.trim() === "")) {
                     $("#msgConfirm-" + i).addClass('input-invalid');
                     $("#msgConfirm-" + i).focus();
                     isPass = false;
@@ -910,7 +945,8 @@
                         isDefault: $("#isDefault").val(),
                         priority: $("#priority").val(),
                         numberDisplay: $("#numberDisplay").val(),
-                        title: $("#title").val()
+                        title: $("#title").val(),
+                        isMyservice: $("#my_service").val()
                     },
                     listCampSv: services,
                     shortCodeId : shortCodeId,

+ 14 - 26
SuperAdmin/SuperAdmin/Views/Admin/CampaignScheduler.cshtml

@@ -21,7 +21,7 @@
 
 <div class="content-header row">
     <div class="content-header-left col-md-6 col-xs-12 mb-1">
-        <h2 class="content-header-title">Service Management</h2>
+        <h2 class="content-header-title">Calendar Management</h2>
     </div>
     <div class="content-header-right breadcrumbs-right breadcrumbs-top col-md-6 col-xs-12">
         <div class="breadcrumb-wrapper col-xs-12">
@@ -96,6 +96,10 @@
                                                 <i class="fa fa-export"></i> Export Excel
                                             </button>
 
+                                            <button type="button" class="btn btn-secondary" onclick="toggleView()" id="btnToggleView">
+                                                <i class="fa fa-calendar-alt"></i> <span id="viewModeText">Calendar View</span>
+                                            </button>
+
                                         </div>
                                     </div>
                                 </div>
@@ -124,8 +128,7 @@
 
 @section Scripts {
     <script>
-
-        var tableDetail;
+        var currentViewType = 'calendar'; // Default to calendar view
 
         $(document).ready(function () {
 
@@ -142,43 +145,28 @@
             search();
         });
 
-
-        function resetTableDetail() {
-            tableDetail = $("#partial-content").find("table").DataTable({
-                orderCellsTop: true,
-                // rowGroup: {
-                //     dataSrc: 0 // column index to group by
-                // }
-            });
-        }
-
-        function clearTable(table) {
-            if (table != null && table != undefined) {
-                table
-                    .clear()
-                    .destroy();
-            }
+        function toggleView() {
+            currentViewType = currentViewType === 'calendar' ? 'table' : 'calendar';
+            var iconClass = currentViewType === 'calendar' ? 'fa-calendar-alt' : 'fa-table';
+            $('#btnToggleView').find('i').removeClass().addClass('fa ' + iconClass);
+            $('#viewModeText').text(currentViewType === 'calendar' ? 'Calendar View' : 'Table View');
+            search();
         }
 
         function search() {
             startSpinner('btnSearch');
-            clearTable(tableDetail);
             $.ajax({
                 url: urlConfig("/Admin/SchedulerSearch"),
                 data: {
                     fromDate: $("#fromDateSearch").val(),
                     toDate: $("#toDateSearch").val(),
-                    status: $("#statusSearch").val()
+                    status: $("#statusSearch").val(),
+                    viewType: currentViewType
                 },
                 type: "POST",
                 success: function (data) {
                     stopSpinner('btnSearch');
-
                     $("#partial-content").html(data);
-                    resetTableDetail();
-                    // $("#partial-content").find("table").DataTable({
-                    //     orderCellsTop: true
-                    // });
                 },
                 error: function (data) {
                     stopSpinner('btnSearch');

+ 1655 - 0
SuperAdmin/SuperAdmin/Views/Admin/DashboardReport.cshtml

@@ -0,0 +1,1655 @@
+@{
+    ViewBag.Title = "Dashboard Report - Hourly Impressions";
+    Layout = "~/Views/Shared/_Layout.cshtml";
+}
+
+@using SuperAdmin.Models;
+@using SuperAdmin.Models.Http;
+@using SuperAdmin.Controllers;
+@using SuperAdmin.Source;
+@using Microsoft.AspNetCore.Http;
+@using SuperCms.Extensions; 
+
+@{
+    var subDomain = Context.Session.GetString("subDomain");
+    var listCampaign = Context.Session.GetComplexData<List<Campaign>>("listCampaign");
+    if (listCampaign == null)
+    {
+        listCampaign = new List<Campaign>();
+    }
+    var listService = Context.Session.GetComplexData<List<Service>>("listService");
+    if (listService == null)
+    {
+        listService = new List<Service>();
+    }
+}
+
+<style>
+    .chart-container {
+        position: relative;
+        height: 400px;
+        margin-bottom: 20px;
+    }
+    
+    .series-controls {
+        background: #f8f9fa;
+        padding: 15px;
+        border-radius: 4px;
+        margin-bottom: 20px;
+    }
+    
+    .series-checkbox {
+        margin-right: 15px;
+        margin-bottom: 10px;
+    }
+    
+    .filter-section {
+        background: white;
+        padding: 15px;
+        border-radius: 4px;
+        margin-bottom: 20px;
+        box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+    }
+</style>
+
+<div class="content-header row">
+    <div class="content-header-left col-md-6 col-xs-12 mb-1">
+        <h2 class="content-header-title">Hourly Impressions (Last 72h)</h2>
+    </div>
+    <div class="content-header-right breadcrumbs-right breadcrumbs-top col-md-6 col-xs-12">
+        <div class="breadcrumb-wrapper col-xs-12">
+            <ol class="breadcrumb">
+                <li class="breadcrumb-item">
+                    <a href="Index">Admin</a>
+                </li>
+                <li class="breadcrumb-item">
+                    <a href="#">Dashboard Report</a>
+                </li>
+            </ol>
+        </div>
+    </div>
+</div>
+
+<div class="content-body">
+    <section id="basic-form-layouts">
+        <div class="row">
+            <div class="col-12">
+                <!-- Filters Section -->
+                <div class="card">
+                    <div class="card-header">
+                        <h4 class="card-title">Filters</h4>
+                        <a class="heading-elements-toggle"><i class="icon-ellipsis font-medium-3"></i></a>
+                        <div class="heading-elements">
+                            <ul class="list-inline mb-0">
+                                <li><a data-action="collapse"><i class="icon-minus4"></i></a></li>
+                                <li><a data-action="expand"><i class="icon-expand2"></i></a></li>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="card-content collapse show">
+                        <div class="card-body">
+                            <div class="row">
+                                <div class="col-md-4">
+                                    <div class="form-group">
+                                        <label>Time Window</label>
+                                        <select class="form-control" id="timeWindow">
+                                            <option value="24h">24h</option>
+                                            <option value="48h">48h</option>
+                                            <option value="72h" selected>72h</option>
+                                        </select>
+                                    </div>
+                                </div>
+                                <div class="col-md-4">
+                                    <div class="form-group">
+                                        <label>Campaign</label>
+                                        <select class="form-control" id="campaignFilter">
+                                            <option value="-1">All</option>
+                                            @if (listCampaign != null)
+                                            {
+                                                @foreach (Campaign camp in listCampaign)
+                                                {
+                                                    <option value="@camp.id">@camp.id - @camp.name</option>
+                                                }
+                                            }
+                                        </select>
+                                    </div>
+                                </div>
+                                <div class="col-md-4">
+                                    <div class="form-group">
+                                        <label>Quick Jump</label>
+                                        <div class='input-group'>
+                                            <input type="text" class="form-control quickJumpDate" id="quickJump" placeholder="Select date and hour">
+                                            <span class="input-group-addon">
+                                                <i class="fa fa-calendar"></i>
+                                            </span>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                            <div class="row">
+                                <div class="col-md-12">
+                                    <button type="button" class="btn btn-info" onclick="refreshChart()" id="btnRefresh">
+                                        <i class="fa fa-refresh"></i> Refresh
+                                    </button>
+                                    <button type="button" class="btn btn-success" onclick="exportToExcel()" id="btnExport">
+                                        <i class="fa fa-file-excel-o"></i> Export to Excel
+                                    </button>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+
+                <!-- Chart Section -->
+                <div class="card">
+                    <div class="card-header">
+                        <h4 class="card-title">Impressions by Hour</h4>
+                    </div>
+                    <div class="card-content">
+                        <div class="card-body">
+                            <div class="chart-container">
+                                <canvas id="chartHourlyImpressions"></canvas>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </section>
+
+    <!-- DAILY IMPRESSIONS SECTION -->
+    <section id="daily-impressions-section">
+        <div class="row">
+            <div class="col-12">
+                <div class="card">
+                    <div class="card-header">
+                        <h4 class="card-title">2 DAILY IMPRESSION</h4>
+                    </div>
+                    <div class="card-content">
+                        <div class="card-body">
+                            <!-- Filters Section -->
+                            <div class="row mb-3">
+                                <div class="col-md-12">
+                                    <div class="card">
+                                        <div class="card-header">
+                                            <h5>Filters</h5>
+                                        </div>
+                                        <div class="card-body">
+                                            <div class="row">
+                                                <div class="col-md-3">
+                                                    <div class="form-group">
+                                                        <label>Time Range (n-31→n)</label>
+                                                        <div class="row">
+                                                            <div class="col-6">
+                                                                <input type="text" class="form-control dailyFromDate" id="dailyFromDate" placeholder="DD/MM/YYYY">
+                                                            </div>
+                                                            <div class="col-6">
+                                                                <input type="text" class="form-control dailyToDate" id="dailyToDate" placeholder="DD/MM/YYYY">
+                                                            </div>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                                <div class="col-md-3">
+                                                    <div class="form-group">
+                                                        <label>&nbsp;</label>
+                                                        <div class="col-6">
+                                                            <button type="button" class="btn btn-success" onclick="exportDailyToExcel()" id="btnExportDaily">
+                                                                <i class="fa fa-file-excel-o"></i> Export to Excel (CSV)
+                                                            </button>
+                                                        </div>
+                                                    </div>
+                                                </div>
+                                                
+                                                <div class="col-md-2">
+                                                    <div class="form-group">
+                                                        <label>&nbsp;</label>
+                                                        <button type="button" class="btn btn-secondary btn-block" onclick="resetDailyFilters()">
+                                                            <i class="fa fa-refresh"></i> Reset
+                                                        </button>
+                                                    </div>
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <!-- Chart Section -->
+                            <div class="card">
+                                <div class="card-header">
+                                    <h4 class="card-title">Impressions by Day</h4>
+                                </div>
+                                <div class="card-content">
+                                    <div class="card-body">
+                                        <div class="chart-container">
+                                            <canvas id="chartDailyImpressions"></canvas>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </section>
+
+    <!-- DAILY UNIQUE IMPRESSED USERS -->
+    <section id="daily-unique-impressions-section">
+        <div class="row">
+            <div class="col-12">
+                <div class="card">
+                    <div class="card-header d-flex justify-content-between align-items-center flex-wrap">
+                        <div>
+                            <h4 class="card-title mb-0">Daily Impressed Users (Unique)</h4>
+                        </div>
+                        <div class="mt-1 mt-sm-0 d-flex align-items-center">
+                            <div class="btn-group mr-1" id="dailyUniqueRangeButtons">
+                                <button type="button" class="btn btn-outline-secondary" data-range="7">7 Day</button>
+                                <button type="button" class="btn btn-outline-secondary" data-range="14">14 Day</button>
+                                <button type="button" class="btn btn-outline-secondary active" data-range="32">32 Day</button>
+                            </div>
+                            <button type="button" class="btn btn-success" id="btnDailyUniqueExport">
+                                <i class="fa fa-file-excel-o"></i> Export ra Excel
+                            </button>
+                        </div>
+                    </div>
+                    <div class="card-content">
+                        <div class="card-body">
+                            <div class="mb-2">
+                                <span id="dailyUniqueDateLabel" class="font-weight-bold"></span>
+                            </div>
+                            <div id="dailyUniqueServiceFilters" class="mb-2 d-flex flex-wrap"></div>
+                            <div class="chart-container">
+                                <canvas id="chartDailyUniqueUsers"></canvas>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </section>
+
+    <!-- DAILY ENGAGED USERS -->
+    <section id="daily-engaged-section">
+        <div class="row">
+            <div class="col-12">
+                <div class="card">
+                    <div class="card-header">
+                        <div class="d-flex justify-content-between align-items-center flex-wrap">
+                            <div>
+                                <h4 class="card-title mb-0">Daily Engaged Users</h4>
+                            </div>
+                            <div class="d-flex align-items-center mt-1 mt-sm-0 flex-wrap">
+                                <div class="d-flex align-items-center mr-2 mb-1">
+                                    <label class="mb-0 mr-1">FromDate</label>
+                                    <input type="text" class="form-control dailyEngagedDatePicker" id="dailyEngagedFromDate" style="width: 140px;" placeholder="DD/MM/YYYY">
+                                </div>
+                                <div class="d-flex align-items-center mr-2 mb-1">
+                                    <label class="mb-0 mr-1">ToDate</label>
+                                    <input type="text" class="form-control dailyEngagedDatePicker" id="dailyEngagedToDate" style="width: 140px;" placeholder="DD/MM/YYYY">
+                                </div>
+                                
+                                <button type="button" class="btn btn-primary mr-1 mb-1" id="btnDailyEngagedSearch">
+                                    <i class="fa fa-search"></i> Search
+                                </button>
+                                <button type="button" class="btn btn-success mb-1" id="btnDailyEngagedExport">
+                                    <i class="fa fa-file-excel-o"></i> Export Excel
+                                </button>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="card-content">
+                        <div class="card-body">
+                            <div class="chart-container">
+                                <canvas id="chartDailyEngaged"></canvas>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </section>
+</div>
+
+@section Scripts {
+    <script src="@ViewBag.MyConfig.MyValue/js/vendors/chart.min.js"></script>
+    <script>
+        var chartInstance = null;
+        var chartData = {
+            labels: [],
+            datasets: []
+        };
+        function getPaletteColor(index, palette) {
+            if (index < palette.length) {
+                return palette[index];
+            }
+            var offset = index - palette.length;
+            var hue = (offset * 53 + 30) % 360;
+            if (hue < 15 || hue > 345) {
+                hue = (hue + 40) % 360;
+            }
+            return "hsl(" + hue + ",70%,45%)";
+        }
+
+        $(document).ready(function () {
+            // Check if Chart.js is loaded
+            if (typeof Chart === 'undefined') {
+                console.error('Chart.js is not loaded. Please check the script path.');
+                Swal.fire("Warning!", "Chart library is not loaded. Please refresh the page.", "warning");
+                return;
+            }
+            
+            // Initialize datetimepicker for Quick Jump
+            $(".quickJumpDate").datetimepicker({
+                format: "YYYY-MM-DD HH",
+                sideBySide: true,
+                showTodayButton: true,
+                showClear: true,
+                icons: {
+                    time: "fa fa-clock-o",
+                    date: "fa fa-calendar",
+                    up: "fa fa-arrow-up",
+                    down: "fa fa-arrow-down",
+                    previous: "fa fa-chevron-left",
+                    next: "fa fa-chevron-right",
+                    today: "fa fa-check-circle",
+                    clear: "fa fa-trash",
+                    close: "fa fa-remove"
+                }
+            });
+            
+            refreshChart();
+        });
+
+        function refreshChart() {
+            startSpinner('btnRefresh');
+            
+            var campaignId = $("#campaignFilter").val();
+            var serviceId = "-1"; // Always show all services
+            var hours = $("#timeWindow").val();
+            var quickJump = $("#quickJump").val() || "-1";
+            
+            // Format quickJump if it has value (ensure format is YYYY-MM-DD HH)
+            if (quickJump && quickJump != "-1") {
+                // If datetimepicker returns moment object, format it
+                var quickJumpMoment = moment(quickJump, "YYYY-MM-DD HH");
+                if (quickJumpMoment.isValid()) {
+                    quickJump = quickJumpMoment.format("YYYY-MM-DD HH");
+                }
+            }
+
+            $.ajax({
+                url: urlConfig("/Admin/DashboardReportGetData"),
+                type: "POST",
+                data: {
+                    campaignId: campaignId,
+                    serviceId: serviceId,
+                    hours: hours,
+                    quickJump: quickJump
+                },
+                success: function (response) {
+                    stopSpinner('btnRefresh');
+                    if (response.error == "0") {
+                        var data = JSON.parse(response.data);
+                        if (data.responseCode == "0" && data.list) {
+                            processChartData(data.list);
+                            renderChart();
+                            updateSeriesControls();
+                        } else {
+                            Swal.fire("Error!", data.responseMessage || "Unknown error", "error");
+                        }
+                    } else {
+                        Swal.fire("Error!", response.content || "Unknown error", "error");
+                    }
+                },
+                error: function (xhr, status, error) {
+                    stopSpinner('btnRefresh');
+                    console.error("Error:", error);
+                    Swal.fire("Error!", "Error loading data: " + error, "error");
+                }
+            });
+        }
+
+        function processChartData(data) {
+            // Store data for export
+            window.exportData = data;
+            
+            // Group data by hour
+            var hourMap = {};
+            var campaignMap = {};
+            var allHours = [];
+
+            // First pass: collect all hours and campaigns
+            data.forEach(function(item) {
+                var hourLabel = item.hourLabel;
+                if (!hourMap[hourLabel]) {
+                    hourMap[hourLabel] = {};
+                    allHours.push(hourLabel);
+                }
+                
+                // Use campaignId as key (not serviceId since we removed service)
+                var campaignKey = item.campaignId || "-1";
+                if (!campaignMap[campaignKey]) {
+                    campaignMap[campaignKey] = {
+                        campaignId: item.campaignId || "-1",
+                        campaignName: item.campaignName || ("Campaign " + (item.campaignId || "All")),
+                        serviceId: "",
+                        serviceName: "",
+                        data: {}
+                    };
+                }
+                
+                // Sum up impressions for the same campaign and hour
+                if (!hourMap[hourLabel][campaignKey]) {
+                    hourMap[hourLabel][campaignKey] = 0;
+                }
+                hourMap[hourLabel][campaignKey] += parseInt(item.countImpressions || 0);
+                campaignMap[campaignKey].data[hourLabel] = hourMap[hourLabel][campaignKey];
+            });
+
+            // Sort hours
+            allHours.sort();
+
+            // Calculate totals
+            var totalData = [];
+            allHours.forEach(function(hour) {
+                var total = 0;
+                Object.keys(hourMap[hour]).forEach(function(key) {
+                    total += hourMap[hour][key];
+                });
+                totalData.push(total);
+            });
+
+            // Build datasets
+            chartData.labels = allHours;
+            chartData.datasets = [];
+
+            // Add Total dataset
+            chartData.datasets.push({
+                label: "Total",
+                data: totalData,
+                borderColor: "#ff0000",
+                backgroundColor: "rgba(255, 0, 0, 0.08)",
+                borderWidth: 3,
+                fill: false,
+                tension: 0.4
+            });
+
+            // Add individual campaign/service datasets
+            var colors = ["#1f77b4", "#2ca02c", "#ff7f0e", "#9467bd", "#17becf", "#bcbd22", "#8c564b", "#e377c2", "#7f7f7f", "#aec7e8"];
+            var colorIndex = 0;
+            Object.keys(campaignMap).forEach(function(key) {
+                var campaign = campaignMap[key];
+                var campaignData = [];
+                allHours.forEach(function(hour) {
+                    campaignData.push(campaign.data[hour] || 0);
+                });
+                
+                var label = campaign.campaignName;
+                if (campaign.serviceId) {
+                    label += " (" + campaign.serviceId + ")";
+                }
+                
+                chartData.datasets.push({
+                    label: label,
+                    data: campaignData,
+                    borderColor: getPaletteColor(colorIndex++, colors),
+                    backgroundColor: "rgba(0, 0, 0, 0)",
+                    borderWidth: 1.5,
+                    fill: false,
+                    tension: 0.4,
+                    hidden: false
+                });
+            });
+
+            // Store campaign map for series controls
+            window.campaignMap = campaignMap;
+        }
+
+        function renderChart() {
+            // Check if Chart.js is loaded
+            if (typeof Chart === 'undefined') {
+                console.error('Chart.js is not loaded');
+                Swal.fire("Warning!", "Chart library is not loaded. Please refresh the page.", "warning");
+                return;
+            }
+            
+            var ctx = document.getElementById('chartHourlyImpressions');
+            if (!ctx) {
+                console.error('Chart canvas element not found');
+                return;
+            }
+            
+            ctx = ctx.getContext('2d');
+            
+            if (chartInstance) {
+                chartInstance.destroy();
+            }
+
+            chartInstance = new Chart(ctx, {
+                type: 'line',
+                data: chartData,
+                options: {
+                    responsive: true,
+                    maintainAspectRatio: false,
+                    interaction: {
+                        intersect: false,
+                        mode: 'index'
+                    },
+                    plugins: {
+                        legend: {
+                            display: true,
+                            position: 'bottom'
+                        },
+                        tooltip: {
+                            enabled: true,
+                            mode: 'index',
+                            intersect: false
+                        }
+                    },
+                    scales: {
+                        x: {
+                            display: true,
+                            title: {
+                                display: true,
+                                text: 'Time'
+                            },
+                            grid: {
+                                color: "#f3f3f3"
+                            }
+                        },
+                        y: {
+                            display: true,
+                            title: {
+                                display: true,
+                                text: 'Impressions'
+                            },
+                            beginAtZero: true,
+                            grid: {
+                                color: "#f3f3f3"
+                            }
+                        }
+                    }
+                }
+            });
+        }
+
+        function updateSeriesControls() {
+            var container = $("#seriesCheckboxes");
+            container.empty();
+            
+            chartData.datasets.forEach(function(dataset, index) {
+                var checkbox = $('<div class="form-check form-check-inline series-checkbox">' +
+                    '<input class="form-check-input" type="checkbox" id="series' + index + '" checked>' +
+                    '<label class="form-check-label" for="series' + index + '">' + dataset.label + '</label>' +
+                    '</div>');
+                
+                checkbox.find('input').on('change', function() {
+                    var isHidden = !$(this).is(':checked');
+                    chartInstance.data.datasets[index].hidden = isHidden;
+                    chartInstance.update();
+                });
+                
+                container.append(checkbox);
+            });
+        }
+
+        function exportToExcel() {
+            startSpinner('btnExport');
+            
+            // Use processed chart data if available
+            if (!window.campaignMap || !chartData.labels || chartData.labels.length === 0) {
+                Swal.fire("Warning!", "No data to export. Please load the chart first.", "warning");
+                stopSpinner('btnExport');
+                return;
+            }
+            
+            // Get all campaigns sorted by name
+            var campaigns = [];
+            Object.keys(window.campaignMap).forEach(function(key) {
+                var campaign = window.campaignMap[key];
+                if (campaign.campaignId && campaign.campaignId != "-1") {
+                    campaigns.push({
+                        key: key,
+                        id: campaign.campaignId,
+                        name: campaign.campaignName || ("Campaign " + campaign.campaignId)
+                    });
+                }
+            });
+            
+            // Sort campaigns by name
+            campaigns.sort(function(a, b) {
+                return a.name.localeCompare(b.name);
+            });
+            
+            // Build CSV header: hour_start, total, then campaign names
+            var csv = "hour_start,total";
+            campaigns.forEach(function(campaign) {
+                csv += "," + '"' + campaign.name + '"';
+            });
+            csv += "\n";
+            
+            // Build CSV rows - group by hour
+            chartData.labels.forEach(function(hourLabel) {
+                // Format hour_start: YYYY-MM-DD HH:MM
+                var hourMoment = moment(hourLabel, "YYYY-MM-DD HH");
+                var hourStart = hourMoment.isValid() ? hourMoment.format("YYYY-MM-DD HH:mm") : hourLabel + ":00";
+                
+                // Calculate total for this hour
+                var total = 0;
+                campaigns.forEach(function(campaign) {
+                    var value = window.campaignMap[campaign.key].data[hourLabel] || 0;
+                    total += parseInt(value);
+                });
+                
+                // Add row: hour_start, total, then each campaign value
+                csv += '"' + hourStart + '",' + total;
+                campaigns.forEach(function(campaign) {
+                    var value = window.campaignMap[campaign.key].data[hourLabel] || 0;
+                    csv += "," + parseInt(value);
+                });
+                csv += "\n";
+            });
+            
+            // Download CSV
+            var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
+            var link = document.createElement("a");
+            var url = URL.createObjectURL(blob);
+            link.setAttribute("href", url);
+            link.setAttribute("download", "hourly_impressions_" + new Date().getTime() + ".csv");
+            link.style.visibility = 'hidden';
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+            
+            stopSpinner('btnExport');
+        }
+
+        // ========== DAILY IMPRESSIONS CHART ==========
+        var dailyChartInstance = null;
+        var dailyChartData = {
+            labels: [],
+            datasets: []
+        };
+        var dailyCampaignMap = {};
+        var dailyDateRange = { from: null, to: null };
+
+        $(document).ready(function () {
+            // Initialize date pickers for daily chart
+            $(".dailyFromDate").datetimepicker({
+                format: "DD/MM/YYYY",
+                defaultDate: moment().subtract(31, 'days')
+            });
+
+            $(".dailyToDate").datetimepicker({
+                format: "DD/MM/YYYY",
+                defaultDate: moment()
+            });
+
+            // Load daily chart on page load
+            refreshDailyChart();
+        });
+
+        function resetDailyFilters() {
+            $("#dailyFromDate").val(moment().subtract(31, 'days').format("DD/MM/YYYY"));
+            $("#dailyToDate").val(moment().format("DD/MM/YYYY"));
+            $("#showTotalLineDaily").prop('checked', true);
+            refreshDailyChart();
+        }
+
+        function refreshDailyChart() {
+            var campaignId = "-1"; // Always show all campaigns
+            var fromDate = $("#dailyFromDate").val();
+            var toDate = $("#dailyToDate").val();
+
+            if (!fromDate || !toDate) {
+                fromDate = moment().subtract(31, 'days').format("DD/MM/YYYY");
+                toDate = moment().format("DD/MM/YYYY");
+                $("#dailyFromDate").val(fromDate);
+                $("#dailyToDate").val(toDate);
+            }
+
+            dailyDateRange.from = moment(fromDate, "DD/MM/YYYY");
+            dailyDateRange.to = moment(toDate, "DD/MM/YYYY");
+
+            $.ajax({
+                url: urlConfig("/Admin/DashboardReportGetDailyData"),
+                type: "POST",
+                data: {
+                    campaignId: campaignId,
+                    fromDate: fromDate,
+                    toDate: toDate
+                },
+                success: function (response) {
+                    if (response.error == "0") {
+                        var data = JSON.parse(response.data);
+                        if (data.responseCode == "0" && data.list) {
+                            processDailyChartData(data.list);
+                            renderDailyChart();
+                            updateDailyTemplateFilters();
+                            updateDailyChartFooter(fromDate, toDate, campaignId);
+                        } else {
+                            Swal.fire("Error!", data.responseMessage || "Unknown error", "error");
+                        }
+                    } else {
+                        Swal.fire("Error!", response.content || "Unknown error", "error");
+                    }
+                },
+                error: function (xhr, status, error) {
+                    console.error("Error:", error);
+                    Swal.fire("Error!", "Error loading daily data: " + error, "error");
+                }
+            });
+        }
+
+        function processDailyChartData(data) {
+            dailyCampaignMap = {};
+            var dayMap = {};
+            var allDays = [];
+            var daySet = {};
+
+            // First pass: collect all days and campaigns
+            data.forEach(function(item) {
+                var dayLabel = item.hourLabel; // Actually day label
+                if (!dayMap[dayLabel]) {
+                    dayMap[dayLabel] = {};
+                    if (!daySet[dayLabel]) {
+                        daySet[dayLabel] = true;
+                        allDays.push(dayLabel);
+                    }
+                }
+                
+                var campaignKey = item.campaignId || "-1";
+                if (!dailyCampaignMap[campaignKey]) {
+                    dailyCampaignMap[campaignKey] = {
+                        campaignId: item.campaignId || "-1",
+                        campaignName: item.campaignName || ("Campaign " + (item.campaignId || "All")),
+                        data: {}
+                    };
+                }
+                
+                if (!dayMap[dayLabel][campaignKey]) {
+                    dayMap[dayLabel][campaignKey] = 0;
+                }
+                dayMap[dayLabel][campaignKey] += parseInt(item.countImpressions || 0);
+                dailyCampaignMap[campaignKey].data[dayLabel] = dayMap[dayLabel][campaignKey];
+            });
+
+            // Ensure full date range is covered
+            var expectedDays = [];
+            if (dailyDateRange.from && dailyDateRange.from.isValid() && dailyDateRange.to && dailyDateRange.to.isValid()) {
+                var cursor = dailyDateRange.from.clone();
+                var end = dailyDateRange.to.clone();
+                while (cursor <= end) {
+                    var label = cursor.format("YYYY-MM-DD");
+                    expectedDays.push(label);
+                    if (!dayMap[label]) {
+                        dayMap[label] = {};
+                    }
+                    cursor.add(1, 'days');
+                }
+            }
+            else {
+                allDays.sort();
+                expectedDays = allDays.slice();
+                expectedDays.forEach(function(label) {
+                    if (!dayMap[label]) {
+                        dayMap[label] = {};
+                    }
+                });
+            }
+            allDays = expectedDays;
+
+            // Calculate totals
+            var totalData = [];
+            allDays.forEach(function(day) {
+                var total = 0;
+                Object.keys(dayMap[day]).forEach(function(key) {
+                    total += dayMap[day][key];
+                });
+                totalData.push(total);
+            });
+
+            // Build datasets
+            dailyChartData.labels = allDays;
+            dailyChartData.datasets = [];
+
+            // Always add Total dataset (red)
+            dailyChartData.datasets.push({
+                label: "Total Impressions",
+                data: totalData,
+                borderColor: "#ff0000",
+                backgroundColor: "rgba(255, 0, 0, 0.08)",
+                borderWidth: 3,
+                fill: false,
+                tension: 0.4
+            });
+
+            // Add individual campaign datasets
+            var colors = ["#1f77b4", "#2ca02c", "#ff7f0e", "#9467bd", "#17becf", "#bcbd22", "#8c564b", "#e377c2", "#7f7f7f", "#aec7e8"];
+            var colorIndex = 0;
+            Object.keys(dailyCampaignMap).forEach(function(key) {
+                var campaign = dailyCampaignMap[key];
+                if (campaign.campaignId && campaign.campaignId != "-1") {
+                    var campaignData = [];
+                    allDays.forEach(function(day) {
+                        campaignData.push(campaign.data[day] || 0);
+                    });
+                    
+                    dailyChartData.datasets.push({
+                        label: campaign.campaignName,
+                        data: campaignData,
+                        borderColor: getPaletteColor(colorIndex++, colors),
+                        backgroundColor: "rgba(0, 0, 0, 0)",
+                        borderWidth: 1.5,
+                        fill: false,
+                        tension: 0.4,
+                        hidden: false
+                    });
+                }
+            });
+        }
+
+        function renderDailyChart() {
+            if (typeof Chart === 'undefined') {
+                console.error('Chart.js is not loaded');
+                return;
+            }
+            
+            var ctx = document.getElementById('chartDailyImpressions');
+            if (!ctx) {
+                console.error('Daily chart canvas element not found');
+                return;
+            }
+            
+            ctx = ctx.getContext('2d');
+            
+            if (dailyChartInstance) {
+                dailyChartInstance.destroy();
+            }
+
+            dailyChartInstance = new Chart(ctx, {
+                type: 'line',
+                data: dailyChartData,
+                options: {
+                    responsive: true,
+                    maintainAspectRatio: false,
+                    interaction: {
+                        intersect: false,
+                        mode: 'index'
+                    },
+                    plugins: {
+                        legend: {
+                            display: true,
+                            position: 'bottom'
+                        },
+                        tooltip: {
+                            enabled: true,
+                            mode: 'index',
+                            intersect: false
+                        }
+                    },
+                    scales: {
+                        x: {
+                            display: true,
+                            title: {
+                                display: true,
+                                text: 'Dates'
+                            },
+                            grid: {
+                                color: "#f3f3f3"
+                            }
+                        },
+                        y: {
+                            display: true,
+                            title: {
+                                display: true,
+                                text: 'Impressions'
+                            },
+                            beginAtZero: true,
+                            grid: {
+                                color: "#f3f3f3"
+                            }
+                        }
+                    }
+                }
+            });
+        }
+
+        function updateDailyTemplateFilters() {
+            var container = $("#dailyTemplateFilters");
+            container.empty();
+            
+            var colorIndex = 0;
+            var colors = ["#3b82f6", "#f59e0b", "#10b981", "#ef4444"];
+            
+            Object.keys(dailyCampaignMap).forEach(function(key) {
+                var campaign = dailyCampaignMap[key];
+                if (campaign.campaignId && campaign.campaignId != "-1") {
+                    var button = $('<button type="button" class="btn mr-2 mb-2 template-filter-btn" data-campaign-key="' + key + '" style="background-color: ' + colors[colorIndex % colors.length] + '; color: white; border: none;">' + campaign.campaignName + '</button>');
+                    
+                    button.on('click', function() {
+                        var campaignKey = $(this).data('campaign-key');
+                        var datasetIndex = dailyChartData.datasets.findIndex(function(ds) {
+                            return ds.label === campaign.campaignName;
+                        });
+                        
+                        if (datasetIndex >= 0) {
+                            var isHidden = dailyChartInstance.data.datasets[datasetIndex].hidden;
+                            dailyChartInstance.data.datasets[datasetIndex].hidden = !isHidden;
+                            dailyChartInstance.update();
+                            
+                            $(this).toggleClass('active');
+                            if (isHidden) {
+                                $(this).css('opacity', '1');
+                            } else {
+                                $(this).css('opacity', '0.5');
+                            }
+                        }
+                    });
+                    
+                    container.append(button);
+                    colorIndex++;
+                }
+            });
+        }
+
+        function updateDailyChartFooter(fromDate, toDate, campaignId) {
+            var footer = $("#dailyChartFooter");
+            footer.html("Showing dates from " + fromDate + " to " + toDate + " - All Campaigns");
+        }
+
+        function exportDailyToExcel() {
+            if (!dailyCampaignMap || !dailyChartData.labels || dailyChartData.labels.length === 0) {
+                Swal.fire("Warning!", "No data to export. Please load the chart first.", "warning");
+                return;
+            }
+            
+            // Get all campaigns sorted by name
+            var campaigns = [];
+            Object.keys(dailyCampaignMap).forEach(function(key) {
+                var campaign = dailyCampaignMap[key];
+                if (campaign.campaignId && campaign.campaignId != "-1") {
+                    campaigns.push({
+                        key: key,
+                        id: campaign.campaignId,
+                        name: campaign.campaignName
+                    });
+                }
+            });
+            
+            campaigns.sort(function(a, b) {
+                return a.name.localeCompare(b.name);
+            });
+            
+            // Build CSV header: Date, Total, then campaign names
+            var csv = "Date,Total";
+            campaigns.forEach(function(campaign) {
+                csv += "," + '"' + campaign.name + '"';
+            });
+            csv += "\n";
+            
+            // Build CSV rows - group by day
+            dailyChartData.labels.forEach(function(dayLabel) {
+                // Calculate total for this day
+                var total = 0;
+                campaigns.forEach(function(campaign) {
+                    var value = dailyCampaignMap[campaign.key].data[dayLabel] || 0;
+                    total += parseInt(value);
+                });
+                
+                // Add row: Date, Total, then each campaign value
+                csv += '"' + dayLabel + '",' + total;
+                campaigns.forEach(function(campaign) {
+                    var value = dailyCampaignMap[campaign.key].data[dayLabel] || 0;
+                    csv += "," + parseInt(value);
+                });
+                csv += "\n";
+            });
+            
+            // Download CSV
+            var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
+            var link = document.createElement("a");
+            var url = URL.createObjectURL(blob);
+            link.setAttribute("href", url);
+            link.setAttribute("download", "daily_impressions_" + new Date().getTime() + ".csv");
+            link.style.visibility = 'hidden';
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+        }
+
+        // ========== DAILY UNIQUE IMPRESSED USERS ==========
+        var dailyUniqueChartInstance = null;
+        var dailyUniqueChartData = {
+            labels: [],
+            datasets: []
+        };
+        var dailyUniqueCampaignMap = {};
+        var dailyUniqueSelectedRange = 32;
+        var dailyUniqueTotalData = [];
+        var dailyUniqueDateRange = { from: null, to: null };
+
+        function refreshDailyUniqueChart() {
+            var endDate = moment();
+            var startDate = moment().clone().subtract(dailyUniqueSelectedRange - 1, 'days');
+
+            var fromDate = startDate.format("DD/MM/YYYY");
+            var toDate = endDate.format("DD/MM/YYYY");
+
+            dailyUniqueDateRange.from = startDate.clone();
+            dailyUniqueDateRange.to = endDate.clone();
+
+            $.ajax({
+                url: urlConfig("/Admin/DashboardReportGetDailyUniqueData"),
+                type: "POST",
+                data: {
+                    campaignId: "-1",
+                    serviceId: "-1",
+                    fromDate: fromDate,
+                    toDate: toDate
+                },
+                success: function (response) {
+                    if (response.error == "0") {
+                        var data = JSON.parse(response.data);
+                        if (data.responseCode == "0" && data.list) {
+                            processDailyUniqueChartData(data.list);
+                            renderDailyUniqueChart();
+                            updateDailyUniqueCampaignFilters();
+                            updateDailyUniqueDateLabel(fromDate, toDate);
+                        } else {
+                            Swal.fire("Error!", data.responseMessage || "Unknown error", "error");
+                        }
+                    } else {
+                        Swal.fire("Error!", response.content || "Unknown error", "error");
+                    }
+                },
+                error: function (xhr, status, error) {
+                    console.error("Error:", error);
+                    Swal.fire("Error!", "Error loading daily unique data: " + error, "error");
+                }
+            });
+        }
+
+        function processDailyUniqueChartData(data) {
+            dailyUniqueCampaignMap = {};
+            var dayMap = {};
+            var allDays = [];
+            var daySet = {};
+
+            data.forEach(function (item) {
+                var dayLabel = item.hourLabel;
+                if (!dayLabel) {
+                    return;
+                }
+                if (!dayMap[dayLabel]) {
+                    dayMap[dayLabel] = {};
+                    if (!daySet[dayLabel]) {
+                        daySet[dayLabel] = true;
+                        allDays.push(dayLabel);
+                    }
+                }
+
+                var campaignKey = item.campaignId || "-1";
+                var campaignName = item.campaignName || (campaignKey === "-1" ? "Tất cả chiến dịch" : "Campaign " + campaignKey);
+                var value = parseInt(item.countImpressions || 0);
+
+                if (!dailyUniqueCampaignMap[campaignKey]) {
+                    dailyUniqueCampaignMap[campaignKey] = {
+                        campaignId: campaignKey,
+                        campaignName: campaignName,
+                        data: {}
+                    };
+                }
+
+                if (!dayMap[dayLabel][campaignKey]) {
+                    dayMap[dayLabel][campaignKey] = 0;
+                }
+                dayMap[dayLabel][campaignKey] += value;
+                dailyUniqueCampaignMap[campaignKey].data[dayLabel] = dayMap[dayLabel][campaignKey];
+            });
+
+            var expectedDays = [];
+            if (dailyUniqueDateRange.from && dailyUniqueDateRange.to) {
+                var cursor = dailyUniqueDateRange.from.clone();
+                var end = dailyUniqueDateRange.to.clone();
+                while (cursor <= end) {
+                    var label = cursor.format("YYYY-MM-DD");
+                    expectedDays.push(label);
+                    if (!dayMap[label]) {
+                        dayMap[label] = {};
+                    }
+                    cursor.add(1, 'days');
+                }
+            } else {
+                allDays.sort();
+                expectedDays = allDays.slice();
+                expectedDays.forEach(function (label) {
+                    if (!dayMap[label]) {
+                        dayMap[label] = {};
+                    }
+                });
+            }
+            allDays = expectedDays;
+
+            dailyUniqueTotalData = [];
+            allDays.forEach(function (day) {
+                var total = 0;
+                Object.keys(dayMap[day]).forEach(function (key) {
+                    if (key !== "-1") {
+                        total += dayMap[day][key];
+                    }
+                });
+                dailyUniqueTotalData.push(total);
+            });
+
+            dailyUniqueChartData.labels = allDays;
+            dailyUniqueChartData.datasets = [];
+
+            dailyUniqueChartData.datasets.push({
+                label: "Tổng unique (Any Campaign)",
+                data: dailyUniqueTotalData,
+                borderColor: "#ff0000",
+                backgroundColor: "rgba(255, 0, 0, 0.08)",
+                borderWidth: 3,
+                fill: false,
+                tension: 0.4,
+                campaignKey: "total"
+            });
+
+            var colors = ["#1f77b4", "#2ca02c", "#ff7f0e", "#9467bd", "#17becf", "#bcbd22", "#8c564b", "#e377c2", "#7f7f7f", "#aec7e8", "#b5bd68", "#f4a259"];
+            var colorIndex = 0;
+
+            Object.keys(dailyUniqueCampaignMap).forEach(function (key) {
+                if (key === "-1") {
+                    return;
+                }
+                var campaign = dailyUniqueCampaignMap[key];
+                var campaignData = [];
+                allDays.forEach(function (day) {
+                    campaignData.push(campaign.data[day] || 0);
+                });
+
+                dailyUniqueChartData.datasets.push({
+                    label: campaign.campaignName,
+                    data: campaignData,
+                    borderColor: getPaletteColor(colorIndex++, colors),
+                    backgroundColor: "rgba(0, 0, 0, 0)",
+                    borderWidth: 1.5,
+                    fill: false,
+                    tension: 0.4,
+                    campaignKey: key
+                });
+            });
+        }
+
+        function renderDailyUniqueChart() {
+            if (typeof Chart === 'undefined') {
+                console.error('Chart.js is not loaded');
+                return;
+            }
+
+            var ctx = document.getElementById('chartDailyUniqueUsers');
+            if (!ctx) {
+                console.error('Daily unique chart canvas element not found');
+                return;
+            }
+
+            ctx = ctx.getContext('2d');
+
+            if (dailyUniqueChartInstance) {
+                dailyUniqueChartInstance.destroy();
+            }
+
+            dailyUniqueChartInstance = new Chart(ctx, {
+                type: 'line',
+                data: dailyUniqueChartData,
+                options: {
+                    responsive: true,
+                    maintainAspectRatio: false,
+                    interaction: {
+                        intersect: false,
+                        mode: 'index'
+                    },
+                    plugins: {
+                        legend: {
+                            display: true,
+                            position: 'bottom'
+                        },
+                        tooltip: {
+                            enabled: true,
+                            mode: 'index',
+                            intersect: false
+                        }
+                    },
+                    scales: {
+                        x: {
+                            display: true,
+                            title: {
+                                display: true,
+                                text: 'Dates'
+                            },
+                            grid: {
+                                color: "#f3f3f3"
+                            }
+                        },
+                        y: {
+                            display: true,
+                            title: {
+                                display: true,
+                                text: 'Unique Users'
+                            },
+                            beginAtZero: true,
+                            grid: {
+                                color: "#f3f3f3"
+                            }
+                        }
+                    }
+                }
+            });
+        }
+
+        function updateDailyUniqueCampaignFilters() {
+            var container = $("#dailyUniqueServiceFilters");
+            container.empty();
+
+            Object.keys(dailyUniqueCampaignMap).forEach(function (key) {
+                if (key === "-1") {
+                    return;
+                }
+
+                var campaign = dailyUniqueCampaignMap[key];
+            });
+        }
+
+        function updateDailyUniqueDateLabel(fromDate, toDate) {
+            $("#dailyUniqueDateLabel").text("Time period: " + fromDate + " → " + toDate);
+        }
+
+        function exportDailyUniqueToExcel() {
+            if (!dailyUniqueCampaignMap || !dailyUniqueChartData.labels || dailyUniqueChartData.labels.length === 0) {
+                Swal.fire("Warning!", "No data to export. Please load the chart first.", "warning");
+                return;
+            }
+
+            var campaigns = [];
+            Object.keys(dailyUniqueCampaignMap).forEach(function (key) {
+                if (key !== "-1") {
+                    var campaign = dailyUniqueCampaignMap[key];
+                    campaigns.push({
+                        key: key,
+                        name: campaign.campaignName || ("Campaign " + key)
+                    });
+                }
+            });
+
+            campaigns.sort(function (a, b) {
+                return a.name.localeCompare(b.name);
+            });
+
+            var csv = "Date,\"Total Unique\"";
+            campaigns.forEach(function (cmp) {
+                csv += "," + '"' + cmp.name + '"';
+            });
+            csv += "\n";
+
+            dailyUniqueChartData.labels.forEach(function (dayLabel, idx) {
+                var total = dailyUniqueTotalData[idx] || 0;
+                csv += '"' + dayLabel + '",' + total;
+
+                campaigns.forEach(function (cmp) {
+                    var value = 0;
+                    var campaign = dailyUniqueCampaignMap[cmp.key];
+                    if (campaign) {
+                        value = campaign.data[dayLabel] || 0;
+                    }
+                    csv += "," + parseInt(value);
+                });
+                csv += "\n";
+            });
+
+            var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
+            var link = document.createElement("a");
+            var url = URL.createObjectURL(blob);
+            link.setAttribute("href", url);
+            link.setAttribute("download", "daily_unique_impressions_" + new Date().getTime() + ".csv");
+            link.style.visibility = 'hidden';
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+        }
+
+        // ========== DAILY ENGAGED USERS ==========
+        var dailyEngagedChartInstance = null;
+        var dailyEngagedChartData = {
+            labels: [],
+            datasets: []
+        };
+        var dailyEngagedCampaignMap = {};
+        var dailyEngagedTotalData = [];
+        var dailyEngagedDayMap = {};
+        var dailyEngagedDateRange = { from: null, to: null };
+
+        function formatNumberLocal(value) {
+            var n = parseInt(value || 0, 10);
+            if (isNaN(n)) {
+                n = 0;
+            }
+            return n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+        }
+
+        function refreshDailyEngagedChart() {
+            var fromDate = $("#dailyEngagedFromDate").val();
+            var toDate = $("#dailyEngagedToDate").val();
+            var campaignId = $("#dailyEngagedCampaign").val() || "-1";
+
+            if (!fromDate || !toDate) {
+                fromDate = moment().subtract(31, 'days').format("DD/MM/YYYY");
+                toDate = moment().format("DD/MM/YYYY");
+                $("#dailyEngagedFromDate").val(fromDate);
+                $("#dailyEngagedToDate").val(toDate);
+            }
+
+            dailyEngagedDateRange.from = moment(fromDate, "DD/MM/YYYY");
+            dailyEngagedDateRange.to = moment(toDate, "DD/MM/YYYY");
+
+            $.ajax({
+                url: urlConfig("/Admin/DashboardReportGetDailyEngagedData"),
+                type: "POST",
+                data: {
+                    campaignId: campaignId,
+                    fromDate: fromDate,
+                    toDate: toDate
+                },
+                success: function (response) {
+                    if (response.error == "0") {
+                        var data = JSON.parse(response.data);
+                        if (data.responseCode == "0" && data.list) {
+                            processDailyEngagedData(data.list);
+                            renderDailyEngagedChart();
+                        } else {
+                            Swal.fire("Error!", data.responseMessage || "Unknown error", "error");
+                        }
+                    } else {
+                        Swal.fire("Error!", response.content || "Unknown error", "error");
+                    }
+                },
+                error: function (xhr, status, error) {
+                    console.error("Error:", error);
+                    Swal.fire("Error!", "Error loading engaged data: " + error, "error");
+                }
+            });
+        }
+
+        function processDailyEngagedData(data) {
+            dailyEngagedCampaignMap = {};
+            dailyEngagedDayMap = {};
+            var allDays = [];
+            var daySet = {};
+
+            data.forEach(function (item) {
+                var dayLabel = item.hourLabel;
+                if (!dayLabel) {
+                    return;
+                }
+                if (!dailyEngagedDayMap[dayLabel]) {
+                    dailyEngagedDayMap[dayLabel] = {};
+                    if (!daySet[dayLabel]) {
+                        daySet[dayLabel] = true;
+                        allDays.push(dayLabel);
+                    }
+                }
+
+                var campaignKey = item.campaignId || "-1";
+                var campaignName = item.campaignName || (campaignKey === "-1" ? "Tất cả chiến dịch" : "Campaign " + campaignKey);
+                var value = parseInt(item.countImpressions || 0);
+
+                if (!dailyEngagedCampaignMap[campaignKey]) {
+                    dailyEngagedCampaignMap[campaignKey] = {
+                        campaignId: campaignKey,
+                        campaignName: campaignName,
+                        data: {}
+                    };
+                }
+
+                if (!dailyEngagedDayMap[dayLabel][campaignKey]) {
+                    dailyEngagedDayMap[dayLabel][campaignKey] = 0;
+                }
+                dailyEngagedDayMap[dayLabel][campaignKey] += value;
+                dailyEngagedCampaignMap[campaignKey].data[dayLabel] = dailyEngagedDayMap[dayLabel][campaignKey];
+            });
+
+            var expectedDays = [];
+            if (dailyEngagedDateRange.from && dailyEngagedDateRange.from.isValid() && dailyEngagedDateRange.to && dailyEngagedDateRange.to.isValid()) {
+                var cursor = dailyEngagedDateRange.from.clone();
+                var end = dailyEngagedDateRange.to.clone();
+                while (cursor <= end) {
+                    var label = cursor.format("YYYY-MM-DD");
+                    expectedDays.push(label);
+                    if (!dailyEngagedDayMap[label]) {
+                        dailyEngagedDayMap[label] = {};
+                    }
+                    cursor.add(1, 'days');
+                }
+            } else {
+                allDays.sort();
+                expectedDays = allDays.slice();
+                expectedDays.forEach(function (label) {
+                    if (!dailyEngagedDayMap[label]) {
+                        dailyEngagedDayMap[label] = {};
+                    }
+                });
+            }
+            allDays = expectedDays;
+
+            dailyEngagedTotalData = [];
+            allDays.forEach(function (day) {
+                var total = 0;
+                Object.keys(dailyEngagedDayMap[day]).forEach(function (key) {
+                    if (key !== "-1") {
+                        total += dailyEngagedDayMap[day][key];
+                    }
+                });
+                dailyEngagedTotalData.push(total);
+            });
+
+            dailyEngagedChartData.labels = allDays;
+            dailyEngagedChartData.datasets = [];
+
+            dailyEngagedChartData.datasets.push({
+                label: "Total Engaged Users",
+                data: dailyEngagedTotalData,
+                borderColor: "#ff0000",
+                backgroundColor: "rgba(255, 0, 0, 0.08)",
+                borderWidth: 3,
+                fill: false,
+                tension: 0.4,
+                campaignKey: "total"
+            });
+
+            var colors = ["#1f77b4", "#2ca02c", "#ff7f0e", "#9467bd", "#17becf", "#bcbd22", "#8c564b", "#e377c2", "#7f7f7f", "#aec7e8", "#b5bd68", "#f4a259"];
+            var colorIndex = 0;
+            Object.keys(dailyEngagedCampaignMap).forEach(function (key) {
+                if (key === "-1") {
+                    return;
+                }
+                var campaign = dailyEngagedCampaignMap[key];
+                var campaignData = [];
+                allDays.forEach(function (day) {
+                    campaignData.push(campaign.data[day] || 0);
+                });
+
+                dailyEngagedChartData.datasets.push({
+                    label: campaign.campaignName,
+                    data: campaignData,
+                    borderColor: getPaletteColor(colorIndex++, colors),
+                    backgroundColor: "rgba(0, 0, 0, 0)",
+                    borderWidth: 1.5,
+                    fill: false,
+                    tension: 0.4,
+                    campaignKey: key
+                });
+            });
+        }
+
+        function renderDailyEngagedChart() {
+            if (typeof Chart === 'undefined') {
+                console.error('Chart.js is not loaded');
+                return;
+            }
+
+            var ctx = document.getElementById('chartDailyEngaged');
+            if (!ctx) {
+                console.error('Daily engaged chart element not found');
+                return;
+            }
+
+            ctx = ctx.getContext('2d');
+
+            if (dailyEngagedChartInstance) {
+                dailyEngagedChartInstance.destroy();
+            }
+
+            dailyEngagedChartInstance = new Chart(ctx, {
+                type: 'line',
+                data: dailyEngagedChartData,
+                options: {
+                    responsive: true,
+                    maintainAspectRatio: false,
+                    interaction: {
+                        intersect: false,
+                        mode: 'index'
+                    },
+                    plugins: {
+                        legend: {
+                            display: true,
+                            position: 'bottom'
+                        },
+                        tooltip: {
+                            enabled: true,
+                            mode: 'index',
+                            intersect: false
+                        }
+                    },
+                    scales: {
+                        x: {
+                            display: true,
+                            title: {
+                                display: true,
+                                text: 'Dates'
+                            },
+                            grid: {
+                                color: "#f3f3f3"
+                            }
+                        },
+                        y: {
+                            display: true,
+                            title: {
+                                display: true,
+                                text: 'Engaged Users'
+                            },
+                            beginAtZero: true,
+                            grid: {
+                                color: "#f3f3f3"
+                            }
+                        }
+                    }
+                }
+            });
+        }
+
+        function exportDailyEngagedToExcel() {
+            if (!dailyEngagedChartData.labels || dailyEngagedChartData.labels.length === 0) {
+                Swal.fire("Warning!", "No data to export. Please load the chart first.", "warning");
+                return;
+            }
+
+            var campaigns = [];
+            Object.keys(dailyEngagedCampaignMap).forEach(function (key) {
+                if (key !== "-1") {
+                    campaigns.push({
+                        key: key,
+                        name: dailyEngagedCampaignMap[key].campaignName
+                    });
+                }
+            });
+            campaigns.sort(function (a, b) {
+                return a.name.localeCompare(b.name);
+            });
+
+            var csv = "Date,\"Total Engaged\"";
+            campaigns.forEach(function (cmp) {
+                csv += "," + '"' + cmp.name + '"';
+            });
+            csv += "\n";
+
+            dailyEngagedChartData.labels.forEach(function (dayLabel, idx) {
+                csv += '"' + dayLabel + '",' + (dailyEngagedTotalData[idx] || 0);
+                campaigns.forEach(function (cmp) {
+                    var value = 0;
+                    if (dailyEngagedCampaignMap[cmp.key]) {
+                        value = dailyEngagedCampaignMap[cmp.key].data[dayLabel] || 0;
+                    }
+                    csv += "," + parseInt(value);
+                });
+                csv += "\n";
+            });
+
+            var blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
+            var link = document.createElement("a");
+            var url = URL.createObjectURL(blob);
+            link.setAttribute("href", url);
+            link.setAttribute("download", "daily_engaged_users_" + new Date().getTime() + ".csv");
+            link.style.visibility = 'hidden';
+            document.body.appendChild(link);
+            link.click();
+            document.body.removeChild(link);
+        }
+
+        // Event handlers
+        $("#dailyFromDate, #dailyToDate").on('change', function() {
+            refreshDailyChart();
+        });
+
+        $("#showTotalLineDaily").on('change', function() {
+            refreshDailyChart();
+        });
+
+        $(document).ready(function () {
+            $("#dailyUniqueRangeButtons button").on('click', function () {
+                $("#dailyUniqueRangeButtons button").removeClass('active');
+                $(this).addClass('active');
+                var range = parseInt($(this).data('range')) || 32;
+                dailyUniqueSelectedRange = range;
+                refreshDailyUniqueChart();
+            });
+
+            $("#btnDailyUniqueExport").on('click', function () {
+                exportDailyUniqueToExcel();
+            });
+
+            refreshDailyUniqueChart();
+        });
+
+        $(document).ready(function () {
+            $(".dailyEngagedDatePicker").datetimepicker({
+                format: "DD/MM/YYYY"
+            });
+
+            $("#dailyEngagedFromDate").val(moment().subtract(31, 'days').format("DD/MM/YYYY"));
+            $("#dailyEngagedToDate").val(moment().format("DD/MM/YYYY"));
+
+            $("#btnDailyEngagedSearch").on('click', function () {
+                refreshDailyEngagedChart();
+            });
+
+            $("#btnDailyEngagedExport").on('click', function () {
+                exportDailyEngagedToExcel();
+            });
+
+            refreshDailyEngagedChart();
+        });
+    </script>
+}
+

+ 473 - 0
SuperAdmin/SuperAdmin/Views/Admin/FunctionManagement.cshtml

@@ -0,0 +1,473 @@
+@{
+    ViewBag.Title = "Function Management";
+    Layout = "~/Views/Shared/_Layout.cshtml";
+}
+
+@using SuperAdmin.Models;
+@using SuperAdmin.Models.Http;
+@using SuperAdmin.Controllers;
+@using SuperAdmin.Source;
+
+<style>
+    img {
+        max-width: 100%;
+    }
+</style>
+
+<div class="content-header row">
+    <div class="content-header-left col-md-6 col-xs-12 mb-1">
+        <h2 class="content-header-title">Function Management</h2>
+    </div>
+    <div class="content-header-right breadcrumbs-right breadcrumbs-top col-md-6 col-xs-12">
+        <div class="breadcrumb-wrapper col-xs-12">
+            <ol class="breadcrumb">
+                <li class="breadcrumb-item">
+                    <a href="Index">Admin</a>
+                </li>
+                <li class="breadcrumb-item">
+                    <a href="#">Function Management</a>
+                </li>
+            </ol>
+        </div>
+    </div>
+</div>
+
+<div class="content-body">
+    <section id="basic-form-layouts">
+        <div class="row service-height">
+            <div class="col-md-12">
+                <div class="card">
+                    <div class="card-header">
+                        <h4 class="card-title" id="basic-layout-form">Search</h4> 
+                    </div>
+                    <div class="card-content collapse show">
+                        <div class="card-body">
+                            <div class="card-block">
+                                <div class="form-body">
+                                    @Html.AntiForgeryToken()
+                                    <div class="row">
+                                        <div class="col-md-4 col-sm-4 col-6">
+                                            <div class="form-group">
+                                                <label>Role</label>
+                                                <input type="text" class="form-control" id="roleSearch" name="roleSearch">
+                                            </div>
+                                        </div>
+                                        <div class="col-md-4 col-sm-4 col-6">
+                                            <div class="form-group">
+                                                <label>Name</label>
+                                                <input type="text" class="form-control" id="nameSearch" name="nameSearch">
+                                            </div>
+                                        </div>
+                                        <div class="col-md-4 col-sm-4 col-6">
+                                            <div class="form-group">
+                                                <label>Link</label>
+                                                <input type="text" class="form-control" id="linkSearch" name="linkSearch">
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="row">
+                                        <div class="col-md-12">
+                                            <button type="button" class="btn btn-info" onclick="search()" id="btnSearch">
+                                                <i class="fa fa-search"></i> Search
+                                            </button>
+                                            <button type="button" class="btn btn-primary" onclick="add()" id="btnAdd">
+                                                <i class="fa fa-plus"></i> Add Function
+                                            </button>
+                                            <button type="button" class="btn btn-success" onclick="assignRole()" id="btnAssignRole">
+                                                <i class="fa fa-users"></i> Assign Role
+                                            </button>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="col-12">
+                <div class="card">
+                    <div class="card-content " id="partial-content">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </section>
+</div>
+
+<!-- Assign Role Modal -->
+<div class="modal fade text-xs-left" id="modal-assign-role" tabindex="-1" role="dialog" aria-labelledby="myModalLabelAssign" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title" id="modalLabelAssign">Assign Functions to Role</h4>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                <div class="row">
+                    <div class="col-md-12">
+                        <div class="form-group">
+                            <label>New Role *</label>
+                            <input type="text" class="form-control" id="newRole" name="newRole" placeholder="e.g. NEW_ROLE">
+                        </div>
+                    </div>
+                    <div class="col-md-12">
+                        <div class="form-group">
+                            <label>Select Functions to Copy</label>
+                            <div class="form-check form-check-inline">
+                                <input class="form-check-input" type="checkbox" id="checkAll" onchange="toggleAllFunctions()">
+                                <label class="form-check-label" for="checkAll"><strong>Select All</strong></label>
+                            </div>
+                            <div id="functionCheckboxes" style="max-height: 400px; overflow-y: auto; margin-top: 10px;">
+                                <!-- Function checkboxes will be populated here -->
+                            </div>
+                        </div>
+                    </div>
+                    <div class="col-md-12">
+                        <label id="lblWarningAssign" class="text-danger"></label>
+                    </div>
+                    <div class="col-md-12" style="text-align: right">
+                        <button type="button" id="btnAssign" class="btn btn-outline-success" onclick="assignAction()">Assign</button>
+                        <button type="button" class="btn grey btn-outline-secondary" data-dismiss="modal">Close</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+<!-- Add/Edit Modal -->
+<div class="modal fade text-xs-left" id="modal-add-function" tabindex="-1" role="dialog" aria-labelledby="myModalLabel17" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title" id="modalLabelFunction">Function Information</h4>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <input type="hidden" id="id" />
+            <div class="modal-body">
+                <div class="row" id="div-info">
+                    <div class="col-md-6">
+                        <div class="form-group">
+                            <label>Role *</label>
+                            <input type="text" class="form-control" id="role" name="role" placeholder="e.g. ADMIN">
+                        </div>
+                    </div>
+                    <div class="col-md-6">
+                        <div class="form-group">
+                            <label>Name *</label>
+                            <input type="text" class="form-control" id="name" name="name" placeholder="e.g. Dashboard">
+                        </div>
+                    </div>
+                    <div class="col-md-12">
+                        <div class="form-group">
+                            <label>Link *</label>
+                            <input type="text" class="form-control" id="link" name="link" placeholder="/Admin/Index">
+                        </div>
+                    </div>
+                    <div class="col-md-12">
+                        <div class="form-group">
+                            <label>Note</label>
+                            <textarea class="form-control" id="note" name="note" rows="3"></textarea>
+                        </div>
+                    </div>
+                </div>
+                <div class="row" id="div-action">
+                    <div class="col-12">
+                        <label id="lblWarning" class="text-danger"></label>
+                    </div>
+                    <div class="col-12" style="text-align: right">
+                        <button type="button" id="btnAddUpdate" class="btn btn-outline-primary" onclick="addUpdateAction()">Add</button>
+                        <button type="button" class="btn grey btn-outline-secondary" data-dismiss="modal">Close</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+@section Scripts {
+    <script>
+        $(document).ready(function () {
+            search();
+        });
+
+        let allFunctions = [];
+
+        function assignRole() {
+            $("#newRole").val("");
+            $("#functionCheckboxes").empty();
+            $("#lblWarningAssign").html("");
+            
+            // Load all unique functions to populate checkboxes
+            $.ajax({
+                url: urlConfig("/Admin/FunctionSearch"),
+                type: "POST",
+                data: {
+                    role: "",
+                    name: "",
+                    link: "",
+                    rowsOnPage: "1000000",
+                    seqPage: "1"
+                },
+                success: function (html) {
+                    // Parse the HTML to extract functions
+                    let tempDiv = $('<div>').html(html);
+                    let functions = [];
+                    tempDiv.find('#grid_detail tbody tr').each(function() {
+                        let func = {
+                            name: $(this).find('td').eq(3).text().trim(),
+                            link: $(this).find('td').eq(4).text().trim()
+                        };
+                        // Check if function already exists
+                        let exists = functions.some(f => f.name === func.name && f.link === func.link);
+                        if (!exists) {
+                            functions.push(func);
+                        }
+                    });
+                    
+                    allFunctions = functions;
+                    
+                    // Create checkboxes
+                    if (functions.length > 0) {
+                        functions.forEach(function(func, index) {
+                            let checkbox = '<div class="form-check" style="margin-bottom: 8px;">' +
+                                '<input class="form-check-input function-checkbox" type="checkbox" id="func_' + index + '" value="' + index + '">' +
+                                '<label class="form-check-label" for="func_' + index + '">' +
+                                '<strong>' + func.name + '</strong> - <small class="text-muted">' + func.link + '</small>' +
+                                '</label>' +
+                                '</div>';
+                            $("#functionCheckboxes").append(checkbox);
+                        });
+                    } else {
+                        $("#functionCheckboxes").html('<p class="text-muted">No functions found</p>');
+                    }
+                    
+                    showModal("modal-assign-role");
+                },
+                error: function (err) {
+                    Swal.fire("Failed!", "Error loading functions", "error");
+                }
+            });
+        }
+
+        function toggleAllFunctions() {
+            let isChecked = $("#checkAll").prop('checked');
+            $(".function-checkbox").prop('checked', isChecked);
+        }
+
+        function validateAssignRole() {
+            let newRole = $("#newRole").val();
+            let selectedCount = $(".function-checkbox:checked").length;
+
+            if (newRole == null || newRole == "") {
+                $("#lblWarningAssign").html("Role is required");
+                return false;
+            }
+            if (selectedCount == 0) {
+                $("#lblWarningAssign").html("Please select at least one function");
+                return false;
+            }
+            return true;
+        }
+
+        function assignAction() {
+            if (!validateAssignRole()) {
+                return;
+            }
+            
+            let newRole = $("#newRole").val();
+            let selectedIndices = [];
+            $(".function-checkbox:checked").each(function() {
+                let index = parseInt($(this).val());
+                selectedIndices.push(index);
+            });
+
+            startSpinner('btnAssign');
+            $("#lblWarningAssign").html("");
+
+            // Create all selected functions
+            let promises = [];
+            let successCount = 0;
+            let failCount = 0;
+
+            selectedIndices.forEach(function(index) {
+                let func = allFunctions[index];
+                let promise = $.ajax({
+                    url: urlConfig('/Admin/FunctionAddUpdate'),
+                    type: "POST",
+                    data: {
+                        id: "",
+                        role: newRole,
+                        name: func.name,
+                        link: func.link,
+                        note: ""
+                    },
+                    success: function (result) {
+                        if (result.error == '0') {
+                            successCount++;
+                        } else {
+                            failCount++;
+                        }
+                    },
+                    error: function (err) {
+                        failCount++;
+                    }
+                });
+                promises.push(promise);
+            });
+
+            Promise.all(promises).then(function() {
+                stopSpinner("btnAssign");
+                let message = successCount + " function(s) assigned successfully";
+                if (failCount > 0) {
+                    message += ", " + failCount + " failed";
+                }
+                Swal.fire("Completed!", message, "success").then(() => {
+                    hideModal("modal-assign-role");
+                    search();
+                });
+            }).catch(function() {
+                stopSpinner("btnAssign");
+                Swal.fire("Error!", "Some functions failed to assign", "error");
+            });
+        }
+
+        function add() {
+            $("#id").val("");
+            $("#role").val("");
+            $("#name").val("");
+            $("#link").val("");
+            $("#note").val("");
+            $("#btnAddUpdate").html("Add");
+            $("#modalLabelFunction").html("Add Function");
+            showModal("modal-add-function");
+        }
+
+        function edit(id) {
+            $("#id").val(id);
+            $("#btnAddUpdate").html("Update");
+            $("#modalLabelFunction").html("Edit Function");
+            loadInfo(id);
+            showModal("modal-add-function");
+        }
+
+        function loadInfo(id) {
+            $.ajax({
+                url: urlConfig('/Admin/FunctionLoadInfo'),
+                type: "POST",
+                data: { id: id },
+                success: function (result) {
+                    if (result.error == '0') {
+                        var func = result.data;
+                        $("#id").val(func.id);
+                        $("#role").val(func.role);
+                        $("#name").val(func.name);
+                        $("#link").val(func.link);
+                        $("#note").val(func.note || "");
+                    } else {
+                        Swal.fire("Failed!", result.content, "error");
+                    }
+                },
+                error: function (err) {
+                    Swal.fire("Failed!", err.statusText, "error");
+                }
+            });
+        }
+
+        function validateInput() {
+            let role = $("#role").val();
+            let name = $("#name").val();
+            let link = $("#link").val();
+
+            if (role == null || role == "") {
+                $("#lblWarning").html("Role is required");
+                return false;
+            }
+            if (name == null || name == "") {
+                $("#lblWarning").html("Name is required");
+                return false;
+            }
+            if (link == null || link == "") {
+                $("#lblWarning").html("Link is required");
+                return false;
+            }
+            return true;
+        }
+
+        function addUpdateAction() {
+            startSpinner('btnAddUpdate');
+
+            if (!validateInput()) {
+                stopSpinner("btnAddUpdate");
+                return;
+            }
+            $("#lblWarning").html("");
+
+            $.ajax({
+                url: urlConfig('/Admin/FunctionAddUpdate'),
+                type: "POST",
+                data: {
+                    id: $("#id").val(),
+                    role: $("#role").val(),
+                    name: $("#name").val(),
+                    link: $("#link").val(),
+                    note: $("#note").val()
+                },
+                success: function (result) {
+                    stopSpinner("btnAddUpdate");
+                    if (result.error == '0') {
+                        Swal.fire("Success!", "Success!", "success").then(() => {
+                            hideModal("modal-add-function");
+                            search();
+                        });
+                    } else {
+                        Swal.fire("Failed!", result.content, "error");
+                    }
+                },
+                error: function (err) {
+                    stopSpinner("btnAddUpdate");
+                    Swal.fire("Failed!", err.statusText, "error");
+                }
+            });
+        }
+
+        function search() {
+            startSpinner('btnSearch');
+            if ($.fn.DataTable.isDataTable('#grid_detail')) {
+                $('#grid_detail').DataTable().destroy();
+            }
+            $.ajax({
+                url: urlConfig("/Admin/FunctionSearch"),
+                data: {
+                    role: $("#roleSearch").val(),
+                    name: $("#nameSearch").val(),
+                    link: $("#linkSearch").val()
+                },
+                type: "POST",
+                success: function (data) {
+                    stopSpinner('btnSearch');
+                    $("#partial-content").html(data);
+                    $("#grid_detail").DataTable({
+                        orderCellsTop: true
+                    });
+                },
+                error: function (data) {
+                    stopSpinner('btnSearch');
+                    console.log(data.error);
+                }
+            })
+        }
+
+        // Clear error
+        $(".modal input, .modal textarea").keyup(function() {
+            $("#lblWarning").html("");
+            $("#lblWarningAssign").html("");
+        })
+    </script>
+}
+

+ 508 - 0
SuperAdmin/SuperAdmin/Views/Admin/ReportCountDaily.cshtml

@@ -0,0 +1,508 @@
+@{
+    ViewBag.Title = "Report Count Daily";
+    Layout = "~/Views/Shared/_Layout.cshtml";
+}
+
+@using SuperAdmin.Models;
+@using SuperAdmin.Models.Http;
+@using SuperAdmin.Controllers;
+@using SuperAdmin.Source;
+@using Microsoft.AspNetCore.Http;
+@using SuperCms.Extensions; 
+
+@{
+    var subDomain = Context.Session.GetString("subDomain");
+    var listCampaign = Context.Session.GetComplexData<List<Campaign>>("listCampaign");
+    if (listCampaign == null)
+    {
+        listCampaign = new List<Campaign>();
+    }
+    var listService = Context.Session.GetComplexData<List<Service>>("listService");
+    if (listService == null)
+    {
+        listService = new List<Service>();
+    }
+}
+
+<style>
+    img {
+        max-width: 100%;
+    }
+    
+    .searchable-dropdown {
+        position: relative;
+        display: inline-block;
+        width: 100%;
+    }
+    
+    .searchable-input {
+        width: 100%;
+        padding: 8px 12px;
+        border: 1px solid #ddd;
+        border-radius: 4px;
+        font-size: 14px;
+        background-color: white;
+    }
+    
+    .searchable-input:focus {
+        border-color: #007bff;
+        box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);
+        outline: none;
+    }
+    
+    .searchable-input::placeholder {
+        color: #999;
+        font-style: italic;
+    }
+    
+    .dropdown-list {
+        position: absolute;
+        top: 100%;
+        left: 0;
+        right: 0;
+        background-color: white;
+        border: 1px solid #ddd;
+        border-top: none;
+        border-radius: 0 0 4px 4px;
+        max-height: 300px;
+        overflow-y: auto;
+        z-index: 1000;
+        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+        display: none;
+    }
+    
+    .dropdown-item {
+        padding: 8px 12px;
+        cursor: pointer;
+        border-bottom: 1px solid #f0f0f0;
+        font-size: 14px;
+    }
+    
+    .dropdown-item:hover {
+        background-color: #f8f9fa;
+    }
+    
+    .dropdown-item:last-child {
+        border-bottom: none;
+    }
+    
+    .dropdown-item.highlighted {
+        background-color: #e3f2fd;
+    }
+</style>
+
+<div class="content-header row">
+    <div class="content-header-left col-md-6 col-xs-12 mb-1">
+        <h2 class="content-header-title">Report Count Daily</h2>
+    </div>
+    <div class="content-header-right breadcrumbs-right breadcrumbs-top col-md-6 col-xs-12">
+        <div class="breadcrumb-wrapper col-xs-12">
+            <ol class="breadcrumb">
+                <li class="breadcrumb-item">
+                    <a href="Index">Admin</a>
+                </li>
+                <li class="breadcrumb-item">
+                    <a href="#">Report Count Daily</a>
+                </li>
+            </ol>
+        </div>
+    </div>
+</div>
+
+<div class="content-body">
+    <!-- Basic form layout section start -->
+    <section id="basic-form-layouts">
+        <div class="row service-height">
+            <div class="col-md-12">
+                <div class="card">
+                    <div class="card-header">
+                        <h4 class="card-title" id="basic-layout-form">Search</h4>
+                        <a class="heading-elements-toggle"><i class="icon-ellipsis font-medium-3"></i></a>
+                        <div class="heading-elements">
+                            <ul class="list-inline mb-0">
+                                <li><a data-action="collapse"><i class="icon-minus4"></i></a></li>
+                                <li><a data-action="expand"><i class="icon-expand2"></i></a></li>
+                            </ul>
+                        </div>
+                    </div>
+                    <div class="card-content collapse show">
+                        <div class="card-body">
+                            <div class="card-block">
+                                <div class="form-body">
+                                    @Html.AntiForgeryToken()
+                                    <div class="row">
+                                        <div class="col-md-4 col-sm-4 col-6">
+                                            <div class="form-group">
+                                                <label>From Date</label>
+                                                <div class='input-group'>
+                                                    <input type="text" class="form-control fromDateSearch" id="fromDateSearch" name="fromDateSearch">
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="col-md-4 col-sm-4 col-6">
+                                            <div class="form-group">
+                                                <label>To date</label>
+                                                <div class='input-group'>
+                                                    <input type="text" class="form-control toDateSearch" id="toDateSearch" name="toDateSearch">
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="col-md-4 col-sm-4 col-6">
+                                            <div class="form-group">
+                                                <label>Campaign</label>
+                                                <div class="searchable-dropdown" id="campaignDropdown">
+                                                    <input type="text" class="form-control searchable-input" id="campaignSearch" 
+                                                           placeholder="Search campaign..."
+                                                           onkeyup="filterCampaignOptions()" 
+                                                           onclick="toggleCampaignDropdown()"
+                                                           autocomplete="off">
+                                                    <div class="dropdown-list" id="campaignList" style="display: none;">
+                                                        <div class="dropdown-item" data-value="" onclick="selectCampaignOption('', '')">
+                                                            -- All Campaigns --
+                                                        </div>
+                                                        @if (listCampaign != null)
+                                                        {
+                                                            @foreach (Campaign camp in listCampaign)
+                                                            {
+                                                                <div class="dropdown-item" data-value="@camp.id" data-text="@camp.id - @camp.name" onclick="selectCampaignOption('@camp.id', '@camp.id - @camp.name')">
+                                                                    @camp.id - @camp.name
+                                                                </div>
+                                                            }
+                                                        }
+                                                    </div>
+                                                    <input type="hidden" id="campaignIdSearch" name="campaignIdSearch" value="">
+                                                </div>
+                                            </div>
+                                        </div>
+                                        <div class="col-md-4 col-sm-4 col-6">
+                                            <div class="form-group">
+                                                <label>Service</label>
+                                                <div class="searchable-dropdown" id="serviceDropdown">
+                                                    <input type="text" class="form-control searchable-input" id="serviceSearch" 
+                                                           placeholder="Search service..."
+                                                           onkeyup="filterServiceOptions()" 
+                                                           onclick="toggleServiceDropdown()"
+                                                           autocomplete="off">
+                                                    <div class="dropdown-list" id="serviceList" style="display: none;">
+                                                        <div class="dropdown-item" data-value="" onclick="selectServiceOption('', '')">
+                                                            -- All Services --
+                                                        </div>
+                                                        @if (listService != null)
+                                                        {
+                                                            @foreach (Service sv in listService)
+                                                            {
+                                                                <div class="dropdown-item" data-value="@sv.id" data-text="@sv.id - @sv.name" onclick="selectServiceOption('@sv.id', '@sv.id - @sv.name')">
+                                                                    @sv.id - @sv.name
+                                                                </div>
+                                                            }
+                                                        }
+                                                    </div>
+                                                    <input type="hidden" id="serviceIdSearch" name="serviceIdSearch" value="">
+                                                </div>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="row">
+                                        <div class="col-md-12">
+                                            <button type="button" class="btn btn-info" onclick="search()" id="btnSearch">
+                                                <i class="fa fa-search"></i> Search
+                                            </button>
+                                            <button type="button" class="btn btn-warning" onclick="exportExcel()" id="btnExportExcel">
+                                                <i class="fa fa-file-export"></i> Export Excel
+                                            </button>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="col-12">
+                <div class="card">
+                    <div class="card-content">
+                        <div class="col-md-12 table-responsive" style="padding-top: 10px" id="partial-content">
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+        </div>
+    </section>
+    <!-- // Basic form layout section end -->
+</div>
+
+
+@section Scripts {
+    <script>
+
+        var tableDetail;
+
+        // Campaign and Service data
+        var campaignData = [];
+        var serviceData = [];
+
+        $(document).ready(function () {
+
+            $(".fromDateSearch").datetimepicker({
+                format: "DD/MM/YYYY",
+                defaultDate: moment().add(-9, 'days')
+            });
+
+            $(".toDateSearch").datetimepicker({
+                format: "DD/MM/YYYY",
+                defaultDate: moment().endOf('day')
+            });
+
+            // Initialize campaign and service data
+            $("#campaignList .dropdown-item").each(function() {
+                campaignData.push({
+                    id: $(this).data('value'),
+                    text: $(this).data('text') || $(this).text().trim()
+                });
+            });
+
+            $("#serviceList .dropdown-item").each(function() {
+                serviceData.push({
+                    id: $(this).data('value'),
+                    text: $(this).data('text') || $(this).text().trim()
+                });
+            });
+
+            // Close dropdowns when clicking outside
+            $(document).on('click', function(e) {
+                if (!$(e.target).closest('.searchable-dropdown').length) {
+                    $("#campaignList, #serviceList").hide();
+                }
+            });
+
+            search();
+        });
+
+        // Campaign functions
+        function toggleCampaignDropdown() {
+            var list = $("#campaignList");
+            if (list.is(':visible')) {
+                list.hide();
+            } else {
+                list.show();
+                filterCampaignOptions();
+            }
+        }
+
+        function filterCampaignOptions() {
+            var searchText = $("#campaignSearch").val().toLowerCase();
+            var list = $("#campaignList");
+            var items = list.find('.dropdown-item');
+            var visibleCount = 0;
+
+            items.each(function() {
+                var text = $(this).text().toLowerCase();
+                if (text.indexOf(searchText) !== -1 || searchText === '') {
+                    $(this).show();
+                    visibleCount++;
+                } else {
+                    $(this).hide();
+                }
+            });
+
+            // Show dropdown if there are visible items or search is empty
+            if (visibleCount > 0) {
+                list.show();
+            } else {
+                list.hide();
+            }
+        }
+
+        function selectCampaignOption(id, text) {
+            $("#campaignIdSearch").val(id);
+            $("#campaignSearch").val(text);
+            $("#campaignList").hide();
+        }
+
+        // Service functions
+        function toggleServiceDropdown() {
+            var list = $("#serviceList");
+            if (list.is(':visible')) {
+                list.hide();
+            } else {
+                list.show();
+                filterServiceOptions();
+            }
+        }
+
+        function filterServiceOptions() {
+            var searchText = $("#serviceSearch").val().toLowerCase();
+            var list = $("#serviceList");
+            var items = list.find('.dropdown-item');
+            var visibleCount = 0;
+
+            items.each(function() {
+                var text = $(this).text().toLowerCase();
+                if (text.indexOf(searchText) !== -1 || searchText === '') {
+                    $(this).show();
+                    visibleCount++;
+                } else {
+                    $(this).hide();
+                }
+            });
+
+            // Show dropdown if there are visible items or search is empty
+            if (visibleCount > 0) {
+                list.show();
+            } else {
+                list.hide();
+            }
+        }
+
+        function selectServiceOption(id, text) {
+            $("#serviceIdSearch").val(id);
+            $("#serviceSearch").val(text);
+            $("#serviceList").hide();
+        }
+
+
+
+        function resetTableDetail() {
+            tableDetail = $("#grid_detail").DataTable({
+                orderCellsTop: true,
+                fixedHeader: true 
+            });
+        }
+
+        function clearTable(table) {
+            if (table != null && table != undefined) {
+                table
+                    .clear()
+                    .destroy();
+            }
+        }
+
+        function search() {
+            console.log("Search data");
+            startSpinner('btnSearch');
+            clearTable(tableDetail);
+            $.ajax({
+                url: urlConfig("/Admin/ReportCountDailySearch"),
+                data: { 
+                    fromDate: $("#fromDateSearch").val(),
+                    toDate: $("#toDateSearch").val(),
+                    campaignId: $("#campaignIdSearch").val(),
+                    serviceId: $("#serviceIdSearch").val()
+                },
+                type: "POST",
+                success: function (data) {
+                    stopSpinner('btnSearch');
+                    if (data.error != null) {
+                        showModal('message-dialog');
+                        $('#message-content').html(data.content.split("\n").join("<br />"));
+                    } else {
+                        $("#partial-content").html(data);
+                        resetTableDetail();
+                    }
+                },
+                error: function (data) {
+                    stopSpinner('btnSearch');
+                    console.log(data.error);
+                }
+            })
+        }
+         
+
+        function exportExcel() {
+            console.log("Export data");
+            startSpinner('btnExportExcel');
+            $.ajax({
+                url: urlConfig("/Admin/ReportCountDailyExport"),
+                data: {
+                    fromDate: $("#fromDateSearch").val(),
+                    toDate: $("#toDateSearch").val(),
+                    campaignId: $("#campaignIdSearch").val(),
+                    serviceId: $("#serviceIdSearch").val()
+                },
+                type: "POST",
+                xhr: function () {
+                    var xhr = new XMLHttpRequest();
+                    xhr.onreadystatechange = function () {
+                        if (xhr.readyState == 2) {
+                            if (xhr.status == 200) {
+                                xhr.responseType = "blob";
+                                console.log("blob");
+                            } else {
+                                xhr.responseType = "text";
+                                console.log("text");
+                            }
+                        }
+                    };
+                    return xhr;
+                },
+                success: function (data, status, xhr) {
+
+                    stopSpinner('btnExportExcel');
+                    let filename = "";
+                    let disposition = xhr.getResponseHeader('Content-Disposition');
+                    if (disposition && disposition.indexOf('attachment') !== -1) {
+                        let filenameRegex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
+                        let matches = filenameRegex.exec(disposition);
+                        if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
+                    }
+                    let a = document.createElement('a');
+                    let url = window.URL.createObjectURL(data);
+                    a.href = url;
+                    a.download = filename.replace('UTF-8', '');
+                    document.body.append(a);
+                    a.click();
+                    a.remove();
+                    window.URL.revokeObjectURL(url);
+                    $("#overlay").fadeOut(300);
+                },
+                error: function (data) {
+                    stopSpinner('btnExportExcel');
+                    console.log(data.error);
+                }
+            })
+        }
+
+        function viewErrorDetail(reportDate, campaignId, serviceId) {
+            // Show modal
+            $('#modal-error-detail').modal('show');
+            
+            // Reset content
+            $('#error-detail-content').html('<div class="text-center"><i class="fa fa-spinner fa-spin fa-3x"></i><p>Đang tải dữ liệu...</p></div>');
+            
+            // Call API to get error details
+            $.ajax({
+                url: urlConfig("/Admin/ReportErrorDailyDetail"),
+                type: "POST",
+                data: {
+                    reportDate: reportDate,
+                    campaignId: campaignId,
+                    serviceId: serviceId
+                },
+                success: function (data) {
+                    if (data.error != null) {
+                        Swal.fire("Error!", data.content, "error");
+                        $('#error-detail-content').html('');
+                    } else {
+                        $('#error-detail-content').html(data);
+                        // Initialize DataTable if needed
+                        if ($.fn.DataTable.isDataTable('#grid_error_detail')) {
+                            $('#grid_error_detail').DataTable().destroy();
+                        }
+                        $('#grid_error_detail').DataTable({
+                            orderCellsTop: true,
+                            fixedHeader: true
+                        });
+                    }
+                },
+                error: function (data) {
+                    Swal.fire("Error!", "Failed to load error details. Please try again.", "error");
+                    $('#error-detail-content').html('');
+                    console.log(data.error);
+                }
+            });
+        }
+    </script>
+}
+

+ 144 - 0
SuperAdmin/SuperAdmin/Views/Admin/ReportUssdDetail.cshtml

@@ -0,0 +1,144 @@
+@{
+    ViewBag.Title = "Campaign Detail";
+    Layout = "~/Views/Shared/_Layout.cshtml";
+}
+
+@using SuperAdmin.Models.Http;
+@using Microsoft.AspNetCore.Http;
+@using SuperCms.Extensions;
+
+@{
+    var listCampaign = Context.Session.GetComplexData<List<Campaign>>("listCampaign") ?? new List<Campaign>();
+    var listService = Context.Session.GetComplexData<List<Service>>("listService") ?? new List<Service>();
+}
+
+<div class="content-header row">
+    <div class="content-header-left col-md-6 col-xs-12 mb-1">
+        <h2 class="content-header-title">Campaign Detail</h2>
+    </div>
+</div>
+
+<div class="content-body">
+    <section>
+        <div class="card">
+            <div class="card-header">
+                <h4 class="card-title">Filters</h4>
+            </div>
+            <div class="card-content">
+                <div class="card-body">
+                    <div class="row">
+                        <div class="col-md-2">
+                            <label>From date</label>
+                            <input type="text" class="form-control" id="pud_fromDate" placeholder="DD/MM/YYYY">
+                        </div>
+                        <div class="col-md-2">
+                            <label>To date</label>
+                            <input type="text" class="form-control" id="pud_toDate" placeholder="DD/MM/YYYY">
+                        </div>
+                        @* <div class="col-md-3">
+                            <label>Campaign</label>
+                            <select id="pud_campaignId" class="form-control">
+                                <option value="-1">All</option>
+                                @foreach (var c in listCampaign)
+                                {
+                                    <option value="@c.id">@c.id - @c.name</option>
+                                }
+                            </select>
+                        </div>
+                        <div class="col-md-3">
+                            <label>Service</label>
+                            <select id="pud_serviceId" class="form-control">
+                                <option value="-1">All</option>
+                                @foreach (var s in listService)
+                                {
+                                    <option value="@s.id">@s.id - @s.name</option>
+                                }
+                            </select>
+                        </div> *@
+                        <div class="col-md-2">
+                            <label>Msisdn <span class="text-danger">*</span></label>
+                            <input type="text" class="form-control" id="pud_msisdn" placeholder="Enter msisdn">
+                            <small class="form-text text-muted">Required phone number to search</small>
+                        </div>
+                    </div>
+                    <div class="row mt-1">
+                        <div class="col-md-8 d-flex align-items-end">
+                            <button class="btn btn-primary mr-1" onclick="pudSearch()"><i class="fa fa-search"></i> Search</button>
+                            <button class="btn btn-success" onclick="pudExport()"><i class="fa fa-file-excel-o"></i> Export</button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div id="pud_result">
+        </div>
+    </section>
+</div>
+
+@section Scripts{
+    <script>
+        $(document).ready(function () {
+            $("#pud_fromDate, #pud_toDate").datetimepicker({
+                format: "DD/MM/YYYY"
+            });
+            $("#pud_fromDate").val(moment().subtract(7, 'days').format("DD/MM/YYYY"));
+            $("#pud_toDate").val(moment().format("DD/MM/YYYY"));
+            pudSearch();
+        });
+
+        function showMsisdnWarning(message) {
+            if (typeof Swal !== "undefined") {
+                Swal.fire("Warning!", message, "warning");
+            } else {
+                alert(message);
+            }
+        }
+
+        function pudSearch() {
+            var msisdn = ($("#pud_msisdn").val() || "").trim();
+            $("#pud_msisdn").val(msisdn);
+            if (msisdn === "") {
+                showMsisdnWarning("Please enter msisdn to search.");
+                return;
+            }
+
+            $.post(urlConfig("/Admin/ReportUssdDetailSearch"),
+                {
+                    fromDate: $("#pud_fromDate").val(),
+                    toDate: $("#pud_toDate").val(),
+                    campaignId: $("#pud_campaignId").val(),
+                    serviceId: $("#pud_serviceId").val(),
+                    msisdn: msisdn,
+                    sendStatus: $("#pud_sendStatus").val(),
+                    isSuccess: $("#pud_isSuccess").val(),
+                    rowsOnPage: "1000000",
+                    seqPage: "1",
+                    order: "desc"
+                },
+                function (html) { $("#pud_result").html(html); }
+            );
+        }
+
+        function pudExport() {
+            var msisdn = ($("#pud_msisdn").val() || "").trim();
+            $("#pud_msisdn").val(msisdn);
+            if (msisdn === "") {
+                showMsisdnWarning("Please enter msisdn before exporting.");
+                return;
+            }
+
+            var form = $('<form method="POST" style="display:none"></form>').attr('action', urlConfig("/Admin/ReportUssdDetailExport"));
+            form.append($('<input>').attr('name', 'fromDate').val($("#pud_fromDate").val()));
+            form.append($('<input>').attr('name', 'toDate').val($("#pud_toDate").val()));
+            form.append($('<input>').attr('name', 'campaignId').val($("#pud_campaignId").val()));
+            form.append($('<input>').attr('name', 'serviceId').val($("#pud_serviceId").val()));
+            form.append($('<input>').attr('name', 'msisdn').val(msisdn));
+            form.append($('<input>').attr('name', 'sendStatus').val($("#pud_sendStatus").val()));
+            form.append($('<input>').attr('name', 'isSuccess').val($("#pud_isSuccess").val()));
+            $("body").append(form);
+            form.submit();
+            form.remove();
+        }
+    </script>
+}
+

+ 16 - 5
SuperAdmin/SuperAdmin/Views/Admin/ServiceManagement.cshtml

@@ -212,7 +212,15 @@
                         </div>
                     </div>
 
-                    
+                    <div class="col-md-6">
+                        <div class="form-group">
+                            <label for="my_service">My Service</label>
+                            <select class="form-control" id="my_service" name="my_service">
+                                <option value="0">Mytel</option>
+                                <option value="1">Viettech</option>
+                            </select>
+                        </div>
+                    </div>
 
                     <input type="hidden" name="result" id="result" />
 
@@ -222,7 +230,7 @@
                         <label id="lblWarning" class="text-danger"></label>
                     </div>
                     <div class="col-12" style="text-align: right">
-                        <button type="button" id="btnAddUpdate" class="btn btn-outline-primary" onclick="addUpdateAction()">Add</button>
+                        <button type="button" id="btnAddUpdate" class="btn btn-outline-primary" onclick="addUpdateActionS()">Add</button>
                         <button type="button" class="btn grey btn-outline-secondary" data-dismiss="modal">Close</button>
                     </div>
                 </div>
@@ -283,7 +291,7 @@
                             select.append('<option value="' + result.data[i].id + '">' + result.data[i].name + '</option>');
                         }
                     } else if (result.error == "-1") {
-                        alert("Please login first!");
+                        Swal.fire("Warning!", "Please login first!", "warning");
                         window.location.href = "/Home/Login";
                     } else {
                         console.warn("⚠️ Không có dữ liệu Service Group");
@@ -345,6 +353,7 @@
             $("#msgConfirm").val("");
             $("#serviceGroupId").val("");
             $("#apiServiceId").val("");
+            $("#my_service").val("0");
             $("#btnAddUpdate").html("Add");
             showModal("modal-add-service");
         }
@@ -401,6 +410,7 @@
                         $("#msgConfirm").val(sv.msgConfirm);
                         $("#serviceGroupId").val(sv.serviceGroupID || "");
                         $("#apiServiceId").val(sv.apiServiceId || "");
+                        $("#my_service").val(sv.isMyService || "0");
                     } else {
                         console.log("error");
                     }
@@ -454,7 +464,7 @@
             return true;
         }
 
-        function addUpdateAction() {
+        function addUpdateActionS() {
             startSpinner('btnAddUpdate');
 
             if (!validateInput()) {
@@ -475,7 +485,8 @@
                     contentLc: $("#contentLc").val(),
                     msgConfirm: $("#msgConfirm").val(),
                     serviceGroupId: $("#serviceGroupId").val(),
-                    apiServiceId: $("#apiServiceId").val()
+                    apiServiceId: $("#apiServiceId").val(),
+                    isMyservice: $("#my_service").val()
                 },
                 success: function (result) {
                     stopSpinner("btnAddUpdate");

+ 334 - 0
SuperAdmin/SuperAdmin/Views/Admin/UserManagement.cshtml

@@ -0,0 +1,334 @@
+@{
+    ViewBag.Title = "User Management";
+    Layout = "~/Views/Shared/_Layout.cshtml";
+}
+
+@using SuperAdmin.Models;
+@using SuperAdmin.Models.Http;
+@using SuperAdmin.Controllers;
+@using SuperAdmin.Source;
+
+<style>
+    img {
+        max-width: 100%;
+    }
+</style>
+
+<div class="content-header row">
+    <div class="content-header-left col-md-6 col-xs-12 mb-1">
+        <h2 class="content-header-title">User Management</h2>
+    </div>
+    <div class="content-header-right breadcrumbs-right breadcrumbs-top col-md-6 col-xs-12">
+        <div class="breadcrumb-wrapper col-xs-12">
+            <ol class="breadcrumb">
+                <li class="breadcrumb-item">
+                    <a href="Index">Admin</a>
+                </li>
+                <li class="breadcrumb-item">
+                    <a href="#">User Management</a>
+                </li>
+            </ol>
+        </div>
+    </div>
+</div>
+
+<div class="content-body">
+    <section id="basic-form-layouts">
+        <div class="row service-height">
+            <div class="col-md-12">
+                <div class="card">
+                    <div class="card-header">
+                        <h4 class="card-title" id="basic-layout-form">Search</h4> 
+                    </div>
+                    <div class="card-content collapse show">
+                        <div class="card-body">
+                            <div class="card-block">
+                                <div class="form-body">
+                                    @Html.AntiForgeryToken()
+                                    <div class="row">
+                                        <div class="col-md-4 col-sm-4 col-6">
+                                            <div class="form-group">
+                                                <label>Username</label>
+                                                <input type="text" class="form-control" id="usernameSearch" name="usernameSearch">
+                                            </div>
+                                        </div>
+                                        <div class="col-md-4 col-sm-4 col-6">
+                                            <div class="form-group">
+                                                <label>Role</label>
+                                                <input type="text" class="form-control" id="roleSearch" name="roleSearch">
+                                            </div>
+                                        </div>
+                                        <div class="col-md-4 col-sm-4 col-6">
+                                            <div class="form-group">
+                                                <label>Status</label>
+                                                <select class="form-control" id="isLockSearch" name="isLockSearch">
+                                                    <option value="-1">-- All --</option>
+                                                    <option value="0" selected>Active</option>
+                                                    <option value="1">Locked</option>
+                                                </select>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="row">
+                                        <div class="col-md-12">
+                                            <button type="button" class="btn btn-info" onclick="search()" id="btnSearch">
+                                                <i class="fa fa-search"></i> Search
+                                            </button>
+                                            <button type="button" class="btn btn-primary" onclick="add()" id="btnAdd">
+                                                <i class="fa fa-plus"></i> Add User
+                                            </button>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="col-12">
+                <div class="card">
+                    <div class="card-content " id="partial-content">
+                    </div>
+                </div>
+            </div>
+        </div>
+    </section>
+</div>
+
+<!-- Add/Edit Modal -->
+<div class="modal fade text-xs-left" id="modal-add-user" tabindex="-1" role="dialog" aria-labelledby="myModalLabel17" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title" id="modalLabelUser">User Information</h4>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <input type="hidden" id="id" />
+            <div class="modal-body">
+                <div class="row" id="div-info">
+                    <div class="col-md-6">
+                        <div class="form-group">
+                            <label>Username *</label>
+                            <input type="text" class="form-control" id="username" name="username">
+                        </div>
+                    </div>
+                    <div class="col-md-6">
+                        <div class="form-group">
+                            <label>Password *</label>
+                            <input type="password" class="form-control" id="password" name="password">
+                        </div>
+                    </div>
+                    <div class="col-md-6">
+                        <div class="form-group">
+                            <label>Role *</label>
+                            <input type="text" class="form-control" id="role" name="role" placeholder="e.g. ADMIN, USER">
+                        </div>
+                    </div>
+                    <div class="col-md-6">
+                        <div class="form-group">
+                            <label>Country Code</label>
+                            <input type="text" class="form-control" id="countryCode" name="countryCode" placeholder="e.g. 509">
+                        </div>
+                    </div>
+                    <div class="col-md-12">
+                        <div class="form-group">
+                            <label>Note</label>
+                            <textarea class="form-control" id="note" name="note" rows="3"></textarea>
+                        </div>
+                    </div>
+                    <div class="col-md-12 hidden" id="divLockFields">
+                        <div class="row">
+                            <div class="col-md-4">
+                                <div class="form-group">
+                                    <label>Is Lock</label>
+                                    <select class="form-control" id="isLock" name="isLock">
+                                        <option value="0">No</option>
+                                        <option value="1">Yes</option>
+                                    </select>
+                                </div>
+                            </div>
+                            <div class="col-md-4">
+                                <div class="form-group">
+                                    <label>Total False</label>
+                                    <input type="number" class="form-control" id="totalFalse" name="totalFalse">
+                                </div>
+                            </div>
+                            <div class="col-md-4">
+                                <div class="form-group">
+                                    <label>Time Lock</label>
+                                    <input type="text" class="form-control" id="timeLock" name="timeLock" readonly>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="row" id="div-action">
+                    <div class="col-12">
+                        <label id="lblWarning" class="text-danger"></label>
+                    </div>
+                    <div class="col-12" style="text-align: right">
+                        <button type="button" id="btnAddUpdate" class="btn btn-outline-primary" onclick="addUpdateAction()">Add</button>
+                        <button type="button" class="btn grey btn-outline-secondary" data-dismiss="modal">Close</button>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</div>
+
+@section Scripts {
+    <script>
+        $(document).ready(function () {
+            search();
+        });
+
+        function add() {
+            $("#id").val("");
+            $("#username").val("");
+            $("#password").val("");
+            $("#role").val("");
+            $("#countryCode").val("");
+            $("#note").val("");
+            $("#isLock").val("0");
+            $("#totalFalse").val("0");
+            $("#timeLock").val("");
+            $("#divLockFields").addClass("hidden");
+            $("#btnAddUpdate").html("Add");
+            $("#modalLabelUser").html("Add User");
+            showModal("modal-add-user");
+        }
+
+        function edit(id) {
+            $("#id").val(id);
+            $("#btnAddUpdate").html("Update");
+            $("#modalLabelUser").html("Edit User");
+            loadInfo(id);
+            showModal("modal-add-user");
+        }
+
+        function loadInfo(id) {
+            $.ajax({
+                url: urlConfig('/Admin/UserLoadInfo'),
+                type: "POST",
+                data: { id: id },
+                success: function (result) {
+                    if (result.error == '0') {
+                        var user = result.data;
+                        $("#id").val(user.id);
+                        $("#username").val(user.username);
+                        $("#role").val(user.role);
+                        $("#countryCode").val(user.countryCode || "");
+                        $("#note").val(user.note || "");
+                        $("#isLock").val(user.isLock || "0");
+                        $("#totalFalse").val(user.totalFalse || "0");
+                        $("#timeLock").val(user.timeLock || "");
+                        $("#password").val("");
+                        $("#divLockFields").removeClass("hidden");
+                    } else {
+                        Swal.fire("Failed!", result.content, "error");
+                    }
+                },
+                error: function (err) {
+                    Swal.fire("Failed!", err.statusText, "error");
+                }
+            });
+        }
+
+        function validateInput() {
+            let username = $("#username").val();
+            let password = $("#password").val();
+            let role = $("#role").val();
+
+            if (username == null || username == "") {
+                $("#lblWarning").html("Username is required");
+                return false;
+            }
+            if ($("#id").val() == "" && (password == null || password == "")) {
+                $("#lblWarning").html("Password is required for new user");
+                return false;
+            }
+            if (role == null || role == "") {
+                $("#lblWarning").html("Role is required");
+                return false;
+            }
+            return true;
+        }
+
+        function addUpdateAction() {
+            startSpinner('btnAddUpdate');
+
+            if (!validateInput()) {
+                stopSpinner("btnAddUpdate");
+                return;
+            }
+            $("#lblWarning").html("");
+
+            $.ajax({
+                url: urlConfig('/Admin/UserAddUpdate'),
+                type: "POST",
+                data: {
+                    id: $("#id").val(),
+                    username: $("#username").val(),
+                    password: $("#password").val(),
+                    role: $("#role").val(),
+                    countryCode: $("#countryCode").val(),
+                    note: $("#note").val(),
+                    isLock: $("#isLock").val(),
+                    timeLock: $("#timeLock").val(),
+                    totalFalse: $("#totalFalse").val()
+                },
+                success: function (result) {
+                    stopSpinner("btnAddUpdate");
+                    if (result.error == '0') {
+                        Swal.fire("Success!", "Success!", "success").then(() => {
+                            hideModal("modal-add-user");
+                            search();
+                        });
+                    } else {
+                        Swal.fire("Failed!", result.content, "error");
+                    }
+                },
+                error: function (err) {
+                    stopSpinner("btnAddUpdate");
+                    Swal.fire("Failed!", err.statusText, "error");
+                }
+            });
+        }
+
+        function search() {
+            startSpinner('btnSearch');
+            if ($.fn.DataTable.isDataTable('#grid_detail')) {
+                $('#grid_detail').DataTable().destroy();
+            }
+            $.ajax({
+                url: urlConfig("/Admin/UserSearch"),
+                data: {
+                    username: $("#usernameSearch").val(),
+                    role: $("#roleSearch").val(),
+                    isLock: $("#isLockSearch").val()
+                },
+                type: "POST",
+                success: function (data) {
+                    stopSpinner('btnSearch');
+                    $("#partial-content").html(data);
+                    $("#grid_detail").DataTable({
+                        orderCellsTop: true
+                    });
+                },
+                error: function (data) {
+                    stopSpinner('btnSearch');
+                    console.log(data.error);
+                }
+            })
+        }
+
+        // Clear error
+        $(".modal input, .modal textarea").keyup(function() {
+            $("#lblWarning").html("");
+        })
+    </script>
+}
+

+ 2 - 6
SuperAdmin/SuperAdmin/Views/Home/Login.cshtml

@@ -7,18 +7,14 @@
 
 <div class="login-background"></div>
 <div class="login-grid"></div>
-<div class="glowing-orb orb-1"></div>
-<div class="glowing-orb orb-2"></div>
-<div class="glowing-orb orb-3"></div>
 
 <div class="login-container">
     <div class="login-form-card">
         <div class="login-header">
             <div class="logo-circle">
-                <i class="ft-lock" style="font-size: 36px; color: #fff;"></i>
+                <i class="ft-lock" style="font-size: 36px; color: #333;"></i>
             </div>
-            <h2 class="login-title">CMS B +</h2>
-            <p class="login-subtitle">Access Control System</p>
+            <h2 class="login-title">BALANCE PLUS MYTEL</h2>
         </div>
         
         <div class="form-section">

+ 4 - 2
SuperAdmin/SuperAdmin/Views/Partial/_ApiWebserviceManagement.cshtml

@@ -6,7 +6,7 @@
 
 @model List<ApiService>
 
-<div class="card-body">
+<div class="card-body table-responsive">
     <table class="table table-striped table-bordered zero-configuration " id="grid_detail">
         <thead class="thead-inverse">
             <tr>
@@ -17,6 +17,7 @@
                 <th scope="col" class="text-center" style="vertical-align:middle">wsdl</th>
                 <th scope="col" class="text-center" style="vertical-align:middle">msg_template</th>
                 <th scope="col" class="text-center" style="vertical-align:middle">error_tag</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">success_code</th>
                 <th scope="col" class="text-center" style="vertical-align:middle">Status</th>
                 <th scope="col" class="text-center" style="vertical-align:middle">Action</th>
             </tr>
@@ -35,6 +36,7 @@
                         <td class="text-left">@ws.wsdl</td>
                         <td class="text-left">@ws.msg_template</td>
                         <td class="text-left">@ws.error_tag</td>
+                        <td class="text-left">@(ws.success_code ?? "")</td>
                         <td class="text-center">
                             @if (ws.isActive == "1")
                             {
@@ -56,7 +58,7 @@
             else
             {
                 <tr>
-                    <td colspan="11">No data</td>
+                    <td colspan="10">No data</td>
                 </tr>
             }
         </tbody>

+ 15 - 31
SuperAdmin/SuperAdmin/Views/Partial/_Campaign.cshtml

@@ -100,6 +100,16 @@
         </div>
     </div>
 
+    <div class="col-md-6">
+        <div class="form-group">
+            <label for="my_service">My Service</label>
+            <select class="form-control" id="my_service" name="my_service" asp-for="isMyService">
+                <option value="0">Mytel</option>
+                <option value="1">Viettech</option>
+            </select>
+        </div>
+    </div>
+
     @* <div class="col-md-6 hidden">
             <div class="form-group">
                 <label for="date">Active dates</label>
@@ -121,41 +131,15 @@
 
     <div class="col-md-12">
         <div class="form-group">
-            <label for="date">Title teaser</label>
+            <label for="date">
+                Title teaser
+                <span class="text-danger" id="title-required-indicator" style="display:none">*</span>
+            </label>
             <input type="text" class="form-control" id="title" name="title" onchange="updateTotalChar();" onkeyup="updateTotalChar();" asp-for="title">
+            <small class="text-muted" id="title-helper" style="display:none">Required when Service Type is Text</small>
         </div>
     </div>
 
-    <div class="col-12 row">
-        <div class="col-md-3">
-            <div class="">
-                <label for="date">Service</label>
-            </div>
-        </div>
-        <div class="col-md-3">
-            <div class="">
-                <label for="date">USSD Teaser 1</label>
-            </div>
-        </div>
-        <div class="col-md-1">
-            <div class="">
-                <label for="date">Key 1</label>
-            </div>
-        </div>
-
-        <div class="col-md-3" id="ussd-teaser-2-header">
-            <div class="">
-                <label for="date">USSD Teaser 2</label>
-            </div>
-        </div>
-        <div class="col-md-1" id="key-2-header">
-            <div class="">
-                <label for="date">Key 2</label>
-            </div>
-        </div>
-        <div class="col-md-1">
-        </div>
-    </div>
     <div class="col-12" id="div-services">
         @Html.Partial("../Partial/_CampaignService", Model != null? Model.listServiceMapCam: null)
     </div>

+ 269 - 0
SuperAdmin/SuperAdmin/Views/Partial/_CampaignSchedulerCalendar.cshtml

@@ -0,0 +1,269 @@
+@using SuperAdmin.Models;
+@using SuperAdmin.Models.Http;
+@using SuperAdmin.Controllers;
+@using SuperAdmin.Source;
+@using Newtonsoft.Json;
+
+@model List<CalendarData>
+
+<style>
+    .calendar-container {
+        overflow-x: auto;
+    }
+    
+    .calendar-header {
+        display: grid;
+        grid-template-columns: repeat(7, 1fr);
+        gap: 1px;
+        background-color: #dee2e6;
+        border: 1px solid #dee2e6;
+        font-weight: bold;
+        text-align: center;
+    }
+    
+    .calendar-header-cell {
+        padding: 10px;
+        background-color: #f8f9fa;
+    }
+    
+    .calendar-grid {
+        display: grid;
+        grid-template-columns: repeat(7, 1fr);
+        gap: 1px;
+        background-color: #dee2e6;
+        border: 1px solid #dee2e6;
+        border-top: none;
+    }
+    
+    .calendar-day {
+        min-height: 100px;
+        background-color: white;
+        padding: 5px;
+        border: 1px solid #dee2e6;
+        position: relative;
+        vertical-align: top;
+    }
+    
+    .calendar-day-header {
+        font-weight: bold;
+        margin-bottom: 5px;
+        text-align: center;
+    }
+    
+    .calendar-day.empty {
+        background-color: #f8f9fa;
+    }
+    
+    .calendar-day.today {
+        background-color: #e7f3ff;
+    }
+    
+    .campaign-dots-container {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 4px;
+        margin-top: 5px;
+    }
+    
+    .campaign-dot {
+        width: 8px;
+        height: 8px;
+        border-radius: 50%;
+        cursor: pointer;
+        position: relative;
+        transition: all 0.2s ease;
+        flex-shrink: 0;
+    }
+    
+    .campaign-dot:hover {
+        z-index: 10;
+        box-shadow: 0 2px 8px rgba(0,0,0,0.3);
+        transform: scale(1.5);
+    }
+    
+    .campaign-dot .campaign-tooltip {
+        display: none;
+        position: absolute;
+        background-color: rgba(0, 0, 0, 0.9);
+        color: white;
+        padding: 6px 12px;
+        border-radius: 4px;
+        white-space: nowrap;
+        z-index: 1000;
+        pointer-events: none;
+        font-size: 12px;
+        bottom: 100%;
+        left: 50%;
+        transform: translateX(-50%);
+        margin-bottom: 5px;
+    }
+    
+    .campaign-dot:hover .campaign-tooltip {
+        display: block;
+    }
+    
+    .campaign-dot.running {
+        background-color: #16D39A; /* badge-success */
+    }
+    
+    .campaign-dot.pending {
+        background-color: #2DCEE3; /* badge-info */
+    }
+    
+    .campaign-dot.completed {
+        background-color: #00B5B8; /* badge-primary */
+    }
+    
+    .campaign-dot.paused {
+        background-color: #2DCEE3; /* badge-info */
+    }
+    
+    .campaign-dot.closed {
+        background-color: #FF7588; /* badge-danger */
+    }
+</style>
+
+<div class="card-body table-responsive calendar-container">
+    <div id="calendar-view">
+        <div class="calendar-header">
+            <div class="calendar-header-cell">Sun</div>
+            <div class="calendar-header-cell">Mon</div>
+            <div class="calendar-header-cell">Tue</div>
+            <div class="calendar-header-cell">Wed</div>
+            <div class="calendar-header-cell">Thur</div>
+            <div class="calendar-header-cell">Fri</div>
+            <div class="calendar-header-cell">Sat</div>
+        </div>
+        <div class="calendar-grid" id="calendar-grid">
+            <!-- Calendar days will be dynamically generated here -->
+        </div>
+    </div>
+</div>
+
+<script>
+    var calendarData = @Html.Raw(JsonConvert.SerializeObject(Model));
+    var campaignColors = {
+        '3': 'running',      // Running
+        '2': 'pending',      // Pending
+        '4': 'completed',    // Completed
+        '5': 'paused',       // Paused
+        '6': 'closed'        // Closed
+    };
+    
+    function renderCalendar() {
+        var grid = $('#calendar-grid');
+        grid.empty();
+        
+        if (!calendarData || calendarData.length === 0) {
+            return;
+        }
+        
+        // Get first and last date to determine calendar bounds
+        var firstDate = moment(calendarData[0].date, 'DD/MM/YYYY');
+        var lastDate = moment(calendarData[calendarData.length - 1].date, 'DD/MM/YYYY');
+        
+        // Get the first day of the month and last day of the month
+        var startOfMonth = firstDate.clone().startOf('month');
+        var endOfMonth = lastDate.clone().endOf('month');
+        var startOfCalendar = startOfMonth.clone().startOf('week');
+        var endOfCalendar = endOfMonth.clone().endOf('week');
+        
+        // Create a map of date to campaigns for quick lookup
+        var campaignMap = {};
+        calendarData.forEach(function(cal) {
+            campaignMap[cal.date] = cal.list || [];
+        });
+        
+        // Create a list of all unique campaigns with their date ranges
+        var allCampaigns = [];
+        var campaignSet = new Set();
+        
+        calendarData.forEach(function(cal) {
+            if (cal.list) {
+                cal.list.forEach(function(campaign) {
+                    if (!campaignSet.has(campaign.id)) {
+                        campaignSet.add(campaign.id);
+                        allCampaigns.push({
+                            id: campaign.id,
+                            name: campaign.name || '',
+                            fromDate: moment(campaign.fromDate, 'DD/MM/YYYY'),
+                            toDate: moment(campaign.toDate, 'DD/MM/YYYY'),
+                            status: campaign.status || '0',
+                            statusText: getStatusText(campaign.status)
+                        });
+                    }
+                });
+            }
+        });
+        
+        // Generate calendar days
+        var currentDate = startOfCalendar.clone();
+        var today = moment();
+        
+        while (currentDate.isSameOrBefore(endOfCalendar)) {
+            var dayData = {
+                date: currentDate.format('DD/MM/YYYY'),
+                isCurrentMonth: currentDate.isSame(firstDate, 'month'),
+                isToday: currentDate.isSame(today, 'day'),
+                dayOfMonth: currentDate.format('D'),
+                campaigns: []
+            };
+            
+            // Find campaigns that span this day
+            allCampaigns.forEach(function(campaign) {
+                if (currentDate.isSameOrAfter(campaign.fromDate, 'day') && 
+                    currentDate.isSameOrBefore(campaign.toDate, 'day')) {
+                    dayData.campaigns.push(campaign);
+                }
+            });
+            
+            renderCalendarDay(dayData, grid);
+            currentDate.add(1, 'day');
+        }
+    }
+    
+    function renderCalendarDay(dayData, grid) {
+        var dayClass = 'calendar-day';
+        if (!dayData.isCurrentMonth) {
+            dayClass += ' empty';
+        }
+        if (dayData.isToday) {
+            dayClass += ' today';
+        }
+        
+        var html = '<div class="' + dayClass + '">';
+        html += '<div class="calendar-day-header">' + dayData.dayOfMonth + '</div>';
+        
+        if (dayData.campaigns && dayData.campaigns.length > 0) {
+            html += '<div class="campaign-dots-container">';
+            dayData.campaigns.forEach(function(campaign) {
+                var dotClass = 'campaign-dot ' + (campaignColors[campaign.status] || '');
+                var displayText = campaign.name + ' ' + campaign.statusText;
+                html += '<div class="' + dotClass + '" data-campaign-name="' + campaign.name + '">';
+                html += '<div class="campaign-tooltip">' + displayText + '</div>';
+                html += '</div>';
+            });
+            html += '</div>';
+        }
+        
+        html += '</div>';
+        
+        grid.append(html);
+    }
+    
+    function getStatusText(status) {
+        switch(status) {
+            case '2': return 'Pending';
+            case '3': return 'Running';
+            case '4': return 'Completed';
+            case '5': return 'Paused';
+            case '6': return 'Closed';
+            default: return '';
+        }
+    }
+    
+    // Initialize calendar on page load
+    $(document).ready(function() {
+        renderCalendar();
+    });
+</script>

+ 13 - 2
SuperAdmin/SuperAdmin/Views/Partial/_CampaignScheduler.cshtml → SuperAdmin/SuperAdmin/Views/Partial/_CampaignSchedulerTable.cshtml

@@ -1,4 +1,4 @@
-@using SuperAdmin.Models;
+@using SuperAdmin.Models;
 @using SuperAdmin.Models.Http;
 @using SuperAdmin.Controllers;
 @using SuperAdmin.Source;
@@ -79,4 +79,15 @@
             }
         </tbody>
     </table>
-</div>
+</div>
+
+<script>
+    $(document).ready(function() {
+        if ($.fn.DataTable.isDataTable('#grid_detail')) {
+            $('#grid_detail').DataTable().destroy();
+        }
+        $('#grid_detail').DataTable({
+            orderCellsTop: true
+        });
+    });
+</script>

+ 8 - 8
SuperAdmin/SuperAdmin/Views/Partial/_CampaignService.cshtml

@@ -69,7 +69,7 @@
             </div>
             
             <!-- Row 2: USSD Teaser 1, Key 1, USSD Teaser 2, Key 2 -->
-            <div class="col-md-3">
+            <div class="col-md-3 service-field service-field--ussd1">
                 <div class="form-group">
                     <label>USSD Teaser 1</label>
                     <textarea class="hidden" id="ussdDisplayMulti-@i">@campaignServiceObj.ussdDisplay</textarea>
@@ -78,20 +78,20 @@
                            value="@campaignServiceObj.ussdDisplay" >
                 </div>
             </div>
-            <div class="col-md-2">
+            <div class="col-md-2 service-field service-field--key1">
                 <div class="form-group">
                     <label>Key 1</label>
                     <input type="text" class="form-control" id="keyRegister-@i" name="keyRegister" onkeypress="clearValidate('keyRegister-@i')"
                            value="@campaignServiceObj.keyRegister">
                 </div>
             </div>
-            <div class="col-md-3">
+            <div class="col-md-3 service-field service-field--ussd2">
                 <div class="form-group">
                     <label>USSD Teaser 2</label>
                     <input type="text" class="form-control" id="msgConfirm-@i" value="@campaignServiceObj.serviceObj.msgConfirm" readonly>
                 </div>
             </div>
-            <div class="col-md-2">
+            <div class="col-md-2 service-field service-field--key2">
                 <div class="form-group">
                     <label>Key 2</label>
                     <input type="text" class="form-control" value="1" readonly>
@@ -157,7 +157,7 @@ else
         </div>
         
         <!-- Row 2: USSD Teaser 1, Key 1, USSD Teaser 2, Key 2 -->
-        <div class="col-md-3">
+        <div class="col-md-3 service-field service-field--ussd1">
             <div class="form-group">
                 <label>USSD Teaser 1</label>
                 <input type="text" class="form-control" id="ussdDisplay-@nextIndex" name="ussdDisplay" onchange="updateTotalChar();" onkeyup="updateTotalChar();"
@@ -165,20 +165,20 @@ else
                        value="">
             </div>
         </div>
-        <div class="col-md-2">
+        <div class="col-md-2 service-field service-field--key1">
             <div class="form-group">
                 <label>Key 1</label>
                 <input type="text" class="form-control" id="keyRegister-@nextIndex" name="keyRegister" onkeypress="clearValidate('keyRegister-@nextIndex')"
                        value="">
             </div>
         </div>
-        <div class="col-md-3">
+        <div class="col-md-3 service-field service-field--ussd2">
             <div class="form-group">
                 <label>USSD Teaser 2</label>
                 <input type="text" class="form-control" id="msgConfirm-@nextIndex" readonly>
             </div>
         </div>
-        <div class="col-md-2">
+        <div class="col-md-2 service-field service-field--key2">
             <div class="form-group">
                 <label>Key 2</label>
                 <input type="text" class="form-control" value="1" readonly>

+ 51 - 0
SuperAdmin/SuperAdmin/Views/Partial/_Functions.cshtml

@@ -0,0 +1,51 @@
+@using SuperAdmin.Models;
+@using SuperAdmin.Models.Http;
+@using SuperAdmin.Controllers;
+@using SuperAdmin.Source;
+
+@model List<FunctionWebCms>
+
+<div class="card-body">
+    <table class="table table-striped table-bordered zero-configuration " id="grid_detail">
+        <thead class="thead-inverse">
+            <tr>
+                <th scope="col">#</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">ID</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Role</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Name</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Link</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Note</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Action</th>
+            </tr>
+        </thead>
+        <tbody id="gridbody_detail">
+            @if (Model != null)
+            {
+                for (int i = 0; i < Model.Count; i++)
+                {
+                    var func = Model[@i];
+                    <tr>
+                        <td scope="row">@(i + 1)</td>
+                        <td class="text-left">@func.id</td>
+                        <td class="text-left">@func.role</td>
+                        <td class="text-left">@func.name</td>
+                        <td class="text-left">@func.link</td>
+                        <td class="text-left">@func.note</td>
+                        <td class="text-center">
+                            <div class="btn-group" role="group">
+                                <button class="btn btn-sm btn-outline-primary" onclick="edit('@func.id')">Edit</button>
+                            </div>
+                        </td>
+                    </tr>
+                }
+            }
+            else
+            {
+                <tr>
+                    <td colspan="7">No data</td>
+                </tr>
+            }
+        </tbody>
+    </table>
+</div>
+

+ 71 - 9
SuperAdmin/SuperAdmin/Views/Partial/_Menu.cshtml

@@ -21,12 +21,21 @@
 
 <ul class="navigation navigation-main" id="main-menu-navigation" data-menu="menu-navigation">
     @{
-        if (BaseController.useVsa == "0" || listMenu.Contains("/Admin/Index"))
+        // if (BaseController.useVsa == "0" || listMenu.Contains("/Admin/Index"))
+        // {
+        //     <li class=" nav-item">
+        //         <a class="nav-link" href="@ViewBag.MyConfig.MyValue/Admin/Index">
+        //             <i class="feather icon-home"></i>
+        //             <span class="menu-title">Home</span>
+        //         </a>
+        //     </li>
+        // }
+         if (BaseController.useVsa == "0" || listMenu.Contains("/Admin/DashboardReport"))
         {
             <li class=" nav-item">
-                <a class="nav-link" href="@ViewBag.MyConfig.MyValue/Admin/Index">
-                    <i class="feather icon-home"></i>
-                    <span class="menu-title">Home</span>
+                <a class="nav-link" href="@ViewBag.MyConfig.MyValue/Admin/DashboardReport">
+                    <i class="feather icon-screen-tablet"></i>
+                    <span class="menu-title">Dashboard</span>
                 </a>
             </li>
         }
@@ -109,6 +118,30 @@
         }
 
 
+        if (BaseController.useVsa == "0" || listMenu.Contains("/Admin/UserManagement"))
+        {
+            <li class="nav-item has-sub">
+                <a href="#">
+                    <i class="feather icon-users"></i><span class="menu-title" data-i18n="nav.page_headers.main">User Management</span>
+                </a>
+                <ul class="menu-content" style="">
+                    <li class="is-shown">
+                        <a class="menu-item" href="@ViewBag.MyConfig.MyValue/Admin/UserManagement"
+                           data-i18n="nav.page_headers.headers_breadcrumbs_basic">
+                           <i class="feather icon-user"></i> Users
+                        </a>
+                    </li>
+                    <li class="is-shown">
+                        <a class="menu-item" href="@ViewBag.MyConfig.MyValue/Admin/FunctionManagement"
+                           data-i18n="nav.page_headers.headers_breadcrumbs_top">
+                           <i class="feather icon-key"></i> Functions
+                        </a>
+                    </li>
+                </ul>
+            </li>
+        }
+
+
         if (BaseController.useVsa == "0" || listMenu.Contains("/Admin/ParamManagement"))
         {
             <li class=" nav-item">
@@ -131,15 +164,44 @@
         }
 
 
-        if (BaseController.useVsa == "0" || listMenu.Contains("/Admin/ReportCampaign"))
+        if (BaseController.useVsa == "0" || listMenu.Contains("/Admin/ReportCampaign") || listMenu.Contains("/Admin/ReportCountDaily") || listMenu.Contains("/Admin/ReportUssdDetail"))
         {
-            <li class=" nav-item">
-                <a class="nav-link" href="@ViewBag.MyConfig.MyValue/Admin/ReportCampaign">
-                    <i class="feather icon-flag"></i>
-                    <span class="menu-title">Report Campaign</span>
+            <li class="nav-item has-sub">
+                <a href="#">
+                    <i class="feather icon-bar-chart"></i><span class="menu-title" data-i18n="nav.page_headers.main">Report</span>
                 </a>
+                <ul class="menu-content" style="">
+                    @if (BaseController.useVsa == "0" || listMenu.Contains("/Admin/ReportCampaign"))
+                    {
+                        <li class="is-shown">
+                            <a class="menu-item" href="@ViewBag.MyConfig.MyValue/Admin/ReportCampaign"
+                               data-i18n="nav.page_headers.headers_breadcrumbs_basic">
+                               <i class="feather icon-flag"></i> Report Campaign
+                            </a>
+                        </li>
+                    }
+                    @if (BaseController.useVsa == "0" || listMenu.Contains("/Admin/ReportCountDaily"))
+                    {
+                        <li class="is-shown">
+                            <a class="menu-item" href="@ViewBag.MyConfig.MyValue/Admin/ReportCountDaily"
+                               data-i18n="nav.page_headers.headers_breadcrumbs_top">
+                               <i class="feather icon-bar-chart"></i> General Campaign
+                            </a>
+                        </li>
+                    }
+                    @if (BaseController.useVsa == "0" || listMenu.Contains("/Admin/ReportUssdDetail"))
+                    {
+                        <li class="is-shown">
+                            <a class="menu-item" href="@ViewBag.MyConfig.MyValue/Admin/ReportUssdDetail"
+                               data-i18n="nav.page_headers.headers_breadcrumbs_top">
+                               <i class="feather icon-list"></i> Campaign Detail
+                            </a>
+                        </li>
+                    }
+                </ul>
             </li>
         }
+       
 
 
         if (BaseController.useVsa == "0" || listMenu.Contains("/Admin/Reload"))

+ 92 - 0
SuperAdmin/SuperAdmin/Views/Partial/_ReportCountDaily.cshtml

@@ -0,0 +1,92 @@
+@using SuperAdmin.Models;
+@using SuperAdmin.Models.Http;
+@using SuperAdmin.Controllers;
+@using SuperAdmin.Source;
+
+@model List<ReportCountDaily>
+<div class="card-body table-responsive">
+    <table class="table table-striped table-bordered zero-configuration" id="grid_detail">
+        <thead class="thead-inverse">
+            <tr>
+                <th scope="col">#</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Report Date</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Campaign ID</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Campaign Name</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Priority</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Campaign Type</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Service Type</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Partner</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Service Name</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Total Send 1</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Total press 1 of send 1</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Total Send 2</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Total press 1 of send 2</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Total Success</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Total Fail</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Action</th>
+            </tr>
+        </thead>
+        <tbody id="gridbody_detail">
+            @if (Model != null)
+            {
+                int c = 1;
+                for (int i = 0; i < Model.Count; i++)
+                {
+                    var ws = Model[i];
+                    <tr>
+                        <td scope="row">@(c++)</td>
+                        <td class="text-center">@ws.reportDate</td>
+                        <td class="text-center">@ws.campaignId</td>
+                        <td class="text-left">@ws.campaignName</td>
+                        <td class="text-center">@ws.priority</td>
+                        <td class="text-center">@(ws.isDefault == "0" ? "Normal" : ws.isDefault == "1" ? "Default" : ws.isDefault)</td>
+                        <td class="text-center">@(ws.addType == "1" ? "Text" : ws.addType == "2" ? "1-Verification" : ws.addType == "3" ? "2-Verification" : ws.addType)</td>
+                        <td class="text-center">@(ws.isMyService == "0" ? "mytel" : ws.isMyService == "1" ? "viettech" : ws.isMyService)</td>
+                        <td class="text-left">@ws.serviceName</td>
+                        <td class="text-right">@ws.countSend1</td>
+                        <td class="text-right">@ws.countPress1</td>
+                        <td class="text-right">@ws.countSend2</td>
+                        <td class="text-right">@ws.countPress2</td>
+                        <td class="text-right">@ws.countRegSuccess</td>
+                        <td class="text-right">@ws.countRegFail</td>
+                        <td class="text-center">
+                            <button type="button" class="btn btn-sm btn-info" onclick="viewErrorDetail('@ws.reportDate', '@ws.campaignId', '@ws.serviceId')">
+                                <i class="fa fa-eye"></i> Detail
+                            </button>
+                        </td>
+                    </tr>
+                }
+            }
+            else
+            {
+                <tr>
+                    <td colspan="20">No data</td>
+                </tr>
+            }
+        </tbody>
+    </table>
+</div>
+
+<!-- Modal for Error Details -->
+<div class="modal fade text-xs-left" id="modal-error-detail" tabindex="-1" role="dialog" aria-labelledby="myModalLabel17" style="display: none;" aria-hidden="true">
+    <div class="modal-dialog modal-lg" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h4 class="modal-title" id="modalLabelErrorDetail">Detail - Report Error Daily</h4>
+                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">×</span>
+                </button>
+            </div>
+            <div class="modal-body" id="error-detail-content">
+                <div class="text-center">
+                    <i class="fa fa-spinner fa-spin fa-3x"></i>
+                    <p>Đang tải dữ liệu...</p>
+                </div>
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn grey btn-outline-secondary" data-dismiss="modal">Đóng</button>
+            </div>
+        </div>
+    </div>
+</div>
+

+ 47 - 0
SuperAdmin/SuperAdmin/Views/Partial/_ReportErrorDaily.cshtml

@@ -0,0 +1,47 @@
+@using SuperAdmin.Models;
+@using SuperAdmin.Models.Http;
+@using SuperAdmin.Controllers;
+@using SuperAdmin.Source;
+
+@model List<ReportErrorDaily>
+<div class="table-responsive">
+    <table class="table table-striped table-bordered" id="grid_error_detail">
+        <thead class="thead-inverse">
+            <tr>
+                <th scope="col">#</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Report Date</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Campaign ID</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Service ID</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Error Code</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Count</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Insert Time</th>
+            </tr>
+        </thead>
+        <tbody id="gridbody_error_detail">
+            @if (Model != null && Model.Count > 0)
+            {
+                int c = 1;
+                for (int i = 0; i < Model.Count; i++)
+                {
+                    var ws = Model[i];
+                    <tr>
+                        <td scope="row">@(c++)</td>
+                        <td class="text-center">@ws.reportDate</td>
+                        <td class="text-center">@ws.campaignId</td>
+                        <td class="text-center">@ws.serviceId</td>
+                        <td class="text-center">@ws.errorCode</td>
+                        <td class="text-right">@ws.countNum</td>
+                        <td class="text-center">@ws.insertTime</td>
+                    </tr>
+                }
+            }
+            else
+            {
+                <tr>
+                    <td colspan="7" class="text-center">No data</td>
+                </tr>
+            }
+        </tbody>
+    </table>
+</div>
+

+ 58 - 0
SuperAdmin/SuperAdmin/Views/Partial/_ReportUssdDetail.cshtml

@@ -0,0 +1,58 @@
+@model List<SuperAdmin.Models.Http.PushUssdDetail>
+@{
+    var data = Model ?? new List<SuperAdmin.Models.Http.PushUssdDetail>();
+}
+<div class="table-responsive">
+    <table class="table table-striped table-bordered">
+        <thead>
+            <tr>
+                <th>No.</th>
+                <th>Request ID</th>
+                <th>Campaign</th>
+                <th>Msisdn</th>
+                <th>Send Time</th>
+                <th>Send Status</th>
+                <th>Total Step</th>
+                <th>Is Step 1</th>
+                <th>Step 1 Time</th>
+                <th>Is Step 2</th>
+                <th>Step 2 Time</th>
+                <th>Error Code</th>
+                <th>Is Success</th>
+                <th>Insert Time</th>
+                <th>Last Update</th>
+            </tr>
+        </thead>
+        <tbody>
+        @if (data.Count == 0)
+        {
+            <tr><td colspan="16" class="text-center">No data</td></tr>
+        }
+        else
+        {
+            for (int i = 0; i < data.Count; i++)
+            {
+                var row = data[i];
+                <tr>
+                    <td>@(i + 1)</td>
+                    <td>@row.requestId</td>
+                    <td>@row.campaignId @if(!string.IsNullOrEmpty(row.campaignName)){<span class="text-muted">- @row.campaignName</span>}</td>
+                    <td>@row.msisdn</td>
+                    <td>@row.sendTime</td>
+                    <td>@row.sendStatus</td>
+                    <td>@row.totalStep</td>
+                    <td>@row.isStep1</td>
+                    <td>@row.step1Time</td>
+                    <td>@row.isStep2</td>
+                    <td>@row.step2Time</td>
+                    <td>@row.errorCode</td>
+                    <td>@row.isSuccess</td>
+                    <td>@row.insertTime</td>
+                    <td>@row.lastUpdate</td>
+                </tr>
+            }
+        }
+        </tbody>
+    </table>
+</div>
+

+ 64 - 0
SuperAdmin/SuperAdmin/Views/Partial/_Users.cshtml

@@ -0,0 +1,64 @@
+@using SuperAdmin.Models;
+@using SuperAdmin.Models.Http;
+@using SuperAdmin.Controllers;
+@using SuperAdmin.Source;
+
+@model List<UserWebCms>
+
+<div class="card-body">
+    <table class="table table-striped table-bordered zero-configuration " id="grid_detail">
+        <thead class="thead-inverse">
+            <tr>
+                <th scope="col">#</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">ID</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Username</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Role</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Country Code</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Status</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Total False</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Note</th>
+                <th scope="col" class="text-center" style="vertical-align:middle">Action</th>
+            </tr>
+        </thead>
+        <tbody id="gridbody_detail">
+            @if (Model != null)
+            {
+                for (int i = 0; i < Model.Count; i++)
+                {
+                    var user = Model[@i];
+                    <tr>
+                        <td scope="row">@(i + 1)</td>
+                        <td class="text-left">@user.id</td>
+                        <td class="text-left">@user.username</td>
+                        <td class="text-left">@user.role</td>
+                        <td class="text-center">@user.countryCode</td>
+                        <td class="text-center">
+                            @if (user.isLock == "0")
+                            {
+                                <span class="badge badge-success">Active</span>
+                            }
+                            else
+                            {
+                                <span class="badge badge-danger">Locked</span>
+                            }
+                        </td>
+                        <td class="text-center">@user.totalFalse</td>
+                        <td class="text-left">@user.note</td>
+                        <td class="text-center">
+                            <div class="btn-group" role="group">
+                                <button class="btn btn-sm btn-outline-primary" onclick="edit('@user.id')">Edit</button>
+                            </div>
+                        </td>
+                    </tr>
+                }
+            }
+            else
+            {
+                <tr>
+                    <td colspan="9">No data</td>
+                </tr>
+            }
+        </tbody>
+    </table>
+</div>
+

+ 15 - 1
SuperAdmin/SuperAdmin/appsettings.json

@@ -5,7 +5,7 @@
   "subDomain": "",
   "subDomain_": "",
   "keyPost": "jqB3Vi1fIlu+9a2ODQs65w==",
-  "useVsa": "0",
+  "useVsa": "1",
   "vsaAppId": "LUMILOTO",
   "vsaWsUrl": "http://154.73.105.33:8080/passportv3/passportWS?wsdl",
   "channel": "WEB",
@@ -49,6 +49,13 @@
   "wsSvUnderUpdate": "http://127.0.0.1:8989/api/balance/gamUpdate/data",
   // report
   "wsReportByCampaign": "http://127.0.0.1:8989/api/balance/reportByCampaign/data",
+  "wsReportCountDailyGetList": "http://127.0.0.1:8989/api/systemApi/sysReportCountDailyGetList/data",
+  "wsReportErrorDailyGetList": "http://127.0.0.1:8989/api/systemApi/sysReportErrorDailyGetList/data",
+  "wsHourlyImpressionsGetList": "http://127.0.0.1:8989/api/systemApi/sysHourlyImpressionsGetList/data",
+  "wsDailyImpressionsGetList": "http://127.0.0.1:8989/api/systemApi/sysDailyImpressionsGetList/data",
+  "wsDailyUniqueImpressionsGetList": "http://127.0.0.1:8989/api/systemApi/sysDailyUniqueImpressionsGetList/data",
+  "wsDailyEngagedGetList": "http://127.0.0.1:8989/api/systemApi/sysDailyEngagedGetList/data",
+  "wsPushUssdDetailGetList": "http://127.0.0.1:8989/api/systemApi/sysPushUssdDetailGetList/data",
   //criteria
   "balanceGetList": "http://127.0.0.1:8989/api/balance/balanceGetList/data",
   "balanceInsert": "http://127.0.0.1:8989/api/balance/balanceInsert/data",
@@ -74,6 +81,13 @@
   "listSubFileInsert": "http://127.0.0.1:8989/api/balance/listSubFileInsert/data",
   "listSubFileUpdate": "http://127.0.0.1:8989/api/balance/listSubFileUpdate/data",
   "changeStatus": "http://127.0.0.1:8989/api/balance/changeStatus/data",
+  // User Management
+  "wsUserWebCmsGetList": "http://127.0.0.1:8989/api/systemApi/sysUserWebCmsGetList/data",
+  "wsUserWebCmsInsert": "http://127.0.0.1:8989/api/systemApi/sysUserWebCmsInsert/data",
+  "wsUserWebCmsUpdate": "http://127.0.0.1:8989/api/systemApi/sysUserWebCmsUpdate/data",
+  "wsFunctionWebCmsGetList": "http://127.0.0.1:8989/api/systemApi/sysFunctionWebCmsGetList/data",
+  "wsFunctionWebCmsInsert": "http://127.0.0.1:8989/api/systemApi/sysFunctionWebCmsInsert/data",
+  "wsFunctionWebCmsUpdate": "http://127.0.0.1:8989/api/systemApi/sysFunctionWebCmsUpdate/data",
 
   // real server
   "service_id": 1,

+ 49 - 117
SuperAdmin/SuperAdmin/wwwroot/css/login.css

@@ -1,20 +1,31 @@
 body {
-    background: #0a0a0a !important;
+    background: #ffffff !important;
     overflow: hidden;
 }
 
-/* Futuristic Background */
+/* Background with Image */
 .login-background {
     position: fixed;
     top: 0;
     left: 0;
     width: 100%;
     height: 100%;
-    background: radial-gradient(circle at 20% 50%, rgba(120, 119, 198, 0.3), transparent 50%),
-                radial-gradient(circle at 80% 80%, rgba(255, 119, 198, 0.3), transparent 50%),
-                radial-gradient(circle at 40% 20%, rgba(99, 215, 246, 0.2), transparent 50%);
-    background-size: 200% 200%;
-    animation: gradientShift 15s ease infinite;
+    /* background: #ffffff url('../images/backgrounds/bg-2.jpg') center center no-repeat; */
+    background: #ffffff url('../images/pexels-tirachard-kumtanom-112571-733852.jpg') center center no-repeat;
+    background-size: cover;
+    background-attachment: fixed;
+    z-index: 0;
+}
+
+/* Overlay để làm mờ ảnh nền một chút (tùy chọn) */
+.login-background::after {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(255, 255, 255, 0.3);
     z-index: 0;
 }
 
@@ -24,49 +35,11 @@
     left: 0;
     width: 100%;
     height: 100%;
-    background-image: 
-        linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
-        linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
-    background-size: 50px 50px;
+    background: transparent;
     z-index: 1;
     pointer-events: none;
 }
 
-.glowing-orb {
-    position: absolute;
-    border-radius: 50%;
-    filter: blur(60px);
-    animation: float 20s ease-in-out infinite;
-    z-index: 1;
-}
-
-.orb-1 {
-    width: 400px;
-    height: 400px;
-    background: rgba(120, 119, 198, 0.4);
-    top: 10%;
-    left: 10%;
-    animation-delay: 0s;
-}
-
-.orb-2 {
-    width: 300px;
-    height: 300px;
-    background: rgba(255, 119, 198, 0.3);
-    top: 60%;
-    right: 15%;
-    animation-delay: 5s;
-}
-
-.orb-3 {
-    width: 350px;
-    height: 350px;
-    background: rgba(99, 215, 246, 0.3);
-    bottom: 10%;
-    left: 50%;
-    animation-delay: 10s;
-}
-
 .login-container {
     position: relative;
     display: flex;
@@ -77,15 +50,20 @@
     padding: 20px;
 }
 
+/* Đảm bảo container nằm trên overlay */
+.login-container > * {
+    position: relative;
+    z-index: 1;
+}
+
 .login-form-card {
     width: 100%;
     max-width: 450px;
-    background: rgba(15, 23, 42, 0.8);
-    backdrop-filter: blur(20px);
-    border: 1px solid rgba(139, 92, 246, 0.3);
+    background: #f5f5f5;
+    border: 1px solid #e0e0e0;
     border-radius: 20px;
     box-shadow: 
-        0 20px 60px rgba(0, 0, 0, 0.5),
+        0 20px 60px rgba(0, 0, 0, 0.1),
         inset 0 1px 0 rgba(255, 255, 255, 0.1);
     overflow: hidden;
 }
@@ -99,34 +77,23 @@
     width: 80px;
     height: 80px;
     margin: 0 auto 20px;
-    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    background: #e0e0e0;
     border-radius: 50%;
     display: flex;
     align-items: center;
     justify-content: center;
-    box-shadow: 0 10px 30px rgba(102, 126, 234, 0.5);
-    position: relative;
+    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
 }
 
-.logo-circle::before {
-    content: '';
-    position: absolute;
-    inset: -2px;
-    border-radius: 50%;
-    background: linear-gradient(45deg, #ff0000, #ff7f00, #ffff00, #00ff00, #0000ff, #4b0082, #8b00ff);
-    background-size: 400%;
-    animation: rainbow 3s ease infinite;
-    z-index: -1;
+.logo-circle i {
+    color: #333 !important;
 }
 
 .login-title {
     font-size: 28px;
     font-weight: 800;
     letter-spacing: 3px;
-    background: linear-gradient(135deg, #ec4899 0%, #8b5cf6 100%);
-    -webkit-background-clip: text;
-    -webkit-text-fill-color: transparent;
-    background-clip: text;
+    color: #000000;
     margin-bottom: 8px;
 }
 
@@ -149,18 +116,17 @@
 .input-enhanced {
     width: 100%;
     padding: 15px 15px 15px 50px;
-    background: rgba(30, 41, 59, 0.5);
-    border: 1px solid rgba(139, 92, 246, 0.2);
+    background: #ffffff;
+    border: 1px solid #d0d0d0;
     border-radius: 12px;
-    color: #fff;
+    color: #000000;
     font-size: 14px;
-    transition: all 0.3s ease;
     font-family: inherit;
     box-sizing: border-box;
 }
 
 .input-enhanced::placeholder {
-    color: rgba(255, 255, 255, 0.4);
+    color: #999999;
     text-transform: uppercase;
     letter-spacing: 1px;
     font-size: 11px;
@@ -168,9 +134,9 @@
 
 .input-enhanced:focus {
     outline: none;
-    border-color: rgba(139, 92, 246, 0.6);
-    box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.1);
-    background: rgba(30, 41, 59, 0.7);
+    border-color: #999999;
+    box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.05);
+    background: #ffffff;
 }
 
 .input-icon {
@@ -178,7 +144,7 @@
     left: 18px;
     top: 50%;
     transform: translateY(-50%);
-    color: rgba(139, 92, 246, 0.7);
+    color: #666666;
     font-size: 16px;
     z-index: 1;
     pointer-events: none;
@@ -186,50 +152,31 @@
 
 .input-enhanced:focus ~ .input-icon,
 .input-enhanced:focus + .input-icon {
-    color: #8b5cf6;
-    text-shadow: 0 0 10px rgba(139, 92, 246, 0.5);
+    color: #333333;
 }
 
 .btn-login {
     width: 100%;
     padding: 16px;
-    background: linear-gradient(135deg, #8b5cf6 0%, #ec4899 100%);
+    background: #e0e0e0;
     border: none;
     border-radius: 12px;
-    color: #fff;
+    color: #000000;
     font-weight: 700;
     font-size: 14px;
     letter-spacing: 2px;
     text-transform: uppercase;
     cursor: pointer;
-    transition: all 0.3s ease;
-    box-shadow: 0 10px 30px rgba(139, 92, 246, 0.4);
-    position: relative;
-    overflow: hidden;
-}
-
-.btn-login::before {
-    content: '';
-    position: absolute;
-    top: 0;
-    left: -100%;
-    width: 100%;
-    height: 100%;
-    background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
-    transition: left 0.5s;
-}
-
-.btn-login:hover::before {
-    left: 100%;
+    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
 }
 
 .btn-login:hover {
-    transform: translateY(-3px);
-    box-shadow: 0 15px 40px rgba(139, 92, 246, 0.6);
+    background: #d0d0d0;
+    box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
 }
 
 .btn-login:active {
-    transform: translateY(-1px);
+    box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
 }
 
 .error-message {
@@ -240,27 +187,12 @@
     min-height: 20px;
 }
 
-@keyframes gradientShift {
-    0%, 100% { background-position: 0% 50%; }
-    50% { background-position: 100% 50%; }
-}
-
-@keyframes float {
-    0%, 100% { transform: translate(0, 0); }
-    33% { transform: translate(30px, -30px); }
-    66% { transform: translate(-20px, 20px); }
-}
-
-@keyframes rainbow {
-    0%, 100% { background-position: 0% 50%; }
-    50% { background-position: 100% 50%; }
-}
 
 .security-note {
     text-align: center;
     margin-top: 20px;
     padding-bottom: 30px;
     font-size: 11px;
-    color: rgba(255, 255, 255, 0.3);
+    color: #666666;
     letter-spacing: 1px;
 }

+ 19 - 15
SuperAdmin/SuperAdmin/wwwroot/css/menu-custom.css

@@ -48,8 +48,8 @@
 }
 
 .navigation-main .nav-link:hover .menu-title {
-    color: #fff;
-    text-shadow: 0 0 10px rgba(139, 92, 246, 0.5);
+    color: #fff !important;
+    text-shadow: none;
 }
 
 /* Active Menu Item */
@@ -69,7 +69,7 @@
 
 /* Menu Icons */
 .navigation-main .nav-link i {
-    color: rgba(139, 92, 246, 0.8);
+    color: #ffffff;
     margin-right: 12px;
     font-size: 18px;
     width: 18px; /* reserve space so icons don't collapse */
@@ -83,9 +83,9 @@
 .navigation-main .nav-link:hover i,
 .navigation-main .nav-item.active > .nav-link i,
 .navigation-main .nav-link.active i {
-    color: #8b5cf6 !important;
-    text-shadow: 0 0 15px rgba(139, 92, 246, 0.6);
-    transform: scale(1.1);
+    color: #ffffff !important;
+    text-shadow: none;
+    transform: none;
 }
 
 /* Menu Title */
@@ -94,15 +94,18 @@
     font-weight: 500;
     letter-spacing: 0.5px;
     transition: all 0.3s ease;
+    color: rgba(255, 255, 255, 0.7);
 }
 
 .navigation-main .nav-link:hover .menu-title,
 .navigation-main .nav-item.active > .nav-link .menu-title,
 .navigation-main .nav-link.active .menu-title {
-    background: linear-gradient(135deg, #ec4899 0%, #8b5cf6 100%);
-    -webkit-background-clip: text;
-    -webkit-text-fill-color: transparent;
-    background-clip: text;
+    color: #ffffff !important;
+    -webkit-text-fill-color: #ffffff;
+    background: none;
+    -webkit-background-clip: unset;
+    background-clip: unset;
+    text-shadow: none;
 }
 
 /* Sub-menu */
@@ -121,6 +124,7 @@
 .navigation-main .menu-item i {
     margin-right: 8px;
     font-size: 14px;
+    color: #ffffff;
     transition: all 0.3s ease;
 }
 
@@ -131,8 +135,8 @@
 }
 
 .navigation-main .menu-item:hover i {
-    color: #8b5cf6;
-    text-shadow: 0 0 10px rgba(139, 92, 246, 0.5);
+    color: #ffffff;
+    text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
 }
 
 /* Sub-menu Item */
@@ -146,14 +150,14 @@
 
 /* Has-sub menu arrow */
 .navigation-main .has-sub > a::after {
-    color: rgba(139, 92, 246, 0.6);
+    color: #ffffff;
     transition: all 0.3s ease;
 }
 
 .navigation-main .has-sub:hover > a::after,
 .navigation-main .has-sub.open > a::after {
-    color: #8b5cf6;
-    text-shadow: 0 0 10px rgba(139, 92, 246, 0.5);
+    color: #ffffff;
+    text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
 }
 
 /* Scrollbar customization */

+ 585 - 1
SuperAdmin/SuperAdmin/wwwroot/css/site.css

@@ -1,4 +1,7 @@
-/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
+body {
+    font-family: Muli, Georgia, 'Times New Roman', Times, serif;
+}
+/* Please see documentation at https://docs.microsoft.com/aspnet/core/client-side/bundling-and-minification
 for details on configuring this project to bundle and minify static web assets. */
 
 a.navbar-brand {
@@ -69,3 +72,584 @@ body {
   white-space: nowrap;
   line-height: 60px; /* Vertically center the text there */
 }
+
+/* Soft Button Styles - Làm mềm mại các nút bấm */
+.btn {
+  border-radius: 12px !important;
+  padding: 10px 20px !important;
+  font-weight: 500 !important;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
+  border: none !important;
+  position: relative;
+  overflow: hidden;
+}
+
+.btn:hover {
+  transform: translateY(-2px) !important;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
+}
+
+.btn:active {
+  transform: translateY(0) !important;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12) !important;
+}
+
+/* Primary Button - Màu cam mềm mại */
+.btn-primary {
+  background: linear-gradient(135deg, #FF6B35 0%, #FF8C42 100%) !important;
+  color: #fff !important;
+  box-shadow: 0 4px 12px rgba(255, 107, 53, 0.3) !important;
+}
+
+.btn-primary:hover {
+  background: linear-gradient(135deg, #FF8C42 0%, #FFA500 100%) !important;
+  box-shadow: 0 6px 16px rgba(255, 140, 66, 0.4) !important;
+}
+
+.btn-primary:active {
+  background: linear-gradient(135deg, #FF6B35 0%, #FF8C42 100%) !important;
+}
+
+/* Info Button - Màu xanh dương mềm */
+.btn-info {
+  background: linear-gradient(135deg, #4A90E2 0%, #5BA3F5 100%) !important;
+  color: #fff !important;
+  box-shadow: 0 4px 12px rgba(74, 144, 226, 0.3) !important;
+}
+
+.btn-info:hover {
+  background: linear-gradient(135deg, #5BA3F5 0%, #6BB6FF 100%) !important;
+  box-shadow: 0 6px 16px rgba(91, 163, 245, 0.4) !important;
+}
+
+/* Success Button - Màu xanh lá mềm */
+.btn-success {
+  background: linear-gradient(135deg, #52C41A 0%, #73D13D 100%) !important;
+  color: #fff !important;
+  box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3) !important;
+}
+
+.btn-success:hover {
+  background: linear-gradient(135deg, #73D13D 0%, #95DE64 100%) !important;
+  box-shadow: 0 6px 16px rgba(115, 209, 61, 0.4) !important;
+}
+
+/* Warning Button - Màu vàng cam mềm */
+.btn-warning {
+  background: linear-gradient(135deg, #FAAD14 0%, #FFC53D 100%) !important;
+  color: #fff !important;
+  box-shadow: 0 4px 12px rgba(250, 173, 20, 0.3) !important;
+}
+
+.btn-warning:hover {
+  background: linear-gradient(135deg, #FFC53D 0%, #FFD666 100%) !important;
+  box-shadow: 0 6px 16px rgba(255, 197, 61, 0.4) !important;
+}
+
+/* Danger Button - Màu đỏ mềm */
+.btn-danger {
+  background: linear-gradient(135deg, #FF4D4F 0%, #FF7875 100%) !important;
+  color: #fff !important;
+  box-shadow: 0 4px 12px rgba(255, 77, 79, 0.3) !important;
+}
+
+.btn-danger:hover {
+  background: linear-gradient(135deg, #FF7875 0%, #FF9C9E 100%) !important;
+  box-shadow: 0 6px 16px rgba(255, 120, 117, 0.4) !important;
+}
+
+/* Outline Buttons - Nút viền mềm mại */
+.btn-outline-primary {
+  background: transparent !important;
+  border: 2px solid #FF8C42 !important;
+  color: #FF8C42 !important;
+  box-shadow: 0 2px 8px rgba(255, 140, 66, 0.15) !important;
+}
+
+.btn-outline-primary:hover {
+  background: rgba(255, 140, 66, 0.1) !important;
+  border-color: #FFA500 !important;
+  color: #FFA500 !important;
+  box-shadow: 0 4px 12px rgba(255, 140, 66, 0.25) !important;
+}
+
+.btn-outline-success {
+  background: transparent !important;
+  border: 2px solid #73D13D !important;
+  color: #73D13D !important;
+  box-shadow: 0 2px 8px rgba(115, 209, 61, 0.15) !important;
+}
+
+.btn-outline-success:hover {
+  background: rgba(115, 209, 61, 0.1) !important;
+  border-color: #95DE64 !important;
+  color: #95DE64 !important;
+  box-shadow: 0 4px 12px rgba(115, 209, 61, 0.25) !important;
+}
+
+.btn-outline-danger {
+  background: transparent !important;
+  border: 2px solid #FF7875 !important;
+  color: #FF7875 !important;
+  box-shadow: 0 2px 8px rgba(255, 120, 117, 0.15) !important;
+}
+
+.btn-outline-danger:hover {
+  background: rgba(255, 120, 117, 0.1) !important;
+  border-color: #FF9C9E !important;
+  color: #FF9C9E !important;
+  box-shadow: 0 4px 12px rgba(255, 120, 117, 0.25) !important;
+}
+
+.btn-outline-secondary {
+  background: transparent !important;
+  border: 2px solid #8C8C8C !important;
+  color: #8C8C8C !important;
+  box-shadow: 0 2px 8px rgba(140, 140, 140, 0.15) !important;
+}
+
+.btn-outline-secondary:hover {
+  background: rgba(140, 140, 140, 0.1) !important;
+  border-color: #A6A6A6 !important;
+  color: #A6A6A6 !important;
+  box-shadow: 0 4px 12px rgba(140, 140, 140, 0.25) !important;
+}
+
+/* Grey Button */
+.btn.grey {
+  background: linear-gradient(135deg, #8C8C8C 0%, #A6A6A6 100%) !important;
+  color: #fff !important;
+  box-shadow: 0 4px 12px rgba(140, 140, 140, 0.3) !important;
+}
+
+.btn.grey:hover {
+  background: linear-gradient(135deg, #A6A6A6 0%, #BFBFBF 100%) !important;
+  box-shadow: 0 6px 16px rgba(166, 166, 166, 0.4) !important;
+}
+
+/* Icon spacing trong button */
+.btn i,
+.btn .fa {
+  margin-right: 6px;
+  transition: transform 0.3s ease;
+}
+
+.btn:hover i,
+.btn:hover .fa {
+  transform: scale(1.1);
+}
+
+/* Disabled state */
+.btn:disabled,
+.btn.disabled {
+  opacity: 0.6 !important;
+  cursor: not-allowed !important;
+  transform: none !important;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1) !important;
+}
+
+.btn:disabled:hover,
+.btn.disabled:hover {
+  transform: none !important;
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1) !important;
+}
+
+/* ============================================
+   ADMIN PAGES ENHANCEMENT - Cải thiện giao diện Admin
+   ============================================ */
+
+/* Content Header - Tiêu đề trang */
+.content-header {
+  margin-bottom: 2rem;
+  padding: 1.5rem 0;
+}
+
+.content-header-title {
+  font-size: 1.75rem;
+  font-weight: 600;
+  color: #2c3e50;
+  margin: 0;
+  letter-spacing: -0.5px;
+}
+
+.breadcrumb {
+  background: transparent;
+  padding: 0;
+  margin: 0;
+  font-size: 0.9rem;
+}
+
+.breadcrumb-item a {
+  color: #6c757d;
+  text-decoration: none;
+  transition: color 0.2s ease;
+}
+
+.breadcrumb-item a:hover {
+  color: #FF8C42;
+}
+
+.breadcrumb-item.active {
+  color: #495057;
+  font-weight: 500;
+}
+
+/* Cards - Cải thiện card styling */
+.card {
+  border: none !important;
+  border-radius: 16px !important;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08) !important;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
+  overflow: hidden;
+  margin-bottom: 1.5rem;
+}
+
+.card:hover {
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12) !important;
+  transform: translateY(-2px);
+}
+
+.card-header {
+  background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%) !important;
+  border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important;
+  padding: 1.25rem 1.5rem !important;
+  border-radius: 16px 16px 0 0 !important;
+}
+
+.card-title {
+  font-size: 1.1rem;
+  font-weight: 600;
+  color: #2c3e50;
+  margin: 0;
+}
+
+.card-body {
+  padding: 1.5rem !important;
+}
+
+.card-content {
+  border-radius: 16px;
+}
+
+/* Form Controls - Input, Select đẹp hơn */
+.form-control {
+  border-radius: 10px !important;
+  border: 1.5px solid #e0e0e0 !important;
+  padding: 0.65rem 1rem !important;
+  font-size: 0.95rem !important;
+  transition: all 0.3s ease !important;
+  background-color: #fff !important;
+}
+
+.form-control:focus {
+  border-color: #FF8C42 !important;
+  box-shadow: 0 0 0 3px rgba(255, 140, 66, 0.1) !important;
+  outline: none !important;
+}
+
+.form-control:hover:not(:focus) {
+  border-color: #d0d0d0 !important;
+}
+
+select.form-control {
+  cursor: pointer;
+  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23FF8C42' d='M6 9L1 4h10z'/%3E%3C/svg%3E");
+  background-repeat: no-repeat;
+  background-position: right 0.75rem center;
+  background-size: 12px;
+  padding-right: 2.5rem !important;
+  appearance: none;
+  -webkit-appearance: none;
+  -moz-appearance: none;
+}
+
+/* Labels */
+label {
+  font-weight: 500;
+  color: #495057;
+  margin-bottom: 0.5rem;
+  font-size: 0.9rem;
+}
+
+.form-group {
+  margin-bottom: 1.25rem;
+}
+
+/* Input Groups */
+.input-group {
+  position: relative;
+}
+
+.input-group .form-control {
+  border-radius: 10px !important;
+}
+
+/* Tables - Cải thiện table styling */
+.table {
+  border-collapse: separate;
+  border-spacing: 0;
+  width: 100%;
+}
+
+.table thead th {
+  background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+  color: #495057;
+  font-weight: 600;
+  font-size: 0.85rem;
+  text-transform: uppercase;
+  letter-spacing: 0.5px;
+  padding: 1rem !important;
+  border-bottom: 2px solid #e9ecef;
+  border-top: none;
+}
+
+.table tbody tr {
+  transition: all 0.2s ease;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.table tbody tr:hover {
+  background-color: #f8f9fa;
+  transform: scale(1.001);
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.table tbody td {
+  padding: 1rem !important;
+  vertical-align: middle;
+  color: #495057;
+  font-size: 0.9rem;
+}
+
+.table-striped tbody tr:nth-of-type(odd) {
+  background-color: rgba(248, 249, 250, 0.5);
+}
+
+.table-striped tbody tr:nth-of-type(odd):hover {
+  background-color: #f8f9fa;
+}
+
+/* Badges - Cải thiện badge styling */
+.badge {
+  padding: 0.4rem 0.75rem !important;
+  border-radius: 8px !important;
+  font-weight: 500 !important;
+  font-size: 0.8rem !important;
+  letter-spacing: 0.3px;
+}
+
+.badge-secondary {
+  background: linear-gradient(135deg, #6c757d 0%, #868e96 100%) !important;
+}
+
+.badge-info {
+  background: linear-gradient(135deg, #4A90E2 0%, #5BA3F5 100%) !important;
+}
+
+.badge-success {
+  background: linear-gradient(135deg, #52C41A 0%, #73D13D 100%) !important;
+}
+
+.badge-warning {
+  background: linear-gradient(135deg, #FAAD14 0%, #FFC53D 100%) !important;
+}
+
+.badge-danger {
+  background: linear-gradient(135deg, #FF4D4F 0%, #FF7875 100%) !important;
+}
+
+.badge-primary {
+  background: linear-gradient(135deg, #FF6B35 0%, #FF8C42 100%) !important;
+}
+
+/* Modal - Cải thiện modal styling */
+.modal-content {
+  border-radius: 16px !important;
+  border: none !important;
+  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2) !important;
+}
+
+.modal-header {
+  border-bottom: 1px solid rgba(0, 0, 0, 0.08) !important;
+  padding: 1.5rem !important;
+  border-radius: 16px 16px 0 0 !important;
+  background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+}
+
+.modal-title {
+  font-weight: 600;
+  color: #2c3e50;
+  font-size: 1.25rem;
+}
+
+.modal-body {
+  padding: 1.5rem !important;
+}
+
+.modal-footer {
+  border-top: 1px solid rgba(0, 0, 0, 0.08) !important;
+  padding: 1.25rem 1.5rem !important;
+  border-radius: 0 0 16px 16px !important;
+}
+
+/* Content Body - Spacing tốt hơn */
+.content-body {
+  padding: 1.5rem 0;
+}
+
+/* Pagination - Cải thiện pagination */
+.pagination {
+  margin-top: 1.5rem;
+}
+
+.page-link {
+  border-radius: 8px !important;
+  margin: 0 0.25rem;
+  border: 1.5px solid #e0e0e0 !important;
+  color: #495057;
+  padding: 0.5rem 0.75rem;
+  transition: all 0.2s ease;
+}
+
+.page-link:hover {
+  background-color: #FF8C42;
+  border-color: #FF8C42;
+  color: #fff;
+  transform: translateY(-2px);
+  box-shadow: 0 2px 8px rgba(255, 140, 66, 0.3);
+}
+
+.page-item.active .page-link {
+  background: linear-gradient(135deg, #FF6B35 0%, #FF8C42 100%);
+  border-color: #FF8C42;
+  box-shadow: 0 2px 8px rgba(255, 140, 66, 0.3);
+}
+
+/* Alerts - Cải thiện alert styling */
+.alert {
+  border-radius: 12px !important;
+  border: none !important;
+  padding: 1rem 1.25rem !important;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+
+.alert-success {
+  background: linear-gradient(135deg, rgba(82, 196, 26, 0.1) 0%, rgba(115, 209, 61, 0.1) 100%);
+  color: #52C41A;
+  border-left: 4px solid #52C41A;
+}
+
+.alert-danger {
+  background: linear-gradient(135deg, rgba(255, 77, 79, 0.1) 0%, rgba(255, 120, 117, 0.1) 100%);
+  color: #FF4D4F;
+  border-left: 4px solid #FF4D4F;
+}
+
+.alert-warning {
+  background: linear-gradient(135deg, rgba(250, 173, 20, 0.1) 0%, rgba(255, 197, 61, 0.1) 100%);
+  color: #FAAD14;
+  border-left: 4px solid #FAAD14;
+}
+
+.alert-info {
+  background: linear-gradient(135deg, rgba(74, 144, 226, 0.1) 0%, rgba(91, 163, 245, 0.1) 100%);
+  color: #4A90E2;
+  border-left: 4px solid #4A90E2;
+}
+
+/* Links - Cải thiện link styling */
+a {
+  color: #FF8C42;
+  text-decoration: none;
+  transition: color 0.2s ease;
+}
+
+a:hover {
+  color: #FF6B35;
+  text-decoration: none;
+}
+
+/* Icons trong buttons và links */
+.btn i,
+.btn .fa,
+a i,
+a .fa {
+  transition: transform 0.2s ease;
+}
+
+.btn:hover i,
+.btn:hover .fa,
+a:hover i,
+a:hover .fa {
+  transform: scale(1.1);
+}
+
+/* Heading Elements trong Card Header */
+.heading-elements a {
+  color: #6c757d;
+  padding: 0.5rem;
+  border-radius: 8px;
+  transition: all 0.2s ease;
+}
+
+.heading-elements a:hover {
+  background-color: rgba(255, 140, 66, 0.1);
+  color: #FF8C42;
+}
+
+/* DataTables - Cải thiện DataTable styling */
+.dataTables_wrapper {
+  margin-top: 1rem;
+}
+
+.dataTables_filter input {
+  border-radius: 8px;
+  border: 1.5px solid #e0e0e0;
+  padding: 0.5rem 0.75rem;
+  transition: all 0.3s ease;
+}
+
+.dataTables_filter input:focus {
+  border-color: #FF8C42;
+  box-shadow: 0 0 0 3px rgba(255, 140, 66, 0.1);
+  outline: none;
+}
+
+.dataTables_length select {
+  border-radius: 8px;
+  border: 1.5px solid #e0e0e0;
+  padding: 0.4rem 2rem 0.4rem 0.75rem;
+}
+
+/* Smooth scrolling */
+html {
+  scroll-behavior: smooth;
+}
+
+/* Loading spinner improvements */
+.spinner-border {
+  border-width: 3px;
+}
+
+/* Text colors */
+.text-primary {
+  color: #FF8C42 !important;
+}
+
+.text-muted {
+  color: #6c757d !important;
+}
+
+/* Utility classes */
+.shadow-soft {
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08) !important;
+}
+
+.rounded-lg {
+  border-radius: 12px !important;
+}
+
+.rounded-xl {
+  border-radius: 16px !important;
+}

BIN
SuperAdmin/SuperAdmin/wwwroot/images/pexels-tirachard-kumtanom-112571-733852.jpg