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.
This commit is contained in:
parent
26abe9e028
commit
f5b1fe6397
13
Surge365.MassEmailReact.API/.config/dotnet-tools.json
Normal file
13
Surge365.MassEmailReact.API/.config/dotnet-tools.json
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"isRoot": true,
|
||||||
|
"tools": {
|
||||||
|
"dotnet-ef": {
|
||||||
|
"version": "9.0.3",
|
||||||
|
"commands": [
|
||||||
|
"dotnet-ef"
|
||||||
|
],
|
||||||
|
"rollForward": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -67,7 +67,6 @@ namespace Surge365.MassEmailReact.API.Controllers
|
|||||||
return mailing is not null ? Ok(mailing) : NotFound($"Mailing statistics with id '{id}' not found.");
|
return mailing is not null ? Ok(mailing) : NotFound($"Mailing statistics with id '{id}' not found.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> CreateMailing([FromBody] MailingUpdateDto mailingUpdateDto)
|
public async Task<IActionResult> CreateMailing([FromBody] MailingUpdateDto mailingUpdateDto)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -78,5 +78,19 @@ namespace Surge365.MassEmailReact.API.Controllers
|
|||||||
return StatusCode(500, new { message = "Error fetching sample data", error = ex.Message });
|
return StatusCode(500, new { message = "Error fetching sample data", error = ex.Message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
[HttpPost("test")]
|
||||||
|
public async Task<IActionResult> 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5,5 +5,22 @@
|
|||||||
"Microsoft.AspNetCore": "Warning"
|
"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"
|
||||||
|
}
|
||||||
18
Surge365.MassEmailReact.API/appsettings.Uat.json
Normal file
18
Surge365.MassEmailReact.API/appsettings.Uat.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
@ -7,18 +7,18 @@
|
|||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"Jwt": {
|
"Jwt": {
|
||||||
"Secret": "Z9R5aFml+eRMeb7tyf8N9wCq3tZpS/EM6nGqOxlXPtOw4cJ3zS1AByczrIlD5F9d"
|
"Secret": "4r1AJ0riBpEhgaTxhTWMIPs5rv9AlVZjTqrGUoU3DUz4i/Dx9ZfGciIubNODQRO0z3qJZq6VqxGXdsFRJgSb6Q=="
|
||||||
},
|
},
|
||||||
"AppCode": "MassEmailReactApi",
|
"AppCode": "MassEmailReactApi",
|
||||||
"AuthAppCode": "MassEmailWeb",
|
"AuthAppCode": "MassEmailWeb",
|
||||||
"EnvironmentCode": "UAT",
|
"EnvironmentCode": "UAT",
|
||||||
"ConnectionStrings": {
|
"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
|
"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=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
|
"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",
|
"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;",
|
"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": "http://emailopentracking.surge365.com/unsubscribe.htm",
|
"DefaultUnsubscribeUrl": "https://emailopentracking.surge365.com/unsubscribe.htm",
|
||||||
"SendGrid_TestMode": false,
|
"SendGrid_TestMode": false,
|
||||||
"RegularExpression_Email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
"RegularExpression_Email": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
||||||
"SendGrid_Url": "smtp.sendgrid.net",
|
"SendGrid_Url": "smtp.sendgrid.net",
|
||||||
|
|||||||
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,8 +13,9 @@ namespace Surge365.MassEmailReact.Application.DTOs
|
|||||||
}
|
}
|
||||||
public class TargetSampleColumn
|
public class TargetSampleColumn
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; } = "";
|
||||||
public string Type { get; set; }
|
public string DataType { get; set; } = "";
|
||||||
|
public string Type { get; set; } = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TargetColumnType
|
public class TargetColumnType
|
||||||
|
|||||||
@ -16,5 +16,6 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
|||||||
public string FilterQuery { get; set; } = "";
|
public string FilterQuery { get; set; } = "";
|
||||||
public bool AllowWriteBack { get; set; } = false;
|
public bool AllowWriteBack { get; set; } = false;
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
|
public List<TargetColumnUpdateDto> Columns { get; set; } = new List<TargetColumnUpdateDto>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
17
Surge365.MassEmailReact.Application/DTOs/TestTargetDto.cs
Normal file
17
Surge365.MassEmailReact.Application/DTOs/TestTargetDto.cs
Normal file
@ -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; } = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,8 +8,8 @@ namespace Surge365.MassEmailReact.Application.Interfaces
|
|||||||
{
|
{
|
||||||
Task<Mailing?> GetByIdAsync(int id);
|
Task<Mailing?> GetByIdAsync(int id);
|
||||||
Task<List<Mailing>> GetAllAsync(bool activeOnly = true);
|
Task<List<Mailing>> GetAllAsync(bool activeOnly = true);
|
||||||
Task<List<Mailing>> GetByStatusAsync(string code, string? startDate, string? endDate);
|
Task<List<Mailing>> GetByStatusAsync(string codes, string? startDate, string? endDate);
|
||||||
Task<List<MailingStatistic>> GetStatisticsByStatusAsync(string code, string? startDate, string? endDate);
|
Task<List<MailingStatistic>> GetStatisticsByStatusAsync(string codes, string? startDate, string? endDate);
|
||||||
Task<MailingStatistic?> GetStatisticByIdAsync(int id);
|
Task<MailingStatistic?> GetStatisticByIdAsync(int id);
|
||||||
Task<bool> NameIsAvailableAsync(int? id, string name);
|
Task<bool> NameIsAvailableAsync(int? id, string name);
|
||||||
Task<string> GetNextAvailableNameAsync(int? id, string name);
|
Task<string> GetNextAvailableNameAsync(int? id, string name);
|
||||||
|
|||||||
@ -9,8 +9,8 @@ namespace Surge365.MassEmailReact.Application.Interfaces
|
|||||||
Task<Mailing?> GetByIdAsync(int id);
|
Task<Mailing?> GetByIdAsync(int id);
|
||||||
Task<List<Mailing>> GetAllAsync(bool activeOnly = true);
|
Task<List<Mailing>> GetAllAsync(bool activeOnly = true);
|
||||||
|
|
||||||
Task<List<Mailing>> GetByStatusAsync(string code, string? startDate, string? endDate);
|
Task<List<Mailing>> GetByStatusAsync(string codes, string? startDate, string? endDate);
|
||||||
Task<List<MailingStatistic>> GetStatisticsByStatusAsync(string code, string? startDate, string? endDate);
|
Task<List<MailingStatistic>> GetStatisticsByStatusAsync(string codes, string? startDate, string? endDate);
|
||||||
Task<MailingStatistic?> GetStatisticByIdAsync(int id);
|
Task<MailingStatistic?> GetStatisticByIdAsync(int id);
|
||||||
Task<bool> NameIsAvailableAsync(int? id, string name);
|
Task<bool> NameIsAvailableAsync(int? id, string name);
|
||||||
Task<string> GetNextAvailableNameAsync(int? id, string name);
|
Task<string> GetNextAvailableNameAsync(int? id, string name);
|
||||||
|
|||||||
@ -10,5 +10,6 @@ namespace Surge365.MassEmailReact.Application.Interfaces
|
|||||||
Task<int?> CreateAsync(TargetUpdateDto targetDto);
|
Task<int?> CreateAsync(TargetUpdateDto targetDto);
|
||||||
Task<bool> UpdateAsync(TargetUpdateDto targetDto);
|
Task<bool> UpdateAsync(TargetUpdateDto targetDto);
|
||||||
Task<TargetSample?> TestTargetAsync(int targetId);
|
Task<TargetSample?> TestTargetAsync(int targetId);
|
||||||
|
Task<TargetSample?> TestTargetAsync(TestTargetDto testTarget);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,7 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
|||||||
{
|
{
|
||||||
public int? MailingId { get; private set; }
|
public int? MailingId { get; private set; }
|
||||||
public string MailingName { get; set; } = "";
|
public string MailingName { get; set; } = "";
|
||||||
|
public string SentDate { get; set; } = "";
|
||||||
public int SpamCount { get; set; }
|
public int SpamCount { get; set; }
|
||||||
public int UniqueClickCount { get; set; }
|
public int UniqueClickCount { get; set; }
|
||||||
public int ClickCount { get; set; }
|
public int ClickCount { get; set; }
|
||||||
@ -26,13 +27,14 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
|||||||
|
|
||||||
public MailingStatistic() { }
|
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 uniqueOpenCount, int openCount, int invalidCount, int blockedCount,
|
||||||
int failedCount, int deliveredCount, int sendCount, int emailCount,
|
int failedCount, int deliveredCount, int sendCount, int emailCount,
|
||||||
int bounceCount, int unsubscribeCount)
|
int bounceCount, int unsubscribeCount)
|
||||||
{
|
{
|
||||||
MailingId = mailingId;
|
MailingId = mailingId;
|
||||||
MailingName = mailingName;
|
MailingName = mailingName;
|
||||||
|
SentDate = sentDate;
|
||||||
SpamCount = spamCount;
|
SpamCount = spamCount;
|
||||||
UniqueClickCount = uniqueClickCount;
|
UniqueClickCount = uniqueClickCount;
|
||||||
ClickCount = clickCount;
|
ClickCount = clickCount;
|
||||||
@ -48,12 +50,12 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
|||||||
UnsubscribeCount = unsubscribeCount;
|
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 clickCount, int uniqueOpenCount, int openCount, int invalidCount,
|
||||||
int blockedCount, int failedCount, int deliveredCount, int sendCount,
|
int blockedCount, int failedCount, int deliveredCount, int sendCount,
|
||||||
int emailCount, int bounceCount, int unsubscribeCount)
|
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,
|
openCount, invalidCount, blockedCount, failedCount, deliveredCount,
|
||||||
sendCount, emailCount, bounceCount, unsubscribeCount);
|
sendCount, emailCount, bounceCount, unsubscribeCount);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,6 +16,7 @@ namespace Surge365.MassEmailReact.Domain.Entities
|
|||||||
public string FilterQuery { get; set; } = "";
|
public string FilterQuery { get; set; } = "";
|
||||||
public bool AllowWriteBack { get; set; }
|
public bool AllowWriteBack { get; set; }
|
||||||
public bool IsActive { get; set; }
|
public bool IsActive { get; set; }
|
||||||
|
public List<TargetColumn> Columns { get; set; } = new List<TargetColumn>();
|
||||||
|
|
||||||
public Target() { }
|
public Target() { }
|
||||||
private Target(int id, int serverId, string name, string databaseName, string viewName, string filterQuery, bool allowWriteBack, bool isActive)
|
private Target(int id, int serverId, string name, string databaseName, string viewName, string filterQuery, bool allowWriteBack, bool isActive)
|
||||||
|
|||||||
39
Surge365.MassEmailReact.Domain/Entities/TargetColumn.cs
Normal file
39
Surge365.MassEmailReact.Domain/Entities/TargetColumn.cs
Normal file
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,7 @@ namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
|||||||
FluentMapper.Initialize(config =>
|
FluentMapper.Initialize(config =>
|
||||||
{
|
{
|
||||||
config.AddMap(new TargetMap());
|
config.AddMap(new TargetMap());
|
||||||
|
config.AddMap(new TargetColumnMap());
|
||||||
config.AddMap(new ServerMap());
|
config.AddMap(new ServerMap());
|
||||||
config.AddMap(new TestEmailListMap());
|
config.AddMap(new TestEmailListMap());
|
||||||
config.AddMap(new BouncedEmailMap());
|
config.AddMap(new BouncedEmailMap());
|
||||||
|
|||||||
@ -9,6 +9,7 @@ namespace Surge365.MassEmailReact.Infrastructure.DapperMaps
|
|||||||
{
|
{
|
||||||
Map(m => m.MailingId).ToColumn("blast_key");
|
Map(m => m.MailingId).ToColumn("blast_key");
|
||||||
Map(m => m.MailingName).ToColumn("blast_name");
|
Map(m => m.MailingName).ToColumn("blast_name");
|
||||||
|
Map(m => m.SentDate).ToColumn("sent_date");
|
||||||
Map(m => m.SpamCount).ToColumn("spam_count");
|
Map(m => m.SpamCount).ToColumn("spam_count");
|
||||||
Map(m => m.UniqueClickCount).ToColumn("unique_click_count");
|
Map(m => m.UniqueClickCount).ToColumn("unique_click_count");
|
||||||
Map(m => m.ClickCount).ToColumn("click_count");
|
Map(m => m.ClickCount).ToColumn("click_count");
|
||||||
|
|||||||
@ -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<TargetColumn>
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,19 +44,19 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||||
return (await conn.QueryAsync<Mailing>("mem_get_blast_all", new { active_only = activeOnly }, commandType: CommandType.StoredProcedure)).ToList();
|
return (await conn.QueryAsync<Mailing>("mem_get_blast_all", new { active_only = activeOnly }, commandType: CommandType.StoredProcedure)).ToList();
|
||||||
}
|
}
|
||||||
public async Task<List<Mailing>> GetByStatusAsync(string code, string? startDate, string? endDate)
|
public async Task<List<Mailing>> GetByStatusAsync(string codes, string? startDate, string? endDate)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
ArgumentNullException.ThrowIfNull(ConnectionString);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||||
return (await conn.QueryAsync<Mailing>("mem_get_blast_by_status", new { blast_status_code = code, start_date = startDate, end_date = endDate }, commandType: CommandType.StoredProcedure)).ToList();
|
return (await conn.QueryAsync<Mailing>("mem_get_blast_by_status", new { blast_status_codes = codes, start_date = startDate, end_date = endDate }, commandType: CommandType.StoredProcedure)).ToList();
|
||||||
}
|
}
|
||||||
public async Task<List<MailingStatistic>> GetStatisticsByStatusAsync(string code, string? startDate, string? endDate)
|
public async Task<List<MailingStatistic>> GetStatisticsByStatusAsync(string codes, string? startDate, string? endDate)
|
||||||
{
|
{
|
||||||
ArgumentNullException.ThrowIfNull(ConnectionString);
|
ArgumentNullException.ThrowIfNull(ConnectionString);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(ConnectionString);
|
using SqlConnection conn = new SqlConnection(ConnectionString);
|
||||||
return (await conn.QueryAsync<MailingStatistic>("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<MailingStatistic>("mem_get_blast_statistic_by_status", new { blast_status_codes = codes, start_date = startDate, end_date = endDate }, commandType: CommandType.StoredProcedure)).ToList();
|
||||||
}
|
}
|
||||||
public async Task<MailingStatistic?> GetStatisticByIdAsync(int id)
|
public async Task<MailingStatistic?> GetStatisticByIdAsync(int id)
|
||||||
{
|
{
|
||||||
@ -75,7 +75,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
parameters.Add("@blast_name", name, DbType.String);
|
parameters.Add("@blast_name", name, DbType.String);
|
||||||
parameters.Add("@available", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
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<bool>("@available");
|
return parameters.Get<bool>("@available");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ using System.Collections.Generic;
|
|||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@ -44,8 +45,22 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||||
|
await conn.OpenAsync();
|
||||||
|
|
||||||
return (await conn.QueryAsync<Target>("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<Target>();
|
||||||
|
if (target == null) return null;
|
||||||
|
|
||||||
|
// Read the second result set (TargetColumns)
|
||||||
|
var columns = await multi.ReadAsync<TargetColumn>();
|
||||||
|
target.Columns = columns.ToList();
|
||||||
|
|
||||||
|
return target;
|
||||||
}
|
}
|
||||||
public async Task<List<Target>> GetAllAsync(bool activeOnly = true)
|
public async Task<List<Target>> GetAllAsync(bool activeOnly = true)
|
||||||
{
|
{
|
||||||
@ -53,8 +68,31 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
ArgumentNullException.ThrowIfNull(_connectionStringName);
|
||||||
|
|
||||||
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
using SqlConnection conn = new SqlConnection(_config.GetConnectionString(_connectionStringName));
|
||||||
|
await conn.OpenAsync();
|
||||||
|
|
||||||
return (await conn.QueryAsync<Target>("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<Target>()).ToList();
|
||||||
|
if (!targets.Any()) return targets;
|
||||||
|
|
||||||
|
// Read the second result set (TargetColumns)
|
||||||
|
var columns = (await multi.ReadAsync<TargetColumn>()).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<int?> CreateAsync(Target target)
|
public async Task<int?> CreateAsync(Target target)
|
||||||
@ -74,6 +112,8 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
parameters.Add("@filter_query", target.FilterQuery, DbType.String);
|
parameters.Add("@filter_query", target.FilterQuery, DbType.String);
|
||||||
parameters.Add("@allow_write_back", target.AllowWriteBack, DbType.Boolean);
|
parameters.Add("@allow_write_back", target.AllowWriteBack, DbType.Boolean);
|
||||||
parameters.Add("@is_active", target.IsActive, 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
|
// Output parameter
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
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("@filter_query", target.FilterQuery, DbType.String);
|
||||||
parameters.Add("@allow_write_back", target.AllowWriteBack, DbType.Boolean);
|
parameters.Add("@allow_write_back", target.AllowWriteBack, DbType.Boolean);
|
||||||
parameters.Add("@is_active", target.IsActive, 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
|
// Output parameter
|
||||||
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
parameters.Add("@success", dbType: DbType.Boolean, direction: ParameterDirection.Output);
|
||||||
@ -211,6 +253,7 @@ namespace Surge365.MassEmailReact.Infrastructure.Repositories
|
|||||||
targetSample.Columns[name] = new TargetSampleColumn
|
targetSample.Columns[name] = new TargetSampleColumn
|
||||||
{
|
{
|
||||||
Name = name,
|
Name = name,
|
||||||
|
DataType = dataType,
|
||||||
Type = typeCode
|
Type = typeCode
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,13 +73,13 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
|
|||||||
{
|
{
|
||||||
return await _mailingRepository.GetAllAsync(activeOnly);
|
return await _mailingRepository.GetAllAsync(activeOnly);
|
||||||
}
|
}
|
||||||
public async Task<List<Mailing>> GetByStatusAsync(string statusCode, string? startDate, string? endDate)
|
public async Task<List<Mailing>> GetByStatusAsync(string codes, string? startDate, string? endDate)
|
||||||
{
|
{
|
||||||
return await _mailingRepository.GetByStatusAsync(statusCode, startDate, endDate);
|
return await _mailingRepository.GetByStatusAsync(codes, startDate, endDate);
|
||||||
}
|
}
|
||||||
public async Task<List<MailingStatistic>> GetStatisticsByStatusAsync(string code, string? startDate, string? endDate)
|
public async Task<List<MailingStatistic>> GetStatisticsByStatusAsync(string codes, string? startDate, string? endDate)
|
||||||
{
|
{
|
||||||
return await _mailingRepository.GetStatisticsByStatusAsync(code, startDate, endDate);
|
return await _mailingRepository.GetStatisticsByStatusAsync(codes, startDate, endDate);
|
||||||
}
|
}
|
||||||
public async Task<MailingStatistic?> GetStatisticByIdAsync(int id)
|
public async Task<MailingStatistic?> GetStatisticByIdAsync(int id)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -55,6 +55,12 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
|
|||||||
target.AllowWriteBack = targetDto.AllowWriteBack;
|
target.AllowWriteBack = targetDto.AllowWriteBack;
|
||||||
target.IsActive = targetDto.IsActive;
|
target.IsActive = targetDto.IsActive;
|
||||||
|
|
||||||
|
target.Columns = new List<TargetColumn>();
|
||||||
|
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);
|
return await _targetRepository.CreateAsync(target);
|
||||||
}
|
}
|
||||||
public async Task<bool> UpdateAsync(TargetUpdateDto targetDto)
|
public async Task<bool> UpdateAsync(TargetUpdateDto targetDto)
|
||||||
@ -73,6 +79,11 @@ namespace Surge365.MassEmailReact.Infrastructure.Services
|
|||||||
target.AllowWriteBack = targetDto.AllowWriteBack;
|
target.AllowWriteBack = targetDto.AllowWriteBack;
|
||||||
target.IsActive = targetDto.IsActive;
|
target.IsActive = targetDto.IsActive;
|
||||||
|
|
||||||
|
target.Columns = new List<TargetColumn>();
|
||||||
|
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);
|
return await _targetRepository.UpdateAsync(target);
|
||||||
}
|
}
|
||||||
public async Task<TargetSample?> TestTargetAsync(int targetId)
|
public async Task<TargetSample?> 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);
|
return await _targetRepository.TestTargetAsync(server.ServerName, server.Port, server.Username, server.Password, target.DatabaseName, target.ViewName, target.FilterQuery);
|
||||||
}
|
}
|
||||||
|
public async Task<TargetSample?> 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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("<strong>" + msg + "</strong>", {
|
|
||||||
type: 'warning',
|
|
||||||
align: 'center',
|
|
||||||
width: 'auto',
|
|
||||||
allow_dismiss: true,
|
|
||||||
offset: { from: 'top', amount: 60 }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function growlSuccess(msg) {
|
|
||||||
$.bootstrapGrowl("<strong>" + msg + "</strong>", {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,342 +1,342 @@
|
|||||||
//iMove utility functions
|
////iMove utility functions
|
||||||
|
|
||||||
|
|
||||||
//ie is the version of IE running.
|
////ie is the version of IE running.
|
||||||
var ie = (function () {
|
//var ie = (function () {
|
||||||
|
|
||||||
var undef,
|
// var undef,
|
||||||
v = 3,
|
// v = 3,
|
||||||
div = document.createElement('div'),
|
// div = document.createElement('div'),
|
||||||
all = div.getElementsByTagName('i');
|
// all = div.getElementsByTagName('i');
|
||||||
|
|
||||||
while (
|
// while (
|
||||||
div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
|
// div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->',
|
||||||
all[0]
|
// all[0]
|
||||||
);
|
// );
|
||||||
|
|
||||||
return v > 4 ? v : undef;
|
// return v > 4 ? v : undef;
|
||||||
|
|
||||||
} ());
|
//} ());
|
||||||
|
|
||||||
$(document).ajaxSend(function (event, xhr, settings) {
|
//$(document).ajaxSend(function (event, xhr, settings) {
|
||||||
var authToken = $.cookie('Auth-Token');
|
// var authToken = $.cookie('Auth-Token');
|
||||||
if (authToken != null && authToken != undefined && authToken.trim().length > 0) {
|
// if (authToken != null && authToken != undefined && authToken.trim().length > 0) {
|
||||||
xhr.setRequestHeader('Auth-Token', authToken);
|
// xhr.setRequestHeader('Auth-Token', authToken);
|
||||||
}
|
// }
|
||||||
|
|
||||||
var impersonateGuid = $.getParameterByName("impersonateid");
|
// var impersonateGuid = $.getParameterByName("impersonateid");
|
||||||
if (impersonateGuid != null && impersonateGuid != undefined && impersonateGuid.trim().length > 0) {
|
// if (impersonateGuid != null && impersonateGuid != undefined && impersonateGuid.trim().length > 0) {
|
||||||
$.sessionStorage('Auth-Impersonate-Guid', impersonateGuid);
|
// $.sessionStorage('Auth-Impersonate-Guid', impersonateGuid);
|
||||||
}
|
// }
|
||||||
|
|
||||||
impersonateGuid = $.sessionStorage('Auth-Impersonate-Guid');
|
// impersonateGuid = $.sessionStorage('Auth-Impersonate-Guid');
|
||||||
if (impersonateGuid != null && impersonateGuid != undefined && impersonateGuid.trim().length > 0) {
|
// if (impersonateGuid != null && impersonateGuid != undefined && impersonateGuid.trim().length > 0) {
|
||||||
xhr.setRequestHeader('Auth-Impersonate-Guid', impersonateGuid);
|
// xhr.setRequestHeader('Auth-Impersonate-Guid', impersonateGuid);
|
||||||
}
|
// }
|
||||||
|
|
||||||
var franchiseCode = $.sessionStorage('franchiseCode');
|
// var franchiseCode = $.sessionStorage('franchiseCode');
|
||||||
if (franchiseCode != null && franchiseCode != undefined && franchiseCode.trim().length > 0) {
|
// if (franchiseCode != null && franchiseCode != undefined && franchiseCode.trim().length > 0) {
|
||||||
xhr.setRequestHeader('Auth-Current-Franchise', franchiseCode);
|
// xhr.setRequestHeader('Auth-Current-Franchise', franchiseCode);
|
||||||
}
|
// }
|
||||||
});
|
//});
|
||||||
|
|
||||||
$.webMethod = function (options) {
|
//$.webMethod = function (options) {
|
||||||
var settings = $.extend({
|
// var settings = $.extend({
|
||||||
'protocol': location.protocol, //http or https
|
// 'protocol': location.protocol, //http or https
|
||||||
'methodPage': '', //This should be something like UserMethods
|
// 'methodPage': '', //This should be something like UserMethods
|
||||||
'methodName': '', //Something like Createuser
|
// 'methodName': '', //Something like Createuser
|
||||||
'contentType': 'application/json; charset=utf-8',
|
// 'contentType': 'application/json; charset=utf-8',
|
||||||
'dataType': 'json',
|
// 'dataType': 'json',
|
||||||
'async': true,
|
// 'async': true,
|
||||||
'cache': false,
|
// 'cache': false,
|
||||||
timeout: 300000,
|
// timeout: 300000,
|
||||||
'parameters': {},
|
// 'parameters': {},
|
||||||
success: function (response) { },
|
// success: function (response) { },
|
||||||
error: function (xhr, error, error_thrown) { }
|
// error: function (xhr, error, error_thrown) { }
|
||||||
}, options);
|
// }, options);
|
||||||
|
|
||||||
if (settings.protocol.indexOf(':') < 0) {
|
// if (settings.protocol.indexOf(':') < 0) {
|
||||||
settings.protocol = settings.protocol + ':';
|
// settings.protocol = settings.protocol + ':';
|
||||||
}
|
// }
|
||||||
|
|
||||||
var result;
|
// var result;
|
||||||
var baseUrl = window.API_BASE_URL;
|
// var baseUrl = window.API_BASE_URL;
|
||||||
//var baseUrl = $("base").attr("href");
|
// //var baseUrl = $("base").attr("href");
|
||||||
if (baseUrl === undefined || baseUrl === null || baseUrl.length === 0)
|
// if (baseUrl === undefined || baseUrl === null || baseUrl.length === 0)
|
||||||
baseUrl = "";
|
// baseUrl = "";
|
||||||
if (baseUrl.length > 0 && baseUrl[baseUrl.length - 1] !== "/")
|
// if (baseUrl.length > 0 && baseUrl[baseUrl.length - 1] !== "/")
|
||||||
baseUrl = baseUrl + "/";
|
// baseUrl = baseUrl + "/";
|
||||||
var url = baseUrl + settings.methodPage + (settings.methodName === undefined || settings.methodName === null || settings.methodName.length === 0 ? "" : "/" + settings.methodName);
|
// var url = baseUrl + settings.methodPage + (settings.methodName === undefined || settings.methodName === null || settings.methodName.length === 0 ? "" : "/" + settings.methodName);
|
||||||
$.ajax({
|
// $.ajax({
|
||||||
type: "POST",
|
// type: "POST",
|
||||||
url: url,
|
// url: url,
|
||||||
data: JSON.stringify(settings.parameters),
|
// data: JSON.stringify(settings.parameters),
|
||||||
contentType: settings.contentType,
|
// contentType: settings.contentType,
|
||||||
dataType: settings.dataType,
|
// dataType: settings.dataType,
|
||||||
async: settings.async,
|
// async: settings.async,
|
||||||
cache: settings.cache,
|
// cache: settings.cache,
|
||||||
timeout: settings.timeout,
|
// timeout: settings.timeout,
|
||||||
beforeSend: null,
|
// beforeSend: null,
|
||||||
success: function (value, textStatus, request) {
|
// success: function (value, textStatus, request) {
|
||||||
if (value.hasOwnProperty("d"))
|
// if (value.hasOwnProperty("d"))
|
||||||
result = $.parseJSON(value.d);
|
// result = $.parseJSON(value.d);
|
||||||
else if (typeof value === 'object')
|
// else if (typeof value === 'object')
|
||||||
result = value;
|
// result = value;
|
||||||
else
|
// else
|
||||||
result = $.parseJSON(value);
|
// result = $.parseJSON(value);
|
||||||
var authToken = request.getResponseHeader('Auth-Token');
|
// var authToken = request.getResponseHeader('Auth-Token');
|
||||||
var loggedIn = $.getBoolean(request.getResponseHeader('usahl_logged_in'));
|
// var loggedIn = $.getBoolean(request.getResponseHeader('usahl_logged_in'));
|
||||||
$.cookie.raw = true;
|
// $.cookie.raw = true;
|
||||||
|
|
||||||
if ($.getRememberMe() === true) {
|
// if ($.getRememberMe() === true) {
|
||||||
$.cookie('Auth-Token', authToken, { expires: 14, path: '/' });
|
// $.cookie('Auth-Token', authToken, { expires: 14, path: '/' });
|
||||||
$.cookie('usahl_logged_in', loggedIn, { expires: 14, path: '/' });
|
// $.cookie('usahl_logged_in', loggedIn, { expires: 14, path: '/' });
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
$.cookie('Auth-Token', authToken, { expires: 365, path: '/' });
|
// $.cookie('Auth-Token', authToken, { expires: 365, path: '/' });
|
||||||
$.cookie('usahl_logged_in', loggedIn, { expires: 365, path: '/' });
|
// $.cookie('usahl_logged_in', loggedIn, { expires: 365, path: '/' });
|
||||||
}
|
// }
|
||||||
if (settings.success !== undefined)
|
// if (settings.success !== undefined)
|
||||||
settings.success(result);
|
// settings.success(result);
|
||||||
},
|
// },
|
||||||
error: function (xhr, error, errorThrown) {
|
// error: function (xhr, error, errorThrown) {
|
||||||
if (settings.error !== undefined)
|
// if (settings.error !== undefined)
|
||||||
settings.error(xhr, error, errorThrown);
|
// settings.error(xhr, error, errorThrown);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
//}
|
||||||
|
|
||||||
$.webMethodAsync = async function (options) {
|
//$.webMethodAsync = async function (options) {
|
||||||
var settings = $.extend({
|
// var settings = $.extend({
|
||||||
'protocol': location.protocol, //http or https
|
// 'protocol': location.protocol, //http or https
|
||||||
'methodPage': '', //This should be something like UserMethods.
|
// 'methodPage': '', //This should be something like UserMethods.
|
||||||
'methodName': '', //Something like Createuser
|
// 'methodName': '', //Something like Createuser
|
||||||
'contentType': 'application/json; charset=utf-8',
|
// 'contentType': 'application/json; charset=utf-8',
|
||||||
'dataType': 'json',
|
// 'dataType': 'json',
|
||||||
'async': true,
|
// 'async': true,
|
||||||
'cache': false,
|
// 'cache': false,
|
||||||
timeout: 300000,
|
// timeout: 300000,
|
||||||
'parameters': {},
|
// 'parameters': {},
|
||||||
success: function (response) { },
|
// success: function (response) { },
|
||||||
error: function (xhr, error, error_thrown) { }
|
// error: function (xhr, error, error_thrown) { }
|
||||||
}, options);
|
// }, options);
|
||||||
|
|
||||||
if (settings.protocol.indexOf(':') < 0) {
|
// if (settings.protocol.indexOf(':') < 0) {
|
||||||
settings.protocol = settings.protocol + ':';
|
// settings.protocol = settings.protocol + ':';
|
||||||
}
|
// }
|
||||||
|
|
||||||
var result;
|
// var result;
|
||||||
var baseUrl = window.API_BASE_URL;
|
// var baseUrl = window.API_BASE_URL;
|
||||||
//var baseUrl = $("base").attr("href");
|
// //var baseUrl = $("base").attr("href");
|
||||||
if (baseUrl === undefined || baseUrl === null || baseUrl.length === 0)
|
// if (baseUrl === undefined || baseUrl === null || baseUrl.length === 0)
|
||||||
baseUrl = "";
|
// baseUrl = "";
|
||||||
if (baseUrl.length > 0 && baseUrl[baseUrl.length - 1] !== "/")
|
// if (baseUrl.length > 0 && baseUrl[baseUrl.length - 1] !== "/")
|
||||||
baseUrl = baseUrl + "/";
|
// baseUrl = baseUrl + "/";
|
||||||
var url = baseUrl + settings.methodPage + (settings.methodName === undefined || settings.methodName === null || settings.methodName.length === 0 ? "" : "/" + settings.methodName);
|
// var url = baseUrl + settings.methodPage + (settings.methodName === undefined || settings.methodName === null || settings.methodName.length === 0 ? "" : "/" + settings.methodName);
|
||||||
return $.ajax({
|
// return $.ajax({
|
||||||
type: "POST",
|
// type: "POST",
|
||||||
url: url,
|
// url: url,
|
||||||
data: JSON.stringify(settings.parameters),
|
// data: JSON.stringify(settings.parameters),
|
||||||
contentType: settings.contentType,
|
// contentType: settings.contentType,
|
||||||
dataType: settings.dataType,
|
// dataType: settings.dataType,
|
||||||
async: settings.async,
|
// async: settings.async,
|
||||||
cache: settings.cache,
|
// cache: settings.cache,
|
||||||
timeout: settings.timeout,
|
// timeout: settings.timeout,
|
||||||
beforeSend: null,
|
// beforeSend: null,
|
||||||
success: function (value, textStatus, request) {
|
// success: function (value, textStatus, request) {
|
||||||
if (value.hasOwnProperty("d"))
|
// if (value.hasOwnProperty("d"))
|
||||||
result = $.parseJSON(value.d);
|
// result = $.parseJSON(value.d);
|
||||||
else if (typeof value === 'object')
|
// else if (typeof value === 'object')
|
||||||
result = value;
|
// result = value;
|
||||||
else
|
// else
|
||||||
result = $.parseJSON(value);
|
// result = $.parseJSON(value);
|
||||||
var authToken = request.getResponseHeader('Auth-Token');
|
// var authToken = request.getResponseHeader('Auth-Token');
|
||||||
var loggedIn = $.getBoolean(request.getResponseHeader('usahl_logged_in'));
|
// var loggedIn = $.getBoolean(request.getResponseHeader('usahl_logged_in'));
|
||||||
$.cookie.raw = true;
|
// $.cookie.raw = true;
|
||||||
|
|
||||||
if ($.getRememberMe() === true) {
|
// if ($.getRememberMe() === true) {
|
||||||
$.cookie('Auth-Token', authToken, { expires: 14, path: '/' });
|
// $.cookie('Auth-Token', authToken, { expires: 14, path: '/' });
|
||||||
$.cookie('usahl_logged_in', loggedIn, { expires: 14, path: '/' });
|
// $.cookie('usahl_logged_in', loggedIn, { expires: 14, path: '/' });
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
$.cookie('Auth-Token', authToken, { expires: 365, path: '/' });
|
// $.cookie('Auth-Token', authToken, { expires: 365, path: '/' });
|
||||||
$.cookie('usahl_logged_in', loggedIn, { expires: 365, path: '/' });
|
// $.cookie('usahl_logged_in', loggedIn, { expires: 365, path: '/' });
|
||||||
}
|
// }
|
||||||
if (settings.success !== undefined)
|
// if (settings.success !== undefined)
|
||||||
settings.success(result);
|
// settings.success(result);
|
||||||
},
|
// },
|
||||||
error: function (xhr, error, errorThrown) {
|
// error: function (xhr, error, errorThrown) {
|
||||||
if (settings.error !== undefined)
|
// if (settings.error !== undefined)
|
||||||
settings.error(xhr, error, errorThrown);
|
// settings.error(xhr, error, errorThrown);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
//}
|
||||||
|
|
||||||
$.getBoolean = function (variable) {
|
//$.getBoolean = function (variable) {
|
||||||
var vtype;
|
// var vtype;
|
||||||
var toReturn;
|
// var toReturn;
|
||||||
|
|
||||||
if (variable != null) {
|
// if (variable != null) {
|
||||||
switch (typeof (variable)) {
|
// switch (typeof (variable)) {
|
||||||
case 'boolean':
|
// case 'boolean':
|
||||||
vtype = "boolean";
|
// vtype = "boolean";
|
||||||
return variable;
|
// return variable;
|
||||||
break;
|
// break;
|
||||||
|
|
||||||
case 'number':
|
// case 'number':
|
||||||
vtype = "number";
|
// vtype = "number";
|
||||||
if (variable == 0)
|
// if (variable == 0)
|
||||||
toReturn = false;
|
// toReturn = false;
|
||||||
else toReturn = true;
|
// else toReturn = true;
|
||||||
break;
|
// break;
|
||||||
|
|
||||||
case 'string':
|
// case 'string':
|
||||||
vtype = "string";
|
// vtype = "string";
|
||||||
if (variable.toLowerCase() == "true" || variable.toLowerCase() == "yes")
|
// if (variable.toLowerCase() == "true" || variable.toLowerCase() == "yes")
|
||||||
toReturn = true;
|
// toReturn = true;
|
||||||
else if (variable.toLowerCase() == "false" || variable.toLowerCase() == "no")
|
// else if (variable.toLowerCase() == "false" || variable.toLowerCase() == "no")
|
||||||
toReturn = false;
|
// toReturn = false;
|
||||||
else if (variable.length > 0)
|
// else if (variable.length > 0)
|
||||||
toReturn = true;
|
// toReturn = true;
|
||||||
else if (variable.length == 0)
|
// else if (variable.length == 0)
|
||||||
toReturn = false;
|
// toReturn = false;
|
||||||
break;
|
// break;
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
return toReturn;
|
// return toReturn;
|
||||||
}
|
// }
|
||||||
};
|
//};
|
||||||
|
|
||||||
$.isLoggedIn = function () {
|
//$.isLoggedIn = function () {
|
||||||
return $.getBoolean($.cookie('usahl_logged_in'));
|
// return $.getBoolean($.cookie('usahl_logged_in'));
|
||||||
}
|
//}
|
||||||
|
|
||||||
//Send the auth-token in all ajax requests
|
////Send the auth-token in all ajax requests
|
||||||
$.ajaxSetup({
|
//$.ajaxSetup({
|
||||||
beforeSend: function (xhr, settings) {
|
// beforeSend: function (xhr, settings) {
|
||||||
xhr.setRequestHeader('Auth-Token', $.cookie('Auth-Token'));
|
// xhr.setRequestHeader('Auth-Token', $.cookie('Auth-Token'));
|
||||||
}
|
// }
|
||||||
});
|
//});
|
||||||
|
|
||||||
$.sessionStorage = function (key, value) {
|
//$.sessionStorage = function (key, value) {
|
||||||
if (value === undefined) {
|
// if (value === undefined) {
|
||||||
var val = window.sessionStorage.getItem(key);
|
// var val = window.sessionStorage.getItem(key);
|
||||||
if ((/^usahl_json/).test(val)) {
|
// if ((/^usahl_json/).test(val)) {
|
||||||
val = val.substring(11, val.length);
|
// val = val.substring(11, val.length);
|
||||||
val = $.parseJSON(val);
|
// val = $.parseJSON(val);
|
||||||
}
|
// }
|
||||||
return val;
|
// return val;
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
var val = value;
|
// var val = value;
|
||||||
if (typeof value === 'object') {
|
// if (typeof value === 'object') {
|
||||||
val = "usahl_json:" + JSON.stringify(value);
|
// val = "usahl_json:" + JSON.stringify(value);
|
||||||
}
|
// }
|
||||||
|
|
||||||
window.sessionStorage.setItem(key, val);
|
// window.sessionStorage.setItem(key, val);
|
||||||
}
|
// }
|
||||||
};
|
//};
|
||||||
|
|
||||||
$.sessionStorageClear = function () {
|
//$.sessionStorageClear = function () {
|
||||||
window.sessionStorage.clear();
|
// window.sessionStorage.clear();
|
||||||
};
|
//};
|
||||||
|
|
||||||
$.sessionStorageRemove = function (key) {
|
//$.sessionStorageRemove = function (key) {
|
||||||
window.sessionStorage.removeItem(key);
|
// window.sessionStorage.removeItem(key);
|
||||||
};
|
//};
|
||||||
|
|
||||||
$.localStorage = function (key, value) {
|
//$.localStorage = function (key, value) {
|
||||||
if (value === undefined) {
|
// if (value === undefined) {
|
||||||
var val = window.localStorage.getItem(key);
|
// var val = window.localStorage.getItem(key);
|
||||||
if ((/^usahl_json/).test(val)) {
|
// if ((/^usahl_json/).test(val)) {
|
||||||
val = val.substring(11, val.length);
|
// val = val.substring(11, val.length);
|
||||||
val = $.parseJSON(val);
|
// val = $.parseJSON(val);
|
||||||
}
|
// }
|
||||||
return val;
|
// return val;
|
||||||
}
|
// }
|
||||||
else {
|
// else {
|
||||||
var val = value;
|
// var val = value;
|
||||||
if (typeof value === 'object') {
|
// if (typeof value === 'object') {
|
||||||
val = "usahl_json:" + JSON.stringify(value);
|
// val = "usahl_json:" + JSON.stringify(value);
|
||||||
}
|
// }
|
||||||
|
|
||||||
window.localStorage.setItem(key, val);
|
// window.localStorage.setItem(key, val);
|
||||||
}
|
// }
|
||||||
};
|
//};
|
||||||
|
|
||||||
$.localStorageClear = function () {
|
//$.localStorageClear = function () {
|
||||||
window.localStorage.clear();
|
// window.localStorage.clear();
|
||||||
};
|
//};
|
||||||
|
|
||||||
$.localStorageRemove = function (key) {
|
//$.localStorageRemove = function (key) {
|
||||||
window.localStorage.removeItem(key);
|
// window.localStorage.removeItem(key);
|
||||||
};
|
//};
|
||||||
|
|
||||||
$.setRememberMe = function (rememberMe) {
|
//$.setRememberMe = function (rememberMe) {
|
||||||
$.sessionStorage("usahl_remember_me", rememberMe);
|
// $.sessionStorage("usahl_remember_me", rememberMe);
|
||||||
}
|
//}
|
||||||
$.getRememberMe = function () {
|
//$.getRememberMe = function () {
|
||||||
return true;
|
// return true;
|
||||||
/*
|
// /*
|
||||||
var rememberMe = $.sessionStorage("usahl_remember_me");
|
// var rememberMe = $.sessionStorage("usahl_remember_me");
|
||||||
if (rememberMe === undefined || rememberMe == null) {
|
// if (rememberMe === undefined || rememberMe == null) {
|
||||||
return false;
|
// return false;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return $.getBoolean(rememberMe);
|
// return $.getBoolean(rememberMe);
|
||||||
*/
|
// */
|
||||||
}
|
//}
|
||||||
|
|
||||||
$.userHasPermission = function (permissionCode) {
|
//$.userHasPermission = function (permissionCode) {
|
||||||
var allowed = false;
|
// var allowed = false;
|
||||||
var user = $.localStorage("session_currentUser");
|
// var user = $.localStorage("session_currentUser");
|
||||||
$.each(user.Permissions, function () {
|
// $.each(user.Permissions, function () {
|
||||||
if ($.compareStrings(this.Code, permissionCode)) {
|
// if ($.compareStrings(this.Code, permissionCode)) {
|
||||||
allowed = true;
|
// allowed = true;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
return allowed;
|
// return allowed;
|
||||||
}
|
//}
|
||||||
|
|
||||||
$.userHasRole = function (role) {
|
//$.userHasRole = function (role) {
|
||||||
var hasRole = false;
|
// var hasRole = false;
|
||||||
var user = $.localStorage("session_currentUser");
|
// var user = $.localStorage("session_currentUser");
|
||||||
$.each(user.Roles, function () {
|
// $.each(user.Roles, function () {
|
||||||
if ($.compareStrings(this.FriendlyName, role)) {
|
// if ($.compareStrings(this.FriendlyName, role)) {
|
||||||
hasRole = true;
|
// hasRole = true;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
return hasRole;
|
// return hasRole;
|
||||||
}
|
//}
|
||||||
|
|
||||||
$.checkForRole = function (user, role) {
|
//$.checkForRole = function (user, role) {
|
||||||
var hasRole = false;
|
// var hasRole = false;
|
||||||
$.each(user.Roles, function () {
|
// $.each(user.Roles, function () {
|
||||||
if ($.compareStrings(this.FriendlyName, role)) {
|
// if ($.compareStrings(this.FriendlyName, role)) {
|
||||||
hasRole = true;
|
// hasRole = true;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
return hasRole;
|
// return hasRole;
|
||||||
}
|
//}
|
||||||
|
|
||||||
$.checkForRoleCode = function (user, role) {
|
//$.checkForRoleCode = function (user, role) {
|
||||||
var hasRole = false;
|
// var hasRole = false;
|
||||||
$.each(user.roles, function () {
|
// $.each(user.roles, function () {
|
||||||
if ($.compareStrings(this.role_code, role)) {
|
// if ($.compareStrings(this.role_code, role)) {
|
||||||
hasRole = true;
|
// hasRole = true;
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
return hasRole;
|
// return hasRole;
|
||||||
}
|
//}
|
||||||
|
|
||||||
$.compareStrings = function (string1, string2) {
|
//$.compareStrings = function (string1, string2) {
|
||||||
return string1.toLowerCase() === string2.toLowerCase();
|
// return string1.toLowerCase() === string2.toLowerCase();
|
||||||
}
|
//}
|
||||||
|
|
||||||
$.getParameterByName = function (name) {
|
//$.getParameterByName = function (name) {
|
||||||
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
// name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
|
||||||
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
// var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
|
||||||
results = regex.exec(location.search);
|
// results = regex.exec(location.search);
|
||||||
return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
// return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
|
||||||
}
|
//}
|
||||||
18
Surge365.MassEmailReact.Web/public/web.config
Normal file
18
Surge365.MassEmailReact.Web/public/web.config
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<configuration>
|
||||||
|
<system.webServer>
|
||||||
|
<rewrite>
|
||||||
|
<rules>
|
||||||
|
<rule name="SPA" stopProcessing="true">
|
||||||
|
<match url=".*" />
|
||||||
|
<conditions logicalGrouping="MatchAll">
|
||||||
|
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" />
|
||||||
|
<add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" />
|
||||||
|
<add input="{URL}" pattern="^/api(/.*|$)" negate="true" />
|
||||||
|
</conditions>
|
||||||
|
<action type="Rewrite" url="index.html" />
|
||||||
|
</rule>
|
||||||
|
</rules>
|
||||||
|
</rewrite>
|
||||||
|
</system.webServer>
|
||||||
|
</configuration>
|
||||||
@ -16,6 +16,7 @@ export const routeRoleRequirements: Record<string, string[]> = {
|
|||||||
'/scheduledMailings': ['ScheduledMailingTab'],
|
'/scheduledMailings': ['ScheduledMailingTab'],
|
||||||
'/activeMailings': ['ActiveMailingTab'],
|
'/activeMailings': ['ActiveMailingTab'],
|
||||||
'/completedMailings': ['CompletedMailingTab'],
|
'/completedMailings': ['CompletedMailingTab'],
|
||||||
|
'/cancelledMailings': ['CompletedMailingTab'],
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProtectedPageWrapper: React.FC<{ title: string; children: React.ReactNode }> = ({ title, children }) => {
|
const ProtectedPageWrapper: React.FC<{ title: string; children: React.ReactNode }> = ({ title, children }) => {
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import ListItemText from '@mui/material/ListItemText';
|
|||||||
import DashboardIcon from '@mui/icons-material/Dashboard';
|
import DashboardIcon from '@mui/icons-material/Dashboard';
|
||||||
import HttpIcon from '@mui/icons-material/Http';
|
import HttpIcon from '@mui/icons-material/Http';
|
||||||
import AccountBoxIcon from '@mui/icons-material/AccountBox';
|
import AccountBoxIcon from '@mui/icons-material/AccountBox';
|
||||||
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
|
|
||||||
import DnsIcon from '@mui/icons-material/Dns';
|
import DnsIcon from '@mui/icons-material/Dns';
|
||||||
import TargetIcon from '@mui/icons-material/TrackChanges';
|
import TargetIcon from '@mui/icons-material/TrackChanges';
|
||||||
@ -78,11 +79,11 @@ interface LayoutProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Layout = ({ children }: 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<HTMLButtonElement>(null);
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm")); //TODO: Move this to shared utils?
|
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<HTMLButtonElement>(null);
|
||||||
const { title } = useTitle();
|
const { title } = useTitle();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@ -98,6 +99,7 @@ const Layout = ({ children }: LayoutProps) => {
|
|||||||
{ text: 'Scheduled Mailings', icon: <ScheduleSendIcon />, path: '/scheduledMailings' },
|
{ text: 'Scheduled Mailings', icon: <ScheduleSendIcon />, path: '/scheduledMailings' },
|
||||||
{ text: 'Active Mailings', icon: <AutorenewIcon />, path: '/activeMailings' },
|
{ text: 'Active Mailings', icon: <AutorenewIcon />, path: '/activeMailings' },
|
||||||
{ text: 'Completed Mailings', icon: <CheckCircleIcon />, path: '/completedMailings' },
|
{ text: 'Completed Mailings', icon: <CheckCircleIcon />, path: '/completedMailings' },
|
||||||
|
{ text: 'Cancelled Mailings', icon: <CancelIcon />, path: '/cancelledMailings' },
|
||||||
];
|
];
|
||||||
const { userRoles, setAuth } = useAuth(); // Use context
|
const { userRoles, setAuth } = useAuth(); // Use context
|
||||||
const [profileMenuAnchorEl, setProfileMenuAnchorEl] = React.useState<null | HTMLElement>(null);
|
const [profileMenuAnchorEl, setProfileMenuAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
@ -281,6 +283,11 @@ const Layout = ({ children }: LayoutProps) => {
|
|||||||
variant={isMobile ? "temporary" : "persistent"}
|
variant={isMobile ? "temporary" : "persistent"}
|
||||||
anchor="left"
|
anchor="left"
|
||||||
open={open}
|
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={{
|
sx={{
|
||||||
width: isMobile ? "100%" : open ? `var(--mui-drawer-width, ${drawerWidth}px)` : 0,
|
width: isMobile ? "100%" : open ? `var(--mui-drawer-width, ${drawerWidth}px)` : 0,
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
|
|||||||
@ -35,18 +35,22 @@ const ForgotPasswordModal: React.FC<ForgotPasswordModalProps> = ({ show, onClose
|
|||||||
|
|
||||||
if (validate()) {
|
if (validate()) {
|
||||||
console.log('Processing forgot password for', username);
|
console.log('Processing forgot password for', username);
|
||||||
await utils.webMethod({
|
const apiUrl = "/api/authentication/generatepasswordrecovery";
|
||||||
methodPage: 'authenticate',
|
const response = await fetch(apiUrl, {
|
||||||
methodName: 'generatepasswordrecovery',
|
method: "POST",
|
||||||
parameters: { username },
|
headers: { "Content-Type": "application/json" },
|
||||||
success: (json: any) => {
|
body: JSON.stringify({ username}),
|
||||||
if (utils.getBoolean(json.success)) {
|
|
||||||
setRecoveryStarted(true);
|
|
||||||
} else {
|
|
||||||
setUsernameNotFound(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
if (data && utils.getBoolean(data.success)) {
|
||||||
|
setRecoveryStarted(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setUsernameNotFound(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import Target from "@/types/target";
|
|||||||
import EmailList from "@/components/forms/EmailList";
|
import EmailList from "@/components/forms/EmailList";
|
||||||
import TestEmailList from "@/types/testEmailList";
|
import TestEmailList from "@/types/testEmailList";
|
||||||
import TemplateViewer from "@/components/modals/TemplateViewer"
|
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 { useSetupData, SetupData } from "@/context/SetupDataContext";
|
||||||
import { useForm, Controller, Resolver } from "react-hook-form";
|
import { useForm, Controller, Resolver } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
@ -176,7 +176,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
const [emails, setEmails] = useState<string[]>([]); // State for email array
|
const [emails, setEmails] = useState<string[]>([]); // State for email array
|
||||||
const [templateViewerOpen, setTemplateViewerOpen] = useState<boolean>(false);
|
const [templateViewerOpen, setTemplateViewerOpen] = useState<boolean>(false);
|
||||||
const [currentTemplate, setCurrentTemplate] = useState<Template | null>(null);
|
const [currentTemplate, setCurrentTemplate] = useState<Template | null>(null);
|
||||||
const [targetSampleViewerOpen, setTargetSampleViewerOpen] = useState<boolean>(false);
|
const [TargetSampleModalOpen, setTargetSampleModalOpen] = useState<boolean>(false);
|
||||||
const [currentTarget, setCurrentTarget] = useState<Target | null>(null);
|
const [currentTarget, setCurrentTarget] = useState<Target | null>(null);
|
||||||
|
|
||||||
const { register, trigger, control, handleSubmit, reset, formState: { errors } } = useForm<Mailing>({
|
const { register, trigger, control, handleSubmit, reset, formState: { errors } } = useForm<Mailing>({
|
||||||
@ -336,8 +336,8 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
const handleTemplateViewerOpen = () => {
|
const handleTemplateViewerOpen = () => {
|
||||||
setTemplateViewerOpen(!templateViewerOpen);
|
setTemplateViewerOpen(!templateViewerOpen);
|
||||||
};
|
};
|
||||||
const handleTargetSampleViewerOpen = () => {
|
const handleTargetSampleModalOpen = () => {
|
||||||
setTargetSampleViewerOpen(!targetSampleViewerOpen);
|
setTargetSampleModalOpen(!TargetSampleModalOpen);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<LocalizationProvider dateAdapter={AdapterDayjs}> {/* Wrap with LocalizationProvider */}
|
<LocalizationProvider dateAdapter={AdapterDayjs}> {/* Wrap with LocalizationProvider */}
|
||||||
@ -433,7 +433,7 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
/>
|
/>
|
||||||
{currentTarget && (
|
{currentTarget && (
|
||||||
<Button
|
<Button
|
||||||
onClick={handleTargetSampleViewerOpen}
|
onClick={handleTargetSampleModalOpen}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
startIcon={<VisibilityIcon />}
|
startIcon={<VisibilityIcon />}
|
||||||
sx={{ height: '100%', alignSelf: 'center' }}
|
sx={{ height: '100%', alignSelf: 'center' }}
|
||||||
@ -562,11 +562,11 @@ const MailingEdit = ({ open, mailing, onClose, onSave }: MailingEditProps) => {
|
|||||||
onClose={() => { setTemplateViewerOpen(false) }}
|
onClose={() => { setTemplateViewerOpen(false) }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{targetSampleViewerOpen && (
|
{TargetSampleModalOpen && (
|
||||||
<TargetSampleViewer
|
<TargetSampleModal
|
||||||
open={targetSampleViewerOpen}
|
open={TargetSampleModalOpen}
|
||||||
target={currentTarget!}
|
target={currentTarget!}
|
||||||
onClose={() => { setTargetSampleViewerOpen(false) }}
|
onClose={() => { setTargetSampleModalOpen(false) }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import Mailing from '@/types/mailing';
|
|||||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import TemplateViewer from "@/components/modals/TemplateViewer"
|
import TemplateViewer from "@/components/modals/TemplateViewer"
|
||||||
import TargetSampleViewer from "@/components/modals/TargetSampleViewer"
|
import TargetSampleModal from "@/components/modals/TargetSampleModal"
|
||||||
|
|
||||||
interface MailingViewProps {
|
interface MailingViewProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -16,7 +16,7 @@ interface MailingViewProps {
|
|||||||
function MailingView({ open, mailing, onClose }: MailingViewProps) {
|
function MailingView({ open, mailing, onClose }: MailingViewProps) {
|
||||||
const setupData = useSetupData();
|
const setupData = useSetupData();
|
||||||
const [templateViewerOpen, setTemplateViewerOpen] = useState<boolean>(false);
|
const [templateViewerOpen, setTemplateViewerOpen] = useState<boolean>(false);
|
||||||
const [targetSampleViewerOpen, setTargetSampleViewerOpen] = useState<boolean>(false);
|
const [TargetSampleModalOpen, setTargetSampleModalOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
if (!mailing) return null;
|
if (!mailing) return null;
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ function MailingView({ open, mailing, onClose }: MailingViewProps) {
|
|||||||
// Navigation handlers for viewing related entities
|
// Navigation handlers for viewing related entities
|
||||||
const handleViewTarget = () => {
|
const handleViewTarget = () => {
|
||||||
if (target) {
|
if (target) {
|
||||||
setTargetSampleViewerOpen(!targetSampleViewerOpen);
|
setTargetSampleModalOpen(!TargetSampleModalOpen);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -114,11 +114,11 @@ function MailingView({ open, mailing, onClose }: MailingViewProps) {
|
|||||||
onClose={() => { setTemplateViewerOpen(false) }}
|
onClose={() => { setTemplateViewerOpen(false) }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{targetSampleViewerOpen && (
|
{TargetSampleModalOpen && (
|
||||||
<TargetSampleViewer
|
<TargetSampleModal
|
||||||
open={targetSampleViewerOpen}
|
open={TargetSampleModalOpen}
|
||||||
target={target!}
|
target={target!}
|
||||||
onClose={() => { setTargetSampleViewerOpen(false) }}
|
onClose={() => { setTargetSampleModalOpen(false) }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@ -9,12 +10,20 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Switch,
|
Switch,
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
|
Typography,
|
||||||
|
IconButton,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||||
|
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||||
import Target from "@/types/target";
|
import Target from "@/types/target";
|
||||||
import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
||||||
import { useForm, Controller, Resolver } from "react-hook-form";
|
import { useForm, Controller, Resolver } from "react-hook-form";
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
import TargetColumn from "@/types/targetColumn";
|
||||||
|
import TargetSampleColumn from "@/types/targetSampleColumn";
|
||||||
|
import TargetSampleDataGrid from "@/components/shared/TargetSampleDataGrid";
|
||||||
|
import { GridColDef } from '@mui/x-data-grid';
|
||||||
|
|
||||||
type TargetEditProps = {
|
type TargetEditProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -32,7 +41,6 @@ const schema = yup.object().shape({
|
|||||||
.test("unique-name", "Name must be unique", function (value) {
|
.test("unique-name", "Name must be unique", function (value) {
|
||||||
const setupData = this.options.context?.setupData as { targets: Target[] };
|
const setupData = this.options.context?.setupData as { targets: Target[] };
|
||||||
if (!setupData) return true;
|
if (!setupData) return true;
|
||||||
|
|
||||||
return !setupData.targets.some(
|
return !setupData.targets.some(
|
||||||
(t) => t.name.toLowerCase() === value?.toLowerCase() && (t.id === 0 || t.id !== this.parent.id)
|
(t) => t.name.toLowerCase() === value?.toLowerCase() && (t.id === 0 || t.id !== this.parent.id)
|
||||||
);
|
);
|
||||||
@ -42,13 +50,32 @@ const schema = yup.object().shape({
|
|||||||
filterQuery: yup.string().nullable(),
|
filterQuery: yup.string().nullable(),
|
||||||
allowWriteBack: yup.boolean().default(false),
|
allowWriteBack: yup.boolean().default(false),
|
||||||
isActive: yup.boolean().default(true),
|
isActive: yup.boolean().default(true),
|
||||||
|
columns: yup
|
||||||
|
.array().of(
|
||||||
|
yup.object().shape({
|
||||||
|
id: yup.number(),
|
||||||
|
targetId: yup.number(),
|
||||||
|
typeCode: yup.string(),
|
||||||
|
dataTypeCode: yup.string(),
|
||||||
|
name: yup.string(),
|
||||||
|
writeBack: yup.boolean(),
|
||||||
|
isEmailAddress: yup.boolean(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.test(
|
||||||
|
"email-address-required",
|
||||||
|
"One column must be marked as the email address",
|
||||||
|
(columns) => columns && columns.filter((col) => col.isEmailAddress === true).length === 1
|
||||||
|
)
|
||||||
|
.test(
|
||||||
|
"unique-id-required",
|
||||||
|
"One column must be marked as the unique ID",
|
||||||
|
(columns) => columns && columns.filter((col) => col.typeCode === "I").length === 1
|
||||||
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
//TODO: Make DatabaseName a select using new array in setupData.servers.databases
|
|
||||||
//TODO: Maybe Make View a select using new array in setupData.servers.databases.views+procs. But would have to allow free form entry just in case/no validation on found in select
|
|
||||||
//TODO: Add verify/test button on form, checks that server, db, view exist, query works and returns data. Show sample data on screen.
|
|
||||||
const defaultTarget: Target = {
|
const defaultTarget: Target = {
|
||||||
id: 0,
|
id: 0,
|
||||||
name: "",
|
name: "",
|
||||||
serverId: 0,
|
serverId: 0,
|
||||||
databaseName: "",
|
databaseName: "",
|
||||||
@ -56,38 +83,57 @@ const defaultTarget: Target = {
|
|||||||
filterQuery: "",
|
filterQuery: "",
|
||||||
allowWriteBack: false,
|
allowWriteBack: false,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
|
columns: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const useColumnErrors = (errors: any, columns: TargetColumn[]) => {
|
||||||
|
const hasEmail = columns.some((col) => col.isEmailAddress);
|
||||||
|
const hasUniqueId = columns.some((col) => col.typeCode === "I");
|
||||||
|
return {
|
||||||
|
emailError: errors.columns && !hasEmail ? "One column must be marked as the email address" : "",
|
||||||
|
uniqueIdError: errors.columns && !hasUniqueId ? "One column must be marked as the unique ID" : "",
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
||||||
const isNew = !target || target.id === 0;
|
const isNew = !target || target.id === 0;
|
||||||
const setupData: SetupData = useSetupData();
|
const setupData: SetupData = useSetupData();
|
||||||
|
const [targetTested, setTargetTested] = useState<boolean>(false);
|
||||||
|
const [targetTestError, setTargetTestError] = useState<boolean>(false);
|
||||||
|
const [targetTestErrorMessage, setTargetTestErrorMessage] = useState<string>('');
|
||||||
|
const [availableColumns, setAvailableColumns] = useState<TargetColumn[]>([]);
|
||||||
|
const [sampleColumns, setSampleColumns] = useState<GridColDef[]>([]);
|
||||||
|
const [sampleRows, setSampleRows] = useState<{ [key: string]: string }[]>([]);
|
||||||
|
const [showSampleGrid, setShowSampleGrid] = useState<boolean>(true);
|
||||||
|
|
||||||
const { register, trigger, control, handleSubmit, reset, formState: { errors } } = useForm<Target>({
|
const { register, trigger, control, handleSubmit, reset, formState: { errors } } = useForm<Target>({
|
||||||
mode: "onBlur",
|
mode: "onBlur",
|
||||||
defaultValues: target || defaultTarget,
|
defaultValues: target || defaultTarget,
|
||||||
resolver: yupResolver(schema) as Resolver<Target>,
|
resolver: yupResolver(schema) as Resolver<Target>,
|
||||||
context: { setupData }
|
context: { setupData },
|
||||||
,
|
|
||||||
});
|
});
|
||||||
//const [formData, setFormData] = useState<Target>(target ? { ...target } : { ...defaultTarget });
|
|
||||||
//const [serverError, setServerError] = useState(false); // Track validation
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => { //Reset form to unedited state on open or target change
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { emailError, uniqueIdError } = useColumnErrors(errors, control._formValues.columns || []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
if (open) {
|
if (open) {
|
||||||
|
if (target && target.id > 0) {
|
||||||
|
setTargetTested(true);
|
||||||
|
}
|
||||||
|
setAvailableColumns(target?.columns || []);
|
||||||
|
setTargetTestErrorMessage('');
|
||||||
|
setTargetTestError(false);
|
||||||
|
setSampleColumns([]);
|
||||||
|
setSampleRows([]);
|
||||||
|
setShowSampleGrid(false);
|
||||||
reset(target || defaultTarget, { keepDefaultValues: true });
|
reset(target || defaultTarget, { keepDefaultValues: true });
|
||||||
}
|
}
|
||||||
}, [open, target, reset]);
|
}, [open, target, reset]);
|
||||||
|
|
||||||
//const handleChange = (field: string, value: any) => {
|
|
||||||
// setFormData((prev) => ({ ...prev, [field]: value?.id || "" }));
|
|
||||||
|
|
||||||
// if (field === "serverId" && value) setServerError(false);
|
|
||||||
//};
|
|
||||||
|
|
||||||
const handleSave = async (formData: Target) => {
|
const handleSave = async (formData: Target) => {
|
||||||
const apiUrl = isNew ? "/api/targets" : `/api/targets/${formData.id}`;
|
const apiUrl = isNew ? "/api/targets" : `/api/targets/${formData.id}`;
|
||||||
const method = isNew ? "POST" : "PUT";
|
const method = isNew ? "POST" : "PUT";
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const response = await fetch(apiUrl, {
|
const response = await fetch(apiUrl, {
|
||||||
@ -95,11 +141,9 @@ const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
|||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify(formData),
|
body: JSON.stringify(formData),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) throw new Error(isNew ? "Failed to create" : "Failed to update");
|
if (!response.ok) throw new Error(isNew ? "Failed to create" : "Failed to update");
|
||||||
|
|
||||||
const updatedTarget = await response.json();
|
const updatedTarget = await response.json();
|
||||||
onSave(updatedTarget);
|
onSave(updatedTarget);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Update error:", error);
|
console.error("Update error:", error);
|
||||||
@ -108,24 +152,120 @@ const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getTargetColumnsFromSample = (columns: TargetSampleColumn[]) => {
|
||||||
|
const targetColumns: TargetColumn[] = [];
|
||||||
|
for (const colName in columns) {
|
||||||
|
const col = columns[colName];
|
||||||
|
targetColumns.push({
|
||||||
|
name: col.name,
|
||||||
|
typeCode: col.type === "E" ? "G" : col.type,
|
||||||
|
dataTypeCode: col.dataType,
|
||||||
|
id: 0,
|
||||||
|
targetId: target?.id || 0,
|
||||||
|
writeBack: false,
|
||||||
|
isEmailAddress: col.type === "E",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return targetColumns;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTestTarget = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/targets/test`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify({
|
||||||
|
serverId: control._formValues.serverId,
|
||||||
|
databaseName: control._formValues.databaseName,
|
||||||
|
viewName: control._formValues.viewName,
|
||||||
|
filterQuery: control._formValues.filterQuery,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
if (response.ok) {
|
||||||
|
let columns: TargetColumn[] = [];
|
||||||
|
if (data?.columns) {
|
||||||
|
columns = getTargetColumnsFromSample(data.columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gridColumns: GridColDef[] = Object.keys(columns ?? {}).map((colName) => ({
|
||||||
|
field: colName,
|
||||||
|
headerName: colName,
|
||||||
|
minWidth: 150,
|
||||||
|
width: 200,
|
||||||
|
flex: 1,
|
||||||
|
sortable: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const gridRows = data?.rows?.map((row: { [key: string]: string }, index: number) => ({
|
||||||
|
id: index.toString(),
|
||||||
|
...row,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
setAvailableColumns(columns);
|
||||||
|
setSampleColumns(gridColumns);
|
||||||
|
setSampleRows(gridRows);
|
||||||
|
setTargetTested(true);
|
||||||
|
setTargetTestError(false);
|
||||||
|
setTargetTestErrorMessage('');
|
||||||
|
reset({ ...control._formValues, columns });
|
||||||
|
} else {
|
||||||
|
setTargetTested(false);
|
||||||
|
setTargetTestError(true);
|
||||||
|
setTargetTestErrorMessage(data.error);
|
||||||
|
setSampleColumns([]);
|
||||||
|
setSampleRows([]);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setTargetTested(false);
|
||||||
|
setTargetTestError(true);
|
||||||
|
setTargetTestErrorMessage(error instanceof Error ? error.message : String(error));
|
||||||
|
setSampleColumns([]);
|
||||||
|
setSampleRows([]);
|
||||||
|
console.error("Test target error:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getColumnOptions = (typeCode: string) => {
|
||||||
|
switch (typeCode) {
|
||||||
|
case "E": // Email
|
||||||
|
case "S": // Status
|
||||||
|
return availableColumns.filter(col => col.dataTypeCode.toLowerCase() === "s");
|
||||||
|
case "I": // Unique Identifier
|
||||||
|
return availableColumns.filter(col =>
|
||||||
|
col.dataTypeCode.toLowerCase() === "s" || col.dataTypeCode.toLowerCase() === "n");
|
||||||
|
case "B": // Bounce
|
||||||
|
case "U": // Unsubscribe
|
||||||
|
return availableColumns.filter(col => col.dataTypeCode.toLowerCase() === "b");
|
||||||
|
case "C": // Soft Bounce Count
|
||||||
|
return availableColumns.filter(col => col.dataTypeCode.toLowerCase() === "n");
|
||||||
|
default:
|
||||||
|
return availableColumns;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
<Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
|
||||||
<DialogTitle>{isNew ? "Add Target" : "Edit Target id=" + target.id}</DialogTitle>
|
<DialogTitle>{isNew ? "Add Target" : "Edit Target id=" + target?.id}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Controller
|
<Controller
|
||||||
name="serverId"
|
name="serverId"
|
||||||
control={control}
|
control={control}
|
||||||
rules={{ required: "Server is required" }}
|
rules={{ required: "Server is required" }}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Autocomplete {...field}
|
<Autocomplete
|
||||||
|
{...field}
|
||||||
options={setupData.servers}
|
options={setupData.servers}
|
||||||
getOptionLabel={(option) => option.name}
|
getOptionLabel={(option) => option.name}
|
||||||
value={setupData.servers.find((s) => s.id === Number(field.value)) || null}
|
value={setupData.servers.find((s) => s.id === Number(field.value)) || null}
|
||||||
onChange={(_, newValue) => {
|
onChange={(_, newValue) => {
|
||||||
field.onChange(newValue ? newValue.id : null);
|
field.onChange(newValue ? newValue.id : null);
|
||||||
trigger("serverId");
|
trigger("serverId");
|
||||||
}}
|
}}
|
||||||
|
|
||||||
renderInput={(params) => (
|
renderInput={(params) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
@ -167,20 +307,16 @@ const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
|||||||
{...register("filterQuery")}
|
{...register("filterQuery")}
|
||||||
label="Filter Query"
|
label="Filter Query"
|
||||||
fullWidth
|
fullWidth
|
||||||
|
multiline
|
||||||
|
rows={4}
|
||||||
margin="dense"
|
margin="dense"
|
||||||
error={!!errors.filterQuery}
|
error={!!errors.filterQuery}
|
||||||
helperText={errors.filterQuery?.message}
|
helperText={errors.filterQuery?.message}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
control={
|
control={<Switch {...register("allowWriteBack")} />}
|
||||||
<Switch
|
|
||||||
{...register("allowWriteBack")}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="Allow Write Back"
|
label="Allow Write Back"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="isActive"
|
name="isActive"
|
||||||
control={control}
|
control={control}
|
||||||
@ -197,7 +333,246 @@ const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<Box sx={{ marginTop: 1, marginBottom: 1, display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
|
<Button
|
||||||
|
onClick={handleTestTarget}
|
||||||
|
variant="outlined"
|
||||||
|
sx={{ height: '100%', alignSelf: 'center' }}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? "Testing..." : "Test Target"}
|
||||||
|
</Button>
|
||||||
|
{targetTested && sampleColumns.length > 0 && (
|
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Typography variant="subtitle1">Sample Data</Typography>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowSampleGrid(prev => !prev)}
|
||||||
|
size="small"
|
||||||
|
sx={{ ml: 1 }}
|
||||||
|
>
|
||||||
|
{showSampleGrid ? <VisibilityOffIcon /> : <VisibilityIcon />}
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
{targetTestError && (
|
||||||
|
<Typography color="error" variant="body2" sx={{ mt: 1 }}>
|
||||||
|
{targetTestErrorMessage}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
{targetTested && sampleColumns.length > 0 && showSampleGrid && (
|
||||||
|
<Box sx={{ mt: 1, mb:1 }}>
|
||||||
|
<TargetSampleDataGrid
|
||||||
|
columns={sampleColumns}
|
||||||
|
rows={sampleRows.slice(0, 4)} // Limit to first 4 rows
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
{control._formValues != null && control._formValues.columns != null && targetTested && (
|
||||||
|
<>
|
||||||
|
{/* Email Address Column Autocomplete */}
|
||||||
|
<Controller
|
||||||
|
name="columns"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
options={getColumnOptions("E")}
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
value={field.value.find((col) => col.isEmailAddress) || null}
|
||||||
|
onChange={(_, newValue) => {
|
||||||
|
const updatedColumns = field.value.map((col) => ({
|
||||||
|
...col,
|
||||||
|
typeCode: newValue && col.name === newValue.name ? "G" : col.typeCode,
|
||||||
|
isEmailAddress: newValue && col.name === newValue.name,
|
||||||
|
}));
|
||||||
|
field.onChange(updatedColumns);
|
||||||
|
trigger("columns");
|
||||||
|
}}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label="Email Address Column"
|
||||||
|
fullWidth
|
||||||
|
margin="dense"
|
||||||
|
error={!!emailError}
|
||||||
|
helperText={emailError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/* Unique ID Column Autocomplete */}
|
||||||
|
<Controller
|
||||||
|
name="columns"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
options={getColumnOptions("I")}
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
value={field.value.find((col) => col.typeCode === "I") || null}
|
||||||
|
onChange={(_, newValue) => {
|
||||||
|
const updatedColumns = field.value.map((col) => ({
|
||||||
|
...col,
|
||||||
|
typeCode: newValue && col.name === newValue.name ? "I" : col.typeCode === "I" ? "G" : col.typeCode,
|
||||||
|
isEmailAddress: newValue && col.name === newValue.name ? false : col.isEmailAddress,
|
||||||
|
}));
|
||||||
|
field.onChange(updatedColumns);
|
||||||
|
trigger("columns");
|
||||||
|
}}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label="Unique ID Column"
|
||||||
|
fullWidth
|
||||||
|
margin="dense"
|
||||||
|
error={!!uniqueIdError}
|
||||||
|
helperText={uniqueIdError}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="columns"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
options={getColumnOptions("S")}
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
value={field.value.find((col) => col.typeCode === "S") || null}
|
||||||
|
onChange={(_, newValue) => {
|
||||||
|
const updatedColumns = field.value.map((col) => ({
|
||||||
|
...col,
|
||||||
|
typeCode: newValue && col.name === newValue.name ? "S" : col.typeCode === "S" ? "G" : col.typeCode,
|
||||||
|
isEmailAddress: newValue && col.name === newValue.name ? false : col.isEmailAddress,
|
||||||
|
}));
|
||||||
|
field.onChange(updatedColumns);
|
||||||
|
trigger("columns");
|
||||||
|
}}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label="Status Code Column"
|
||||||
|
fullWidth
|
||||||
|
margin="dense"
|
||||||
|
/*error={!!uniqueIdError}
|
||||||
|
helperText={uniqueIdError}*/
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="columns"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
options={getColumnOptions("C")}
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
value={field.value.find((col) => col.typeCode === "C") || null}
|
||||||
|
onChange={(_, newValue) => {
|
||||||
|
const updatedColumns = field.value.map((col) => ({
|
||||||
|
...col,
|
||||||
|
typeCode: newValue && col.name === newValue.name ? "C" : col.typeCode === "C" ? "G" : col.typeCode,
|
||||||
|
isEmailAddress: newValue && col.name === newValue.name ? false : col.isEmailAddress,
|
||||||
|
}));
|
||||||
|
field.onChange(updatedColumns);
|
||||||
|
trigger("columns");
|
||||||
|
}}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label="Soft Bounce Count Column"
|
||||||
|
fullWidth
|
||||||
|
margin="dense"
|
||||||
|
/*error={!!uniqueIdError}
|
||||||
|
helperText={uniqueIdError}*/
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="columns"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
options={getColumnOptions("B")}
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
value={field.value.find((col) => col.typeCode === "B") || null}
|
||||||
|
onChange={(_, newValue) => {
|
||||||
|
const updatedColumns = field.value.map((col) => ({
|
||||||
|
...col,
|
||||||
|
typeCode: newValue && col.name === newValue.name ? "B" : col.typeCode === "B" ? "G" : col.typeCode,
|
||||||
|
isEmailAddress: newValue && col.name === newValue.name ? false : col.isEmailAddress,
|
||||||
|
}));
|
||||||
|
field.onChange(updatedColumns);
|
||||||
|
trigger("columns");
|
||||||
|
}}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label="Bounce Column"
|
||||||
|
fullWidth
|
||||||
|
margin="dense"
|
||||||
|
/*error={!!uniqueIdError}
|
||||||
|
helperText={uniqueIdError}*/
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
name="columns"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Autocomplete
|
||||||
|
options={getColumnOptions("U")}
|
||||||
|
getOptionLabel={(option) => option.name}
|
||||||
|
value={field.value.find((col) => col.typeCode === "U") || null}
|
||||||
|
onChange={(_, newValue) => {
|
||||||
|
const updatedColumns = field.value.map((col) => ({
|
||||||
|
...col,
|
||||||
|
typeCode: newValue && col.name === newValue.name ? "U" : col.typeCode === "U" ? "G" : col.typeCode,
|
||||||
|
isEmailAddress: newValue && col.name === newValue.name ? false : col.isEmailAddress,
|
||||||
|
}));
|
||||||
|
field.onChange(updatedColumns);
|
||||||
|
trigger("columns");
|
||||||
|
}}
|
||||||
|
renderInput={(params) => (
|
||||||
|
<TextField
|
||||||
|
{...params}
|
||||||
|
label="Unsubscribe Column"
|
||||||
|
fullWidth
|
||||||
|
margin="dense"
|
||||||
|
/*error={!!uniqueIdError}
|
||||||
|
helperText={uniqueIdError}*/
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
/*
|
||||||
|
B Bounce status
|
||||||
|
C Soft bounce count
|
||||||
|
G General
|
||||||
|
I Unique identifier
|
||||||
|
S Status code
|
||||||
|
U Unsubscribe status*/
|
||||||
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={onClose} disabled={loading}>Cancel</Button>
|
<Button onClick={onClose} disabled={loading}>Cancel</Button>
|
||||||
@ -209,4 +584,4 @@ const TargetEdit = ({ open, target, onClose, onSave }: TargetEditProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TargetEdit;
|
export default TargetEdit;
|
||||||
@ -9,9 +9,10 @@ import {
|
|||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import Target from "@/types/target";
|
import Target from "@/types/target";
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import { DataGrid, GridColDef } from '@mui/x-data-grid';
|
import TargetSampleDataGrid from "@/components/shared/TargetSampleDataGrid";
|
||||||
|
import { GridColDef } from '@mui/x-data-grid';
|
||||||
|
|
||||||
type TargetSampleViewerProps = {
|
type TargetSampleModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
target: Target;
|
target: Target;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@ -26,7 +27,7 @@ type TargetSample = {
|
|||||||
rows: { [key: string]: string }[];
|
rows: { [key: string]: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const TargetSampleViewer = ({ open, target, onClose }: TargetSampleViewerProps) => {
|
const TargetSampleModal = ({ open, target, onClose }: TargetSampleModalProps) => {
|
||||||
const [targetSample, setTargetSample] = useState<TargetSample | null>(null);
|
const [targetSample, setTargetSample] = useState<TargetSample | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
@ -34,7 +35,6 @@ const TargetSampleViewer = ({ open, target, onClose }: TargetSampleViewerProps)
|
|||||||
if (open) {
|
if (open) {
|
||||||
const fetchSampleData = async () => {
|
const fetchSampleData = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
//await new Promise(resolve => setTimeout(resolve, 3000)); // Simulate loading delay
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`/api/targets/${target.id}/sample`);
|
const response = await fetch(`/api/targets/${target.id}/sample`);
|
||||||
if (!response.ok) throw new Error("Failed to fetch sample data");
|
if (!response.ok) throw new Error("Failed to fetch sample data");
|
||||||
@ -54,27 +54,26 @@ const TargetSampleViewer = ({ open, target, onClose }: TargetSampleViewerProps)
|
|||||||
const columns: GridColDef[] = Object.keys(targetSample?.columns ?? {}).map((colName) => ({
|
const columns: GridColDef[] = Object.keys(targetSample?.columns ?? {}).map((colName) => ({
|
||||||
field: colName,
|
field: colName,
|
||||||
headerName: colName,
|
headerName: colName,
|
||||||
minWidth: 150, // Minimum width to prevent excessive truncation
|
minWidth: 150,
|
||||||
width: 200, // Default width
|
width: 200,
|
||||||
flex: 1, // Allow columns to grow/shrink proportionally
|
flex: 1,
|
||||||
sortable: true,
|
sortable: true,
|
||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
const rows = targetSample?.rows?.map((row, index) => {
|
const rows = targetSample?.rows?.map((row, index) => {
|
||||||
const rowData: { [key: string]: string } = { id: index.toString() };
|
const rowData: { [key: string]: string } = { id: index.toString() };
|
||||||
Object.keys(targetSample.columns).forEach((key) => {
|
Object.keys(targetSample?.columns || {}).forEach((key) => {
|
||||||
rowData[key] = row[key] ?? "";
|
rowData[key] = row[key] ?? "";
|
||||||
});
|
});
|
||||||
return rowData;
|
return rowData;
|
||||||
}) || [];
|
}) || [];
|
||||||
|
|
||||||
// Calculate dialog width based on number of columns
|
|
||||||
const columnCount = columns.length;
|
const columnCount = columns.length;
|
||||||
const defaultColumnWidth = 200; // Match width from columns
|
const defaultColumnWidth = 200;
|
||||||
const minDialogWidth = 300; // Minimum dialog width for very small datasets
|
const minDialogWidth = 300;
|
||||||
const calculatedWidth = Math.max(
|
const calculatedWidth = Math.max(
|
||||||
minDialogWidth,
|
minDialogWidth,
|
||||||
Math.min(columnCount * defaultColumnWidth, 0.98 * window.innerWidth) // Cap at 98% of viewport
|
Math.min(columnCount * defaultColumnWidth, 0.98 * window.innerWidth)
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -84,12 +83,12 @@ const TargetSampleViewer = ({ open, target, onClose }: TargetSampleViewerProps)
|
|||||||
maxWidth={false}
|
maxWidth={false}
|
||||||
sx={{
|
sx={{
|
||||||
'& .MuiDialog-paper': {
|
'& .MuiDialog-paper': {
|
||||||
width: `${calculatedWidth}px`, // Dynamic width
|
width: `${calculatedWidth}px`,
|
||||||
maxWidth: '98vw', // Upper limit
|
maxWidth: '98vw',
|
||||||
margin: 'auto',
|
margin: 'auto',
|
||||||
height: loading ? '200px' : 'auto', // Explicitly set height for loading, reset to auto when done
|
height: loading ? '200px' : 'auto',
|
||||||
maxHeight: '90vh', // Optional: Prevent dialog from growing too tall
|
maxHeight: '90vh',
|
||||||
}
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{loading && (
|
{loading && (
|
||||||
@ -114,34 +113,7 @@ const TargetSampleViewer = ({ open, target, onClose }: TargetSampleViewerProps)
|
|||||||
</Box>
|
</Box>
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DataGrid
|
<TargetSampleDataGrid columns={columns} rows={rows} />
|
||||||
rows={rows}
|
|
||||||
columns={columns}
|
|
||||||
autoHeight
|
|
||||||
hideFooter
|
|
||||||
hideFooterPagination
|
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
maxHeight: "800px",
|
|
||||||
'& .MuiDataGrid-main': {
|
|
||||||
overflowX: 'auto',
|
|
||||||
},
|
|
||||||
'& .MuiDataGrid-columnHeaderTitle': {
|
|
||||||
overflow: 'visible',
|
|
||||||
whiteSpace: 'normal',
|
|
||||||
lineHeight: '1.2em',
|
|
||||||
textOverflow: 'ellipsis',
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
initialState={{
|
|
||||||
pagination: {
|
|
||||||
paginationModel: {
|
|
||||||
pageSize: 10,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
pageSizeOptions={[]}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -149,4 +121,4 @@ const TargetSampleViewer = ({ open, target, onClose }: TargetSampleViewerProps)
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TargetSampleViewer;
|
export default TargetSampleModal;
|
||||||
@ -18,6 +18,7 @@ import NewMailings from '@/components/pages/NewMailings';
|
|||||||
import ScheduledMailings from '@/components/pages/ScheduledMailings';
|
import ScheduledMailings from '@/components/pages/ScheduledMailings';
|
||||||
import ActiveMailings from '@/components/pages/ActiveMailings';
|
import ActiveMailings from '@/components/pages/ActiveMailings';
|
||||||
import CompletedMailings from '@/components/pages/CompletedMailings';
|
import CompletedMailings from '@/components/pages/CompletedMailings';
|
||||||
|
import CancelledMailings from '@/components/pages/CancelledMailings';
|
||||||
import AuthCheck from '@/components/auth/AuthCheck';
|
import AuthCheck from '@/components/auth/AuthCheck';
|
||||||
|
|
||||||
import { ColorModeContext } from '@/theme/theme';
|
import { ColorModeContext } from '@/theme/theme';
|
||||||
@ -211,6 +212,16 @@ const App = () => {
|
|||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path="/cancelledMailings"
|
||||||
|
element={
|
||||||
|
<PageWrapper title="Cancelled Mailings">
|
||||||
|
<Layout>
|
||||||
|
<CancelledMailings />
|
||||||
|
</Layout>
|
||||||
|
</PageWrapper>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route
|
<Route
|
||||||
path="/completedMailings"
|
path="/completedMailings"
|
||||||
element={
|
element={
|
||||||
|
|||||||
@ -0,0 +1,150 @@
|
|||||||
|
import { useState, useRef, useEffect } from 'react';
|
||||||
|
import { useSetupData, SetupData } from "@/context/SetupDataContext";
|
||||||
|
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||||
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||||
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
|
import { Box, useTheme, CircularProgress, IconButton } from '@mui/material';
|
||||||
|
import { DataGrid, GridColDef, GridRenderCellParams, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
|
||||||
|
import Mailing from '@/types/mailing';
|
||||||
|
import MailingEdit from "@/components/modals/MailingEdit";
|
||||||
|
import MailingView from "@/components/modals/MailingView"; // Assume this is a new read-only view component
|
||||||
|
|
||||||
|
function CancelledMailings() {
|
||||||
|
const theme = useTheme();
|
||||||
|
const setupData: SetupData = useSetupData();
|
||||||
|
|
||||||
|
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [mailingsLoading, setMailingsLoading] = useState<boolean>(false);
|
||||||
|
const [mailings, setMailings] = useState<Mailing[]>([]);
|
||||||
|
const [selectedRow, setSelectedRow] = useState<Mailing | null>(null);
|
||||||
|
const [viewOpen, setViewOpen] = useState<boolean>(false);
|
||||||
|
const [editOpen, setEditOpen] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const columns: GridColDef<Mailing>[] = [
|
||||||
|
{
|
||||||
|
field: "actions",
|
||||||
|
headerName: "",
|
||||||
|
sortable: false,
|
||||||
|
width: 140,
|
||||||
|
renderCell: (params: GridRenderCellParams<Mailing>) => (
|
||||||
|
<>
|
||||||
|
<IconButton color="primary" onClick={(e) => { e.stopPropagation(); handleView(params.row); }}>
|
||||||
|
<VisibilityIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton color="primary" onClick={(e) => { e.stopPropagation(); handleCopy(params.row); }}>
|
||||||
|
<ContentCopyIcon />
|
||||||
|
</IconButton>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ field: "id", headerName: "ID", width: 80 },
|
||||||
|
{ field: "name", headerName: "Name", flex: 1, minWidth: 160 },
|
||||||
|
{ field: "description", headerName: "Description", flex: 1, minWidth: 200 },
|
||||||
|
{
|
||||||
|
field: "templateId",
|
||||||
|
headerName: "Subject",
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 160,
|
||||||
|
valueGetter: (_: number, row: Mailing) => setupData.templates.find(t => t.id === row.templateId)?.subject || 'Unknown',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const reloadMailings = async () => {
|
||||||
|
setMailingsLoading(true);
|
||||||
|
const mailingsResponse = await fetch("/api/mailings/status/c");
|
||||||
|
const mailingsData = await mailingsResponse.json();
|
||||||
|
if (mailingsData) {
|
||||||
|
setMailings(mailingsData);
|
||||||
|
setMailingsLoading(false);
|
||||||
|
} else {
|
||||||
|
console.error("Failed to fetch cancelled mailings");
|
||||||
|
setMailingsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleView = (row: Mailing) => {
|
||||||
|
setSelectedRow(row);
|
||||||
|
setViewOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCopy = (row: Mailing) => {
|
||||||
|
const newMailing = { ...row, id: 0 }; // Copy all fields except ID
|
||||||
|
setSelectedRow(newMailing);
|
||||||
|
setEditOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateRow = () => {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
reloadMailings();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box ref={gridContainerRef} sx={{
|
||||||
|
position: 'relative', left: 0, right: 0, height: "calc(100vh - 124px)", overflow: "hidden",
|
||||||
|
transition: theme.transitions.create(['width', 'height'], {
|
||||||
|
easing: theme.transitions.easing.easeInOut,
|
||||||
|
duration: theme.transitions.duration.standard,
|
||||||
|
})
|
||||||
|
}}>
|
||||||
|
<Box sx={{ position: 'absolute', inset: 0 }}>
|
||||||
|
<DataGrid
|
||||||
|
rows={mailings}
|
||||||
|
columns={columns}
|
||||||
|
autoPageSize
|
||||||
|
sx={{ minWidth: "600px" }}
|
||||||
|
slots={{
|
||||||
|
toolbar: () => (
|
||||||
|
<GridToolbarContainer sx={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<IconButton size="small" color="primary" onClick={() => reloadMailings()} sx={{ marginLeft: 1 }}>
|
||||||
|
{mailingsLoading ? <CircularProgress size={24} color="inherit" /> : <RefreshIcon />}
|
||||||
|
</IconButton>
|
||||||
|
<GridToolbarColumnsButton />
|
||||||
|
<GridToolbarDensitySelector />
|
||||||
|
<GridToolbarExport />
|
||||||
|
<GridToolbarQuickFilter sx={{ ml: "auto" }} />
|
||||||
|
</GridToolbarContainer>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
slotProps={{
|
||||||
|
toolbar: {
|
||||||
|
showQuickFilter: true,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
initialState={{
|
||||||
|
pagination: {
|
||||||
|
paginationModel: {
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sorting: {
|
||||||
|
sortModel: [{ field: 'id', sort: 'desc' }], // Default sort by scheduleDate, descending
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
pageSizeOptions={[10, 20, 50, 100]}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{viewOpen && (
|
||||||
|
<MailingView
|
||||||
|
open={viewOpen}
|
||||||
|
mailing={selectedRow}
|
||||||
|
onClose={() => setViewOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{editOpen && (
|
||||||
|
<MailingEdit
|
||||||
|
open={editOpen}
|
||||||
|
mailing={selectedRow}
|
||||||
|
onClose={(reason) => { if (reason !== 'backdropClick') setEditOpen(false) }}
|
||||||
|
onSave={handleUpdateRow}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CancelledMailings;
|
||||||
@ -45,20 +45,26 @@ function CompletedMailings() {
|
|||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ field: "mailingId", headerName: "Mailing ID", width: 100 },
|
{ field: "mailingId", headerName: "Mailing ID", width: 90 },
|
||||||
{ field: "mailingName", headerName: "Name", flex: 1, minWidth: 160 },
|
{ field: "mailingName", headerName: "Name", flex: 1, minWidth: 160 },
|
||||||
{ field: "emailCount", headerName: "Emails", width: 100 },
|
{ field: "sentDate", headerName: "Sent Date", width: 180,
|
||||||
{ field: "sendCount", headerName: "Active", width: 100 },
|
renderCell: (params: GridRenderCellParams<MailingStatistic>) => (
|
||||||
{ field: "deliveredCount", headerName: "Delivered", width: 100 },
|
<>
|
||||||
{ field: "failedCount", headerName: "Failed", width: 100 },
|
{params.value ? new Date(params.value).toLocaleString() : ''}
|
||||||
{ field: "blockedCount", headerName: "Blocked", width: 100 },
|
</>
|
||||||
{ field: "invalidCount", headerName: "Invalid", width: 100 },
|
)},
|
||||||
{ field: "openCount", headerName: "Opens", width: 100 },
|
{ field: "emailCount", headerName: "Emails", width: 70 },
|
||||||
|
{ field: "sendCount", headerName: "Active", width: 70 },
|
||||||
|
{ field: "deliveredCount", headerName: "Delivered", width: 90 },
|
||||||
|
{ field: "failedCount", headerName: "Failed", width: 70 },
|
||||||
|
{ field: "blockedCount", headerName: "Blocked", width: 80 },
|
||||||
|
{ field: "invalidCount", headerName: "Invalid", width: 80 },
|
||||||
|
{ field: "openCount", headerName: "Opens", width: 70 },
|
||||||
{ field: "uniqueOpenCount", headerName: "Unique Opens", width: 120 },
|
{ field: "uniqueOpenCount", headerName: "Unique Opens", width: 120 },
|
||||||
{ field: "clickCount", headerName: "Clicks", width: 100 },
|
{ field: "clickCount", headerName: "Clicks", width: 70 },
|
||||||
{ field: "uniqueClickCount", headerName: "Unique Clicks", width: 120 },
|
{ field: "uniqueClickCount", headerName: "Unique Clicks", width: 120 },
|
||||||
{ field: "bounceCount", headerName: "Bounces", width: 100 },
|
{ field: "bounceCount", headerName: "Bounces", width: 80 },
|
||||||
{ field: "spamCount", headerName: "Spam", width: 100 },
|
{ field: "spamCount", headerName: "Spam", width: 60 },
|
||||||
{ field: "unsubscribeCount", headerName: "Unsubscribes", width: 120 },
|
{ field: "unsubscribeCount", headerName: "Unsubscribes", width: 120 },
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -261,6 +267,9 @@ function CompletedMailings() {
|
|||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sorting: {
|
||||||
|
sortModel: [{ field: 'sentDate', sort: 'desc' }], // Default sort by scheduleDate, descending
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
pageSizeOptions={[10, 20, 50, 100]}
|
pageSizeOptions={[10, 20, 50, 100]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,34 +1,19 @@
|
|||||||
// src/components/pages/Home.tsx
|
// src/components/pages/Home.tsx
|
||||||
//import React from 'react';
|
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Grid2 from '@mui/material/Grid2'; // v6 Grid2
|
import Grid2 from '@mui/material/Grid2'; // v6 Grid2
|
||||||
//import Card from '@mui/material/Card';
|
|
||||||
//import CardContent from '@mui/material/CardContent';
|
|
||||||
//import { CardActionArea } from '@mui/material';
|
|
||||||
import { BarChart } from '@mui/x-charts/BarChart';
|
|
||||||
|
|
||||||
import LineChartSample from '@/components/widgets/LineChartSample';
|
import RecentMailingStatsChart from '@/components/widgets/RecentMailingStatsChart';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{
|
||||||
|
position: 'relative', left: 0, right: 0, height: "calc(100vh - 124px)", maxHeight: "700px", overflow: "hidden",
|
||||||
|
}}>
|
||||||
<Typography variant="h4" component="h1" gutterBottom>
|
<Typography variant="h4" component="h1" gutterBottom>
|
||||||
Welcome to Surge365
|
Sent Mailing Statistics
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid2 container spacing={2}>
|
<RecentMailingStatsChart days={14} />
|
||||||
<Grid2 size={{ xs: 12, sm: 6, md: 6 }}>
|
|
||||||
<BarChart
|
|
||||||
xAxis={[{ scaleType: 'band', data: ['group A', 'group B', 'group C'] }]}
|
|
||||||
series={[{ data: [4, 3, 5] }, { data: [1, 6, 3] }, { data: [2, 5, 6] }]}
|
|
||||||
width={500}
|
|
||||||
height={300}
|
|
||||||
/>
|
|
||||||
</Grid2>
|
|
||||||
<Grid2 size={{ xs: 12, sm: 6, md: 4 }}>
|
|
||||||
<LineChartSample />
|
|
||||||
</Grid2>
|
|
||||||
</Grid2>
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -8,8 +8,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Alert,
|
Alert,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { AuthResponse, AuthErrorResponse, User, isAuthErrorResponse } from '@/types/auth';
|
import { AuthResponse, User } from '@/types/auth';
|
||||||
import utils from '@/ts/utils';
|
|
||||||
//import ForgotPasswordModal from '@/components/modals/ForgotPasswordModal';
|
//import ForgotPasswordModal from '@/components/modals/ForgotPasswordModal';
|
||||||
|
|
||||||
type SpinnerState = Record<string, boolean>;
|
type SpinnerState = Record<string, boolean>;
|
||||||
@ -72,35 +71,46 @@ function Login() {
|
|||||||
let loggedInUser: User | null = null;
|
let loggedInUser: User | null = null;
|
||||||
let hadLoginError: boolean = false;
|
let hadLoginError: boolean = false;
|
||||||
let hadLoginErrorMessage: string = '';
|
let hadLoginErrorMessage: string = '';
|
||||||
await utils.webMethod<AuthResponse>({
|
|
||||||
methodPage: 'authentication',
|
const apiUrl = "/api/authentication/authenticate";
|
||||||
methodName: 'authenticate',
|
const response = await fetch(apiUrl, {
|
||||||
parameters: { username, password },
|
method: "POST",
|
||||||
success: (json: AuthResponse) => {
|
headers: { "Content-Type": "application/json" },
|
||||||
try {
|
body: JSON.stringify({ username, password }),
|
||||||
localStorage.setItem('accessToken', json.accessToken);
|
|
||||||
loggedInUser = json.user;
|
|
||||||
} catch {
|
|
||||||
const errorMsg: string = 'Unexpected Error';
|
|
||||||
hadLoginError = true;
|
|
||||||
hadLoginErrorMessage = errorMsg;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: (err: unknown) => {
|
|
||||||
let errorMsg: string = 'Unexpected Error';
|
|
||||||
if (isAuthErrorResponse(err)) {
|
|
||||||
const errorResponse = err as AuthErrorResponse;
|
|
||||||
if (errorResponse.data?.message) {
|
|
||||||
errorMsg = errorResponse.data.message;
|
|
||||||
}
|
|
||||||
console.error(errorMsg);
|
|
||||||
setLoginErrorMessage(errorMsg);
|
|
||||||
}
|
|
||||||
hadLoginError = true;
|
|
||||||
hadLoginErrorMessage = errorMsg;
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//if (!response.ok) throw new Error(isNew ? "Failed to create" : "Failed to update");
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const json: AuthResponse = await response.json();
|
||||||
|
try {
|
||||||
|
localStorage.setItem('accessToken', json.accessToken);
|
||||||
|
loggedInUser = json.user;
|
||||||
|
|
||||||
|
if (loggedInUser == null) {
|
||||||
|
setLoginError(true);
|
||||||
|
setIsLoading(false);
|
||||||
|
setSpinners({ Login: false });
|
||||||
|
} else {
|
||||||
|
await finishUserLogin(loggedInUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
hadLoginError = true;
|
||||||
|
hadLoginErrorMessage = 'Unexpected Error';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let data = null;
|
||||||
|
try {
|
||||||
|
data = await response.json();
|
||||||
|
} catch {
|
||||||
|
// Intentionally empty, if not json ignore
|
||||||
|
}
|
||||||
|
hadLoginError = true;
|
||||||
|
hadLoginErrorMessage = data?.message ?? 'Unexpected Error';
|
||||||
|
}
|
||||||
|
|
||||||
if (hadLoginError) {
|
if (hadLoginError) {
|
||||||
setLoginErrorMessage(hadLoginErrorMessage);
|
setLoginErrorMessage(hadLoginErrorMessage);
|
||||||
setLoginError(true);
|
setLoginError(true);
|
||||||
@ -131,19 +141,20 @@ function Login() {
|
|||||||
resetAppSettings();
|
resetAppSettings();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const finishUserLogin = async (loggedInUser: User) => {
|
const finishUserLogin = async (_: User) => {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
setSpinners({ Login: false, LoginWithPasskey: false });
|
setSpinners({ Login: false, LoginWithPasskey: false });
|
||||||
|
|
||||||
utils.localStorage('session_currentUser', loggedInUser);
|
//utils.localStorage('session_currentUser', loggedInUser);
|
||||||
|
|
||||||
const redirectUrl = utils.sessionStorage('redirect_url');
|
//const redirectUrl = utils.sessionStorage('redirect_url');
|
||||||
if (redirectUrl) {
|
//if (redirectUrl) {
|
||||||
utils.sessionStorage('redirect_url', null);
|
// utils.sessionStorage('redirect_url', null);
|
||||||
document.location.href = redirectUrl;
|
// document.location.href = redirectUrl;
|
||||||
} else {
|
//} else {
|
||||||
document.location.href = '/home';
|
// document.location.href = '/home';
|
||||||
}
|
//}
|
||||||
|
document.location.href = '/home';
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -154,6 +154,9 @@ function NewMailings() {
|
|||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sorting: {
|
||||||
|
sortModel: [{ field: 'id', sort: 'asc' }],
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
pageSizeOptions={[10, 20, 50, 100]}
|
pageSizeOptions={[10, 20, 50, 100]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import VisibilityIcon from '@mui/icons-material/Visibility';
|
|||||||
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
|
||||||
import CancelIcon from '@mui/icons-material/Cancel';
|
import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import RefreshIcon from '@mui/icons-material/Refresh';
|
import RefreshIcon from '@mui/icons-material/Refresh';
|
||||||
import { Box, useTheme, CircularProgress, IconButton } from '@mui/material';
|
import { Box, useTheme, useMediaQuery, CircularProgress, IconButton, List, Card, CardContent, Typography } from '@mui/material';
|
||||||
import { DataGrid, GridColDef, GridRenderCellParams, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
|
import { DataGrid, GridColDef, GridRenderCellParams, GridToolbarContainer, GridToolbarQuickFilter, GridToolbarExport, GridToolbarDensitySelector, GridToolbarColumnsButton } from '@mui/x-data-grid';
|
||||||
import Mailing from '@/types/mailing';
|
import Mailing from '@/types/mailing';
|
||||||
import MailingEdit from "@/components/modals/MailingEdit";
|
import MailingEdit from "@/components/modals/MailingEdit";
|
||||||
@ -12,6 +12,7 @@ import ConfirmationDialog from "@/components/modals/ConfirmationDialog";
|
|||||||
|
|
||||||
function ScheduleMailings() {
|
function ScheduleMailings() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
|
||||||
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
const gridContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const [mailingsLoading, setMailingsLoading] = useState<boolean>(false);
|
const [mailingsLoading, setMailingsLoading] = useState<boolean>(false);
|
||||||
@ -21,6 +22,7 @@ function ScheduleMailings() {
|
|||||||
const [editOpen, setEditOpen] = useState<boolean>(false);
|
const [editOpen, setEditOpen] = useState<boolean>(false);
|
||||||
const [confirmDialogOpen, setConfirmDialogOpen] = useState<boolean>(false);
|
const [confirmDialogOpen, setConfirmDialogOpen] = useState<boolean>(false);
|
||||||
const [mailingToCancel, setMailingToCancel] = useState<Mailing | null>(null);
|
const [mailingToCancel, setMailingToCancel] = useState<Mailing | null>(null);
|
||||||
|
const [forceRender, setForceRender] = useState(false);
|
||||||
|
|
||||||
const formatRecurringString = (typeCode: string, startDate: string): string => {
|
const formatRecurringString = (typeCode: string, startDate: string): string => {
|
||||||
const date = new Date(startDate);
|
const date = new Date(startDate);
|
||||||
@ -82,6 +84,7 @@ function ScheduleMailings() {
|
|||||||
if (mailingsData) {
|
if (mailingsData) {
|
||||||
setMailings(mailingsData);
|
setMailings(mailingsData);
|
||||||
setMailingsLoading(false);
|
setMailingsLoading(false);
|
||||||
|
setForceRender(true);
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to fetch scheduled mailings");
|
console.error("Failed to fetch scheduled mailings");
|
||||||
setMailingsLoading(false);
|
setMailingsLoading(false);
|
||||||
@ -135,6 +138,12 @@ function ScheduleMailings() {
|
|||||||
reloadMailings();
|
reloadMailings();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (forceRender) {
|
||||||
|
setForceRender(false);
|
||||||
|
}
|
||||||
|
}, [forceRender]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box ref={gridContainerRef} sx={{
|
<Box ref={gridContainerRef} sx={{
|
||||||
position: 'relative', left: 0, right: 0, height: "calc(100vh - 124px)", overflow: "hidden",
|
position: 'relative', left: 0, right: 0, height: "calc(100vh - 124px)", overflow: "hidden",
|
||||||
@ -143,7 +152,43 @@ function ScheduleMailings() {
|
|||||||
duration: theme.transitions.duration.standard,
|
duration: theme.transitions.duration.standard,
|
||||||
})
|
})
|
||||||
}}>
|
}}>
|
||||||
<Box sx={{ position: 'absolute', inset: 0 }}>
|
<Box sx={{ position: 'absolute', inset: 0 }}>{isMobile ? (
|
||||||
|
mailingsLoading ? (
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<List>
|
||||||
|
{mailings.map((mailing) => (
|
||||||
|
<Card key={mailing.id} sx={{ marginBottom: 2 }}>
|
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<CardContent>
|
||||||
|
<Typography variant="h6">{mailing.name}</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Schedule Date: {mailing.scheduleDate ? new Date(mailing.scheduleDate).toLocaleString() : 'N/A'}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2">
|
||||||
|
Recurring: {mailing.recurringTypeCode && mailing.recurringStartDate
|
||||||
|
? formatRecurringString(mailing.recurringTypeCode, mailing.recurringStartDate)
|
||||||
|
: 'None'}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
<Box>
|
||||||
|
<IconButton onClick={() => handleView(mailing)}>
|
||||||
|
<VisibilityIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={() => handleCopy(mailing)}>
|
||||||
|
<ContentCopyIcon />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton color="secondary" onClick={() => handleCancelClick(mailing)}>
|
||||||
|
<CancelIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
)) : (
|
||||||
<DataGrid
|
<DataGrid
|
||||||
rows={mailings}
|
rows={mailings}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
@ -173,11 +218,14 @@ function ScheduleMailings() {
|
|||||||
pageSize: 20,
|
pageSize: 20,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sorting: {
|
||||||
|
sortModel: [{ field: 'scheduleDate', sort: 'asc' }], // Default sort by scheduleDate, descending
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
pageSizeOptions={[10, 20, 50, 100]}
|
pageSizeOptions={[10, 20, 50, 100]}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{viewOpen && (
|
{viewOpen && (
|
||||||
<MailingView
|
<MailingView
|
||||||
open={viewOpen}
|
open={viewOpen}
|
||||||
|
|||||||
@ -32,14 +32,14 @@ function Targets() {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{ field: "id", headerName: "ID", width: 60 },
|
{ field: "id", headerName: "ID", width: 100 },
|
||||||
//{ field: "serverKey", headerName: "Server Key", flex: 1, minWidth: 140 },
|
//{ field: "serverKey", headerName: "Server Key", flex: 1, minWidth: 140 },
|
||||||
{ field: "name", headerName: "Name", flex: 1, minWidth: 160 },
|
{ field: "name", headerName: "Name", flex: 1, minWidth: 160 },
|
||||||
{ field: "databaseName", headerName: "Database", flex: 1, minWidth: 100 },
|
{ field: "databaseName", headerName: "Database", flex: 1, minWidth: 100 },
|
||||||
{ field: "viewName", headerName: "View", flex: 1, minWidth: 300 },
|
{ field: "viewName", headerName: "View", flex: 1, minWidth: 300 },
|
||||||
{ field: "filterQuery", headerName: "Filter Query", flex: 1, minWidth: 100 },
|
{ field: "filterQuery", headerName: "Filter Query", flex: 1, minWidth: 100 },
|
||||||
{ field: "allowWriteBack", headerName: "WriteBack?", width: 100 },
|
{ field: "allowWriteBack", headerName: "Write Back", width: 150 },
|
||||||
{ field: "isActive", headerName: "Active", width: 75 },
|
{ field: "isActive", headerName: "Active", width: 115 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,41 @@
|
|||||||
|
import { DataGrid, GridColDef } from '@mui/x-data-grid';
|
||||||
|
|
||||||
|
type TargetSampleDataGridProps = {
|
||||||
|
columns: GridColDef[];
|
||||||
|
rows: { [key: string]: string }[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const TargetSampleDataGrid = ({ columns, rows }: TargetSampleDataGridProps) => {
|
||||||
|
return (
|
||||||
|
<DataGrid
|
||||||
|
rows={rows}
|
||||||
|
columns={columns}
|
||||||
|
autoHeight
|
||||||
|
hideFooter
|
||||||
|
hideFooterPagination
|
||||||
|
sx={{
|
||||||
|
width: "100%",
|
||||||
|
maxHeight: "800px",
|
||||||
|
'& .MuiDataGrid-main': {
|
||||||
|
overflowX: 'auto',
|
||||||
|
},
|
||||||
|
'& .MuiDataGrid-columnHeaderTitle': {
|
||||||
|
overflow: 'visible',
|
||||||
|
whiteSpace: 'normal',
|
||||||
|
lineHeight: '1.2em',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
initialState={{
|
||||||
|
pagination: {
|
||||||
|
paginationModel: {
|
||||||
|
pageSize: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
pageSizeOptions={[]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TargetSampleDataGrid;
|
||||||
@ -0,0 +1,108 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { Box } from '@mui/material';
|
||||||
|
import { BarChart } from '@mui/x-charts/BarChart';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import MailingStatistic from '@/types/mailingStatistic';
|
||||||
|
|
||||||
|
export default function RecentMailingStatsChart({ days = 7 }: { days?: number }) {
|
||||||
|
const [stats, setStats] = useState<MailingStatistic[]>([]);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
var startDate = dayjs().subtract(days, 'day').format('YYYY-MM-DD');
|
||||||
|
var endDate = dayjs().format('YYYY-MM-DD');
|
||||||
|
|
||||||
|
const fetchStats = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/mailings/status/s%2Csd/stats?startDate=${startDate}&endDate=${endDate}`);
|
||||||
|
const data: MailingStatistic[] = await response.json();
|
||||||
|
setStats(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching mailing stats:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchStats();
|
||||||
|
const intervalId = setInterval(fetchStats, 5000);
|
||||||
|
return () => clearInterval(intervalId);
|
||||||
|
}, [days]);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>Loading...</Box>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stats.length) {
|
||||||
|
return <Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', height: '100%' }}>No data available</Box>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate stats by date
|
||||||
|
const aggregatedStats = stats.reduce((acc, stat) => {
|
||||||
|
const dateStr = stat.sentDate ? dayjs(stat.sentDate).format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD');
|
||||||
|
if (!acc[dateStr]) {
|
||||||
|
acc[dateStr] = {
|
||||||
|
sentCount: 0,
|
||||||
|
deliveredCount: 0,
|
||||||
|
failedCount: 0,
|
||||||
|
bounceCount: 0,
|
||||||
|
blockedCount: 0,
|
||||||
|
invalidCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
acc[dateStr].sentCount += stat.sendCount;
|
||||||
|
acc[dateStr].deliveredCount += stat.deliveredCount;
|
||||||
|
acc[dateStr].failedCount += stat.failedCount;
|
||||||
|
acc[dateStr].bounceCount += stat.bounceCount;
|
||||||
|
acc[dateStr].blockedCount += stat.blockedCount;
|
||||||
|
acc[dateStr].invalidCount += stat.invalidCount;
|
||||||
|
return acc;
|
||||||
|
}, {} as Record<string, { sentCount: number; deliveredCount: number; failedCount: number; bounceCount: number; blockedCount: number; invalidCount: number }>);
|
||||||
|
|
||||||
|
// Prepare chart data, sorted by date ascending
|
||||||
|
const sortedDates = Object.keys(aggregatedStats).sort((a, b) => dayjs(a).isBefore(dayjs(b)) ? -1 : 1);
|
||||||
|
const dates = sortedDates.map(date => new Date(date));
|
||||||
|
const sentData = sortedDates.map(date => aggregatedStats[date].sentCount);
|
||||||
|
const deliveredData = sortedDates.map(date => aggregatedStats[date].deliveredCount);
|
||||||
|
const errorData = sortedDates.map(date => (
|
||||||
|
aggregatedStats[date].failedCount +
|
||||||
|
aggregatedStats[date].bounceCount +
|
||||||
|
aggregatedStats[date].blockedCount +
|
||||||
|
aggregatedStats[date].invalidCount
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<BarChart
|
||||||
|
margin={{
|
||||||
|
left: 80,
|
||||||
|
right: 80,
|
||||||
|
top: 80,
|
||||||
|
bottom: 80,
|
||||||
|
}}
|
||||||
|
series={[
|
||||||
|
{
|
||||||
|
data: errorData,
|
||||||
|
label: 'Errors',
|
||||||
|
stack: 'total',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: deliveredData,
|
||||||
|
label: 'Delivered',
|
||||||
|
stack: 'total',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: sentData,
|
||||||
|
label: 'Sent',
|
||||||
|
stack: 'total',
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
xAxis={[
|
||||||
|
{
|
||||||
|
scaleType: 'band',
|
||||||
|
data: dates,
|
||||||
|
valueFormatter: (value) => dayjs(value).format("MMM DD"),
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -46,91 +46,91 @@ const utils = {
|
|||||||
const results = regex.exec(window.location.search);
|
const results = regex.exec(window.location.search);
|
||||||
return results ? decodeURIComponent(results[1].replace(/\+/g, ' ')) : null;
|
return results ? decodeURIComponent(results[1].replace(/\+/g, ' ')) : null;
|
||||||
},
|
},
|
||||||
addAuthHeaders: (headers: Record<string, string> = {}): Record<string, string> => {
|
//addAuthHeaders: (headers: Record<string, string> = {}): Record<string, string> => {
|
||||||
const authToken = utils.getCookie('Auth-Token');
|
// const authToken = utils.getCookie('Auth-Token');
|
||||||
if (authToken) {
|
// if (authToken) {
|
||||||
headers['Auth-Token'] = authToken;
|
// headers['Auth-Token'] = authToken;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const impersonateGuid = utils.getParameterByName("impersonateid") || sessionStorage.getItem('Auth-Impersonate-Guid');
|
// const impersonateGuid = utils.getParameterByName("impersonateid") || sessionStorage.getItem('Auth-Impersonate-Guid');
|
||||||
if (impersonateGuid) {
|
// if (impersonateGuid) {
|
||||||
sessionStorage.setItem('Auth-Impersonate-Guid', impersonateGuid);
|
// sessionStorage.setItem('Auth-Impersonate-Guid', impersonateGuid);
|
||||||
headers['Auth-Impersonate-Guid'] = impersonateGuid;
|
// headers['Auth-Impersonate-Guid'] = impersonateGuid;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const franchiseCode = sessionStorage.getItem('franchiseCode');
|
// const franchiseCode = sessionStorage.getItem('franchiseCode');
|
||||||
if (franchiseCode) {
|
// if (franchiseCode) {
|
||||||
headers['Auth-Current-Franchise'] = franchiseCode;
|
// headers['Auth-Current-Franchise'] = franchiseCode;
|
||||||
}
|
// }
|
||||||
|
|
||||||
return headers;
|
// return headers;
|
||||||
},
|
//},
|
||||||
webMethod: async <T = unknown>({
|
//webMethod: async <T = unknown>({
|
||||||
httpMethod = 'POST',
|
// httpMethod = 'POST',
|
||||||
baseMethodPath = 'api/',
|
// baseMethodPath = 'api/',
|
||||||
methodPage = '',
|
// methodPage = '',
|
||||||
methodName = '',
|
// methodName = '',
|
||||||
parameters = {} as Record<string, unknown>,
|
// parameters = {} as Record<string, unknown>,
|
||||||
contentType = 'application/json;',
|
// contentType = 'application/json;',
|
||||||
timeout = 300000,
|
// timeout = 300000,
|
||||||
success = (_data: T) => { },
|
// success = (_data: T) => { },
|
||||||
error = (_err: unknown) => { },
|
// error = (_err: unknown) => { },
|
||||||
}: {
|
//}: {
|
||||||
httpMethod?: string;
|
// httpMethod?: string;
|
||||||
baseMethodPath?: string;
|
// baseMethodPath?: string;
|
||||||
methodPage?: string;
|
// methodPage?: string;
|
||||||
methodName?: string;
|
// methodName?: string;
|
||||||
contentType?: string;
|
// contentType?: string;
|
||||||
parameters?: Record<string, unknown>;
|
// parameters?: Record<string, unknown>;
|
||||||
timeout?: number;
|
// timeout?: number;
|
||||||
success?: (_data: T) => void;
|
// success?: (_data: T) => void;
|
||||||
error?: (_err: unknown) => void;
|
// error?: (_err: unknown) => void;
|
||||||
}): Promise<void> => {
|
//}): Promise<void> => {
|
||||||
try {
|
// try {
|
||||||
const baseUrl = window.API_BASE_URL || '';
|
// const baseUrl = window.API_BASE_URL || '';
|
||||||
const url = `${baseUrl.replace(/\/$/, '')}/${baseMethodPath.replace(/\/$/, '')}/${methodPage}${methodName ? '/' + methodName : ''}`;
|
// const url = `${baseUrl.replace(/\/$/, '')}/${baseMethodPath.replace(/\/$/, '')}/${methodPage}${methodName ? '/' + methodName : ''}`;
|
||||||
|
|
||||||
const headers = utils.addAuthHeaders({
|
// const headers = utils.addAuthHeaders({
|
||||||
'Content-Type': contentType,
|
// 'Content-Type': contentType,
|
||||||
});
|
// });
|
||||||
|
|
||||||
const controller = new AbortController();
|
// const controller = new AbortController();
|
||||||
//const timeoutId = setTimeout(() => controller.abort(), timeout);
|
// //const timeoutId = setTimeout(() => controller.abort(), timeout);
|
||||||
setTimeout(() => controller.abort(), timeout);
|
// setTimeout(() => controller.abort(), timeout);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
// const response = await fetch(url, {
|
||||||
method: httpMethod,
|
// method: httpMethod,
|
||||||
headers,
|
// headers,
|
||||||
body: (httpMethod.toUpperCase() == "GET" ? null : JSON.stringify(parameters)),
|
// body: (httpMethod.toUpperCase() == "GET" ? null : JSON.stringify(parameters)),
|
||||||
signal: controller.signal,
|
// signal: controller.signal,
|
||||||
});
|
// });
|
||||||
|
|
||||||
if (!response.ok) {
|
// if (!response.ok) {
|
||||||
let data = null;
|
// let data = null;
|
||||||
try {
|
// try {
|
||||||
data = await response.json();
|
// data = await response.json();
|
||||||
} catch {
|
// } catch {
|
||||||
// Intentionally empty, if not json ignore
|
// // Intentionally empty, if not json ignore
|
||||||
}
|
// }
|
||||||
throw new ApiError(response.status, data);
|
// throw new ApiError(response.status, data);
|
||||||
}
|
// }
|
||||||
|
|
||||||
const authToken = response.headers.get('Auth-Token');
|
// const authToken = response.headers.get('Auth-Token');
|
||||||
const loggedIn = response.headers.get('usahl_logged_in') === 'true';
|
// const loggedIn = response.headers.get('usahl_logged_in') === 'true';
|
||||||
|
|
||||||
const expires = loggedIn ? 365 : 14;
|
// const expires = loggedIn ? 365 : 14;
|
||||||
document.cookie = `Auth-Token=${authToken};path=/;max-age=${expires * 24 * 60 * 60}`;
|
// document.cookie = `Auth-Token=${authToken};path=/;max-age=${expires * 24 * 60 * 60}`;
|
||||||
document.cookie = `usahl_logged_in=${loggedIn};path=/;max-age=${expires * 24 * 60 * 60}`;
|
// document.cookie = `usahl_logged_in=${loggedIn};path=/;max-age=${expires * 24 * 60 * 60}`;
|
||||||
|
|
||||||
const data = await response.json();
|
// const data = await response.json();
|
||||||
success(data);
|
// success(data);
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
if ((err as Error).name === 'AbortError') {
|
// if ((err as Error).name === 'AbortError') {
|
||||||
console.error('Request timed out');
|
// console.error('Request timed out');
|
||||||
}
|
// }
|
||||||
error(err);
|
// error(err);
|
||||||
}
|
// }
|
||||||
},
|
//},
|
||||||
getBoolean: (variable: any): boolean => {
|
getBoolean: (variable: any): boolean => {
|
||||||
if (variable != null) {
|
if (variable != null) {
|
||||||
switch (typeof variable) {
|
switch (typeof variable) {
|
||||||
@ -144,47 +144,47 @@ const utils = {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
isLoggedIn: (): boolean => {
|
//isLoggedIn: (): boolean => {
|
||||||
return utils.getBoolean(utils.getCookie('usahl_logged_in'));
|
// return utils.getBoolean(utils.getCookie('usahl_logged_in'));
|
||||||
},
|
//},
|
||||||
sessionStorage: (key: string, value?: any): any => {
|
//sessionStorage: (key: string, value?: any): any => {
|
||||||
if (value === undefined) {
|
// if (value === undefined) {
|
||||||
let val = window.sessionStorage.getItem(key);
|
// let val = window.sessionStorage.getItem(key);
|
||||||
if (val && val.startsWith('usahl_json:')) {
|
// if (val && val.startsWith('usahl_json:')) {
|
||||||
val = val.substring(11);
|
// val = val.substring(11);
|
||||||
return JSON.parse(val);
|
// return JSON.parse(val);
|
||||||
}
|
// }
|
||||||
return val;
|
// return val;
|
||||||
} else {
|
// } else {
|
||||||
const val = typeof value === 'object' ? `usahl_json:${JSON.stringify(value)}` : value;
|
// const val = typeof value === 'object' ? `usahl_json:${JSON.stringify(value)}` : value;
|
||||||
window.sessionStorage.setItem(key, val);
|
// window.sessionStorage.setItem(key, val);
|
||||||
}
|
// }
|
||||||
},
|
//},
|
||||||
sessionStorageClear: (): void => {
|
//sessionStorageClear: (): void => {
|
||||||
window.sessionStorage.clear();
|
// window.sessionStorage.clear();
|
||||||
},
|
//},
|
||||||
sessionStorageRemove: (key: string): void => {
|
//sessionStorageRemove: (key: string): void => {
|
||||||
window.sessionStorage.removeItem(key);
|
// window.sessionStorage.removeItem(key);
|
||||||
},
|
//},
|
||||||
localStorage: (key: string, value?: any): any => {
|
//localStorage: (key: string, value?: any): any => {
|
||||||
if (value === undefined) {
|
// if (value === undefined) {
|
||||||
let val = window.localStorage.getItem(key);
|
// let val = window.localStorage.getItem(key);
|
||||||
if (val && val.startsWith('usahl_json:')) {
|
// if (val && val.startsWith('usahl_json:')) {
|
||||||
val = val.substring(11);
|
// val = val.substring(11);
|
||||||
return JSON.parse(val);
|
// return JSON.parse(val);
|
||||||
}
|
// }
|
||||||
return val;
|
// return val;
|
||||||
} else {
|
// } else {
|
||||||
const val = typeof value === 'object' ? `usahl_json:${JSON.stringify(value)}` : value;
|
// const val = typeof value === 'object' ? `usahl_json:${JSON.stringify(value)}` : value;
|
||||||
window.localStorage.setItem(key, val);
|
// window.localStorage.setItem(key, val);
|
||||||
}
|
// }
|
||||||
},
|
//},
|
||||||
localStorageClear: (): void => {
|
//localStorageClear: (): void => {
|
||||||
window.localStorage.clear();
|
// window.localStorage.clear();
|
||||||
},
|
//},
|
||||||
localStorageRemove: (key: string): void => {
|
//localStorageRemove: (key: string): void => {
|
||||||
window.localStorage.removeItem(key);
|
// window.localStorage.removeItem(key);
|
||||||
},
|
//},
|
||||||
};
|
};
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
export default interface MailingStatistic {
|
export default interface MailingStatistic {
|
||||||
mailingId: number;
|
mailingId: number;
|
||||||
mailingName: string;
|
mailingName: string;
|
||||||
|
sentDate: string;
|
||||||
spamCount: number;
|
spamCount: number;
|
||||||
uniqueClickCount: number;
|
uniqueClickCount: number;
|
||||||
clickCount: number;
|
clickCount: number;
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import TargetColumn from './targetColumn';
|
||||||
export interface Target {
|
export interface Target {
|
||||||
id: number;
|
id: number;
|
||||||
serverId: number;
|
serverId: number;
|
||||||
@ -7,6 +8,7 @@ export interface Target {
|
|||||||
filterQuery: string;
|
filterQuery: string;
|
||||||
allowWriteBack: boolean;
|
allowWriteBack: boolean;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
|
columns: TargetColumn[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Target;
|
export default Target;
|
||||||
11
Surge365.MassEmailReact.Web/src/types/targetColumn.ts
Normal file
11
Surge365.MassEmailReact.Web/src/types/targetColumn.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export interface TargetColumn {
|
||||||
|
id: number;
|
||||||
|
targetId: number;
|
||||||
|
typeCode: string;
|
||||||
|
dataTypeCode: string;
|
||||||
|
name: string;
|
||||||
|
writeBack: boolean;
|
||||||
|
isEmailAddress: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TargetColumn;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
export interface TargetSampleColumn {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
dataType: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TargetSampleColumn;
|
||||||
Loading…
x
Reference in New Issue
Block a user