From f5b1fe6397aebbb6e6d10072995bd6a6d5c5c656 Mon Sep 17 00:00:00 2001 From: David Headrick Date: Mon, 7 Apr 2025 12:13:44 -0500 Subject: [PATCH] Update mailing and target management features - Added new methods for creating mailings and testing targets. - Updated configuration files for JWT settings and connection strings. - Introduced new DTOs for target column updates and test targets. - Enhanced MailingStatistic with a new SentDate property. - Created new components for handling cancelled mailings and target samples. - Refactored authentication in Login.tsx to use fetch API. - Updated various services and repositories to support new functionalities. --- .../.config/dotnet-tools.json | 13 + .../Controllers/MailingsController.cs | 1 - .../Controllers/TargetsController.cs | 14 + .../appsettings.Development.json | 21 +- .../appsettings.Uat.json | 18 + Surge365.MassEmailReact.API/appsettings.json | 10 +- .../DTOs/TargetColumnUpdateDto.cs | 18 + .../DTOs/TargetSample.cs | 5 +- .../DTOs/TargetUpdateDto.cs | 3 +- .../DTOs/TestTargetDto.cs | 17 + .../Interfaces/IMailingRepository.cs | 4 +- .../Interfaces/IMailingService.cs | 4 +- .../Interfaces/ITargetService.cs | 1 + .../Entities/MailingStatistic.cs | 8 +- .../Entities/Target.cs | 1 + .../Entities/TargetColumn.cs | 39 ++ .../DapperMaps/DapperConfiguration.cs | 1 + .../DapperMaps/MailingStatisticMap.cs | 1 + .../DapperMaps/TargetColumnMap.cs | 24 + .../Repositories/MailingRepository.cs | 10 +- .../Repositories/TargetRepository.cs | 47 +- .../Services/MailingService.cs | 8 +- .../Services/TargetService.cs | 18 + .../content/js/ytb-massemail-.global.ts | 610 ------------------ .../content/js/ytb-massemail-utilities-1.0.ts | 600 ++++++++--------- Surge365.MassEmailReact.Web/public/web.config | 18 + .../components/auth/ProtectedPageWrapper.tsx | 1 + .../src/components/layouts/Layout.tsx | 13 +- .../components/modals/ForgotPasswordModal.tsx | 26 +- .../src/components/modals/MailingEdit.tsx | 18 +- .../src/components/modals/MailingView.tsx | 14 +- .../src/components/modals/TargetEdit.tsx | 445 ++++++++++++- ...SampleViewer.tsx => TargetSampleModal.tsx} | 64 +- .../src/components/pages/App.tsx | 11 + .../components/pages/CancelledMailings.tsx | 150 +++++ .../components/pages/CompletedMailings.tsx | 31 +- .../src/components/pages/Home.tsx | 27 +- .../src/components/pages/Login.tsx | 87 +-- .../src/components/pages/NewMailings.tsx | 3 + .../components/pages/ScheduledMailings.tsx | 54 +- .../src/components/pages/Targets.tsx | 6 +- .../shared/TargetSampleDataGrid.tsx | 41 ++ .../widgets/RecentMailingStatsChart.tsx | 108 ++++ Surge365.MassEmailReact.Web/src/ts/utils.ts | 232 +++---- .../src/types/mailingStatistic.ts | 1 + .../src/types/target.ts | 2 + .../src/types/targetColumn.ts | 11 + .../src/types/targetSampleColumn.ts | 7 + 48 files changed, 1624 insertions(+), 1242 deletions(-) create mode 100644 Surge365.MassEmailReact.API/.config/dotnet-tools.json create mode 100644 Surge365.MassEmailReact.API/appsettings.Uat.json create mode 100644 Surge365.MassEmailReact.Application/DTOs/TargetColumnUpdateDto.cs create mode 100644 Surge365.MassEmailReact.Application/DTOs/TestTargetDto.cs create mode 100644 Surge365.MassEmailReact.Domain/Entities/TargetColumn.cs create mode 100644 Surge365.MassEmailReact.Infrastructure/DapperMaps/TargetColumnMap.cs delete mode 100644 Surge365.MassEmailReact.Web/public/content/js/ytb-massemail-.global.ts create mode 100644 Surge365.MassEmailReact.Web/public/web.config rename Surge365.MassEmailReact.Web/src/components/modals/{TargetSampleViewer.tsx => TargetSampleModal.tsx} (58%) create mode 100644 Surge365.MassEmailReact.Web/src/components/pages/CancelledMailings.tsx create mode 100644 Surge365.MassEmailReact.Web/src/components/shared/TargetSampleDataGrid.tsx create mode 100644 Surge365.MassEmailReact.Web/src/components/widgets/RecentMailingStatsChart.tsx create mode 100644 Surge365.MassEmailReact.Web/src/types/targetColumn.ts create mode 100644 Surge365.MassEmailReact.Web/src/types/targetSampleColumn.ts diff --git a/Surge365.MassEmailReact.API/.config/dotnet-tools.json b/Surge365.MassEmailReact.API/.config/dotnet-tools.json new file mode 100644 index 0000000..0280531 --- /dev/null +++ b/Surge365.MassEmailReact.API/.config/dotnet-tools.json @@ -0,0 +1,13 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "9.0.3", + "commands": [ + "dotnet-ef" + ], + "rollForward": false + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/Controllers/MailingsController.cs b/Surge365.MassEmailReact.API/Controllers/MailingsController.cs index a7225ac..9e76a09 100644 --- a/Surge365.MassEmailReact.API/Controllers/MailingsController.cs +++ b/Surge365.MassEmailReact.API/Controllers/MailingsController.cs @@ -67,7 +67,6 @@ namespace Surge365.MassEmailReact.API.Controllers return mailing is not null ? Ok(mailing) : NotFound($"Mailing statistics with id '{id}' not found."); } - [HttpPost] public async Task CreateMailing([FromBody] MailingUpdateDto mailingUpdateDto) { diff --git a/Surge365.MassEmailReact.API/Controllers/TargetsController.cs b/Surge365.MassEmailReact.API/Controllers/TargetsController.cs index 961000d..5d7f987 100644 --- a/Surge365.MassEmailReact.API/Controllers/TargetsController.cs +++ b/Surge365.MassEmailReact.API/Controllers/TargetsController.cs @@ -78,5 +78,19 @@ namespace Surge365.MassEmailReact.API.Controllers return StatusCode(500, new { message = "Error fetching sample data", error = ex.Message }); } } + [HttpPost("test")] + public async Task TestTargeByID(TestTargetDto testTarget) + { + try + { + var sample = await _targetService.TestTargetAsync(testTarget); + return Ok(sample); + } + catch (Exception ex) + { + // Log the exception (e.g., using ILogger if injected) + return StatusCode(500, new { message = "Error fetching sample data", error = ex.Message }); + } + } } } \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/appsettings.Development.json b/Surge365.MassEmailReact.API/appsettings.Development.json index 9a63e2f..a5465e6 100644 --- a/Surge365.MassEmailReact.API/appsettings.Development.json +++ b/Surge365.MassEmailReact.API/appsettings.Development.json @@ -5,5 +5,22 @@ "Microsoft.AspNetCore": "Warning" } }, - "ConnectionStringTemplate": "data source=##server_name##,##port##;initial catalog=##database_name##;User ID=##username##;Password=##password##;persist security info=False;packet size=4096;TrustServerCertificate=True;" -} + "AllowedHosts": "*", + "Jwt": { + "Secret": "Z9R5aFml+eRMeb7tyf8N9wCq3tZpS/EM6nGqOxlXPtOw4cJ3zS1AByczrIlD5F9d" + }, + "AppCode": "MassEmailReactApi", + "AuthAppCode": "MassEmailWeb", + "EnvironmentCode": "UAT", + "ConnectionStrings": { + "Marketing.ConnectionString": "data source=uat.surge365.com;initial catalog=Marketing;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;", //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT + "MassEmail.ConnectionString": "data source=uat.surge365.com;initial catalog=MassEmail;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;" //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT + }, + "TestTargetSql": "CREATE TABLE #columns\r\n(\r\n primary_key INT NOT NULL IDENTITY(1,1) PRIMARY KEY,\r\n name VARCHAR(255),\r\n data_type CHAR(1)\r\n)\r\nSELECT TOP 10 *\r\nINTO #list\r\nFROM ##database_name##..##view_name##\r\n##filter##\r\n\r\nDECLARE @row_count INT\r\nSELECT @row_count = COUNT(*)\r\nFROM ##database_name##..##view_name##\r\n##filter##\r\n\r\nDECLARE c_curs CURSOR FOR \r\nSELECT c.name AS column_name, t.name AS data_type\r\nFROM tempdb.sys.columns c\r\nINNER JOIN tempdb.sys.types t ON c.user_type_id = t.user_type_id\r\n AND t.name NOT IN ('text','ntext','image','binary','varbinary','image','cursor','timestamp','hierarchyid','sql_variant','xml','table')\r\nWHERE object_id = object_id('tempdb..#list') \r\n AND ((t.name IN ('char','varchar') AND c.max_length <= 255)\r\n OR (t.name IN ('nchar','nvarchar') AND c.max_length <= 510)\r\n OR (t.name NOT IN ('char','varchar','nchar','nvarchar')))\r\n \r\nOPEN c_curs\r\nDECLARE @column_name VARCHAR(255), @column_type VARCHAR(255)\r\n\r\nFETCH NEXT FROM c_curs INTO @column_name, @column_type\r\nWHILE(@@FETCH_STATUS = 0)\r\nBEGIN \r\n DECLARE @data_type CHAR(1) = 'S'\r\n IF(@column_type IN ('date','datetime','datetime2','datetimeoffset','smalldatetime','time'))\r\n BEGIN\r\n SET @data_type = 'D'\r\n END\r\n ELSE IF(@column_type IN ('bit'))\r\n BEGIN\r\n SET @data_type = 'B'\r\n END\r\n ELSE IF(@column_type IN ('bigint','numeric','smallint','decimal','smallmoney','int','tinyint','money','float','real'))\r\n BEGIN\r\n SET @data_type = 'N'\r\n END\r\n INSERT INTO #columns(name, data_type) VALUES(@column_name, @data_type)\r\n FETCH NEXT FROM c_curs INTO @column_name, @column_type\r\nEND\r\nCLOSE c_curs\r\nDEALLOCATE c_curs\r\nSELECT * FROM #columns ORDER BY primary_key\r\nSELECT * FROM #list\r\nSELECT @row_count AS row_count\r\nDROP TABLE #columns\r\nDROP TABLE #list", + "ConnectionStringTemplate": "data source=##server_name##,##port##;initial catalog=##database_name##;User ID=##username##;Password=##password##;persist security info=False;packet size=4096;TrustServerCertificate=True;", + "DefaultUnsubscribeUrl": "https://uat.emailopentracking.surge365.com/unsubscribe.htm", + "SendGrid_TestMode": false, + "RegularExpression_Email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", + "SendGrid_Url": "smtp.sendgrid.net", + "SendGrid_Port": "587" +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/appsettings.Uat.json b/Surge365.MassEmailReact.API/appsettings.Uat.json new file mode 100644 index 0000000..fe842cd --- /dev/null +++ b/Surge365.MassEmailReact.API/appsettings.Uat.json @@ -0,0 +1,18 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "Jwt": { + "Secret": "1bXgXk7v/W9XksGoNiqWvM7+9/BERZonShxqoCVvdi8Ew47M1VFzJGA9sPMgkmn/HRmuZ83iytNsHXI6GkAb8g==" + }, + "EnvironmentCode": "UAT", + "DefaultUnsubscribeUrl": "https://uat.emailopentracking.surge365.com/unsubscribe.htm", + "SendGrid_TestMode": true, + "RegularExpression_Email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", + "SendGrid_Url": "smtp.sendgrid.net", + "SendGrid_Port": "587" +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.API/appsettings.json b/Surge365.MassEmailReact.API/appsettings.json index c0c5865..29bbe73 100644 --- a/Surge365.MassEmailReact.API/appsettings.json +++ b/Surge365.MassEmailReact.API/appsettings.json @@ -7,18 +7,18 @@ }, "AllowedHosts": "*", "Jwt": { - "Secret": "Z9R5aFml+eRMeb7tyf8N9wCq3tZpS/EM6nGqOxlXPtOw4cJ3zS1AByczrIlD5F9d" + "Secret": "4r1AJ0riBpEhgaTxhTWMIPs5rv9AlVZjTqrGUoU3DUz4i/Dx9ZfGciIubNODQRO0z3qJZq6VqxGXdsFRJgSb6Q==" }, "AppCode": "MassEmailReactApi", "AuthAppCode": "MassEmailWeb", "EnvironmentCode": "UAT", "ConnectionStrings": { - "Marketing.ConnectionString": "data source=uat.surge365.com;initial catalog=Marketing;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;", //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT - "MassEmail.ConnectionString": "data source=uat.surge365.com;initial catalog=MassEmail;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;" //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT + "Marketing.ConnectionString": "data source=localhost;initial catalog=Marketing;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;", //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT + "MassEmail.ConnectionString": "data source=localhost;initial catalog=MassEmail;User ID=ytb;Password=YTB()nl!n3;Encrypt=yes;TrustServerCertificate=yes;Connection Timeout=3;Application Name=##application_name##;" //TODO: Move this to development.json, on server should go somewhere secure. GET IT OUT OF GIT }, "TestTargetSql": "CREATE TABLE #columns\r\n(\r\n primary_key INT NOT NULL IDENTITY(1,1) PRIMARY KEY,\r\n name VARCHAR(255),\r\n data_type CHAR(1)\r\n)\r\nSELECT TOP 10 *\r\nINTO #list\r\nFROM ##database_name##..##view_name##\r\n##filter##\r\n\r\nDECLARE @row_count INT\r\nSELECT @row_count = COUNT(*)\r\nFROM ##database_name##..##view_name##\r\n##filter##\r\n\r\nDECLARE c_curs CURSOR FOR \r\nSELECT c.name AS column_name, t.name AS data_type\r\nFROM tempdb.sys.columns c\r\nINNER JOIN tempdb.sys.types t ON c.user_type_id = t.user_type_id\r\n AND t.name NOT IN ('text','ntext','image','binary','varbinary','image','cursor','timestamp','hierarchyid','sql_variant','xml','table')\r\nWHERE object_id = object_id('tempdb..#list') \r\n AND ((t.name IN ('char','varchar') AND c.max_length <= 255)\r\n OR (t.name IN ('nchar','nvarchar') AND c.max_length <= 510)\r\n OR (t.name NOT IN ('char','varchar','nchar','nvarchar')))\r\n \r\nOPEN c_curs\r\nDECLARE @column_name VARCHAR(255), @column_type VARCHAR(255)\r\n\r\nFETCH NEXT FROM c_curs INTO @column_name, @column_type\r\nWHILE(@@FETCH_STATUS = 0)\r\nBEGIN \r\n DECLARE @data_type CHAR(1) = 'S'\r\n IF(@column_type IN ('date','datetime','datetime2','datetimeoffset','smalldatetime','time'))\r\n BEGIN\r\n SET @data_type = 'D'\r\n END\r\n ELSE IF(@column_type IN ('bit'))\r\n BEGIN\r\n SET @data_type = 'B'\r\n END\r\n ELSE IF(@column_type IN ('bigint','numeric','smallint','decimal','smallmoney','int','tinyint','money','float','real'))\r\n BEGIN\r\n SET @data_type = 'N'\r\n END\r\n INSERT INTO #columns(name, data_type) VALUES(@column_name, @data_type)\r\n FETCH NEXT FROM c_curs INTO @column_name, @column_type\r\nEND\r\nCLOSE c_curs\r\nDEALLOCATE c_curs\r\nSELECT * FROM #columns ORDER BY primary_key\r\nSELECT * FROM #list\r\nSELECT @row_count AS row_count\r\nDROP TABLE #columns\r\nDROP TABLE #list", - "ConnectionStringTemplate": "data source=##server_name##,##port##;initial catalog=##database_name##;User ID=##username##;Password=##password##;persist security info=False;packet size=4096;", - "DefaultUnsubscribeUrl": "http://emailopentracking.surge365.com/unsubscribe.htm", + "ConnectionStringTemplate": "data source=##server_name##,##port##;initial catalog=##database_name##;User ID=##username##;Password=##password##;persist security info=False;packet size=4096;TrustServerCertificate=True;", + "DefaultUnsubscribeUrl": "https://emailopentracking.surge365.com/unsubscribe.htm", "SendGrid_TestMode": false, "RegularExpression_Email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", "SendGrid_Url": "smtp.sendgrid.net", diff --git a/Surge365.MassEmailReact.Application/DTOs/TargetColumnUpdateDto.cs b/Surge365.MassEmailReact.Application/DTOs/TargetColumnUpdateDto.cs new file mode 100644 index 0000000..a5cc210 --- /dev/null +++ b/Surge365.MassEmailReact.Application/DTOs/TargetColumnUpdateDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class TargetColumnUpdateDto + { + public int? Id { get; private set; } + public string TypeCode { get; set; } = ""; + public string DataTypeCode { get; set; } = ""; + public string Name { get; set; } = ""; + public bool WriteBack { get; set; } + public bool IsEmailAddress { get; set; } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/DTOs/TargetSample.cs b/Surge365.MassEmailReact.Application/DTOs/TargetSample.cs index 78a2284..c9e7d4a 100644 --- a/Surge365.MassEmailReact.Application/DTOs/TargetSample.cs +++ b/Surge365.MassEmailReact.Application/DTOs/TargetSample.cs @@ -13,8 +13,9 @@ namespace Surge365.MassEmailReact.Application.DTOs } public class TargetSampleColumn { - public string Name { get; set; } - public string Type { get; set; } + public string Name { get; set; } = ""; + public string DataType { get; set; } = ""; + public string Type { get; set; } = ""; } public class TargetColumnType diff --git a/Surge365.MassEmailReact.Application/DTOs/TargetUpdateDto.cs b/Surge365.MassEmailReact.Application/DTOs/TargetUpdateDto.cs index 5839a6e..53c67bc 100644 --- a/Surge365.MassEmailReact.Application/DTOs/TargetUpdateDto.cs +++ b/Surge365.MassEmailReact.Application/DTOs/TargetUpdateDto.cs @@ -16,5 +16,6 @@ namespace Surge365.MassEmailReact.Domain.Entities public string FilterQuery { get; set; } = ""; public bool AllowWriteBack { get; set; } = false; public bool IsActive { get; set; } = true; + public List Columns { get; set; } = new List(); } -} +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Application/DTOs/TestTargetDto.cs b/Surge365.MassEmailReact.Application/DTOs/TestTargetDto.cs new file mode 100644 index 0000000..c69d770 --- /dev/null +++ b/Surge365.MassEmailReact.Application/DTOs/TestTargetDto.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class TestTargetDto + { + public int? Id { get; set; } + public int ServerId { get; set; } + public string DatabaseName { get; set; } = ""; + public string ViewName { get; set; } = ""; + public string FilterQuery { get; set; } = ""; + } +} diff --git a/Surge365.MassEmailReact.Application/Interfaces/IMailingRepository.cs b/Surge365.MassEmailReact.Application/Interfaces/IMailingRepository.cs index 382c997..ca6ccc0 100644 --- a/Surge365.MassEmailReact.Application/Interfaces/IMailingRepository.cs +++ b/Surge365.MassEmailReact.Application/Interfaces/IMailingRepository.cs @@ -8,8 +8,8 @@ namespace Surge365.MassEmailReact.Application.Interfaces { Task GetByIdAsync(int id); Task> GetAllAsync(bool activeOnly = true); - Task> GetByStatusAsync(string code, string? startDate, string? endDate); - Task> GetStatisticsByStatusAsync(string code, string? startDate, string? endDate); + Task> GetByStatusAsync(string codes, string? startDate, string? endDate); + Task> GetStatisticsByStatusAsync(string codes, string? startDate, string? endDate); Task GetStatisticByIdAsync(int id); Task NameIsAvailableAsync(int? id, string name); Task GetNextAvailableNameAsync(int? id, string name); diff --git a/Surge365.MassEmailReact.Application/Interfaces/IMailingService.cs b/Surge365.MassEmailReact.Application/Interfaces/IMailingService.cs index 8277d0f..8b60e40 100644 --- a/Surge365.MassEmailReact.Application/Interfaces/IMailingService.cs +++ b/Surge365.MassEmailReact.Application/Interfaces/IMailingService.cs @@ -9,8 +9,8 @@ namespace Surge365.MassEmailReact.Application.Interfaces Task GetByIdAsync(int id); Task> GetAllAsync(bool activeOnly = true); - Task> GetByStatusAsync(string code, string? startDate, string? endDate); - Task> GetStatisticsByStatusAsync(string code, string? startDate, string? endDate); + Task> GetByStatusAsync(string codes, string? startDate, string? endDate); + Task> GetStatisticsByStatusAsync(string codes, string? startDate, string? endDate); Task GetStatisticByIdAsync(int id); Task NameIsAvailableAsync(int? id, string name); Task GetNextAvailableNameAsync(int? id, string name); diff --git a/Surge365.MassEmailReact.Application/Interfaces/ITargetService.cs b/Surge365.MassEmailReact.Application/Interfaces/ITargetService.cs index 8783524..76bb3f5 100644 --- a/Surge365.MassEmailReact.Application/Interfaces/ITargetService.cs +++ b/Surge365.MassEmailReact.Application/Interfaces/ITargetService.cs @@ -10,5 +10,6 @@ namespace Surge365.MassEmailReact.Application.Interfaces Task CreateAsync(TargetUpdateDto targetDto); Task UpdateAsync(TargetUpdateDto targetDto); Task TestTargetAsync(int targetId); + Task TestTargetAsync(TestTargetDto testTarget); } } diff --git a/Surge365.MassEmailReact.Domain/Entities/MailingStatistic.cs b/Surge365.MassEmailReact.Domain/Entities/MailingStatistic.cs index ca9a250..70290b2 100644 --- a/Surge365.MassEmailReact.Domain/Entities/MailingStatistic.cs +++ b/Surge365.MassEmailReact.Domain/Entities/MailingStatistic.cs @@ -10,6 +10,7 @@ namespace Surge365.MassEmailReact.Domain.Entities { public int? MailingId { get; private set; } public string MailingName { get; set; } = ""; + public string SentDate { get; set; } = ""; public int SpamCount { get; set; } public int UniqueClickCount { get; set; } public int ClickCount { get; set; } @@ -26,13 +27,14 @@ namespace Surge365.MassEmailReact.Domain.Entities public MailingStatistic() { } - private MailingStatistic(int? mailingId, string mailingName, int spamCount, int uniqueClickCount, int clickCount, + private MailingStatistic(int? mailingId, string mailingName, string sentDate, int spamCount, int uniqueClickCount, int clickCount, int uniqueOpenCount, int openCount, int invalidCount, int blockedCount, int failedCount, int deliveredCount, int sendCount, int emailCount, int bounceCount, int unsubscribeCount) { MailingId = mailingId; MailingName = mailingName; + SentDate = sentDate; SpamCount = spamCount; UniqueClickCount = uniqueClickCount; ClickCount = clickCount; @@ -48,12 +50,12 @@ namespace Surge365.MassEmailReact.Domain.Entities UnsubscribeCount = unsubscribeCount; } - public static MailingStatistic Create(int? mailingId, string mailingName, int spamCount, int uniqueClickCount, + public static MailingStatistic Create(int? mailingId, string mailingName, string sentDate, int spamCount, int uniqueClickCount, int clickCount, int uniqueOpenCount, int openCount, int invalidCount, int blockedCount, int failedCount, int deliveredCount, int sendCount, int emailCount, int bounceCount, int unsubscribeCount) { - return new MailingStatistic(mailingId, mailingName, spamCount, uniqueClickCount, clickCount, uniqueOpenCount, + return new MailingStatistic(mailingId, mailingName, sentDate, spamCount, uniqueClickCount, clickCount, uniqueOpenCount, openCount, invalidCount, blockedCount, failedCount, deliveredCount, sendCount, emailCount, bounceCount, unsubscribeCount); } diff --git a/Surge365.MassEmailReact.Domain/Entities/Target.cs b/Surge365.MassEmailReact.Domain/Entities/Target.cs index 6db2fcf..df14df2 100644 --- a/Surge365.MassEmailReact.Domain/Entities/Target.cs +++ b/Surge365.MassEmailReact.Domain/Entities/Target.cs @@ -16,6 +16,7 @@ namespace Surge365.MassEmailReact.Domain.Entities public string FilterQuery { get; set; } = ""; public bool AllowWriteBack { get; set; } public bool IsActive { get; set; } + public List Columns { get; set; } = new List(); public Target() { } private Target(int id, int serverId, string name, string databaseName, string viewName, string filterQuery, bool allowWriteBack, bool isActive) diff --git a/Surge365.MassEmailReact.Domain/Entities/TargetColumn.cs b/Surge365.MassEmailReact.Domain/Entities/TargetColumn.cs new file mode 100644 index 0000000..7befa93 --- /dev/null +++ b/Surge365.MassEmailReact.Domain/Entities/TargetColumn.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Surge365.MassEmailReact.Domain.Entities +{ + public class TargetColumn + { + public int Id { get; private set; } + public int TargetId { get; set; } + public string TypeCode { get; set; } = ""; + public string DataTypeCode { get; set; } = ""; + public string Name { get; set; } = ""; + public bool WriteBack { get; set; } + public bool IsEmailAddress { get; set; } + + public TargetColumn() { } + + private TargetColumn(int id, int targetId, string typeCode, string dataTypeCode, string name, + bool writeBack, bool isEmailAddress) + { + Id = id; + TargetId = targetId; + TypeCode = typeCode; + DataTypeCode = dataTypeCode; + Name = name; + WriteBack = writeBack; + IsEmailAddress = isEmailAddress; + } + + public static TargetColumn Create(int id, int targetId, string typeCode, string dataTypeCode, + string name, bool writeBack, bool isEmailAddress) + { + return new TargetColumn(id, targetId, typeCode, dataTypeCode, name, writeBack, isEmailAddress); + } + } +} diff --git a/Surge365.MassEmailReact.Infrastructure/DapperMaps/DapperConfiguration.cs b/Surge365.MassEmailReact.Infrastructure/DapperMaps/DapperConfiguration.cs index 5579772..e9308cf 100644 --- a/Surge365.MassEmailReact.Infrastructure/DapperMaps/DapperConfiguration.cs +++ b/Surge365.MassEmailReact.Infrastructure/DapperMaps/DapperConfiguration.cs @@ -18,6 +18,7 @@ namespace Surge365.MassEmailReact.Infrastructure.DapperMaps FluentMapper.Initialize(config => { config.AddMap(new TargetMap()); + config.AddMap(new TargetColumnMap()); config.AddMap(new ServerMap()); config.AddMap(new TestEmailListMap()); config.AddMap(new BouncedEmailMap()); diff --git a/Surge365.MassEmailReact.Infrastructure/DapperMaps/MailingStatisticMap.cs b/Surge365.MassEmailReact.Infrastructure/DapperMaps/MailingStatisticMap.cs index b246762..1fb2174 100644 --- a/Surge365.MassEmailReact.Infrastructure/DapperMaps/MailingStatisticMap.cs +++ b/Surge365.MassEmailReact.Infrastructure/DapperMaps/MailingStatisticMap.cs @@ -9,6 +9,7 @@ namespace Surge365.MassEmailReact.Infrastructure.DapperMaps { Map(m => m.MailingId).ToColumn("blast_key"); Map(m => m.MailingName).ToColumn("blast_name"); + Map(m => m.SentDate).ToColumn("sent_date"); Map(m => m.SpamCount).ToColumn("spam_count"); Map(m => m.UniqueClickCount).ToColumn("unique_click_count"); Map(m => m.ClickCount).ToColumn("click_count"); diff --git a/Surge365.MassEmailReact.Infrastructure/DapperMaps/TargetColumnMap.cs b/Surge365.MassEmailReact.Infrastructure/DapperMaps/TargetColumnMap.cs new file mode 100644 index 0000000..8a538a7 --- /dev/null +++ b/Surge365.MassEmailReact.Infrastructure/DapperMaps/TargetColumnMap.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Dapper.FluentMap.Mapping; +using Surge365.MassEmailReact.Domain.Entities; + +namespace Surge365.MassEmailReact.Infrastructure.DapperMaps +{ + public class TargetColumnMap : EntityMap + { + public TargetColumnMap() + { + Map(tc => tc.Id).ToColumn("target_column_key"); + Map(tc => tc.TargetId).ToColumn("target_key"); + Map(tc => tc.TypeCode).ToColumn("target_column_type_code"); + Map(tc => tc.DataTypeCode).ToColumn("data_type_code"); + Map(tc => tc.Name).ToColumn("name"); + Map(tc => tc.WriteBack).ToColumn("write_back"); + Map(tc => tc.IsEmailAddress).ToColumn("is_email_address"); + } + } +} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Infrastructure/Repositories/MailingRepository.cs b/Surge365.MassEmailReact.Infrastructure/Repositories/MailingRepository.cs index 8f6c1fa..0bfad72 100644 --- a/Surge365.MassEmailReact.Infrastructure/Repositories/MailingRepository.cs +++ b/Surge365.MassEmailReact.Infrastructure/Repositories/MailingRepository.cs @@ -44,19 +44,19 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories using SqlConnection conn = new SqlConnection(ConnectionString); return (await conn.QueryAsync("mem_get_blast_all", new { active_only = activeOnly }, commandType: CommandType.StoredProcedure)).ToList(); } - public async Task> GetByStatusAsync(string code, string? startDate, string? endDate) + public async Task> GetByStatusAsync(string codes, string? startDate, string? endDate) { ArgumentNullException.ThrowIfNull(ConnectionString); using SqlConnection conn = new SqlConnection(ConnectionString); - return (await conn.QueryAsync("mem_get_blast_by_status", new { blast_status_code = code, start_date = startDate, end_date = endDate }, commandType: CommandType.StoredProcedure)).ToList(); + return (await conn.QueryAsync("mem_get_blast_by_status", new { blast_status_codes = codes, start_date = startDate, end_date = endDate }, commandType: CommandType.StoredProcedure)).ToList(); } - public async Task> GetStatisticsByStatusAsync(string code, string? startDate, string? endDate) + public async Task> GetStatisticsByStatusAsync(string codes, string? startDate, string? endDate) { ArgumentNullException.ThrowIfNull(ConnectionString); using SqlConnection conn = new SqlConnection(ConnectionString); - return (await conn.QueryAsync("mem_get_blast_statistic_by_status", new { blast_status_code = code, start_date = startDate, end_date = endDate }, commandType: CommandType.StoredProcedure)).ToList(); + return (await conn.QueryAsync("mem_get_blast_statistic_by_status", new { blast_status_codes = codes, start_date = startDate, end_date = endDate }, commandType: CommandType.StoredProcedure)).ToList(); } public async Task GetStatisticByIdAsync(int id) { @@ -75,7 +75,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories parameters.Add("@blast_name", name, DbType.String); parameters.Add("@available", dbType: DbType.Boolean, direction: ParameterDirection.Output); - await conn.ExecuteAsync("mem_is_blast_name_available2", parameters, commandType: CommandType.StoredProcedure); + await conn.ExecuteAsync("mem_is_blast_name_available", parameters, commandType: CommandType.StoredProcedure); return parameters.Get("@available"); } diff --git a/Surge365.MassEmailReact.Infrastructure/Repositories/TargetRepository.cs b/Surge365.MassEmailReact.Infrastructure/Repositories/TargetRepository.cs index 406bf60..bf23cda 100644 --- a/Surge365.MassEmailReact.Infrastructure/Repositories/TargetRepository.cs +++ b/Surge365.MassEmailReact.Infrastructure/Repositories/TargetRepository.cs @@ -12,6 +12,7 @@ using System.Collections.Generic; using System.Data; using System.Linq; using System.Text; +using System.Text.Json; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -44,8 +45,22 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories ArgumentNullException.ThrowIfNull(_connectionStringName); using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName)); + await conn.OpenAsync(); - return (await conn.QueryAsync("mem_get_target_by_id", new { target_key = targetKey }, commandType: CommandType.StoredProcedure)).FirstOrDefault(); + using var multi = await conn.QueryMultipleAsync( + "mem_get_target_by_id", + new { target_key = targetKey }, + commandType: CommandType.StoredProcedure); + + // Read the first result set (Target) + var target = await multi.ReadSingleOrDefaultAsync(); + if (target == null) return null; + + // Read the second result set (TargetColumns) + var columns = await multi.ReadAsync(); + target.Columns = columns.ToList(); + + return target; } public async Task> GetAllAsync(bool activeOnly = true) { @@ -53,8 +68,31 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories ArgumentNullException.ThrowIfNull(_connectionStringName); using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName)); + await conn.OpenAsync(); - return (await conn.QueryAsync("mem_get_target_all", new { active_only = activeOnly }, commandType: CommandType.StoredProcedure)).ToList(); + using var multi = await conn.QueryMultipleAsync( + "mem_get_target_all", + new { active_only = activeOnly }, + commandType: CommandType.StoredProcedure); + + // Read the first result set (Targets) + var targets = (await multi.ReadAsync()).ToList(); + if (!targets.Any()) return targets; + + // Read the second result set (TargetColumns) + var columns = (await multi.ReadAsync()).ToList(); + + // Map columns to their respective targets + var targetDictionary = targets.ToDictionary(t => t.Id!.Value); + foreach (var column in columns) + { + if (targetDictionary.TryGetValue(column.TargetId, out var target)) + { + target.Columns.Add(column); + } + } + + return targets; } public async Task CreateAsync(Target target) @@ -74,6 +112,8 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories parameters.Add("@filter_query", target.FilterQuery, DbType.String); parameters.Add("@allow_write_back", target.AllowWriteBack, DbType.Boolean); parameters.Add("@is_active", target.IsActive, DbType.Boolean); + if(target.Columns != null) + parameters.Add("@column_json", JsonSerializer.Serialize(target.Columns), DbType.String); // Output parameter parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output); @@ -103,6 +143,8 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories parameters.Add("@filter_query", target.FilterQuery, DbType.String); parameters.Add("@allow_write_back", target.AllowWriteBack, DbType.Boolean); parameters.Add("@is_active", target.IsActive, DbType.Boolean); + if (target.Columns != null) + parameters.Add("@column_json", JsonSerializer.Serialize(target.Columns), DbType.String); // Output parameter parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output); @@ -211,6 +253,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories targetSample.Columns[name] = new TargetSampleColumn { Name = name, + DataType = dataType, Type = typeCode }; } diff --git a/Surge365.MassEmailReact.Infrastructure/Services/MailingService.cs b/Surge365.MassEmailReact.Infrastructure/Services/MailingService.cs index e442e02..2841b8d 100644 --- a/Surge365.MassEmailReact.Infrastructure/Services/MailingService.cs +++ b/Surge365.MassEmailReact.Infrastructure/Services/MailingService.cs @@ -73,13 +73,13 @@ namespace Surge365.MassEmailReact.Infrastructure.Services { return await _mailingRepository.GetAllAsync(activeOnly); } - public async Task> GetByStatusAsync(string statusCode, string? startDate, string? endDate) + public async Task> GetByStatusAsync(string codes, string? startDate, string? endDate) { - return await _mailingRepository.GetByStatusAsync(statusCode, startDate, endDate); + return await _mailingRepository.GetByStatusAsync(codes, startDate, endDate); } - public async Task> GetStatisticsByStatusAsync(string code, string? startDate, string? endDate) + public async Task> GetStatisticsByStatusAsync(string codes, string? startDate, string? endDate) { - return await _mailingRepository.GetStatisticsByStatusAsync(code, startDate, endDate); + return await _mailingRepository.GetStatisticsByStatusAsync(codes, startDate, endDate); } public async Task GetStatisticByIdAsync(int id) { diff --git a/Surge365.MassEmailReact.Infrastructure/Services/TargetService.cs b/Surge365.MassEmailReact.Infrastructure/Services/TargetService.cs index 9ea9e22..960d84e 100644 --- a/Surge365.MassEmailReact.Infrastructure/Services/TargetService.cs +++ b/Surge365.MassEmailReact.Infrastructure/Services/TargetService.cs @@ -55,6 +55,12 @@ namespace Surge365.MassEmailReact.Infrastructure.Services target.AllowWriteBack = targetDto.AllowWriteBack; target.IsActive = targetDto.IsActive; + target.Columns = new List(); + foreach (var columnDto in targetDto.Columns) + { + target.Columns.Add(TargetColumn.Create(columnDto.Id ?? 0, target.Id ?? 0, columnDto.TypeCode, columnDto.DataTypeCode, columnDto.Name, columnDto.WriteBack, columnDto.IsEmailAddress)); + } + return await _targetRepository.CreateAsync(target); } public async Task UpdateAsync(TargetUpdateDto targetDto) @@ -73,6 +79,11 @@ namespace Surge365.MassEmailReact.Infrastructure.Services target.AllowWriteBack = targetDto.AllowWriteBack; target.IsActive = targetDto.IsActive; + target.Columns = new List(); + foreach (var columnDto in targetDto.Columns) + { + target.Columns.Add(TargetColumn.Create(columnDto.Id ?? 0, target.Id ?? 0, columnDto.TypeCode, columnDto.DataTypeCode, columnDto.Name, columnDto.WriteBack, columnDto.IsEmailAddress)); + } return await _targetRepository.UpdateAsync(target); } public async Task TestTargetAsync(int targetId) @@ -85,5 +96,12 @@ namespace Surge365.MassEmailReact.Infrastructure.Services return await _targetRepository.TestTargetAsync(server.ServerName, server.Port, server.Username, server.Password, target.DatabaseName, target.ViewName, target.FilterQuery); } + public async Task TestTargetAsync(TestTargetDto testTarget) + { + Server? server = await _serverRepository.GetByIdAsync(testTarget.ServerId, true); + if (server == null) return null; + + return await _targetRepository.TestTargetAsync(server.ServerName, server.Port, server.Username, server.Password, testTarget.DatabaseName, testTarget.ViewName, testTarget.FilterQuery); + } } } diff --git a/Surge365.MassEmailReact.Web/public/content/js/ytb-massemail-.global.ts b/Surge365.MassEmailReact.Web/public/content/js/ytb-massemail-.global.ts deleted file mode 100644 index 3f486a7..0000000 --- a/Surge365.MassEmailReact.Web/public/content/js/ytb-massemail-.global.ts +++ /dev/null @@ -1,610 +0,0 @@ - - -//global variables -var table; -var allUsers = []; -var user = null; - -//login stuff -function logout() { - $.webMethod({ - 'methodPage': 'UserMethods', - 'methodName': 'LogOut', - 'parameters': {}, - success: function (json) { - - document.location.href = '/login.aspx'; - - - } - }); -} - -function checkLoggedInStatus() { - $.webMethod({ - 'methodPage': 'UserMethods', - 'methodName': 'CheckAuthToken', - 'parameters': {}, - success: function (json) { - if ($.getBoolean(json.success)) { - $.localStorage("session_currentUser", json.data); - - - //set user info - user = $.localStorage("session_currentUser"); - - //set franchise if it isn't set yet. - //franchiseDeferred = $.Deferred(); - - //call back to page that called this method. - // $.when(franchiseDeferred).done(function () { - /* - if ($.sessionStorage("Auth-Impersonate-Guid") != null) { - //session is being impersonated, override name and signout - $("#spanProfileName").html("Signed in as " + user.FirstName + ' ' + user.LastName); - $("#spanProfileNameTitle").html("Signed in as " + user.FirstName + ' ' + user.LastName); - } - else { - $("#spanProfileName").html(user.FirstName + ' ' + user.LastName); - $("#spanProfileNameTitle").html(user.FirstName + ' ' + user.LastName); - } */ - - // $("#spanMenuName").html(user.FirstName + ' ' + user.LastName); - - // var userId = $.localStorage("session_currentUser").userId; - // var imgid = userId + "&t=" + new Date().getTime(); - /* - if (user.HasProfileImage) { - $("#imgRightProfile").attr("src", "/webservices/getprofileimage.ashx?id=" + imgid); - $("#imgRightProfile").on('error', function () { - $("#imgRightProfile").attr("src", "/img/generic_avatar.jpg"); - }); - - $("#imgLeftProfile").attr("src", "/webservices/getprofileimage.ashx?id=" + imgid); - $("#imgLeftProfile").on('error', function () { - $("#imgLeftProfile").attr("src", "/img/generic_avatar.jpg"); - }); - - $("#imgMainProfile").attr("src", "/webservices/getprofileimage.ashx?id=" + imgid); - $("#imgMainProfile").on('error', function () { - $("#imgMainProfile").attr("src", "/img/generic_avatar.jpg"); - }); - } - else { - $("#imgRightProfile").attr("src", "/img/generic_avatar.jpg"); - $("#imgLeftProfile").attr("src", "/img/generic_avatar.jpg"); - $("#imgMainProfile").attr("src", "/img/generic_avatar.jpg"); - } */ - - loadPage(); - - } - else { - $.sessionStorage("redirect_url", document.location.href); - document.location.href = '/login'; - } - } - }); -} - -function setCurrentFranchise(d) { - $.usaHaulersDB.getFranchises().then((data) => { - $.sessionStorage("currentFranchise", data); - if (d != null) { - d.resolve(); - } - }); - - /* - search = {}; - search.FranchiseCode = $.sessionStorage("franchiseCode"); - - $.webMethod({ - 'methodPage': 'UserMethods/', - 'methodName': 'GetReport_Franchise', - 'parameters': { "search": search }, - success: function (json) { - - if ($.getBoolean(json.success)) { - $.sessionStorage("currentFranchise", json.data[0]); - if (d != null) { - d.resolve(); - } - } - } - }); */ - -} - -function createSetting(obj, settingTypeCode, value) { - - //see if setting exists - var bFound = false; - for (var x = 0; x < obj.Settings.length; x++) { - if (obj.Settings[x].SettingTypeCode == settingTypeCode) { - bFound = true; - break; - } - } - if (bFound) { - //existing setting - obj.Settings[x].Value = value; - } - else { - - - setting = {} - setting.SettingTypeCode = settingTypeCode; - setting.Value = value; - - if (obj.Settings == null) { - obj.Settings = []; - } - obj.Settings.push(setting); - } -} - -function createCaseSetting(obj, settingTypeCode, value) { - - //see if setting exists - var bFound = false; - for (var x = 0; x < obj.settings.length; x++) { - if (obj.settings[x].case_setting_type_code == settingTypeCode) { - bFound = true; - break; - } - } - if (bFound) { - //existing setting - obj.settings[x].value = value; - } - else { - - - setting = {} - setting.case_setting_type_code = settingTypeCode; - setting.value = value; - - if (obj.settings == null) { - obj.settings = []; - } - obj.settings.push(setting); - } -} - -function getReportSetting(settings, setting_field, setting_code) { - var value = ""; - if (settings != null) { - for (var x = 0; x < settings.length; x++) { - setting = settings[x]; - if (setting[setting_field] == setting_code) { - value = setting["value"]; - break; - } - } - } - return value; -} - -function getSetting(settings, settingTypeCode) { - var value = ""; - for (var x = 0; x < settings.length; x++) { - setting = settings[x]; - if (setting.SettingTypeCode == settingTypeCode) { - value = setting.Value; - break; - } - } - return value; -} - - - - -//web calls -function callMethod(methodname, callback, sessionName, deferred, search) { - - var params = {}; - - if (search == null) { - search = {}; - } - - - $.webMethod({ - 'methodPage': 'UserMethods/', - 'methodName': methodname, - 'parameters': { "search": search }, - success: function (json) { - - $.sessionStorage(sessionName, json.data); - - if ($.getBoolean(json.success)) { - if (deferred != null) { - deferred.resolve(true); - } - if (callback != null) { - callback(); - } - } - else { - notify("Error", "There was an error performing this action. Please try again.") - } - } - }); -} -function callSearchTransactions(methodname, callback, sessionName, deferred, search) { - //REPEAT OF CALL METHOD,, JSON.DATA DOESN'T WORK HERE.. - //TODO MAKE DYNAMIC LATER. - - var params = {}; - - if (search == null) { - search = {}; - } - - - $.webMethod({ - 'methodPage': 'UserMethods/', - 'methodName': methodname, - 'parameters': { "search": search }, - success: function (json) { - - $.sessionStorage(sessionName, json); - - if ($.getBoolean(json.success)) { - if (deferred != null) { - deferred.resolve(true); - } - if (callback != null) { - callback(); - } - } - else { - notify("Error", "There was an error performing this action. Please try again.") - } - } - }); -} -//overlay -function showwait(item) { - $("#waitOverlay").show(); - if (item) { - item.show(); - - } - else { - $("#loading").show(); - - } - - -} -function hidewait(item) { - $("#waitOverlay").hide(); - if (item) { - item.hide(); - } - else { - $("#loading").hide(); - - } -} - -//helper function -function getParameterByName(name) { - name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); - var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(location.search); - return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); -} -function getCurrency(val) { - return '$' + parseFloat(val, 10).toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, "$1,").toString() -} -function getDate(val) { - var newDate = new Date(Date.parse(val)).toLocaleDateString() - return newDate; -} -function changeDateFormat(inputDate) { // expects Y-m-d - var splitDate = inputDate.split('-'); - if (splitDate.count == 0) { - return null; - } - - var year = splitDate[0]; - var month = splitDate[1]; - var day = splitDate[2]; - - return month + '/' + day + '/' + year; -} - -function getTime(val) { - var newDate = new Date(Date.parse(val)).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - return newDate; -} -function getDateAndTime(val) { - var newDate = new Date(Date.parse(val)).toLocaleDateString() + ' ' + new Date(Date.parse(val)).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true }) - return newDate; -} -function getDateAndTimeAndSeconds(val) { - var newDate = new Date(Date.parse(val)).toLocaleDateString() + ' ' + new Date(Date.parse(val)).toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit', hour12: true, second: '2-digit' }) - return newDate; -} -function notify(title, message) { - $.gritter.add({ - title: title, - text: message, - sticky: false, - time: '2000', - - }); -} -function getObjectByKey(key, value, objectArray) { - for (var x = 0; x < objectArray.length; x++) { - if (objectArray[x][key] == value) { - return objectArray[x]; - break; - } - } -} - -function formatSelect2Data(id, name, data) { - var newdata = []; - //var blank = { }; - // blank.id = ""; - //blank.text = ""; - //data.push(blank); - for (var x = 0; x < data.length; x++) { - var obj = {}; - obj.id = data[x][id]; - obj.text = data[x][name] - newdata.push(obj); - - } - return newdata; -} - -function formatSelect2DataForReturnedBatched(id, name, data) { - var newdata = []; - //var blank = { }; - // blank.id = ""; - //blank.text = ""; - //data.push(blank); - for (var x = 0; x < data.length; x++) { - if (data[x].Type != "ShiftCharges") { - var obj = {}; - obj.id = data[x][id]; - obj.text = data[x][name] - newdata.push(obj); - } - - } - return newdata; -} - -function formatSelect2DataForUser(id, data) { - var newdata = []; - //var blank = { }; - // blank.id = ""; - //blank.text = ""; - //data.push(blank); - for (var x = 0; x < data.length; x++) { - var obj = {}; - obj.id = data[x][id]; - obj.text = data[x]["FirstName"] + ' ' + data[x]["LastName"] + ' - ' + data[x]["userId"]; - newdata.push(obj); - - } - return newdata; -} - -//batches -function fillDropDown(dropdown, method, session, deferred, fieldname, fieldvalue, placeholder) { - if ($.sessionStorage(session) == null) { - callMethod(method, fillDropDown, session, deferred, null); - } - else { - var data = formatSelect2Data(fieldname, fieldvalue, $.sessionStorage(session)); - $(control).select2({ - placeholder: placeholder, - allowClear: true, - selectOnClose: false, - //closeOnSelect: false, - data: data, - width: "200px" - }); - batchDeferred.resolve(true); - } -} - - -var currentDropdown; -//location -function fillLocationsDropDown(deferred) { - - - - var search = {}; - if (!$.userHasRole("Administrators")) { - search.userId = $.localStorage("session_currentUser").userId; - } - else { - search = null; - } - - $.webMethod({ - 'methodPage': 'UserMethods/', - 'methodName': 'SearchLocations', - 'parameters': { "search": search }, - success: function (json) { - - $.sessionStorage("session_locations", json.data); - - if ($.getBoolean(json.success)) { - var data = formatSelect2Data("ID", "Name", $.sessionStorage("session_locations")); - - if ($.userHasRole("Administrators")) { - $("#selLocation").select2({ - placeholder: "Select a location...", - allowClear: true, - selectOnClose: false, - //closeOnSelect: false, - data: data - }); - $("#selLocation").enable(true); - } - else { - $("#selLocation").select2({ - - data: data - }); - $("#selLocation").select2('val', data[0].id); - $("#selLocation").enable(false); - } - - if (deferred != null) { - deferred.resolve(true); - } - } - else { - notify("Error", "There was an error performing this action. Please try again.") - } - } - }); - - -} - -var currentSpinner = null -function spin2(spin) { - //spinner = $("#spanSpinner"); - currentSpinner.show(); - - if (spin) { - rotation = function () { - currentSpinner.rotate({ - angle: 0, - animateTo: 360, - callback: rotation - }); - } - rotation(); - } - else { - currentSpinner.stopRotate(); - currentSpinner.hide(); - } -} - -function spin(spin, spinner) { - //spinner = $("#spanSpinner"); - spinner.show(); - - if (spin) { - rotation = function () { - spinner.rotate({ - angle: 0, - animateTo: 360, - callback: rotation - }); - } - rotation(); - } - else { - spinner.stopRotate(); - spinner.hide(); - } -} - -//employee -function fillEmployeesDropDown(deferred, dropdown) { - - if (dropdown == null) { - dropdown = $("#selEmployee"); - } - $.webMethod({ - 'methodPage': 'UserMethods/', - 'methodName': 'GetUsers', - 'parameters': { "activeOnly": false }, - success: function (json) { - - $.sessionStorage("session_users", json.data); - - if ($.getBoolean(json.success)) { - var data = formatSelect2DataForUser("userId", $.sessionStorage("session_users")); - dropdown.select2({ - placeholder: "Select an employee...", - allowClear: true, - selectOnClose: false, - //closeOnSelect: false, - data: data - }); - - if (deferred != null) { - deferred.resolve(true); - } - } - else { - notify("Error", "There was an error performing this action. Please try again.") - } - } - }); - - -} - - -//new imove global functions - - -function stripCharacters(str) { - return str.replace(/[-' ]/g, '').toUpperCase(); -} - -function searchUsers(d) { - search = {}; - - $.webMethod({ - 'methodPage': 'UserMethods/', - 'methodName': 'SearchUsers', - 'parameters': { "search": search }, - success: function (json) { - - - if ($.getBoolean(json.success)) { - - allUsers = json.data; - d.resolve(); - } - } - }); -} - -function growlWarn(msg) { - $.bootstrapGrowl("" + msg + "", { - type: 'warning', - align: 'center', - width: 'auto', - allow_dismiss: true, - offset: { from: 'top', amount: 60 } - }); -} -function growlSuccess(msg) { - $.bootstrapGrowl("" + msg + "", { - type: 'success', - align: 'center', - width: 'auto', - allow_dismiss: true, - offset: { from: 'top', amount: 60 } - }); -} - -function getUser(users, id) { - for (var x = 0; x < users.length; x++) { - if (users[x].userId == id) { - return users[x]; - break; - } - } -} - diff --git a/Surge365.MassEmailReact.Web/public/content/js/ytb-massemail-utilities-1.0.ts b/Surge365.MassEmailReact.Web/public/content/js/ytb-massemail-utilities-1.0.ts index 7ad8c3d..34d3e50 100644 --- a/Surge365.MassEmailReact.Web/public/content/js/ytb-massemail-utilities-1.0.ts +++ b/Surge365.MassEmailReact.Web/public/content/js/ytb-massemail-utilities-1.0.ts @@ -1,342 +1,342 @@ -//iMove utility functions +////iMove utility functions -//ie is the version of IE running. -var ie = (function () { +////ie is the version of IE running. +//var ie = (function () { - var undef, - v = 3, - div = document.createElement('div'), - all = div.getElementsByTagName('i'); +// var undef, +// v = 3, +// div = document.createElement('div'), +// all = div.getElementsByTagName('i'); - while ( - div.innerHTML = '', - all[0] - ); +// while ( +// div.innerHTML = '', +// all[0] +// ); - return v > 4 ? v : undef; +// return v > 4 ? v : undef; -} ()); +//} ()); -$(document).ajaxSend(function (event, xhr, settings) { - var authToken = $.cookie('Auth-Token'); - if (authToken != null && authToken != undefined && authToken.trim().length > 0) { - xhr.setRequestHeader('Auth-Token', authToken); - } +//$(document).ajaxSend(function (event, xhr, settings) { +// var authToken = $.cookie('Auth-Token'); +// if (authToken != null && authToken != undefined && authToken.trim().length > 0) { +// xhr.setRequestHeader('Auth-Token', authToken); +// } - var impersonateGuid = $.getParameterByName("impersonateid"); - if (impersonateGuid != null && impersonateGuid != undefined && impersonateGuid.trim().length > 0) { - $.sessionStorage('Auth-Impersonate-Guid', impersonateGuid); - } +// var impersonateGuid = $.getParameterByName("impersonateid"); +// if (impersonateGuid != null && impersonateGuid != undefined && impersonateGuid.trim().length > 0) { +// $.sessionStorage('Auth-Impersonate-Guid', impersonateGuid); +// } - impersonateGuid = $.sessionStorage('Auth-Impersonate-Guid'); - if (impersonateGuid != null && impersonateGuid != undefined && impersonateGuid.trim().length > 0) { - xhr.setRequestHeader('Auth-Impersonate-Guid', impersonateGuid); - } +// impersonateGuid = $.sessionStorage('Auth-Impersonate-Guid'); +// if (impersonateGuid != null && impersonateGuid != undefined && impersonateGuid.trim().length > 0) { +// xhr.setRequestHeader('Auth-Impersonate-Guid', impersonateGuid); +// } - var franchiseCode = $.sessionStorage('franchiseCode'); - if (franchiseCode != null && franchiseCode != undefined && franchiseCode.trim().length > 0) { - xhr.setRequestHeader('Auth-Current-Franchise', franchiseCode); - } -}); +// var franchiseCode = $.sessionStorage('franchiseCode'); +// if (franchiseCode != null && franchiseCode != undefined && franchiseCode.trim().length > 0) { +// xhr.setRequestHeader('Auth-Current-Franchise', franchiseCode); +// } +//}); -$.webMethod = function (options) { - var settings = $.extend({ - 'protocol': location.protocol, //http or https - 'methodPage': '', //This should be something like UserMethods - 'methodName': '', //Something like Createuser - 'contentType': 'application/json; charset=utf-8', - 'dataType': 'json', - 'async': true, - 'cache': false, - timeout: 300000, - 'parameters': {}, - success: function (response) { }, - error: function (xhr, error, error_thrown) { } - }, options); +//$.webMethod = function (options) { +// var settings = $.extend({ +// 'protocol': location.protocol, //http or https +// 'methodPage': '', //This should be something like UserMethods +// 'methodName': '', //Something like Createuser +// 'contentType': 'application/json; charset=utf-8', +// 'dataType': 'json', +// 'async': true, +// 'cache': false, +// timeout: 300000, +// 'parameters': {}, +// success: function (response) { }, +// error: function (xhr, error, error_thrown) { } +// }, options); - if (settings.protocol.indexOf(':') < 0) { - settings.protocol = settings.protocol + ':'; - } +// if (settings.protocol.indexOf(':') < 0) { +// settings.protocol = settings.protocol + ':'; +// } - var result; - var baseUrl = window.API_BASE_URL; - //var baseUrl = $("base").attr("href"); - if (baseUrl === undefined || baseUrl === null || baseUrl.length === 0) - baseUrl = ""; - if (baseUrl.length > 0 && baseUrl[baseUrl.length - 1] !== "/") - baseUrl = baseUrl + "/"; - var url = baseUrl + settings.methodPage + (settings.methodName === undefined || settings.methodName === null || settings.methodName.length === 0 ? "" : "/" + settings.methodName); - $.ajax({ - type: "POST", - url: url, - data: JSON.stringify(settings.parameters), - contentType: settings.contentType, - dataType: settings.dataType, - async: settings.async, - cache: settings.cache, - timeout: settings.timeout, - beforeSend: null, - success: function (value, textStatus, request) { - if (value.hasOwnProperty("d")) - result = $.parseJSON(value.d); - else if (typeof value === 'object') - result = value; - else - result = $.parseJSON(value); - var authToken = request.getResponseHeader('Auth-Token'); - var loggedIn = $.getBoolean(request.getResponseHeader('usahl_logged_in')); - $.cookie.raw = true; +// var result; +// var baseUrl = window.API_BASE_URL; +// //var baseUrl = $("base").attr("href"); +// if (baseUrl === undefined || baseUrl === null || baseUrl.length === 0) +// baseUrl = ""; +// if (baseUrl.length > 0 && baseUrl[baseUrl.length - 1] !== "/") +// baseUrl = baseUrl + "/"; +// var url = baseUrl + settings.methodPage + (settings.methodName === undefined || settings.methodName === null || settings.methodName.length === 0 ? "" : "/" + settings.methodName); +// $.ajax({ +// type: "POST", +// url: url, +// data: JSON.stringify(settings.parameters), +// contentType: settings.contentType, +// dataType: settings.dataType, +// async: settings.async, +// cache: settings.cache, +// timeout: settings.timeout, +// beforeSend: null, +// success: function (value, textStatus, request) { +// if (value.hasOwnProperty("d")) +// result = $.parseJSON(value.d); +// else if (typeof value === 'object') +// result = value; +// else +// result = $.parseJSON(value); +// var authToken = request.getResponseHeader('Auth-Token'); +// var loggedIn = $.getBoolean(request.getResponseHeader('usahl_logged_in')); +// $.cookie.raw = true; - if ($.getRememberMe() === true) { - $.cookie('Auth-Token', authToken, { expires: 14, path: '/' }); - $.cookie('usahl_logged_in', loggedIn, { expires: 14, path: '/' }); - } - else { - $.cookie('Auth-Token', authToken, { expires: 365, path: '/' }); - $.cookie('usahl_logged_in', loggedIn, { expires: 365, path: '/' }); - } - if (settings.success !== undefined) - settings.success(result); - }, - error: function (xhr, error, errorThrown) { - if (settings.error !== undefined) - settings.error(xhr, error, errorThrown); - } - }); -} +// if ($.getRememberMe() === true) { +// $.cookie('Auth-Token', authToken, { expires: 14, path: '/' }); +// $.cookie('usahl_logged_in', loggedIn, { expires: 14, path: '/' }); +// } +// else { +// $.cookie('Auth-Token', authToken, { expires: 365, path: '/' }); +// $.cookie('usahl_logged_in', loggedIn, { expires: 365, path: '/' }); +// } +// if (settings.success !== undefined) +// settings.success(result); +// }, +// error: function (xhr, error, errorThrown) { +// if (settings.error !== undefined) +// settings.error(xhr, error, errorThrown); +// } +// }); +//} -$.webMethodAsync = async function (options) { - var settings = $.extend({ - 'protocol': location.protocol, //http or https - 'methodPage': '', //This should be something like UserMethods. - 'methodName': '', //Something like Createuser - 'contentType': 'application/json; charset=utf-8', - 'dataType': 'json', - 'async': true, - 'cache': false, - timeout: 300000, - 'parameters': {}, - success: function (response) { }, - error: function (xhr, error, error_thrown) { } - }, options); +//$.webMethodAsync = async function (options) { +// var settings = $.extend({ +// 'protocol': location.protocol, //http or https +// 'methodPage': '', //This should be something like UserMethods. +// 'methodName': '', //Something like Createuser +// 'contentType': 'application/json; charset=utf-8', +// 'dataType': 'json', +// 'async': true, +// 'cache': false, +// timeout: 300000, +// 'parameters': {}, +// success: function (response) { }, +// error: function (xhr, error, error_thrown) { } +// }, options); - if (settings.protocol.indexOf(':') < 0) { - settings.protocol = settings.protocol + ':'; - } +// if (settings.protocol.indexOf(':') < 0) { +// settings.protocol = settings.protocol + ':'; +// } - var result; - var baseUrl = window.API_BASE_URL; - //var baseUrl = $("base").attr("href"); - if (baseUrl === undefined || baseUrl === null || baseUrl.length === 0) - baseUrl = ""; - if (baseUrl.length > 0 && baseUrl[baseUrl.length - 1] !== "/") - baseUrl = baseUrl + "/"; - var url = baseUrl + settings.methodPage + (settings.methodName === undefined || settings.methodName === null || settings.methodName.length === 0 ? "" : "/" + settings.methodName); - return $.ajax({ - type: "POST", - url: url, - data: JSON.stringify(settings.parameters), - contentType: settings.contentType, - dataType: settings.dataType, - async: settings.async, - cache: settings.cache, - timeout: settings.timeout, - beforeSend: null, - success: function (value, textStatus, request) { - if (value.hasOwnProperty("d")) - result = $.parseJSON(value.d); - else if (typeof value === 'object') - result = value; - else - result = $.parseJSON(value); - var authToken = request.getResponseHeader('Auth-Token'); - var loggedIn = $.getBoolean(request.getResponseHeader('usahl_logged_in')); - $.cookie.raw = true; +// var result; +// var baseUrl = window.API_BASE_URL; +// //var baseUrl = $("base").attr("href"); +// if (baseUrl === undefined || baseUrl === null || baseUrl.length === 0) +// baseUrl = ""; +// if (baseUrl.length > 0 && baseUrl[baseUrl.length - 1] !== "/") +// baseUrl = baseUrl + "/"; +// var url = baseUrl + settings.methodPage + (settings.methodName === undefined || settings.methodName === null || settings.methodName.length === 0 ? "" : "/" + settings.methodName); +// return $.ajax({ +// type: "POST", +// url: url, +// data: JSON.stringify(settings.parameters), +// contentType: settings.contentType, +// dataType: settings.dataType, +// async: settings.async, +// cache: settings.cache, +// timeout: settings.timeout, +// beforeSend: null, +// success: function (value, textStatus, request) { +// if (value.hasOwnProperty("d")) +// result = $.parseJSON(value.d); +// else if (typeof value === 'object') +// result = value; +// else +// result = $.parseJSON(value); +// var authToken = request.getResponseHeader('Auth-Token'); +// var loggedIn = $.getBoolean(request.getResponseHeader('usahl_logged_in')); +// $.cookie.raw = true; - if ($.getRememberMe() === true) { - $.cookie('Auth-Token', authToken, { expires: 14, path: '/' }); - $.cookie('usahl_logged_in', loggedIn, { expires: 14, path: '/' }); - } - else { - $.cookie('Auth-Token', authToken, { expires: 365, path: '/' }); - $.cookie('usahl_logged_in', loggedIn, { expires: 365, path: '/' }); - } - if (settings.success !== undefined) - settings.success(result); - }, - error: function (xhr, error, errorThrown) { - if (settings.error !== undefined) - settings.error(xhr, error, errorThrown); - } - }); -} +// if ($.getRememberMe() === true) { +// $.cookie('Auth-Token', authToken, { expires: 14, path: '/' }); +// $.cookie('usahl_logged_in', loggedIn, { expires: 14, path: '/' }); +// } +// else { +// $.cookie('Auth-Token', authToken, { expires: 365, path: '/' }); +// $.cookie('usahl_logged_in', loggedIn, { expires: 365, path: '/' }); +// } +// if (settings.success !== undefined) +// settings.success(result); +// }, +// error: function (xhr, error, errorThrown) { +// if (settings.error !== undefined) +// settings.error(xhr, error, errorThrown); +// } +// }); +//} -$.getBoolean = function (variable) { - var vtype; - var toReturn; +//$.getBoolean = function (variable) { +// var vtype; +// var toReturn; - if (variable != null) { - switch (typeof (variable)) { - case 'boolean': - vtype = "boolean"; - return variable; - break; +// if (variable != null) { +// switch (typeof (variable)) { +// case 'boolean': +// vtype = "boolean"; +// return variable; +// break; - case 'number': - vtype = "number"; - if (variable == 0) - toReturn = false; - else toReturn = true; - break; +// case 'number': +// vtype = "number"; +// if (variable == 0) +// toReturn = false; +// else toReturn = true; +// break; - case 'string': - vtype = "string"; - if (variable.toLowerCase() == "true" || variable.toLowerCase() == "yes") - toReturn = true; - else if (variable.toLowerCase() == "false" || variable.toLowerCase() == "no") - toReturn = false; - else if (variable.length > 0) - toReturn = true; - else if (variable.length == 0) - toReturn = false; - break; +// case 'string': +// vtype = "string"; +// if (variable.toLowerCase() == "true" || variable.toLowerCase() == "yes") +// toReturn = true; +// else if (variable.toLowerCase() == "false" || variable.toLowerCase() == "no") +// toReturn = false; +// else if (variable.length > 0) +// toReturn = true; +// else if (variable.length == 0) +// toReturn = false; +// break; - } +// } - return toReturn; - } -}; +// return toReturn; +// } +//}; -$.isLoggedIn = function () { - return $.getBoolean($.cookie('usahl_logged_in')); -} +//$.isLoggedIn = function () { +// return $.getBoolean($.cookie('usahl_logged_in')); +//} -//Send the auth-token in all ajax requests -$.ajaxSetup({ - beforeSend: function (xhr, settings) { - xhr.setRequestHeader('Auth-Token', $.cookie('Auth-Token')); - } -}); +////Send the auth-token in all ajax requests +//$.ajaxSetup({ +// beforeSend: function (xhr, settings) { +// xhr.setRequestHeader('Auth-Token', $.cookie('Auth-Token')); +// } +//}); -$.sessionStorage = function (key, value) { - if (value === undefined) { - var val = window.sessionStorage.getItem(key); - if ((/^usahl_json/).test(val)) { - val = val.substring(11, val.length); - val = $.parseJSON(val); - } - return val; - } - else { - var val = value; - if (typeof value === 'object') { - val = "usahl_json:" + JSON.stringify(value); - } +//$.sessionStorage = function (key, value) { +// if (value === undefined) { +// var val = window.sessionStorage.getItem(key); +// if ((/^usahl_json/).test(val)) { +// val = val.substring(11, val.length); +// val = $.parseJSON(val); +// } +// return val; +// } +// else { +// var val = value; +// if (typeof value === 'object') { +// val = "usahl_json:" + JSON.stringify(value); +// } - window.sessionStorage.setItem(key, val); - } -}; +// window.sessionStorage.setItem(key, val); +// } +//}; -$.sessionStorageClear = function () { - window.sessionStorage.clear(); -}; +//$.sessionStorageClear = function () { +// window.sessionStorage.clear(); +//}; -$.sessionStorageRemove = function (key) { - window.sessionStorage.removeItem(key); -}; +//$.sessionStorageRemove = function (key) { +// window.sessionStorage.removeItem(key); +//}; -$.localStorage = function (key, value) { - if (value === undefined) { - var val = window.localStorage.getItem(key); - if ((/^usahl_json/).test(val)) { - val = val.substring(11, val.length); - val = $.parseJSON(val); - } - return val; - } - else { - var val = value; - if (typeof value === 'object') { - val = "usahl_json:" + JSON.stringify(value); - } +//$.localStorage = function (key, value) { +// if (value === undefined) { +// var val = window.localStorage.getItem(key); +// if ((/^usahl_json/).test(val)) { +// val = val.substring(11, val.length); +// val = $.parseJSON(val); +// } +// return val; +// } +// else { +// var val = value; +// if (typeof value === 'object') { +// val = "usahl_json:" + JSON.stringify(value); +// } - window.localStorage.setItem(key, val); - } -}; +// window.localStorage.setItem(key, val); +// } +//}; -$.localStorageClear = function () { - window.localStorage.clear(); -}; +//$.localStorageClear = function () { +// window.localStorage.clear(); +//}; -$.localStorageRemove = function (key) { - window.localStorage.removeItem(key); -}; +//$.localStorageRemove = function (key) { +// window.localStorage.removeItem(key); +//}; -$.setRememberMe = function (rememberMe) { - $.sessionStorage("usahl_remember_me", rememberMe); -} -$.getRememberMe = function () { - return true; - /* - var rememberMe = $.sessionStorage("usahl_remember_me"); - if (rememberMe === undefined || rememberMe == null) { - return false; - } +//$.setRememberMe = function (rememberMe) { +// $.sessionStorage("usahl_remember_me", rememberMe); +//} +//$.getRememberMe = function () { +// return true; +// /* +// var rememberMe = $.sessionStorage("usahl_remember_me"); +// if (rememberMe === undefined || rememberMe == null) { +// return false; +// } - return $.getBoolean(rememberMe); - */ -} +// return $.getBoolean(rememberMe); +// */ +//} -$.userHasPermission = function (permissionCode) { - var allowed = false; - var user = $.localStorage("session_currentUser"); - $.each(user.Permissions, function () { - if ($.compareStrings(this.Code, permissionCode)) { - allowed = true; - } - }); - return allowed; -} +//$.userHasPermission = function (permissionCode) { +// var allowed = false; +// var user = $.localStorage("session_currentUser"); +// $.each(user.Permissions, function () { +// if ($.compareStrings(this.Code, permissionCode)) { +// allowed = true; +// } +// }); +// return allowed; +//} -$.userHasRole = function (role) { - var hasRole = false; - var user = $.localStorage("session_currentUser"); - $.each(user.Roles, function () { - if ($.compareStrings(this.FriendlyName, role)) { - hasRole = true; - } - }); - return hasRole; -} +//$.userHasRole = function (role) { +// var hasRole = false; +// var user = $.localStorage("session_currentUser"); +// $.each(user.Roles, function () { +// if ($.compareStrings(this.FriendlyName, role)) { +// hasRole = true; +// } +// }); +// return hasRole; +//} -$.checkForRole = function (user, role) { - var hasRole = false; - $.each(user.Roles, function () { - if ($.compareStrings(this.FriendlyName, role)) { - hasRole = true; - } - }); - return hasRole; -} +//$.checkForRole = function (user, role) { +// var hasRole = false; +// $.each(user.Roles, function () { +// if ($.compareStrings(this.FriendlyName, role)) { +// hasRole = true; +// } +// }); +// return hasRole; +//} -$.checkForRoleCode = function (user, role) { - var hasRole = false; - $.each(user.roles, function () { - if ($.compareStrings(this.role_code, role)) { - hasRole = true; - } - }); - return hasRole; -} +//$.checkForRoleCode = function (user, role) { +// var hasRole = false; +// $.each(user.roles, function () { +// if ($.compareStrings(this.role_code, role)) { +// hasRole = true; +// } +// }); +// return hasRole; +//} -$.compareStrings = function (string1, string2) { - return string1.toLowerCase() === string2.toLowerCase(); -} +//$.compareStrings = function (string1, string2) { +// return string1.toLowerCase() === string2.toLowerCase(); +//} -$.getParameterByName = function (name) { - name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); - var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), - results = regex.exec(location.search); - return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); -} \ No newline at end of file +//$.getParameterByName = function (name) { +// name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]"); +// var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"), +// results = regex.exec(location.search); +// return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " ")); +//} \ No newline at end of file diff --git a/Surge365.MassEmailReact.Web/public/web.config b/Surge365.MassEmailReact.Web/public/web.config new file mode 100644 index 0000000..6b32de7 --- /dev/null +++ b/Surge365.MassEmailReact.Web/public/web.config @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Surge365.MassEmailReact.Web/src/components/auth/ProtectedPageWrapper.tsx b/Surge365.MassEmailReact.Web/src/components/auth/ProtectedPageWrapper.tsx index 9f1f5e7..9bc0519 100644 --- a/Surge365.MassEmailReact.Web/src/components/auth/ProtectedPageWrapper.tsx +++ b/Surge365.MassEmailReact.Web/src/components/auth/ProtectedPageWrapper.tsx @@ -16,6 +16,7 @@ export const routeRoleRequirements: Record = { '/scheduledMailings': ['ScheduledMailingTab'], '/activeMailings': ['ActiveMailingTab'], '/completedMailings': ['CompletedMailingTab'], + '/cancelledMailings': ['CompletedMailingTab'], }; const ProtectedPageWrapper: React.FC<{ title: string; children: React.ReactNode }> = ({ title, children }) => { diff --git a/Surge365.MassEmailReact.Web/src/components/layouts/Layout.tsx b/Surge365.MassEmailReact.Web/src/components/layouts/Layout.tsx index 0629389..c64c795 100644 --- a/Surge365.MassEmailReact.Web/src/components/layouts/Layout.tsx +++ b/Surge365.MassEmailReact.Web/src/components/layouts/Layout.tsx @@ -26,6 +26,7 @@ import ListItemText from '@mui/material/ListItemText'; import DashboardIcon from '@mui/icons-material/Dashboard'; import HttpIcon from '@mui/icons-material/Http'; import AccountBoxIcon from '@mui/icons-material/AccountBox'; +import CancelIcon from '@mui/icons-material/Cancel'; import DnsIcon from '@mui/icons-material/Dns'; import TargetIcon from '@mui/icons-material/TrackChanges'; @@ -78,11 +79,11 @@ interface LayoutProps { } const Layout = ({ children }: LayoutProps) => { - const [open, setOpen] = React.useState(true); - const { mode, setMode } = useColorScheme(); // MUI v6 hook for theme switching - const iconButtonRef = React.useRef(null); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down("sm")); //TODO: Move this to shared utils? + const [open, setOpen] = React.useState(!isMobile); + const { mode, setMode } = useColorScheme(); // MUI v6 hook for theme switching + const iconButtonRef = React.useRef(null); const { title } = useTitle(); const navigate = useNavigate(); @@ -98,6 +99,7 @@ const Layout = ({ children }: LayoutProps) => { { text: 'Scheduled Mailings', icon: , path: '/scheduledMailings' }, { text: 'Active Mailings', icon: , path: '/activeMailings' }, { text: 'Completed Mailings', icon: , path: '/completedMailings' }, + { text: 'Cancelled Mailings', icon: , path: '/cancelledMailings' }, ]; const { userRoles, setAuth } = useAuth(); // Use context const [profileMenuAnchorEl, setProfileMenuAnchorEl] = React.useState(null); @@ -281,6 +283,11 @@ const Layout = ({ children }: LayoutProps) => { variant={isMobile ? "temporary" : "persistent"} anchor="left" open={open} + onClose={handleDrawerClose} + ModalProps={{ + keepMounted: true, // Keep mounted to avoid re-render issues + hideBackdrop: isMobile && !open, // Explicitly hide backdrop when closed in mobile + }} sx={{ width: isMobile ? "100%" : open ? `var(--mui-drawer-width, ${drawerWidth}px)` : 0, flexShrink: 0, diff --git a/Surge365.MassEmailReact.Web/src/components/modals/ForgotPasswordModal.tsx b/Surge365.MassEmailReact.Web/src/components/modals/ForgotPasswordModal.tsx index 4a5cfe7..d2e5fed 100644 --- a/Surge365.MassEmailReact.Web/src/components/modals/ForgotPasswordModal.tsx +++ b/Surge365.MassEmailReact.Web/src/components/modals/ForgotPasswordModal.tsx @@ -35,18 +35,22 @@ const ForgotPasswordModal: React.FC = ({ show, onClose if (validate()) { console.log('Processing forgot password for', username); - await utils.webMethod({ - methodPage: 'authenticate', - methodName: 'generatepasswordrecovery', - parameters: { username }, - success: (json: any) => { - if (utils.getBoolean(json.success)) { - setRecoveryStarted(true); - } else { - setUsernameNotFound(true); - } - }, + const apiUrl = "/api/authentication/generatepasswordrecovery"; + const response = await fetch(apiUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username}), }); + + if (response.ok) { + const data = await response.json(); + if (data && utils.getBoolean(data.success)) { + setRecoveryStarted(true); + return; + } + } + + setUsernameNotFound(true); } }; diff --git a/Surge365.MassEmailReact.Web/src/components/modals/MailingEdit.tsx b/Surge365.MassEmailReact.Web/src/components/modals/MailingEdit.tsx index 912e020..3e5517a 100644 --- a/Surge365.MassEmailReact.Web/src/components/modals/MailingEdit.tsx +++ b/Surge365.MassEmailReact.Web/src/components/modals/MailingEdit.tsx @@ -23,7 +23,7 @@ import Target from "@/types/target"; import EmailList from "@/components/forms/EmailList"; import TestEmailList from "@/types/testEmailList"; import TemplateViewer from "@/components/modals/TemplateViewer" -import TargetSampleViewer from "@/components/modals/TargetSampleViewer" +import TargetSampleModal from "@/components/modals/TargetSampleModal" import { useSetupData, SetupData } from "@/context/SetupDataContext"; import { useForm, Controller, Resolver } from "react-hook-form"; import { yupResolver } from "@hookform/resolvers/yup"; @@ -176,7 +176,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => { const [emails, setEmails] = useState([]); // State for email array const [templateViewerOpen, setTemplateViewerOpen] = useState(false); const [currentTemplate, setCurrentTemplate] = useState