LamGiang 1 hónapja
szülő
commit
fc32d90d46
31 módosított fájl, 763 hozzáadás és 216 törlés
  1. 6 0
      ApiWeb/ApiProcessToken/ApiProcessToken.sln
  2. 14 4
      ApiWeb/ApiProcessToken/Controllers/balance.cs
  3. 1 0
      ApiWeb/ApiProcessToken/Controllers/systemApi.cs
  4. 12 2
      ApiWeb/ApiProcessToken/Models/banlance/balanceDataAccess.cs
  5. 0 2
      ApiWeb/ApiProcessToken/Models/banlance/campaignObj.cs
  6. 2 0
      ApiWeb/ApiProcessToken/Models/reportCountDailyObj.cs
  7. 2 2
      ApiWeb/ApiProcessToken/Models/systemDataAccess.cs
  8. 72 0
      ApiWeb/ApiProcessToken/Process/AdminUserCheckService.cs
  9. 2 0
      ApiWeb/ApiProcessToken/Startup.cs
  10. 6 6
      ApiWeb/ApiProcessToken/appsettings.json
  11. 133 37
      SuperAdmin/SuperAdmin/Controllers/AdminController.cs
  12. 1 1
      SuperAdmin/SuperAdmin/Controllers/BaseController.cs
  13. 4 1
      SuperAdmin/SuperAdmin/Controllers/HomeController.cs
  14. 1 0
      SuperAdmin/SuperAdmin/Models/Http/ReportCountDaily.cs
  15. 2 4
      SuperAdmin/SuperAdmin/Views/Admin/ApiWebserviceManagement.cshtml
  16. 103 3
      SuperAdmin/SuperAdmin/Views/Admin/CampaignManagement.cshtml
  17. 3 18
      SuperAdmin/SuperAdmin/Views/Admin/DashboardReport.cshtml
  18. 26 6
      SuperAdmin/SuperAdmin/Views/Admin/ReportUssdDetail.cshtml
  19. 14 5
      SuperAdmin/SuperAdmin/Views/Admin/ServiceManagement.cshtml
  20. 1 1
      SuperAdmin/SuperAdmin/Views/File/BlacklistManagement.cshtml
  21. 96 6
      SuperAdmin/SuperAdmin/Views/Home/Login.cshtml
  22. 5 13
      SuperAdmin/SuperAdmin/Views/Partial/_Campaign.cshtml
  23. 3 3
      SuperAdmin/SuperAdmin/Views/Partial/_CampaignCriteria.cshtml
  24. 10 33
      SuperAdmin/SuperAdmin/Views/Partial/_CampaignSchedulerCalendar.cshtml
  25. 92 16
      SuperAdmin/SuperAdmin/Views/Partial/_CampaignService.cshtml
  26. 1 1
      SuperAdmin/SuperAdmin/Views/Partial/_ListCampaign.cshtml
  27. 2 0
      SuperAdmin/SuperAdmin/Views/Partial/_ReportUssdDetail.cshtml
  28. 1 1
      SuperAdmin/SuperAdmin/Views/Shared/_Layout.cshtml
  29. 1 1
      SuperAdmin/SuperAdmin/appsettings.json
  30. 29 12
      SuperAdmin/SuperAdmin/wwwroot/css/login.css
  31. 118 38
      SuperAdmin/SuperAdmin/wwwroot/css/menu-custom.css

+ 6 - 0
ApiWeb/ApiProcessToken/ApiProcessToken.sln

@@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiProcessToken", "ApiProce
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonObj", "..\CommonObj\CommonObj.csproj", "{95419B0D-C91E-48E2-8BDA-B774E2785F1D}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConvertLog", "..\..\ConvertLog\ConvertLog.csproj", "{F6BF0945-01BC-76A7-6206-A2199E964AAF}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
 		{95419B0D-C91E-48E2-8BDA-B774E2785F1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{95419B0D-C91E-48E2-8BDA-B774E2785F1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{95419B0D-C91E-48E2-8BDA-B774E2785F1D}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F6BF0945-01BC-76A7-6206-A2199E964AAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F6BF0945-01BC-76A7-6206-A2199E964AAF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F6BF0945-01BC-76A7-6206-A2199E964AAF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F6BF0945-01BC-76A7-6206-A2199E964AAF}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 14 - 4
ApiWeb/ApiProcessToken/Controllers/balance.cs

@@ -65,10 +65,12 @@ namespace ApiProcess.Controllers
                 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-----------------------------
+                logger.Debug("steep 1");
                 redisConnection _redis;
                 memoryCache.TryGetValue("redis", out _redis);
                 if (_redis == null)
                 {
+                    logger.Debug("steep 1.1");
                     var cacheExpiryOptions = new MemoryCacheEntryOptions
                     {
                         AbsoluteExpiration = DateTime.Now.AddMonths(12),
@@ -82,8 +84,10 @@ namespace ApiProcess.Controllers
                 }
                 if (!_redis.isConnet())
                 {
+                    logger.Debug("!_redis.connet()");
                     if (!_redis.connet())
                     {
+                        logger.Debug("steep 2:!_redis.connet()");
                         logger.Info("Connect to redis false");
                         response.status = "-2";
                         response.message = "System Update";
@@ -92,6 +96,7 @@ namespace ApiProcess.Controllers
                     }
                     else
                     {
+                        logger.Debug("steep 3 created:!_redis.connet()");
                         var cacheExpiryOptions = new MemoryCacheEntryOptions
                         {
                             AbsoluteExpiration = DateTime.Now.AddMonths(12),
@@ -115,9 +120,7 @@ namespace ApiProcess.Controllers
                 }
 
 
-
-
-
+                logger.Debug("steep 2");
                 DataSet ds_regist = balanceDataAccess.ADMIN_USERS_INFO(users, ResfullApi.Models.CustomEncryption.Encrypt(pass));
                 logger.Info("Call database ADMIN_USERS_INFO success:");
 
@@ -8861,9 +8864,16 @@ namespace ApiProcess.Controllers
                     response.responseCode = "-2";
                     response.responseMessage = "System upgrade";
                 }
+                if (endDate < startDate)
+                {
+                    var temp = startDate;
+                    startDate = endDate;
+                    endDate = temp;
+                }
+
                 TimeSpan ts = endDate - startDate;
 
-                int totalDay = ((int)ts.TotalDays);
+                int totalDay = ((int)ts.TotalDays) + 1;
                 List<campaignCalebdarObj> _listCalendar=new List<campaignCalebdarObj>();
                 for(int i=0;i<totalDay;i++)
                 {

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

@@ -1756,6 +1756,7 @@ namespace ApiProcess.Controllers
                         _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") : "";
+                        _obj.msgContent = dataTable.Rows[j].Table.Columns.Contains("MSG_CONTENT") && dataTable.Rows[j]["MSG_CONTENT"] != DBNull.Value ? dataTable.Rows[j]["MSG_CONTENT"].ToString() : "";
                         response.list[j] = _obj;
                     }
                 }

+ 12 - 2
ApiWeb/ApiProcessToken/Models/banlance/balanceDataAccess.cs

@@ -2354,7 +2354,19 @@ namespace ResfullApi.Models.balance
         }
 
 
+        public static DataSet callCheckDB()
+        {
+            string str;
+            str = "";
+            str = "BALANCE_PKG.ADMIN_CHECKDB";
+            OracleParameter[] parms;
+            parms = new OracleParameter[]
+                            {
+                                new OracleParameter("P_RESULT",OracleDbType.RefCursor,ParameterDirection.Output),
+            };
 
+            return DataAccess.getDataFromProcedure(str, "", parms);
+        }
 
 
 
@@ -2711,8 +2723,6 @@ namespace ResfullApi.Models.balance
             parms[1].Value = V_USERS;
             
 
-
-
             return DataAccess.getDataFromProcedure(str, "", parms);
         }
 

+ 0 - 2
ApiWeb/ApiProcessToken/Models/banlance/campaignObj.cs

@@ -83,8 +83,6 @@ namespace ApiProcess.Models.balance
         public string note { get; set; }
 
 
-
-
         [JsonProperty("usersCreated")]
 
         public string usersCreated { get; set; }

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

@@ -208,6 +208,8 @@ namespace ResfullApi.Models
         public string insertTime { get; set; }
         [JsonProperty("lastUpdate")]
         public string lastUpdate { get; set; }
+        [JsonProperty("msgContent")]
+        public string msgContent { get; set; }
     }
 
     public class pushUssdDetailObjList : Response

+ 2 - 2
ApiWeb/ApiProcessToken/Models/systemDataAccess.cs

@@ -46,9 +46,9 @@ namespace ResfullApi.Models
 
                 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
+                                          PUD.IS_STEP_2, PUD.STEP_2_TIME, PUD.ERROR_CODE, PUD.IS_SUCCESS, PUD.INSERT_TIME, PUD.LAST_UPDATE,PUD.MSG_CONTENT
                                    FROM PUSH_USSD_DETAIL PUD
-                                        LEFT JOIN CAMPAIGN CAMP ON PUD.CAMPAIGN_ID = CAMP.ID
+                                        LEFT JOIN B_CAMPAIGN CAMP ON PUD.CAMPAIGN_ID = CAMP.ID
                                    WHERE 1=1";
 
                 string where = "";

+ 72 - 0
ApiWeb/ApiProcessToken/Process/AdminUserCheckService.cs

@@ -0,0 +1,72 @@
+using System;
+using System.Data;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Hosting;
+using ResfullApi.Models;
+using ResfullApi.Models.balance;
+using log4net;
+
+namespace ApiProcess.Process
+{
+    public class AdminUserCheckService : BackgroundService
+    {
+        private static readonly ILog logger = LogManager.GetLogger(typeof(AdminUserCheckService));
+        private const int INTERVAL_MINUTES = 1;
+
+        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            logger.Info($"AdminUserCheckService started. Will check every {INTERVAL_MINUTES} minutes.");
+
+            while (!stoppingToken.IsCancellationRequested)
+            {
+                try
+                {
+                    bool result = CheckAdminUser();
+                    logger.Info($"AdminUserCheckService: Check result = {result}");
+
+                    // Wait for 10 minutes before next check
+                    await Task.Delay(TimeSpan.FromMinutes(INTERVAL_MINUTES), stoppingToken);
+                }
+                catch (OperationCanceledException)
+                {
+                    logger.Info("AdminUserCheckService: Service is stopping.");
+                    break;
+                }
+                catch (Exception ex)
+                {
+                    logger.Error($"AdminUserCheckService: Error occurred: {ex.ToString()}");
+                    // Wait 1 minute before retrying on error
+                    await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
+                }
+            }
+
+            logger.Info("AdminUserCheckService: Service stopped.");
+        }
+
+        private bool CheckAdminUser()
+        {
+            try
+            {
+                DataSet ds = balanceDataAccess.callCheckDB();
+
+                // Check if DataSet is valid and has data
+                if (ds == null || ds.Tables.Count == 0 || ds.Tables[0].Rows.Count == 0)
+                {
+                    logger.Info("AdminUserCheckService: No data returned. Result = false");
+                    return false;
+                }
+
+                // If we have data, consider it as success (true)
+                logger.Info("AdminUserCheckService: Data returned successfully. Result = true");
+                return true;
+            }
+            catch (Exception ex)
+            {
+                logger.Error($"AdminUserCheckService: Exception in CheckAdminUser: {ex.ToString()}");
+                return false;
+            }
+        }
+    }
+}
+

+ 2 - 0
ApiWeb/ApiProcessToken/Startup.cs

@@ -69,6 +69,8 @@ namespace ApiProcess
             //services.AddDbContext<ModelContext>(options => options.UseOracle(conn));
             Variable.LoadConfigData();
 
+            // Register background service for admin user check
+            services.AddHostedService<AdminUserCheckService>();
 
         }
 

+ 6 - 6
ApiWeb/ApiProcessToken/appsettings.json

@@ -8,7 +8,7 @@
     "isCheckToken": "1",
     //"Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=171.244.50.177)(PORT=1530))(CONNECT_DATA=(SERVICE_NAME=orcl)));User Id=LOTO_THAILAN;Password=loto098;Connection Timeout=120;",
     "Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1539))(CONNECT_DATA=(SERVICE_NAME=ORA12C)));User Id=MYTEL_BLANCE;Password=123456;Connection Timeout=120;",
-    //"Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=10.225.5.163)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=vasnew)));User Id=luckycard;Password=V@sLuckY13#$68;Connection Timeout=120; Connection Lifetime=180;Pooling=true;Min Pool Size=1;Max Pool Size=100;Incr Pool Size=10;Validate Connection=true",
+    //"Connection": "Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=127.0.0.1)(PORT=1561))(CONNECT_DATA=(SERVICE_NAME=REP_DB_DG)));User Id=bplus_app;Password=BbQ09grnqF1CM0ob;Connection Timeout=120; Connection Lifetime=180;Pooling=true;Min Pool Size=1;Max Pool Size=100;Incr Pool Size=10;Validate Connection=true",
     "urlPostGetErrCodeMessage": "http://localhost:41341/api/luckyGame/executes/data",
     "keyPostGetErrCodeMessage": "jqB3Vi1fIlu+9a2ODQs65w==",
     "betProcessListPower": "127.0.0.1:8017:60000$127.0.0.1:8018:60000$127.0.0.1:8019:60000",
@@ -20,14 +20,14 @@
     "chargeLogPath": "D:\\Code\\Telemor\\TimorBplus\\ApiWeb\\ApiProcessToken\\logChargeErr\\",
 
     //for B+
-    /*
-    "RedisIp": "27.71.225.61",
-    "RedisPort": "6379",
-    "RedisPass": "p@ss$12E45"
-    */
+    //"RedisIp": "127.0.0.1",
+    //"RedisPort": "9379",
+    //"RedisPass": "p@ss$12E45",
+
     "RedisIp": "171.244.50.177",
     "RedisPort": "9379",
     "RedisPass": "p@ss$12E45",
+
     "MPS_IP": "171.244.50.177",
 
     "MPS_PORT": "6018",

+ 133 - 37
SuperAdmin/SuperAdmin/Controllers/AdminController.cs

@@ -55,10 +55,10 @@ namespace SuperAdmin.Controllers
             req.rowsOnPage = req.rowsOnPage ?? "100";
             req.seqPage = req.seqPage ?? "1";
             req.order = req.order ?? "desc";
-
+            req.msisdn =  ValidateMsisdn(req.msisdn);
             String url = GetParameter(CommonUtils.WsType.PushUssdDetailGetList);
             PushUssdDetailRes res = PushUssdDetailRes.Parse(SendPost(req, url));
-
+            var fdsds = "dsds";
             return PartialView("../Partial/_ReportUssdDetail", res.list);
         }
 
@@ -70,7 +70,10 @@ namespace SuperAdmin.Controllers
                 return null;
             }
 
-            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
+            // Áp dụng cùng logic phân trang/thứ tự như ReportUssdDetailSearch để dữ liệu export khớp với search
+            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));
@@ -86,8 +89,7 @@ namespace SuperAdmin.Controllers
                     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 = 30; worksheet.Cell(1, i++).Value = "Campaign";
                     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";
@@ -100,6 +102,7 @@ namespace SuperAdmin.Controllers
                     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.Column(i).Width = 50; worksheet.Cell(1, i++).Value = "Msg Content";
                     worksheet.Row(1).Style.Font.Bold = true;
                     worksheet.Row(1).Style.Fill.BackgroundColor = XLColor.Yellow;
 
@@ -110,21 +113,27 @@ namespace SuperAdmin.Controllers
                             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;
+                            worksheet.Cell(index + 1, i++).Value = obj.requestId ?? "";
+                            // Campaign: ID + Name (giống view)
+                            string campaignDisplay = obj.campaignId ?? "";
+                            if (!string.IsNullOrEmpty(obj.campaignName))
+                            {
+                                campaignDisplay += " - " + obj.campaignName;
+                            }
+                            worksheet.Cell(index + 1, i++).Value = campaignDisplay;
+                            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 ?? "";
+                            worksheet.Cell(index + 1, i++).Value = obj.msgContent ?? "";
                         }
                     }
                     using (var stream = new MemoryStream())
@@ -191,6 +200,11 @@ namespace SuperAdmin.Controllers
         {
 
             DashboardModel model = LoadDashboard(fromDate, toDate);
+            var listService = HttpContext.Session.GetComplexData<List<Service>>("listService");
+            if (listService == null)
+            {
+                PreLoadService();
+            }
             //
             return PartialView("../Partial/_Dashboard", model);
         }
@@ -287,6 +301,7 @@ namespace SuperAdmin.Controllers
             req2.subType = CommonUtils.ListSubType.Whitelist;
             GetListSubRes res4 = GetListSubRes.Parse(SendPost(req, GetParameter(CommonUtils.WsType.listSubGetList)));
             HttpContext.Session.SetComplexData("listSubFile", res4.list);
+            PreLoadService();
             return View();
         }
 
@@ -327,7 +342,6 @@ namespace SuperAdmin.Controllers
                 return null;
             }
 
-            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
 
             String url = GetParameter(CommonUtils.WsType.CamGetList);
             CampaignGetListRes res = CampaignGetListRes.Parse(SendPost(req, url));
@@ -753,6 +767,77 @@ namespace SuperAdmin.Controllers
             return PartialView("../Partial/_ApiWebserviceManagement", res.list);
         }
 
+        [HttpPost]
+        public IActionResult ApiServiceExport(ApiServiceReq req)
+        {
+            if (!CheckAuthToken())
+            {
+                return null;
+            }
+
+
+            // Ensure same paging/order behavior as search
+            req.rowsOnPage = "1000000";
+            req.seqPage = "1";
+            req.order = "desc";
+
+            string url = GetParameter(CommonUtils.WsType.ApiServiceLoad);
+            ApiServiceRes res = ApiServiceRes.Parse(SendPost(req, url));
+            var list = res.list ?? new List<ApiService>();
+
+            string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+            string fileName = "api_services.xlsx";
+            try
+            {
+                using (var workbook = new XLWorkbook())
+                {
+                    int i = 1;
+                    IXLWorksheet worksheet = workbook.Worksheets.Add("API Services");
+                    worksheet.Column(i).Width = 10; worksheet.Cell(1, i++).Value = "No.";
+                    worksheet.Column(i).Width = 10; worksheet.Cell(1, i++).Value = "ID";
+                    worksheet.Column(i).Width = 30; worksheet.Cell(1, i++).Value = "ws_name";
+                    worksheet.Column(i).Width = 20; worksheet.Cell(1, i++).Value = "ws_code";
+                    worksheet.Column(i).Width = 50; worksheet.Cell(1, i++).Value = "wsdl";
+                    worksheet.Column(i).Width = 40; worksheet.Cell(1, i++).Value = "msg_template";
+                    worksheet.Column(i).Width = 20; worksheet.Cell(1, i++).Value = "error_tag";
+                    worksheet.Column(i).Width = 20; worksheet.Cell(1, i++).Value = "success_code";
+                    worksheet.Column(i).Width = 12; worksheet.Cell(1, i++).Value = "Status";
+
+                    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.ws_id;
+                            worksheet.Cell(index + 1, i++).Value = obj.ws_name;
+                            worksheet.Cell(index + 1, i++).Value = obj.ws_code;
+                            worksheet.Cell(index + 1, i++).Value = obj.wsdl;
+                            worksheet.Cell(index + 1, i++).Value = obj.msg_template;
+                            worksheet.Cell(index + 1, i++).Value = obj.error_tag;
+                            worksheet.Cell(index + 1, i++).Value = obj.success_code;
+                            worksheet.Cell(index + 1, i++).Value = obj.isActive == "1" ? "Active" : "Inactive";
+                        }
+                    }
+
+                    using (var stream = new MemoryStream())
+                    {
+                        workbook.SaveAs(stream);
+                        var content = stream.ToArray();
+                        return File(content, contentType, fileName);
+                    }
+                }
+            }
+            catch (Exception)
+            {
+                return null;
+            }
+        }
+
 
         [HttpPost]
         public JsonResult ApiServiceLoadInfo(ApiServiceReq request)
@@ -853,7 +938,7 @@ namespace SuperAdmin.Controllers
 
             String url = GetParameter(CommonUtils.WsType.SvGetList);
             ServiceGetListRes res = ServiceGetListRes.Parse(SendPost(req, url));
-
+            HttpContext.Session.SetComplexData("listService", res.list);
             return PartialView("../Partial/_Services", res.list);
         }
 
@@ -1105,16 +1190,26 @@ namespace SuperAdmin.Controllers
                 return null;
             }
 
-            ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
-
-            String url = GetParameter(CommonUtils.WsType.SvGetList);
-            ServiceGetListRes res = ServiceGetListRes.Parse(SendPost(req, url));
-            var list = res.list;
-
-            string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
-            string fileName = "services.xlsx";
             try
             {
+                // Initialize req if null
+                if (req == null)
+                {
+                    req = new ServiceGetListReq();
+                }
+
+                // Set pagination to get all records, same as ServiceSearch
+                req.rowsOnPage = "1000000";
+                req.seqPage = "1";
+                req.order = "desc";
+
+                String url = GetParameter(CommonUtils.WsType.SvGetList);
+                ServiceGetListRes res = ServiceGetListRes.Parse(SendPost(req, url));
+                var list = res.list ?? new List<Service>();
+
+                string contentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
+                string fileName = "services.xlsx";
+                
                 using (var workbook = new XLWorkbook())
                 {
                     int i = 1;
@@ -1146,13 +1241,13 @@ namespace SuperAdmin.Controllers
                             i = 1;
                             var obj = list[index - 1];
                             worksheet.Cell(index + 1, i++).Value = index;
-                            worksheet.Cell(index + 1, i++).Value = obj.name;
-                            worksheet.Cell(index + 1, i++).Value = obj.shortCode;
-                            worksheet.Cell(index + 1, i++).Value = obj.command;
-                            worksheet.Cell(index + 1, i++).Value = obj.description;
-                            worksheet.Cell(index + 1, i++).Value = obj.dateCreated;
-                            worksheet.Cell(index + 1, i++).Value = obj.msgRegisterSuccess;
-                            worksheet.Cell(index + 1, i++).Value = obj.msgRegisterFlase;
+                            worksheet.Cell(index + 1, i++).Value = obj.name ?? "";
+                            worksheet.Cell(index + 1, i++).Value = obj.shortCode ?? "";
+                            worksheet.Cell(index + 1, i++).Value = obj.command ?? "";
+                            worksheet.Cell(index + 1, i++).Value = obj.description ?? "";
+                            worksheet.Cell(index + 1, i++).Value = obj.dateCreated ?? "";
+                            worksheet.Cell(index + 1, i++).Value = obj.msgRegisterSuccess ?? "";
+                            worksheet.Cell(index + 1, i++).Value = obj.msgRegisterFlase ?? "";
                         }
                     }
                     using (var stream = new MemoryStream())
@@ -1165,6 +1260,7 @@ namespace SuperAdmin.Controllers
             }
             catch (Exception ex)
             {
+                log.Error("Error in ServiceExport: " + ex.Message, ex);
                 return null;
             }
         }

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

@@ -36,7 +36,7 @@ namespace SuperAdmin.Controllers
         public static String RoleAdminCC = "admin_cc_loto";
         public static String RoleStaffSale = "staff_sale_loto";
         public static String NumberSeparated = ".";
-        public static String CountryCode = "509";
+        public static String CountryCode = "95";
         public static String ServiceId = "1";
 
         public BaseController() { }

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

@@ -76,8 +76,10 @@ namespace SuperAdmin.Controllers
 
             //var fullUrl = this.Url.Action(name, module.Substring(0, module.Length - 10), null, null);
 
-            if (account == null || password == null)
+            // Validate required fields
+            if (string.IsNullOrWhiteSpace(account) || string.IsNullOrWhiteSpace(password))
             {
+                TempData["ErrorMessage"] = "Please enter full account and password";
                 return Redirect(GetParameter(UtilsController.Constant.SUB_DOMAIN) + "/Home/Login");
             }
             //// vsa
@@ -107,6 +109,7 @@ namespace SuperAdmin.Controllers
             }
             else
             {
+                TempData["ErrorMessage"] = res.message;
                 return Redirect(GetParameter(UtilsController.Constant.SUB_DOMAIN) + "/Home/Login");
             }
         }

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

@@ -174,6 +174,7 @@ namespace SuperAdmin.Models.Http
         public string isSuccess { get; set; }
         public string insertTime { get; set; }
         public string lastUpdate { get; set; }
+        public string msgContent { get; set; }
     }
 
     public class PushUssdDetailRes

+ 2 - 4
SuperAdmin/SuperAdmin/Views/Admin/ApiWebserviceManagement.cshtml

@@ -369,11 +369,9 @@
             console.log("Export data");
             startSpinner('btnExportExcel');
             $.ajax({
-                url: urlConfig("/Admin/ServiceExport"),
+                url: urlConfig("/Admin/ApiServiceExport"),
                 data: {
-                    fromDate: $("#fromDateSearch").val(),
-                    toDate: $("#toDateSearch").val(),
-                    status: $("#statusSearch").val()
+                    isActive: $("#statusSearch").val()
                 },
                 type: "POST",
                 xhr: function () {

+ 103 - 3
SuperAdmin/SuperAdmin/Views/Admin/CampaignManagement.cshtml

@@ -403,6 +403,10 @@
                 var index = $(this).attr('id').split('-')[1];
                 toggleServiceFieldsByType(serviceType, index);
             });
+
+            if (serviceType === "1") {
+                syncTitleWithFirstUssd();
+            }
         }
 
         function toggleServiceFieldsByType(serviceType, index) {
@@ -505,7 +509,27 @@
             dropdownList.toggle();
         }
 
-        function selectServiceOption(index, serviceId, ussd1, ussd2, displayText) {
+        function normalizePartnerValue(partner) {
+            if (partner === undefined || partner === null) {
+                return "";
+            }
+
+            var normalized = partner.toString().trim().toLowerCase();
+            if (normalized === "") {
+                return "";
+            }
+
+            if (normalized === "viettech" || normalized === "1") {
+                return "1";
+            }
+            if (normalized === "mytel" || normalized === "0") {
+                return "0";
+            }
+
+            return partner.toString().trim();
+        }
+
+        function selectServiceOption(index, serviceId, ussd1, ussd2, displayText, partner, serviceGroupId) {
             // Set the input value to display text
             $("#serviceSearch-" + index).val(displayText);
             
@@ -518,8 +542,29 @@
             // Auto-fill both USSD Teaser 1 and 2 when selecting a service
             $("#ussdDisplay-" + index).val(ussd1);
             $("#msgConfirm-" + index).val(ussd2);
+
+            var servicePartner = normalizePartnerValue(partner);
+            var partnerValue = servicePartner !== "" ? servicePartner : normalizePartnerValue($("#my_service").val());
+            if (partnerValue === "") {
+                partnerValue = "0";
+            }
+            $("#my_service").val(partnerValue);
+            $("#partner-" + index).val(partnerValue);
+            
+            // Only set Service Group if user hasn't selected one yet (preserve user's selection)
+            var currentServiceGroup = $("#serviceGroup-" + index).val();
+            if (currentServiceGroup === "" || currentServiceGroup === null || currentServiceGroup === undefined) {
+                // User hasn't selected a group, so set it from the service's group
+                if (serviceGroupId !== undefined && serviceGroupId !== null && serviceGroupId !== "") {
+                    $("#serviceGroup-" + index).val(serviceGroupId);
+                }
+            }
+            // If user already selected a Service Group, keep it unchanged
             
             console.log("Selected service " + serviceId + " for index " + index);
+
+            syncTitleWithFirstUssd();
+            syncCampaignPartner();
         }
 
         // Close dropdown when clicking outside
@@ -534,14 +579,63 @@
             
             // Find the selected service data from dropdown items
             let selectedItem = $("#serviceList-" + i).find(".dropdown-item[data-value='" + svId + "']");
+            if (selectedItem.length === 0) {
+                return;
+            }
             let ussd1 = selectedItem.attr("data-ussd1");
             let ussd2 = selectedItem.attr("data-ussd2");
+            let partner = selectedItem.attr("data-partner");
             
             $("#ussdDisplay-" + i).val(ussd1);
             
             // Always auto-fill USSD Teaser 2 when selecting a service
             // The readonly/editable state is controlled by toggleServiceFieldsByType
             $("#msgConfirm-" + i).val(ussd2);
+
+            var servicePartner = normalizePartnerValue(partner);
+            var partnerValue = servicePartner !== "" ? servicePartner : normalizePartnerValue($("#my_service").val());
+            if (partnerValue === "") {
+                partnerValue = "0";
+            }
+            $("#my_service").val(partnerValue);
+            $("#partner-" + i).val(partnerValue);
+
+            syncTitleWithFirstUssd();
+            syncCampaignPartner();
+        }
+
+        function syncTitleWithFirstUssd() {
+            if ($("#addType").val() !== "1") {
+                return;
+            }
+
+            var firstUssdInput = $("input[id^='ussdDisplay-']").first();
+            if (firstUssdInput.length) {
+                var ussdValue = firstUssdInput.val() || "";
+                $("#title").val(ussdValue);
+                updateTotalChar();
+            }
+        }
+
+        function syncCampaignPartner() {
+            var campaignPartner = normalizePartnerValue($("#my_service").val());
+            if (campaignPartner === undefined || campaignPartner === null || campaignPartner === "") {
+                var firstPartner = normalizePartnerValue($(".partner-display").first().val());
+                if (typeof firstPartner !== "undefined" && firstPartner !== null && firstPartner !== "") {
+                    campaignPartner = firstPartner;
+                }
+            }
+
+            if (campaignPartner === "") {
+                campaignPartner = "0";
+            }
+            $("#my_service").val(campaignPartner);
+
+            if (campaignPartner !== undefined && campaignPartner !== null && campaignPartner !== "") {
+                $(".partner-display").each(function () {
+                    $(this).val(campaignPartner);
+                });
+            }
         }
 
         function loadSvGroup() {
@@ -582,6 +676,7 @@
                     // Apply service type visibility rules after loading services
                     setTimeout(function() {
                         toggleServiceTypeFields();
+                        syncCampaignPartner();
                     }, 100);
                 },
                 error: function (data) {
@@ -687,6 +782,7 @@
             $("#campService-" + id).remove();
             //
             updateTotalChar();
+            syncCampaignPartner();
         };
 
         function addService() {
@@ -721,6 +817,7 @@
                     
                     // Clear search input for new service
                     $("#serviceSearch-" + nextIndex).val("");
+                    syncCampaignPartner();
 
                 },
                 error: function (data) {
@@ -758,7 +855,7 @@
             $("#name").val("");
             $("#title").val("");
             $("#priority").val(1);
-            $("#my_service").val("0");
+            // $("#my_service").val("0");
             $("#total_characters").val("0");
             $("#div-services").html("");
             $("#list-campBalance").html("");
@@ -1117,6 +1214,8 @@
         //    $("#gridbody_detail").html(html);
         //}
 
+        syncCampaignPartner();
+
         // clear error
         $(".modal input").keyup(function() {
             $("#lblWarning").html("");
@@ -1127,7 +1226,8 @@
             $("#lblWarning").html("");
             $("#" + id).removeClass("input-invalid");
         }
-
+        
     </script>
 }
 
+

+ 3 - 18
SuperAdmin/SuperAdmin/Views/Admin/DashboardReport.cshtml

@@ -89,7 +89,7 @@
                     <div class="card-content collapse show">
                         <div class="card-body">
                             <div class="row">
-                                <div class="col-md-4">
+                                <div class="col-md-6">
                                     <div class="form-group">
                                         <label>Time Window</label>
                                         <select class="form-control" id="timeWindow">
@@ -99,22 +99,7 @@
                                         </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="col-md-6">
                                     <div class="form-group">
                                         <label>Quick Jump</label>
                                         <div class='input-group'>
@@ -365,7 +350,7 @@
         function refreshChart() {
             startSpinner('btnRefresh');
             
-            var campaignId = $("#campaignFilter").val();
+            var campaignId = "-1"; // Always show all campaigns
             var serviceId = "-1"; // Always show all services
             var hours = $("#timeWindow").val();
             var quickJump = $("#quickJump").val() || "-1";

+ 26 - 6
SuperAdmin/SuperAdmin/Views/Admin/ReportUssdDetail.cshtml

@@ -63,19 +63,28 @@
                     </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 id="btnSearch" 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 id="pud_result" class="pud-result-panel">
         </div>
     </section>
 </div>
 
 @section Scripts{
+    <style>
+        .pud-result-panel {
+            background: #ffffff;
+            border-radius: 6px;
+            padding: 1.5rem;
+            min-height: 150px;
+            box-shadow: 0 6px 18px rgba(0, 0, 0, 0.08);
+        }
+    </style>
     <script>
         $(document).ready(function () {
             $("#pud_fromDate, #pud_toDate").datetimepicker({
@@ -102,8 +111,11 @@
                 return;
             }
 
-            $.post(urlConfig("/Admin/ReportUssdDetailSearch"),
-                {
+            startSpinner('btnSearch');
+            $.ajax({
+                url: urlConfig("/Admin/ReportUssdDetailSearch"),
+                type: "POST",
+                data: {
                     fromDate: $("#pud_fromDate").val(),
                     toDate: $("#pud_toDate").val(),
                     campaignId: $("#pud_campaignId").val(),
@@ -115,8 +127,16 @@
                     seqPage: "1",
                     order: "desc"
                 },
-                function (html) { $("#pud_result").html(html); }
-            );
+                success: function (html) {
+                    $("#pud_result").html(html);
+                },
+                error: function () {
+                    console.error("Search request failed");
+                },
+                complete: function () {
+                    stopSpinner('btnSearch');
+                }
+            });
         }
 
         function pudExport() {

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

@@ -547,7 +547,7 @@
                 data: {
                     fromDate: $("#fromDateSearch").val(),
                     toDate: $("#toDateSearch").val(),
-                    status: $("#statusSearch").val()
+                    isActive: $("#statusSearch").val()
                 },
                 type: "POST",
                 xhr: function () {
@@ -566,7 +566,6 @@
                     return xhr;
                 },
                 success: function (data, status, xhr) {
-
                     stopSpinner('btnExportExcel');
                     let filename = "";
                     let disposition = xhr.getResponseHeader('Content-Disposition');
@@ -578,16 +577,26 @@
                     let a = document.createElement('a');
                     let url = window.URL.createObjectURL(data);
                     a.href = url;
-                    a.download = filename.replace('UTF-8', '');
+                    a.download = filename.replace('UTF-8', '') || 'services.xlsx';
                     document.body.append(a);
                     a.click();
                     a.remove();
                     window.URL.revokeObjectURL(url);
                     $("#overlay").fadeOut(300);
                 },
-                error: function (data) {
+                error: function (xhr, status, error) {
                     stopSpinner('btnExportExcel');
-                    console.log(data.error);
+                    console.log("Export error:", error);
+                    if (xhr.responseText) {
+                        try {
+                            var errorData = JSON.parse(xhr.responseText);
+                            Swal.fire("Error!", errorData.content || "Export failed", "error");
+                        } catch (e) {
+                            Swal.fire("Error!", "Export failed: " + error, "error");
+                        }
+                    } else {
+                        Swal.fire("Error!", "Export failed: " + error, "error");
+                    }
                 }
             })
         }

+ 1 - 1
SuperAdmin/SuperAdmin/Views/File/BlacklistManagement.cshtml

@@ -334,7 +334,7 @@
            console.log("viewFile " + id);
            $("#file_upload").val("");
            $(".files").html("");
-
+           $("#id").val(id);
            // load file
             getSubFile(id);
 

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

@@ -11,26 +11,27 @@
 <div class="login-container">
     <div class="login-form-card">
         <div class="login-header">
-            <div class="logo-circle">
+            @* <div class="logo-circle">
                 <i class="ft-lock" style="font-size: 36px; color: #333;"></i>
-            </div>
+            </div> *@
             <h2 class="login-title">BALANCE PLUS MYTEL</h2>
         </div>
         
         <div class="form-section">
-            <form class="form-horizontal" action="@ViewBag.MyConfig.MyValue/Home/LoginAction" method="post">
+            <form class="form-horizontal" id="loginForm" action="@ViewBag.MyConfig.MyValue/Home/LoginAction" method="post">
                 <div class="input-wrapper">
-                    <input type="text" class="input-enhanced" id="user-name" placeholder="USERNAME" name="account" required>
+                    <input type="text" class="input-enhanced" id="user-name" placeholder="USERNAME" name="account">
                     <i class="ft-user input-icon"></i>
                 </div>
                 
                 <div class="input-wrapper">
-                    <input type="password" class="input-enhanced" id="user-password" placeholder="PASSWORD" name="password" required>
+                    <input type="password" class="input-enhanced input-password" id="user-password" placeholder="PASSWORD" name="password">
                     <i class="fa fa-key input-icon"></i>
+                    <span class="toggle-password" id="togglePassword" title="Show/Hide Password">👁</span>
                 </div>
                 
                 <div class="error-message">
-                    <span class="text-danger"></span>
+                    <span class="text-danger" id="errorMessage">@(TempData["ErrorMessage"]?.ToString())</span>
                 </div>
                 
                 <button type="submit" class="btn-login">
@@ -40,6 +41,95 @@
             </form>
         </div>
         
+        <script>
+            // Toggle password visibility
+            document.getElementById('togglePassword').addEventListener('click', function() {
+                var passwordInput = document.getElementById('user-password');
+                var toggleIcon = document.getElementById('togglePassword');
+                
+                if (passwordInput.type === 'password') {
+                    passwordInput.type = 'text';
+                    toggleIcon.textContent = '🙈';
+                    toggleIcon.title = 'Hide Password';
+                } else {
+                    passwordInput.type = 'password';
+                    toggleIcon.textContent = '👁';
+                    toggleIcon.title = 'Show Password';
+                }
+            });
+            
+            // Form validation
+            document.getElementById('loginForm').addEventListener('submit', function(e) {
+                var account = document.getElementById('user-name').value.trim();
+                var password = document.getElementById('user-password').value.trim();
+                var errorMessage = document.getElementById('errorMessage');
+                
+                // Clear previous error
+                errorMessage.textContent = '';
+                document.getElementById('user-name').classList.remove('input-error');
+                document.getElementById('user-password').classList.remove('input-error');
+                
+                // Validate account
+                if (!account) {
+                    e.preventDefault();
+                    errorMessage.textContent = 'Please enter infomation in this field';
+                    document.getElementById('user-name').classList.add('input-error');
+                    document.getElementById('user-name').focus();
+                    return false;
+                }
+                
+                // Validate password
+                if (!password) {
+                    e.preventDefault();
+                    errorMessage.textContent = 'Please enter password';
+                    document.getElementById('user-password').classList.add('input-error');
+                    document.getElementById('user-password').focus();
+                    return false;
+                }
+                
+                return true;
+            });
+        </script>
+        
+        <style>
+            .input-error {
+                border-color: #dc3545 !important;
+                box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25) !important;
+            }
+            .error-message {
+                min-height: 20px;
+                margin-bottom: 10px;
+            }
+            .error-message .text-danger {
+                font-size: 14px;
+                display: block;
+            }
+            .input-password {
+                padding-right: 50px !important;
+            }
+            .toggle-password {
+                position: absolute;
+                right: 18px;
+                top: 50%;
+                transform: translateY(-50%);
+                color: #666666;
+                font-size: 18px;
+                cursor: pointer;
+                z-index: 2;
+                transition: opacity 0.3s ease;
+                pointer-events: auto;
+                user-select: none;
+                display: inline-block;
+                line-height: 1;
+            }
+            .toggle-password:hover {
+                opacity: 0.7;
+            }
+            .input-wrapper {
+                position: relative;
+            }
+        </style>
+        
         <div class="security-note">
             ⚡ Protected Access - Authorized Personnel Only
         </div>

+ 5 - 13
SuperAdmin/SuperAdmin/Views/Partial/_Campaign.cshtml

@@ -68,8 +68,8 @@
             <label for="wsName">Service Type</label>
             <select class="form-control" id="addType" name="addType" asp-for="addType" onchange="toggleServiceTypeFields()">
                 <option value="1">Text</option>
-                <option value="2">1-Verification</option>
-                <option value="3">2-Verification</option>
+                 <option value="2">1-Verification</option>
+                @*<option value="3">2-Verification</option> *@
             </select>
         </div>
     </div>
@@ -100,15 +100,7 @@
         </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>
+    <input type="hidden" id="my_service" name="my_service" asp-for="isMyService" />
 
     @* <div class="col-md-6 hidden">
             <div class="form-group">
@@ -132,7 +124,7 @@
     <div class="col-md-12">
         <div class="form-group">
             <label for="date">
-                Title teaser
+                Content
                 <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">
@@ -144,7 +136,7 @@
         @Html.Partial("../Partial/_CampaignService", Model != null? Model.listServiceMapCam: null)
     </div>
     <div class="col-12">
-        <button onclick="addService();" class="btn btn-outline-success btn-add-service"><span class="fa fa-plus"></span> Add Service</button>
+        @* <button onclick="addService();" class="btn btn-outline-success btn-add-service"><span class="fa fa-plus"></span> Add Service</button> *@
         <span style="float:right">Total character: <span id="total_characters">0</span></span>
     </div>
 

+ 3 - 3
SuperAdmin/SuperAdmin/Views/Partial/_CampaignCriteria.cshtml

@@ -8,9 +8,9 @@
 @model Campaign
 @{
 
-    var listBalance = Context.Session.GetComplexData<List<CriteriaData>>("listBalance");
-    var listExpire = Context.Session.GetComplexData<List<CriteriaData>>("listExpire");
-    var listSubFile = Context.Session.GetComplexData<List<SubFileData>>("listSubFile");
+    var listBalance = Context.Session.GetComplexData<List<CriteriaData>>("listBalance") ?? new List<CriteriaData>();
+    var listExpire = Context.Session.GetComplexData<List<CriteriaData>>("listExpire") ?? new List<CriteriaData>();
+    var listSubFile = Context.Session.GetComplexData<List<SubFileData>>("listSubFile") ?? new List<SubFileData>();
 
     var nextIdxBal = 0;
     var nextIdxExp = 0;

+ 10 - 33
SuperAdmin/SuperAdmin/Views/Partial/_CampaignSchedulerCalendar.cshtml

@@ -162,7 +162,7 @@
         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
+        // Display full months that contain the selected range
         var startOfMonth = firstDate.clone().startOf('month');
         var endOfMonth = lastDate.clone().endOf('month');
         var startOfCalendar = startOfMonth.clone().startOf('week');
@@ -174,49 +174,26 @@
             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 dateKey = currentDate.format('DD/MM/YYYY');
+            var campaignsForDay = (campaignMap[dateKey] || []).map(function(campaign) {
+                return Object.assign({}, campaign, {
+                    statusText: getStatusText(campaign.status)
+                });
+            });
+        
             var dayData = {
-                date: currentDate.format('DD/MM/YYYY'),
+                date: dateKey,
                 isCurrentMonth: currentDate.isSame(firstDate, 'month'),
                 isToday: currentDate.isSame(today, 'day'),
                 dayOfMonth: currentDate.format('D'),
-                campaigns: []
+                campaigns: campaignsForDay
             };
             
-            // 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');
         }

+ 92 - 16
SuperAdmin/SuperAdmin/Views/Partial/_CampaignService.cshtml

@@ -1,4 +1,6 @@
-@using SuperAdmin.Models;
+@using System;
+@using System.Linq;
+@using SuperAdmin.Models;
 @using SuperAdmin.Models.Http;
 @using SuperAdmin.Controllers;
 @using SuperAdmin.Source;
@@ -7,7 +9,7 @@
 @model List<ServiceMapCam>
 @{
 
-    var listService = Context.Session.GetComplexData<List<Service>>("listService");
+    var listService = Context.Session.GetComplexData<List<Service>>("listService") ?? new List<Service>();
     var nextIndex = ViewBag.nextIndex;
     if (nextIndex == null || nextIndex == "")
     {
@@ -21,24 +23,72 @@
     for (int i = 0; i < Model.Count; i++)
     {
         var campaignServiceObj = Model[i];
+        var serviceAddId = campaignServiceObj.serviceAddId?.Trim();
+        Service currentService = campaignServiceObj.serviceObj;
+        Service serviceFromList = null;
+        if (!string.IsNullOrWhiteSpace(serviceAddId) && listService != null)
+        {
+            serviceFromList = listService.FirstOrDefault(s =>
+                string.Equals((s.id ?? string.Empty).Trim(), serviceAddId, StringComparison.OrdinalIgnoreCase));
+        }
+        if (currentService == null)
+        {
+            currentService = serviceFromList;
+        }
+        var groupSource = !string.IsNullOrWhiteSpace(campaignServiceObj.serviceObj?.serviceGroupID)
+            ? campaignServiceObj.serviceObj
+            : serviceFromList ?? campaignServiceObj.serviceObj;
+
+        var partnerValue = "0";
+        var partnerRaw = currentService?.isMyService ?? groupSource?.isMyService;
+        if (!string.IsNullOrWhiteSpace(partnerRaw))
+        {
+            partnerRaw = partnerRaw.Trim();
+            if (partnerRaw.Equals("1", StringComparison.OrdinalIgnoreCase) || partnerRaw.Equals("viettech", StringComparison.OrdinalIgnoreCase))
+            {
+                partnerValue = "1";
+            }
+        }
+
+        var serviceGroupId = groupSource?.serviceGroupID ?? string.Empty;
+        var normalizedServiceGroupId = string.IsNullOrWhiteSpace(serviceGroupId) ? string.Empty : serviceGroupId.Trim();
+        var serviceDisplayText = currentService != null ? $"{currentService.id} - {currentService.name}" : string.Empty;
+        var ussd2Value = currentService?.msgConfirm ?? string.Empty;
         <div class="row" name="campService" id="campService-@i">
             <!-- Row 1: Service Group và Service -->
             <div class="col-md-3">
                 <div class="form-group">
                     <label>Service Group</label>
                     <select class="form-control" id="serviceGroup-@i" onchange="filterServicesByGroup('@i')">
-                        <option value="">-- All Groups --</option>
+                        @if (string.IsNullOrEmpty(normalizedServiceGroupId))
+                        {
+                            <option value="" selected>-- All Groups --</option>
+                        }
+                        else
+                        {
+                            <option value="">-- All Groups --</option>
+                        }
                         @if (serviceGroupData != null)
                         {
-                            @foreach (ServiceGroupData group in serviceGroupData)
+                            foreach (ServiceGroupData group in serviceGroupData)
                             {
-                                <option value="@group.id">@group.name</option>
+                                var groupIdValue = string.IsNullOrWhiteSpace(group.id) ? string.Empty : group.id.Trim();
+                                bool isSelected = !string.IsNullOrEmpty(normalizedServiceGroupId) &&
+                                                  string.Equals(groupIdValue, normalizedServiceGroupId, StringComparison.OrdinalIgnoreCase);
+                                if (isSelected)
+                                {
+                                    <option value="@group.id" selected>@group.name</option>
+                                }
+                                else
+                                {
+                                    <option value="@group.id">@group.name</option>
+                                }
                             }
                         }
                     </select>
                 </div>
             </div>
-            <div class="col-md-6">
+            <div class="col-md-5">
                 <div class="form-group">
                     <label>Service</label>
                     <div class="searchable-dropdown" id="serviceDropdown-@i">
@@ -47,12 +97,12 @@
                                onkeyup="filterServiceOptions('@i')" 
                                onclick="toggleDropdown('@i')"
                                autocomplete="off"
-                               value="@(campaignServiceObj.serviceObj != null ? campaignServiceObj.serviceObj.id + " - " + campaignServiceObj.serviceObj.name : "")">
+                               value="@serviceDisplayText">
                         <div class="dropdown-list" id="serviceList-@i" style="display: none;">
                             @foreach (Service sv in listService)
                             {
-                                <div class="dropdown-item" data-value="@sv.id" data-ussd1="@sv.contentLc" data-ussd2="@sv.msgConfirm" data-group="@sv.serviceGroupID"
-                                     onclick="selectServiceOption('@i', '@sv.id', '@sv.contentLc', '@sv.msgConfirm', '@sv.id - @sv.name')">
+                                <div class="dropdown-item" data-value="@sv.id" data-ussd1="@sv.contentLc" data-ussd2="@sv.msgConfirm" data-group="@sv.serviceGroupID" data-partner="@sv.isMyService"
+                                     onclick="selectServiceOption('@i', '@sv.id', '@sv.contentLc', '@sv.msgConfirm', '@sv.id - @sv.name', '@sv.isMyService', '@sv.serviceGroupID')">
                                     @sv.id - @sv.name
                                 </div>
                             }
@@ -61,7 +111,24 @@
                     </div>
                 </div>
             </div>
-            <div class="col-md-3">
+            <div class="col-md-2">
+                <div class="form-group">
+                    <label>Partner</label>
+                    <select class="form-control partner-display" id="partner-@i" disabled>
+                        @if (partnerValue == "0")
+                        {
+                            <option value="0" selected>Mytel</option>
+                        }
+                       
+                        @if (partnerValue == "1")
+                        {
+                            <option value="1" selected>Viettech</option>
+                        }
+                       
+                    </select>
+                </div>
+            </div>
+            <div class="col-md-2">
                 <div class="form-group">
                     <label>&nbsp;</label>
                     <button onclick="removeService(@i);" class="btn btn-outline-danger btn-add-service form-control"><span class="fa fa-minus"></span> Remove</button>
@@ -88,7 +155,7 @@
             <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>
+                    <input type="text" class="form-control" id="msgConfirm-@i" value="@ussd2Value" readonly>
                 </div>
             </div>
             <div class="col-md-2 service-field service-field--key2">
@@ -119,7 +186,7 @@ else
                     <option value="">-- All Groups --</option>
                     @if (serviceGroupData != null)
                     {
-                        @foreach (ServiceGroupData group in serviceGroupData)
+                        foreach (ServiceGroupData group in serviceGroupData)
                         {
                             <option value="@group.id">@group.name</option>
                         }
@@ -127,7 +194,7 @@ else
                 </select>
             </div>
         </div>
-        <div class="col-md-6">
+        <div class="col-md-5">
             <div class="form-group">
                 <label>Service</label>
                 <div class="searchable-dropdown" id="serviceDropdown-@nextIndex">
@@ -139,8 +206,8 @@ else
                     <div class="dropdown-list" id="serviceList-@nextIndex" style="display: none;">
                         @foreach (Service sv in listService)
                         {
-                            <div class="dropdown-item" data-value="@sv.id" data-ussd1="@sv.contentLc" data-ussd2="@sv.msgConfirm" data-group="@sv.serviceGroupID"
-                                 onclick="selectServiceOption('@nextIndex', '@sv.id', '@sv.contentLc', '@sv.msgConfirm', '@sv.id - @sv.name')">
+                            <div class="dropdown-item" data-value="@sv.id" data-ussd1="@sv.contentLc" data-ussd2="@sv.msgConfirm" data-group="@sv.serviceGroupID" data-partner="@sv.isMyService"
+                                 onclick="selectServiceOption('@nextIndex', '@sv.id', '@sv.contentLc', '@sv.msgConfirm', '@sv.id - @sv.name', '@sv.isMyService')">
                                 @sv.id - @sv.name
                             </div>
                         }
@@ -149,7 +216,16 @@ else
                 </div>
             </div>
         </div>
-        <div class="col-md-3">
+        <div class="col-md-2">
+            <div class="form-group">
+                <label>Partner</label>
+                <select class="form-control partner-display" id="partner-@nextIndex" disabled>
+                    <option value="0" selected>Mytel</option>
+                    <option value="1">Viettech</option>
+                </select>
+            </div>
+        </div>
+        <div class="col-md-2">
             <div class="form-group">
                 <label>&nbsp;</label>
                 <button onclick="removeService(@nextIndex);" class="btn btn-outline-danger btn-add-service form-control"><span class="fa fa-minus"></span> Remove</button>

+ 1 - 1
SuperAdmin/SuperAdmin/Views/Partial/_ListCampaign.cshtml

@@ -35,7 +35,7 @@
                         <td class="text-left">@ws.fromDate</td>
                         <td class="text-left">@ws.toDate</td>
                         <td class="text-left">@(ws.isDefault == "1" ? "Default" : "Normal")</td>
-                        <td class="text-left">@((int.Parse(ws.addType) - 1) + "-Verification")</td>
+                        <td class="text-left">@((int.Parse(ws.addType) - 1) + "-USSD Text")</td>
                         <td class="text-left">
                             @if (ws.listServiceMapCam != null)
                             {

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

@@ -21,6 +21,7 @@
                 <th>Is Success</th>
                 <th>Insert Time</th>
                 <th>Last Update</th>
+                <th>Msg Content</th>
             </tr>
         </thead>
         <tbody>
@@ -49,6 +50,7 @@
                     <td>@row.isSuccess</td>
                     <td>@row.insertTime</td>
                     <td>@row.lastUpdate</td>
+                    <td>@row.msgContent</td>
                 </tr>
             }
         }

+ 1 - 1
SuperAdmin/SuperAdmin/Views/Shared/_Layout.cshtml

@@ -141,7 +141,7 @@
     </nav>
     <!-- END: Header-->
     <!-- END: Main Menu-->
-    <div class="main-menu menu-fixed menu-dark menu-accordion menu-shadow" data-scroll-to-active="true">
+    <div class="main-menu menu-fixed menu-light menu-accordion menu-shadow" data-scroll-to-active="true">
         <div class="main-menu-content">
             @Html.Partial("../Partial/_Menu")
         </div>

+ 1 - 1
SuperAdmin/SuperAdmin/appsettings.json

@@ -11,7 +11,7 @@
   "channel": "WEB",
   "numberSeparated": ".",
   "PARENT_ID": "100207",
-  "CountryCode": "509",
+  "CountryCode": "95",
 
   //"usersadminlogin": "http://127.0.0.1:8989/api/balance/usersAdminLogin/data",
   "usersadminlogin": "http://127.0.0.1:8989/api/balance/usersAdminLogin/data",

+ 29 - 12
SuperAdmin/SuperAdmin/wwwroot/css/login.css

@@ -3,30 +3,47 @@
     overflow: hidden;
 }
 
-/* Background with Image */
+/* Background with faceted gradient similar to reference */
 .login-background {
     position: fixed;
     top: 0;
     left: 0;
     width: 100%;
     height: 100%;
-    /* 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;
+    background: linear-gradient(135deg,
+        #561C36 0%,
+        #6F253B 18%,
+        #923338 35%,
+        #C45B35 55%,
+        #E08330 70%,
+        #F9BE57 90%,
+        #F8E19C 100%);
+    overflow: hidden;
     z-index: 0;
 }
 
-/* Overlay để làm mờ ảnh nền một chút (tùy chọn) */
+.login-background::before,
 .login-background::after {
     content: '';
     position: absolute;
-    top: 0;
-    left: 0;
-    width: 100%;
-    height: 100%;
-    background: rgba(255, 255, 255, 0.3);
-    z-index: 0;
+    inset: 0;
+    pointer-events: none;
+}
+
+/* warm polygons on right side */
+.login-background::before {
+    background:
+        linear-gradient(150deg, #FEE7A3 0%, #F4C769 55%, #E89E46 100%);
+    clip-path: polygon(55% 0%, 100% 0%, 100% 65%, 70% 85%, 50% 60%, 45% 35%);
+    opacity: 0.9;
+}
+
+/* darker polygons on left side */
+.login-background::after {
+    background:
+        linear-gradient(210deg, #4F1D2F 0%, #6A2336 40%, #7F2F3B 75%, #9A3C3F 100%);
+    clip-path: polygon(0% 0%, 55% 0%, 45% 40%, 60% 70%, 20% 100%, 0% 100%);
+    opacity: 0.9;
 }
 
 .login-grid {

+ 118 - 38
SuperAdmin/SuperAdmin/wwwroot/css/menu-custom.css

@@ -1,15 +1,14 @@
-/* Custom Menu Styles - Dark Theme với gradient purple/pink */
+/* Custom Menu Styles - Light Theme với nền trắng chủ đạo */
 
 /* Menu Container */
 .navigation-main {
-    background: linear-gradient(135deg, rgba(15, 23, 42, 0.95) 0%, rgba(30, 41, 59, 0.95) 100%);
-    backdrop-filter: blur(10px);
-    border-right: 1px solid rgba(139, 92, 246, 0.2);
+    background: #ffffff;
+    border-right: 1px solid rgba(0, 0, 0, 0.1);
 }
 
 /* Menu Items */
 .navigation-main .nav-item {
-    border-bottom: 1px solid rgba(139, 92, 246, 0.1);
+    border-bottom: 1px solid rgba(0, 0, 0, 0.05);
 }
 
 .navigation-main .nav-item:last-child {
@@ -18,7 +17,7 @@
 
 /* Menu Links */
 .navigation-main .nav-link {
-    color: rgba(255, 255, 255, 0.7) !important;
+    color: rgba(0, 0, 0, 0.7) !important;
     padding: 12px 20px;
     transition: all 0.3s ease;
     position: relative;
@@ -32,14 +31,14 @@
     top: 0;
     height: 100%;
     width: 3px;
-    background: linear-gradient(135deg, #8b5cf6 0%, #ec4899 100%);
+    background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%);
     transform: scaleY(0);
     transition: transform 0.3s ease;
 }
 
 .navigation-main .nav-link:hover {
-    background: rgba(139, 92, 246, 0.15);
-    color: #fff !important;
+    background: rgba(79, 70, 229, 0.08);
+    color: #1e293b !important;
     padding-left: 28px;
 }
 
@@ -55,11 +54,10 @@
 /* Active Menu Item */
 .navigation-main .nav-item.active > .nav-link,
 .navigation-main .nav-link.active {
-    background: linear-gradient(90deg, rgba(139, 92, 246, 0.25) 0%, rgba(236, 72, 153, 0.15) 100%);
-    color: #fff !important;
+    background: rgba(79, 70, 229, 0.1);
+    color: #4f46e5 !important;
     padding-left: 28px;
-    border-left: 3px solid;
-    border-image: linear-gradient(135deg, #8b5cf6 0%, #ec4899 100%) 1;
+    border-left: 3px solid #4f46e5;
 }
 
 .navigation-main .nav-item.active > .nav-link::before,
@@ -69,7 +67,7 @@
 
 /* Menu Icons */
 .navigation-main .nav-link i {
-    color: #ffffff;
+    color: rgba(0, 0, 0, 0.6);
     margin-right: 12px;
     font-size: 18px;
     width: 18px; /* reserve space so icons don't collapse */
@@ -77,14 +75,12 @@
     line-height: 1;
     display: inline-block;
     transition: all 0.3s ease;
-    display: inline-block;
 }
 
 .navigation-main .nav-link:hover i,
 .navigation-main .nav-item.active > .nav-link i,
 .navigation-main .nav-link.active i {
-    color: #ffffff !important;
-    text-shadow: none;
+    color: #4f46e5 !important;
     transform: none;
 }
 
@@ -94,14 +90,14 @@
     font-weight: 500;
     letter-spacing: 0.5px;
     transition: all 0.3s ease;
-    color: rgba(255, 255, 255, 0.7);
+    color: rgba(0, 0, 0, 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 {
-    color: #ffffff !important;
-    -webkit-text-fill-color: #ffffff;
+    color: #1e293b !important;
+    -webkit-text-fill-color: #1e293b;
     background: none;
     -webkit-background-clip: unset;
     background-clip: unset;
@@ -110,13 +106,13 @@
 
 /* Sub-menu */
 .navigation-main .menu-content {
-    background: rgba(0, 0, 0, 0.3);
-    border-left: 2px solid rgba(139, 92, 246, 0.3);
+    background: rgba(0, 0, 0, 0.02);
+    border-left: 2px solid rgba(79, 70, 229, 0.2);
     padding-left: 0;
 }
 
 .navigation-main .menu-item {
-    color: rgba(255, 255, 255, 0.6) !important;
+    color: rgba(0, 0, 0, 0.6) !important;
     padding: 10px 15px 10px 50px;
     transition: all 0.3s ease;
 }
@@ -124,24 +120,23 @@
 .navigation-main .menu-item i {
     margin-right: 8px;
     font-size: 14px;
-    color: #ffffff;
+    color: rgba(0, 0, 0, 0.5);
     transition: all 0.3s ease;
 }
 
 .navigation-main .menu-item:hover {
-    background: rgba(139, 92, 246, 0.15);
-    color: #fff !important;
+    background: rgba(79, 70, 229, 0.08);
+    color: #1e293b !important;
     padding-left: 55px;
 }
 
 .navigation-main .menu-item:hover i {
-    color: #ffffff;
-    text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
+    color: #4f46e5;
 }
 
 /* Sub-menu Item */
 .navigation-main .menu-content li {
-    border-bottom: 1px solid rgba(139, 92, 246, 0.05);
+    border-bottom: 1px solid rgba(0, 0, 0, 0.03);
 }
 
 .navigation-main .menu-content li:last-child {
@@ -150,14 +145,13 @@
 
 /* Has-sub menu arrow */
 .navigation-main .has-sub > a::after {
-    color: #ffffff;
+    color: rgba(0, 0, 0, 0.5);
     transition: all 0.3s ease;
 }
 
 .navigation-main .has-sub:hover > a::after,
 .navigation-main .has-sub.open > a::after {
-    color: #ffffff;
-    text-shadow: 0 0 10px rgba(255, 255, 255, 0.5);
+    color: #4f46e5;
 }
 
 /* Scrollbar customization */
@@ -166,16 +160,16 @@
 }
 
 .navigation-main::-webkit-scrollbar-track {
-    background: rgba(15, 23, 42, 0.5);
+    background: rgba(0, 0, 0, 0.05);
 }
 
 .navigation-main::-webkit-scrollbar-thumb {
-    background: linear-gradient(135deg, #8b5cf6 0%, #ec4899 100%);
+    background: rgba(79, 70, 229, 0.3);
     border-radius: 10px;
 }
 
 .navigation-main::-webkit-scrollbar-thumb:hover {
-    background: linear-gradient(135deg, #ec4899 0%, #8b5cf6 100%);
+    background: rgba(79, 70, 229, 0.5);
 }
 
 /* Animation for menu items */
@@ -194,7 +188,7 @@
     animation: menuSlideIn 0.5s ease forwards;
 }
 
-/* Glass effect */
+/* Light theme effect */
 .navigation-main {
     position: relative;
 }
@@ -207,8 +201,8 @@
     right: 0;
     bottom: 0;
     background: 
-        radial-gradient(circle at 0% 0%, rgba(139, 92, 246, 0.1), transparent 50%),
-        radial-gradient(circle at 100% 100%, rgba(236, 72, 153, 0.1), transparent 50%);
+        radial-gradient(circle at 0% 0%, rgba(79, 70, 229, 0.03), transparent 50%),
+        radial-gradient(circle at 100% 100%, rgba(124, 58, 237, 0.03), transparent 50%);
     pointer-events: none;
     z-index: 0;
 }
@@ -218,3 +212,89 @@
     z-index: 1;
 }
 
+/* Navbar Wrapper - Gradient màu tươi */
+.navbar-wrapper {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 25%, #f093fb 50%, #4facfe 75%, #00f2fe 100%);
+    background-size: 200% 200%;
+    animation: gradientShift 8s ease infinite;
+    box-shadow: 0 2px 10px rgba(102, 126, 234, 0.3);
+}
+
+@keyframes gradientShift {
+    0% {
+        background-position: 0% 50%;
+    }
+    50% {
+        background-position: 100% 50%;
+    }
+    100% {
+        background-position: 0% 50%;
+    }
+}
+
+/* Navbar header và container */
+.navbar-semi-dark .navbar-header,
+.navbar-semi-dark .navbar-container {
+    background: transparent;
+}
+
+/* Navbar links */
+.navbar-semi-dark .navbar-nav .nav-link {
+    color: rgba(255, 255, 255, 0.9) !important;
+    transition: all 0.3s ease;
+}
+
+.navbar-semi-dark .navbar-nav .nav-link:hover {
+    color: #ffffff !important;
+    background: rgba(255, 255, 255, 0.15);
+    border-radius: 6px;
+}
+
+/* Navbar brand text */
+.navbar-semi-dark .brand-text {
+    color: #ffffff !important;
+    font-weight: 600;
+    text-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}
+
+/* Navbar icons */
+.navbar-semi-dark .navbar-nav .nav-link i {
+    color: rgba(255, 255, 255, 0.9) !important;
+}
+
+.navbar-semi-dark .navbar-nav .nav-link:hover i {
+    color: #ffffff !important;
+    transform: scale(1.1);
+}
+
+/* Dropdown menu */
+.navbar-semi-dark .dropdown-menu {
+    background: #ffffff;
+    border: 1px solid rgba(0, 0, 0, 0.1);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.navbar-semi-dark .dropdown-item {
+    color: rgba(0, 0, 0, 0.7);
+    transition: all 0.2s ease;
+}
+
+.navbar-semi-dark .dropdown-item:hover {
+    background: rgba(102, 126, 234, 0.1);
+    color: #667eea;
+}
+
+/* Avatar và user name */
+.navbar-semi-dark .dropdown-user-link {
+    color: rgba(255, 255, 255, 0.9) !important;
+}
+
+.navbar-semi-dark .user-name {
+    color: rgba(255, 255, 255, 0.9) !important;
+    font-weight: 500;
+}
+
+.navbar-semi-dark .dropdown-user-link:hover {
+    color: #ffffff !important;
+}
+